@archivist-gg/dnd5e 0.1.1 → 0.3.0

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 (87) hide show
  1. package/dist/background/background.codec.js +4 -4
  2. package/dist/background/background.parser.js +3 -3
  3. package/dist/background/background.schema.js +2 -2
  4. package/dist/{chunk-FFULAF2P.js → chunk-2BWAID3Y.js} +2 -2
  5. package/dist/chunk-353WXS6S.js +25 -0
  6. package/dist/{chunk-FUK7QSN5.js → chunk-42Y5IJKT.js} +1 -1
  7. package/dist/{chunk-TPZSPU4L.js → chunk-4JHOHWBI.js} +1 -1
  8. package/dist/{chunk-DC74TZNA.js → chunk-4LZJXJJS.js} +7 -2
  9. package/dist/chunk-5MZSB6OM.js +1089 -0
  10. package/dist/{chunk-FEOB24KP.js → chunk-73HOFQQ5.js} +1 -1
  11. package/dist/{chunk-YDPWD2T7.js → chunk-7VJ6BOA5.js} +4 -1
  12. package/dist/{chunk-ZLG6FQFB.js → chunk-A4WENOUQ.js} +21 -2
  13. package/dist/{chunk-GEJSJP6E.js → chunk-AIRFC5NQ.js} +3 -1
  14. package/dist/{chunk-DABBCYTT.js → chunk-CZPMTQOY.js} +1 -1
  15. package/dist/{chunk-DRNZJNJP.js → chunk-DZXSWSBP.js} +1 -1
  16. package/dist/{chunk-ADLTSHOR.js → chunk-EMOORSQ7.js} +59 -30
  17. package/dist/{chunk-RVYYKYFL.js → chunk-GE4TLPWM.js} +1 -1
  18. package/dist/{chunk-VLKIB33F.js → chunk-K6Q5OUGK.js} +6 -0
  19. package/dist/{chunk-PMADYMUI.js → chunk-L7E2UT5A.js} +1 -1
  20. package/dist/{chunk-3TRGRSCH.js → chunk-LV4SNWZQ.js} +7 -1
  21. package/dist/{chunk-IXZXFFFW.js → chunk-M2EMFD6S.js} +1 -1
  22. package/dist/{chunk-UC4FE5X3.js → chunk-N6N74K6X.js} +1 -1
  23. package/dist/{chunk-CDW7R2QT.js → chunk-NXSWH3AE.js} +4 -1
  24. package/dist/{chunk-IDTDACM6.js → chunk-PHQRGDRQ.js} +1 -1
  25. package/dist/{chunk-BAXWWGFJ.js → chunk-Q5K6LWBZ.js} +1 -1
  26. package/dist/{chunk-Z6GXV2GO.js → chunk-TLYDBT5F.js} +1 -1
  27. package/dist/{chunk-A7T5L7SS.js → chunk-TUIFSVLI.js} +1 -1
  28. package/dist/{chunk-J2RYSZMQ.js → chunk-VTTA2ALZ.js} +2 -2
  29. package/dist/{chunk-Z4TL2V7S.js → chunk-VTYUBINW.js} +1 -1
  30. package/dist/{chunk-B7UXKNLD.js → chunk-W7HWMMBF.js} +1 -1
  31. package/dist/{chunk-SSXBYF3P.js → chunk-XLSVNUTK.js} +1 -1
  32. package/dist/{chunk-GIOH5JB5.js → chunk-XTIQWSFM.js} +1 -1
  33. package/dist/{chunk-PKC77VZP.js → chunk-ZZGEVJKX.js} +1 -1
  34. package/dist/class/class.codec.js +5 -5
  35. package/dist/class/class.parser.js +4 -4
  36. package/dist/class/class.schema.js +3 -3
  37. package/dist/class/class.slug.d.ts +7 -5
  38. package/dist/class/class.slug.js +1 -1
  39. package/dist/entities/base-item-resolver.d.ts +9 -0
  40. package/dist/entities/base-item-resolver.js +6 -4
  41. package/dist/entities/slug.d.ts +7 -0
  42. package/dist/entities/slug.js +3 -1
  43. package/dist/feat/feat.codec.js +4 -4
  44. package/dist/feat/feat.parser.js +3 -3
  45. package/dist/feat/feat.schema.js +2 -2
  46. package/dist/index.js +4116 -3662
  47. package/dist/item/item.actions-map.d.ts +18 -10
  48. package/dist/item/item.actions-map.js +16 -12
  49. package/dist/item/item.codec.js +3 -3
  50. package/dist/item/item.parser.js +2 -2
  51. package/dist/item/item.schema.d.ts +3 -0
  52. package/dist/item/item.schema.js +1 -1
  53. package/dist/item/item.types.d.ts +8 -0
  54. package/dist/pc/pc.decision-engine.d.ts +0 -3
  55. package/dist/pc/pc.decision-engine.js +8 -3
  56. package/dist/pc/pc.equipment.js +5 -8
  57. package/dist/pc/pc.feature-effects.d.ts +5 -0
  58. package/dist/pc/pc.feature-effects.js +2 -4
  59. package/dist/pc/pc.parser.js +1 -1
  60. package/dist/pc/pc.pools.js +8 -3
  61. package/dist/pc/pc.recalc.d.ts +7 -0
  62. package/dist/pc/pc.recalc.js +94 -40
  63. package/dist/pc/pc.resolver.d.ts +59 -1
  64. package/dist/pc/pc.resolver.js +21 -343
  65. package/dist/pc/pc.rest.d.ts +1 -1
  66. package/dist/pc/pc.rest.js +20 -11
  67. package/dist/pc/pc.schema.d.ts +23 -0
  68. package/dist/pc/pc.schema.js +1 -1
  69. package/dist/pc/pc.slotting.js +3 -3
  70. package/dist/pc/pc.spellcasting.js +2 -2
  71. package/dist/pc/pc.types.d.ts +65 -5
  72. package/dist/race/race.codec.js +5 -5
  73. package/dist/race/race.parser.js +4 -4
  74. package/dist/race/race.schema.js +3 -3
  75. package/dist/schemas/choice-schema.js +1 -1
  76. package/dist/schemas/feature-schema.js +2 -2
  77. package/dist/spell/spell.access.js +1 -1
  78. package/dist/subclass/subclass.codec.js +5 -5
  79. package/dist/subclass/subclass.parser.js +4 -4
  80. package/dist/subclass/subclass.schema.js +3 -3
  81. package/dist/types/choice.d.ts +5 -0
  82. package/dist/weapon/weapon-mastery.d.ts +3 -0
  83. package/dist/weapon/weapon.classify.d.ts +21 -0
  84. package/dist/weapon/weapon.classify.js +7 -0
  85. package/package.json +1 -1
  86. package/dist/chunk-6PXE7T7C.js +0 -79
  87. package/dist/chunk-TJCNX6UZ.js +0 -521
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseFeat
3
- } from "./chunk-GIOH5JB5.js";
3
+ } from "./chunk-XTIQWSFM.js";
4
4
 
5
5
  // src/feat/feat.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  itemEntitySchema
3
- } from "./chunk-VLKIB33F.js";
3
+ } from "./chunk-K6Q5OUGK.js";
4
4
 
5
5
  // src/item/item.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -38,6 +38,9 @@ var KNOWN_KEYS = /* @__PURE__ */ new Set([
38
38
  "effects",
39
39
  "damage_riders",
40
40
  "raw",
41
+ "scroll_level",
42
+ "unidentified",
43
+ "masked_category",
41
44
  // Legacy fields kept (removed in Slice 7 after grep verifies no consumer)
42
45
  "damage_dice",
43
46
  "damage_type",
@@ -35,7 +35,19 @@ var equipmentEntryOverridesSchema = z.object({
35
35
  extra_damage: z.string().optional(),
36
36
  ac_bonus: z.number().int().optional(),
37
37
  action: z.enum(["action", "bonus-action", "reaction", "free", "special"]).optional(),
38
- range: z.string().optional()
38
+ range: z.string().optional(),
39
+ // Per-instance defense arrays. The EquipmentEntryOverrides type declares
40
+ // these and pc.equipment reads them into defenses, but the strict schema
41
+ // used to omit them, so a file carrying one failed the WHOLE parse. Admitted
42
+ // here so a per-instance resist/immune/vulnerable/condition_immune loads.
43
+ resist: z.array(z.string()).optional(),
44
+ immune: z.array(z.string()).optional(),
45
+ vulnerable: z.array(z.string()).optional(),
46
+ condition_immune: z.array(z.string()).optional(),
47
+ // Scroll authoring: a per-instance spell payload (e.g. a Spell Scroll's spell)
48
+ // plus the ability that governs its save DC / attack when cast from the scroll.
49
+ spell: z.string().optional(),
50
+ spell_ability: abilityEnum.optional()
39
51
  }).strict();
40
52
  var equipmentEntryStateSchema = z.object({
41
53
  charges: z.object({
@@ -82,7 +94,11 @@ var characterOverridesSchema = z.object({
82
94
  investigation: z.number().int().optional(),
83
95
  insight: z.number().int().optional()
84
96
  }).partial().optional(),
85
- hp: z.object({ max: z.number().int().positive().optional() }).optional(),
97
+ hp: z.object({
98
+ max: z.number().int().positive().optional(),
99
+ rolled: z.number().int().positive().optional(),
100
+ modifier: z.number().int().optional()
101
+ }).optional(),
86
102
  ac: z.number().int().optional(),
87
103
  speed: z.number().int().optional(),
88
104
  initiative: z.number().int().optional(),
@@ -90,6 +106,9 @@ var characterOverridesSchema = z.object({
90
106
  saveDC: z.number().int().optional(),
91
107
  attackBonus: z.number().int().optional()
92
108
  }).optional(),
109
+ // Character-level DC-ability fallback for own-ability-less spells (e.g. a Spell
110
+ // Scroll cast by a non-caster). Same ability enum as the per-instance spell_ability.
111
+ spellcasting_ability: abilityEnum.optional(),
93
112
  spell_slots: z.record(z.coerce.number().int(), z.number().int().nonnegative()).optional(),
94
113
  attunement_limit: z.number().int().nonnegative().optional()
95
114
  }).default({});
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  bareEntitySlug
3
- } from "./chunk-TJCNX6UZ.js";
3
+ } from "./chunk-LV4SNWZQ.js";
4
4
  import {
5
5
  ABILITY_KEYS
6
6
  } from "./chunk-WP2CX2JO.js";
@@ -10,6 +10,7 @@ function emptyFeatureEffectTotals() {
10
10
  return {
11
11
  initiative_bonus: 0,
12
12
  hp_per_level_bonus: 0,
13
+ hp_per_level_terms: [],
13
14
  speed_walk_bonus: 0,
14
15
  speed_walk_set: 0,
15
16
  senses: { darkvision: 0, blindsight: 0, tremorsense: 0, truesight: 0 },
@@ -66,6 +67,7 @@ function applyEffect(out, eff, label) {
66
67
  break;
67
68
  case "hp-per-level-bonus":
68
69
  out.hp_per_level_bonus += eff.value;
70
+ out.hp_per_level_terms.push({ label, value: eff.value });
69
71
  break;
70
72
  case "speed-bonus":
71
73
  if (eff.mode === "walk") {
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-PZOWBI3B.js";
8
8
  import {
9
9
  choiceSchema
10
- } from "./chunk-CDW7R2QT.js";
10
+ } from "./chunk-NXSWH3AE.js";
11
11
  import {
12
12
  featureEffectSchema
13
13
  } from "./chunk-NXR3BD2V.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  backgroundEntitySchema
3
- } from "./chunk-A7T5L7SS.js";
3
+ } from "./chunk-TUIFSVLI.js";
4
4
 
5
5
  // src/background/background.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -1,31 +1,38 @@
1
+ import {
2
+ classifyWeaponRange
3
+ } from "./chunk-353WXS6S.js";
1
4
  import {
2
5
  defaultSlotForType,
3
6
  effectiveArmor,
4
7
  isItemEntity,
5
8
  isWeaponEntity,
6
9
  resolveEntityForEntry
7
- } from "./chunk-UC4FE5X3.js";
8
- import {
9
- bareEntitySlug
10
- } from "./chunk-TJCNX6UZ.js";
10
+ } from "./chunk-N6N74K6X.js";
11
11
  import {
12
12
  readNumericBonus
13
13
  } from "./chunk-LWFTHJIE.js";
14
14
  import {
15
15
  resolveBaseItem
16
- } from "./chunk-DC74TZNA.js";
16
+ } from "./chunk-4LZJXJJS.js";
17
+ import {
18
+ bareEntitySlug
19
+ } from "./chunk-LV4SNWZQ.js";
17
20
 
18
21
  // src/weapon/weapon-mastery.ts
19
22
  var MASTERY = {
20
- cleave: { label: "Cleave", description: "If you hit a creature with a melee attack roll using this weapon, you can make an attack roll with the weapon against a second creature within 5 feet of the first that is also within your reach. On a hit, the second creature takes the weapon's damage, but don't add your ability modifier to that damage unless it is negative. You can make this extra attack only once per turn." },
21
- graze: { label: "Graze", description: "If your attack roll with this weapon misses a creature, you can deal damage to that creature equal to the ability modifier you used to make the attack roll. This damage is the same type dealt by the weapon, and the damage can be increased only by increasing the ability modifier." },
22
- nick: { label: "Nick", description: "When you make the extra attack of the Light property, you can make it as part of the Attack action instead of as a Bonus Action. You can make this extra attack only once per turn." },
23
- push: { label: "Push", description: "If you hit a creature with this weapon, you can push the creature up to 10 feet straight away from yourself if it is Large or smaller." },
24
- sap: { label: "Sap", description: "If you hit a creature with this weapon, that creature has Disadvantage on its next attack roll before the start of your next turn." },
25
- slow: { label: "Slow", description: "If you hit a creature with this weapon and deal damage, you can reduce its Speed by 10 feet until the start of your next turn. If the creature is hit more than once by weapons that have this property, the Speed reduction doesn't exceed 10 feet." },
26
- topple: { label: "Topple", description: "If you hit a creature with this weapon, you can force the creature to make a Constitution saving throw (DC 8 plus the ability modifier used to make the attack roll and your Proficiency Bonus). On a failed save, the creature has the Prone condition." },
27
- vex: { label: "Vex", description: "If you hit a creature with this weapon and deal damage, you have Advantage on your next attack roll against that creature before the end of your next turn." }
23
+ cleave: { label: "Cleave", description: "If you hit a creature with a melee attack roll using this weapon, you can make an attack roll with the weapon against a second creature within 5 feet of the first that is also within your reach. On a hit, the second creature takes the weapon's damage, but don't add your ability modifier to that damage unless it is negative. You can make this extra attack only once per turn.", gist: "hit a 2nd creature in reach" },
24
+ graze: { label: "Graze", description: "If your attack roll with this weapon misses a creature, you can deal damage to that creature equal to the ability modifier you used to make the attack roll. This damage is the same type dealt by the weapon, and the damage can be increased only by increasing the ability modifier.", gist: "on miss: ability-mod damage" },
25
+ nick: { label: "Nick", description: "When you make the extra attack of the Light property, you can make it as part of the Attack action instead of as a Bonus Action. You can make this extra attack only once per turn.", gist: "extra Light attack (no Bonus Action)" },
26
+ push: { label: "Push", description: "If you hit a creature with this weapon, you can push the creature up to 10 feet straight away from yourself if it is Large or smaller.", gist: "on hit: pushed 10 ft" },
27
+ sap: { label: "Sap", description: "If you hit a creature with this weapon, that creature has Disadvantage on its next attack roll before the start of your next turn.", gist: "on hit: target Disadvantage" },
28
+ slow: { label: "Slow", description: "If you hit a creature with this weapon and deal damage, you can reduce its Speed by 10 feet until the start of your next turn. If the creature is hit more than once by weapons that have this property, the Speed reduction doesn't exceed 10 feet.", gist: "on hit: -10 ft Speed" },
29
+ topple: { label: "Topple", description: "If you hit a creature with this weapon, you can force the creature to make a Constitution saving throw (DC 8 plus the ability modifier used to make the attack roll and your Proficiency Bonus). On a failed save, the creature has the Prone condition.", gist: "on fail: Prone" },
30
+ vex: { label: "Vex", description: "If you hit a creature with this weapon and deal damage, you have Advantage on your next attack roll against that creature before the end of your next turn.", gist: "on hit: you gain Advantage next" }
28
31
  };
32
+ function masteryGist(slug) {
33
+ var _a;
34
+ return (_a = MASTERY[slug]) == null ? void 0 : _a.gist;
35
+ }
29
36
  function masteryDerived(slug, abilityMod, proficiencyBonus) {
30
37
  if (slug === "topple") return { label: "Save DC", value: 8 + proficiencyBonus + abilityMod };
31
38
  if (slug === "graze") return { label: "On miss", value: abilityMod };
@@ -241,7 +248,14 @@ function assignSlots(resolved, registry, warnings) {
241
248
  const { entity, entityType } = resolveEntityForEntry(entry.item, registry);
242
249
  if (!entity || !entityType) continue;
243
250
  const defaultSlot = defaultSlotForType(entityType, entity, registry);
244
- if (!defaultSlot) continue;
251
+ if (!defaultSlot) {
252
+ if (isItemEntity(entity) && typeof entity.base_item === "string" && entity.base_item.length > 0) {
253
+ warnings.push(
254
+ `Magic item "${entity.name}" base_item ${entity.base_item} did not resolve to a base weapon or armor; not slotted (AC/attack not applied).`
255
+ );
256
+ }
257
+ continue;
258
+ }
245
259
  const placed = { index: i, entity, entityType, entry };
246
260
  if (defaultSlot === "mainhand") {
247
261
  if (!slots.mainhand) slots.mainhand = placed;
@@ -261,12 +275,17 @@ function assignSlots(resolved, registry, warnings) {
261
275
  }
262
276
  return slots;
263
277
  }
264
- function computeAC(equippedSlots, resolved, mods, registry) {
278
+ function computeAC(equippedSlots, resolved, mods, registry, warnings) {
265
279
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
266
280
  const breakdown = [];
267
281
  const armorSlot = equippedSlots.armor;
268
282
  const armorEntity = (_a = armorSlot == null ? void 0 : armorSlot.entity) != null ? _a : null;
269
283
  const armor = effectiveArmor(armorEntity, registry);
284
+ if (isItemEntity(armorEntity) && typeof armorEntity.base_item === "string" && !armor) {
285
+ warnings.push(
286
+ `Magic armor "${armorEntity.name}" base_item ${armorEntity.base_item} did not resolve to a base armor; AC not applied.`
287
+ );
288
+ }
270
289
  let ac = 10;
271
290
  if (armor) {
272
291
  const armorBase = armor.ac.base + ((_b = armor.ac.flat) != null ? _b : 0);
@@ -325,8 +344,9 @@ function hasProperty(weapon, prop) {
325
344
  return weapon.properties.some((p) => p === prop);
326
345
  }
327
346
  function attackAbility(weapon, mods, weaponAbilities) {
328
- const isRanged = /ranged/.test(weapon.category);
329
- if (!isRanged) {
347
+ const modes = classifyWeaponRange(weapon);
348
+ const finesse = hasProperty(weapon, "finesse");
349
+ if (modes.melee) {
330
350
  const scoped = weaponAbilities.find((o2) => {
331
351
  var _a;
332
352
  return (_a = o2.weaponSlugs) == null ? void 0 : _a.includes(bareEntitySlug(weapon.slug));
@@ -334,10 +354,10 @@ function attackAbility(weapon, mods, weaponAbilities) {
334
354
  const glob = weaponAbilities.find((o2) => !o2.weaponSlugs);
335
355
  const o = scoped != null ? scoped : glob;
336
356
  if (o) return o.ability;
357
+ if (finesse) return mods.dex >= mods.str ? "dex" : "str";
358
+ return "str";
337
359
  }
338
- if (isRanged) return "dex";
339
- if (hasProperty(weapon, "finesse")) return mods.dex >= mods.str ? "dex" : "str";
340
- return "str";
360
+ return finesse && mods.str > mods.dex ? "str" : "dex";
341
361
  }
342
362
  function isWeaponSlugProficient(weapon, weaponSlug, profs) {
343
363
  const wp = profs.weapons;
@@ -416,8 +436,10 @@ function buildAttackRow(args) {
416
436
  { source: `${ability.toUpperCase()} modifier`, amount: abilityMod, kind: "ability" },
417
437
  { source: magicSource, amount: magic.dmg, kind: "item" }
418
438
  ];
419
- const isRanged = /ranged/.test(weapon.category);
420
- const range = isRanged && weapon.range ? `${weapon.range.normal}/${weapon.range.long}` : "melee";
439
+ const modes = classifyWeaponRange(weapon);
440
+ const fmtRanged = (r) => `${r.normal}/${r.long} ft`;
441
+ const range = modes.melee ? `${modes.melee.reach} ft` : modes.ranged ? fmtRanged(modes.ranged) : void 0;
442
+ const thrownRange = modes.melee && modes.ranged ? fmtRanged(modes.ranged) : void 0;
421
443
  const stringProps = weapon.properties.filter(
422
444
  (p) => typeof p === "string"
423
445
  );
@@ -425,7 +447,8 @@ function buildAttackRow(args) {
425
447
  const row = {
426
448
  id,
427
449
  name,
428
- range,
450
+ ...range !== void 0 ? { range } : {},
451
+ ...thrownRange !== void 0 ? { thrownRange } : {},
429
452
  toHit,
430
453
  damageDice,
431
454
  damageType: (_b = magic.damageTypeOverride) != null ? _b : weapon.damage.type,
@@ -442,7 +465,7 @@ function buildAttackRow(args) {
442
465
  breakdown: { toHit: toHitBreakdown, damage: damageBreakdown },
443
466
  informational: args.magic.informational.length > 0 ? args.magic.informational : void 0,
444
467
  slotKey: args.slotKey,
445
- subLabel: formatWeaponSubLabel(weapon, finalProps),
468
+ subLabel: formatWeaponSubLabel(weapon, finalProps, modes),
446
469
  actionCost: args.actionCost,
447
470
  versatile: args.versatileDice ? { damageDice: formatDice(args.versatileDice) } : void 0
448
471
  };
@@ -451,15 +474,21 @@ function buildAttackRow(args) {
451
474
  const g = MASTERY[mSlug];
452
475
  if (g) {
453
476
  const derived = masteryDerived(mSlug, mods[ability], proficiencyBonus);
454
- row.mastery = { slug: mSlug, label: g.label, description: g.description, ...derived ? { derived } : {} };
477
+ row.mastery = { slug: mSlug, label: g.label, description: g.description, gist: masteryGist(mSlug), ...derived ? { derived } : {} };
455
478
  }
456
479
  }
457
480
  return row;
458
481
  }
459
- function formatWeaponSubLabel(weapon, properties) {
482
+ function humanizeWeaponToken(s) {
483
+ return s.replace(/[-_]/g, " ").replace(/(^|[\s(])\w/g, (c) => c.toUpperCase());
484
+ }
485
+ function formatWeaponSubLabel(weapon, properties, modes) {
460
486
  const parts = [];
461
- if (weapon.category) parts.push(weapon.category);
462
- if (properties.length > 0) parts.push(properties.join(", "));
487
+ if (weapon.category) {
488
+ const cat = modes.melee && /ranged/.test(weapon.category) ? weapon.category.replace("ranged", "melee") : weapon.category;
489
+ parts.push(humanizeWeaponToken(cat));
490
+ }
491
+ if (properties.length > 0) parts.push(properties.map(humanizeWeaponToken).join(", "));
463
492
  return parts.join(" \xB7 ");
464
493
  }
465
494
  function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx, weaponMasteries, weaponAbilities = []) {
@@ -482,7 +511,7 @@ function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficie
482
511
  if (found && found.entityType === "weapon" && isWeaponEntity(found.data)) {
483
512
  weapon = found.data;
484
513
  } else {
485
- warnings.push(`Magic weapon ${entity.name} references missing base_item [[${baseSlug}]].`);
514
+ warnings.push(`Magic weapon ${entity.name} references missing base_item ${baseSlug}.`);
486
515
  continue;
487
516
  }
488
517
  } else {
@@ -543,7 +572,7 @@ function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, profi
543
572
  var _a, _b, _c;
544
573
  const equippedSlots = assignSlots(resolved, registry, warnings);
545
574
  const overrides = (_a = resolved.definition.overrides) != null ? _a : {};
546
- const acOut = computeAC(equippedSlots, resolved, mods, registry);
575
+ const acOut = computeAC(equippedSlots, resolved, mods, registry, warnings);
547
576
  const ctx = buildConditionContext(resolved, equippedSlots);
548
577
  const attacks = computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx, weaponMasteries, weaponAbilities);
549
578
  return {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseClass
3
- } from "./chunk-FUK7QSN5.js";
3
+ } from "./chunk-42Y5IJKT.js";
4
4
 
5
5
  // src/class/class.codec.ts
6
6
  import yaml from "js-yaml";
@@ -176,6 +176,12 @@ var itemEntitySchema = z.object({
176
176
  applies_to: z.enum(["weapon", "spell", "all"]).optional(),
177
177
  condition: z.string().optional()
178
178
  })).optional(),
179
+ // Scroll & unidentified-item markers (Phase 4). Declared here so they survive
180
+ // parse (the schema strips undeclared keys) and are ALSO listed in the
181
+ // parser's KNOWN_KEYS so they are not additionally copied into `raw`.
182
+ scroll_level: z.number().optional(),
183
+ unidentified: z.boolean().optional(),
184
+ masked_category: z.string().optional(),
179
185
  raw: z.record(z.string(), z.unknown()).optional(),
180
186
  // Legacy fields preserved
181
187
  damage_dice: z.string().optional(),
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  subclassEntitySchema
3
- } from "./chunk-B7UXKNLD.js";
3
+ } from "./chunk-W7HWMMBF.js";
4
4
 
5
5
  // src/subclass/subclass.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -2,7 +2,13 @@
2
2
  function slugify(name) {
3
3
  return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/[\s]+/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
4
4
  }
5
+ function bareEntitySlug(slug) {
6
+ if (!slug) return "";
7
+ const p = slug.split("_");
8
+ return p.length >= 3 ? p.slice(2).join("_") : p[p.length - 1];
9
+ }
5
10
 
6
11
  export {
7
- slugify
12
+ slugify,
13
+ bareEntitySlug
8
14
  };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-5XUXXAQJ.js";
4
4
  import {
5
5
  bareSlug
6
- } from "./chunk-J2RYSZMQ.js";
6
+ } from "./chunk-VTTA2ALZ.js";
7
7
  import {
8
8
  abilityModifier
9
9
  } from "./chunk-NAEPSZAM.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveBaseItem
3
- } from "./chunk-DC74TZNA.js";
3
+ } from "./chunk-4LZJXJJS.js";
4
4
 
5
5
  // src/pc/pc.slotting.ts
6
6
  function isItemEntity(e) {
@@ -11,7 +11,10 @@ var entityFilterSchema = z.object({
11
11
  parent_class: z.literal("self").optional(),
12
12
  available_to: z.literal("self").optional(),
13
13
  weapon_category: z.enum(["simple", "martial"]).optional(),
14
- armor_category: z.enum(["light", "medium", "heavy", "shield"]).optional()
14
+ armor_category: z.enum(["light", "medium", "heavy", "shield"]).optional(),
15
+ list: z.string().min(1).optional(),
16
+ level: z.number().int().min(0).max(9).optional(),
17
+ edition: z.string().min(1).optional()
15
18
  }).strict();
16
19
  var inlineOptionSchema = z.lazy(() => z.object({
17
20
  value: z.string().min(1),
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raceEntitySchema
3
- } from "./chunk-FFULAF2P.js";
3
+ } from "./chunk-2BWAID3Y.js";
4
4
 
5
5
  // src/race/race.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  choiceSchema
3
- } from "./chunk-CDW7R2QT.js";
3
+ } from "./chunk-NXSWH3AE.js";
4
4
  import {
5
5
  featureEffectSchema
6
6
  } from "./chunk-NXR3BD2V.js";
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-4TIJKN6O.js";
6
6
  import {
7
7
  featureSchema
8
- } from "./chunk-DABBCYTT.js";
8
+ } from "./chunk-CZPMTQOY.js";
9
9
  import {
10
10
  resourceSchema
11
11
  } from "./chunk-PZOWBI3B.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-3DZ74AKK.js";
4
4
  import {
5
5
  choiceSchema
6
- } from "./chunk-CDW7R2QT.js";
6
+ } from "./chunk-NXSWH3AE.js";
7
7
 
8
8
  // src/background/background.schema.ts
9
9
  import { z } from "zod";
@@ -6,8 +6,8 @@ function bareSlug(ref) {
6
6
  }
7
7
  function baseClassName(ref) {
8
8
  const bare = bareSlug(ref);
9
- const sep = bare.indexOf("_");
10
- return sep >= 0 ? bare.slice(sep + 1) : bare;
9
+ const p = bare.split("_");
10
+ return p.length >= 3 ? p.slice(2).join("_") : p[p.length - 1];
11
11
  }
12
12
 
13
13
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseBackground
3
- } from "./chunk-DRNZJNJP.js";
3
+ } from "./chunk-DZXSWSBP.js";
4
4
 
5
5
  // src/background/background.codec.ts
6
6
  import yaml from "js-yaml";
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-4TIJKN6O.js";
6
6
  import {
7
7
  featureSchema
8
- } from "./chunk-DABBCYTT.js";
8
+ } from "./chunk-CZPMTQOY.js";
9
9
  import {
10
10
  resourceSchema
11
11
  } from "./chunk-PZOWBI3B.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseSubclass
3
- } from "./chunk-PMADYMUI.js";
3
+ } from "./chunk-L7E2UT5A.js";
4
4
 
5
5
  // src/subclass/subclass.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  featEntitySchema
3
- } from "./chunk-BAXWWGFJ.js";
3
+ } from "./chunk-Q5K6LWBZ.js";
4
4
 
5
5
  // src/feat/feat.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseRace
3
- } from "./chunk-IDTDACM6.js";
3
+ } from "./chunk-PHQRGDRQ.js";
4
4
 
5
5
  // src/race/race.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  classCodec
3
- } from "../chunk-RVYYKYFL.js";
4
- import "../chunk-FUK7QSN5.js";
5
- import "../chunk-Z6GXV2GO.js";
3
+ } from "../chunk-GE4TLPWM.js";
4
+ import "../chunk-42Y5IJKT.js";
5
+ import "../chunk-TLYDBT5F.js";
6
6
  import "../chunk-4TIJKN6O.js";
7
- import "../chunk-DABBCYTT.js";
7
+ import "../chunk-CZPMTQOY.js";
8
8
  import "../chunk-WYGD2UGN.js";
9
9
  import "../chunk-PZOWBI3B.js";
10
10
  import "../chunk-3DZ74AKK.js";
11
- import "../chunk-CDW7R2QT.js";
11
+ import "../chunk-NXSWH3AE.js";
12
12
  import "../chunk-NXR3BD2V.js";
13
13
  export {
14
14
  classCodec
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  parseClass
3
- } from "../chunk-FUK7QSN5.js";
4
- import "../chunk-Z6GXV2GO.js";
3
+ } from "../chunk-42Y5IJKT.js";
4
+ import "../chunk-TLYDBT5F.js";
5
5
  import "../chunk-4TIJKN6O.js";
6
- import "../chunk-DABBCYTT.js";
6
+ import "../chunk-CZPMTQOY.js";
7
7
  import "../chunk-WYGD2UGN.js";
8
8
  import "../chunk-PZOWBI3B.js";
9
9
  import "../chunk-3DZ74AKK.js";
10
- import "../chunk-CDW7R2QT.js";
10
+ import "../chunk-NXSWH3AE.js";
11
11
  import "../chunk-NXR3BD2V.js";
12
12
  export {
13
13
  parseClass
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  classEntitySchema
3
- } from "../chunk-Z6GXV2GO.js";
3
+ } from "../chunk-TLYDBT5F.js";
4
4
  import "../chunk-4TIJKN6O.js";
5
- import "../chunk-DABBCYTT.js";
5
+ import "../chunk-CZPMTQOY.js";
6
6
  import "../chunk-WYGD2UGN.js";
7
7
  import "../chunk-PZOWBI3B.js";
8
8
  import "../chunk-3DZ74AKK.js";
9
- import "../chunk-CDW7R2QT.js";
9
+ import "../chunk-NXSWH3AE.js";
10
10
  import "../chunk-NXR3BD2V.js";
11
11
  export {
12
12
  classEntitySchema
@@ -2,10 +2,12 @@ export declare function bareSlug(ref: string): string;
2
2
  /**
3
3
  * Bare class name for profile / spell-`classes` lookups.
4
4
  *
5
- * Compendium-qualified slugs are `<compendium>_<name>` — e.g. `srd-5e_wizard`,
6
- * `srd-2024_bard` (compendium slugs are hyphen-cased, so `_` only ever separates
7
- * the compendium from the entity name). Strip that prefix to get the canonical
8
- * class key (`wizard`). Hand-written/test slugs without a `_` pass through
9
- * unchanged (`wizard` → `wizard`).
5
+ * Compendium-qualified slugs are `<compendium>_<type>_<name>` (3-part, e.g.
6
+ * `srd-2024_class_wizard`) or the legacy `<compendium>_<name>` (2-part, e.g.
7
+ * `srd-5e_wizard`); compendium/type segments are hyphen-cased, so `_` only ever
8
+ * separates the structural parts from the entity name. Strip that prefix to get
9
+ * the canonical class key (`wizard`). Name-slugs never contain `_`, so a 3-part
10
+ * slug's name is `parts.slice(2).join("_")`. Hand-written/test slugs without a
11
+ * `_` pass through unchanged (`wizard` → `wizard`).
10
12
  */
11
13
  export declare function baseClassName(ref: string): string;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bareSlug,
3
3
  baseClassName
4
- } from "../chunk-J2RYSZMQ.js";
4
+ } from "../chunk-VTTA2ALZ.js";
5
5
  export {
6
6
  bareSlug,
7
7
  baseClassName
@@ -1,4 +1,13 @@
1
1
  import type { EntityRegistry, RegisteredEntity } from "@archivist-gg/core";
2
+ /**
3
+ * Convert a vault-path target (e.g. `SRD 5e/Weapons/Longsword`) to a
4
+ * registry-compatible slug (`srd-5e_weapon_longsword`). The middle Type folder
5
+ * (`segments[length-2]`) supplies the entity-type token so the slug matches
6
+ * the now type-namespaced registry key. For a bare basename without any `/`,
7
+ * returns the slugified basename - which lets legacy wikilinks like
8
+ * `[[longsword]]` continue to resolve when registered under that bare slug.
9
+ */
10
+ export declare function vaultPathToSlug(target: string): string;
2
11
  /**
3
12
  * Public: take a `base_item` string (vault-path wikilink, alias-form
4
13
  * wikilink, bare-slug wikilink, or bare slug) and return the matching
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  resolveBaseItem,
3
- resolveBaseItemOfType
4
- } from "../chunk-DC74TZNA.js";
5
- import "../chunk-3TRGRSCH.js";
3
+ resolveBaseItemOfType,
4
+ vaultPathToSlug
5
+ } from "../chunk-4LZJXJJS.js";
6
+ import "../chunk-LV4SNWZQ.js";
6
7
  export {
7
8
  resolveBaseItem,
8
- resolveBaseItemOfType
9
+ resolveBaseItemOfType,
10
+ vaultPathToSlug
9
11
  };
@@ -3,3 +3,10 @@
3
3
  * "Ancient Red Dragon" -> "ancient-red-dragon"
4
4
  */
5
5
  export declare function slugify(name: string): string;
6
+ /** "srd-2024_fighter" → "fighter", "srd-2024_background_acolyte" → "acolyte".
7
+ * Arity-robust: strips a `<prefix>_<type>_<name>` (3-part) or legacy `<prefix>_<name>`
8
+ * (2-part) slug down to the bare NAME, and passes a bare slug through unchanged.
9
+ * Name-slugs never contain `_`, so for a 3-part-or-longer slug the name is
10
+ * `parts.slice(2).join("_")`. Tolerates a nullish slug (degrades to "") so a
11
+ * malformed entity can't hard-crash the builder; the resolver backfills real slugs. */
12
+ export declare function bareEntitySlug(slug: string | null | undefined): string;
@@ -1,6 +1,8 @@
1
1
  import {
2
+ bareEntitySlug,
2
3
  slugify
3
- } from "../chunk-3TRGRSCH.js";
4
+ } from "../chunk-LV4SNWZQ.js";
4
5
  export {
6
+ bareEntitySlug,
5
7
  slugify
6
8
  };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  featCodec
3
- } from "../chunk-FEOB24KP.js";
4
- import "../chunk-GIOH5JB5.js";
5
- import "../chunk-BAXWWGFJ.js";
6
- import "../chunk-CDW7R2QT.js";
3
+ } from "../chunk-73HOFQQ5.js";
4
+ import "../chunk-XTIQWSFM.js";
5
+ import "../chunk-Q5K6LWBZ.js";
6
+ import "../chunk-NXSWH3AE.js";
7
7
  import "../chunk-NXR3BD2V.js";
8
8
  export {
9
9
  featCodec