Fix PF2e stat block senses and attack trait rendering
All checks were successful
CI / check (push) Successful in 2m20s
CI / build-image (push) Successful in 17s

- Format senses with type (imprecise/precise) and range in feet,
  and strip {@ability} tags (e.g. tremorsense)
- Strip angle-bracket dice notation in attack traits (<d8> → d8)
- Fix existing weakness/resistance tests to nest under defenses
- Fix non-null assertions in 5e bestiary adapter tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-04-07 12:23:08 +02:00
parent 8dbff66ce1
commit 65e4db153b
3 changed files with 124 additions and 30 deletions

View File

@@ -15,7 +15,7 @@ interface RawPf2eCreature {
level?: number;
traits?: string[];
perception?: { std?: number };
senses?: { name?: string; type?: string }[];
senses?: { name?: string; type?: string; range?: number }[];
languages?: { languages?: string[] };
skills?: Record<string, { std?: number }>;
abilityMods?: Record<string, number>;
@@ -79,6 +79,10 @@ function capitalize(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1);
}
function stripDiceBrackets(s: string): string {
return s.replaceAll(/<(\d*d\d+)>/g, "$1");
}
function makeCreatureId(source: string, name: string): CreatureId {
const slug = name
.toLowerCase()
@@ -119,13 +123,19 @@ function formatSkills(
}
function formatSenses(
senses: readonly { name?: string; type?: string }[] | undefined,
senses:
| readonly { name?: string; type?: string; range?: number }[]
| undefined,
): string | undefined {
if (!senses || senses.length === 0) return undefined;
return senses
.map((s) => {
const label = s.name ?? s.type ?? "";
return label ? capitalize(label) : "";
const label = stripTags(s.name ?? s.type ?? "");
if (!label) return "";
const parts = [capitalize(label)];
if (s.type && s.name) parts.push(`(${s.type})`);
if (s.range != null) parts.push(`${s.range} feet`);
return parts.join(" ");
})
.filter(Boolean)
.join(", ");
@@ -253,7 +263,7 @@ function normalizeAttacks(
const attackMod = a.attack == null ? "" : ` +${a.attack}`;
const traits =
a.traits && a.traits.length > 0
? ` (${a.traits.map((t) => stripTags(t)).join(", ")})`
? ` (${a.traits.map((t) => stripDiceBrackets(stripTags(t))).join(", ")})`
: "";
const damage = a.damage ? `, ${stripTags(a.damage)}` : "";
return {