fix(backend): a Foundry creature keeps its defences, its prose, and its format (#553) #562

Merged
claude-bot merged 2 commits from fix/553-foundry-pf2e-body into main 2026-09-08 08:06:39 +00:00
Contributor

Three gaps in the Foundry adapter (#559) found by wiring the pf2e starter build onto it. Part of #553; branched from #560's head, so the diff is one commit.

Defences were dropped. The pf2e and dnd5e body maps rendered strikes, abilities and spells but left saves, skills, senses, languages and immunities/weaknesses/resistances buried in extra["system"], which nothing renders. Both maps now emit Statistics (perception and senses, languages, skills) and Defences (AC and saves, HP with immunities/weaknesses/resistances) before the strikes, in Monster Core order. For dnd5e, saves and skills are listed as proficiencies rather than bonuses, because the 5e system stores the proficiency flag and not the computed number; inventing a figure the file does not contain would be a policy change, not a fix.

Bare enrichers read as data. @Check[fortitude|dc:26|basic:true] came out as "fortitude, dc:26, basic:true". The enricher pass is now a per-form renderer with balanced-bracket reading: "DC 26 basic Fortitude", "30-foot emanation", "4d6 fire", "DC 11 flat check", plus [[/r …]]{label} inline rolls and the pre-remaster type: spelling. Sixteen parametrised cases and an end-to-end fixture assertion.

A kilobyte-record Open5e array still detected as native. The bare-array gate only declined on a positive signal (_id), so any array whose first record overflows the 512-byte sniff window and has no _id stayed with the native reader and imported as empty records with no error. The rule is now closed rather than a list of other formats' fingerprints: a truncated first element is native only if kind is inside the window. That rests on a promise about our own writer, which is verified (export_service emits kind first) and pinned by a test, and the one cost — a hand-written array that buries kind past byte 512 gets "could not tell what format" and needs format=questboard — is tested and documented next to the bare-array example in docs/CONTENT-PACKS.md.

Tests: 228 across the Foundry, detection, import, search, 5etools and orcbrew suites. Fixtures remain invented.

🤖 Generated with Claude Code

Three gaps in the Foundry adapter (#559) found by wiring the pf2e starter build onto it. Part of #553; branched from #560's head, so the diff is one commit. **Defences were dropped.** The pf2e and dnd5e body maps rendered strikes, abilities and spells but left saves, skills, senses, languages and immunities/weaknesses/resistances buried in `extra["system"]`, which nothing renders. Both maps now emit **Statistics** (perception and senses, languages, skills) and **Defences** (AC and saves, HP with immunities/weaknesses/resistances) before the strikes, in Monster Core order. For dnd5e, saves and skills are listed as *proficiencies* rather than bonuses, because the 5e system stores the proficiency flag and not the computed number; inventing a figure the file does not contain would be a policy change, not a fix. **Bare enrichers read as data.** `@Check[fortitude|dc:26|basic:true]` came out as "fortitude, dc:26, basic:true". The enricher pass is now a per-form renderer with balanced-bracket reading: "DC 26 basic Fortitude", "30-foot emanation", "4d6 fire", "DC 11 flat check", plus `[[/r …]]{label}` inline rolls and the pre-remaster `type:` spelling. Sixteen parametrised cases and an end-to-end fixture assertion. **A kilobyte-record Open5e array still detected as native.** The bare-array gate only declined on a positive signal (`_id`), so any array whose first record overflows the 512-byte sniff window and has no `_id` stayed with the native reader and imported as empty records with no error. The rule is now closed rather than a list of other formats' fingerprints: a truncated first element is native only if `kind` is inside the window. That rests on a promise about our own writer, which is verified (`export_service` emits `kind` first) and pinned by a test, and the one cost — a hand-written array that buries `kind` past byte 512 gets "could not tell what format" and needs `format=questboard` — is tested and documented next to the bare-array example in `docs/CONTENT-PACKS.md`. Tests: 228 across the Foundry, detection, import, search, 5etools and orcbrew suites. Fixtures remain invented. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(backend): a Foundry creature keeps its defences, its prose, and its format (#553)
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m17s
CI / Bot/backend version sync (pull_request) Successful in 24s
CI / Backend lint (ruff) (pull_request) Successful in 41s
CI / Bot tests and audit (pull_request) Successful in 2m26s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m49s
CI / Backend migration, tests, and audit (pull_request) Failing after 4m49s
CI / Docker image build (pull_request) Successful in 4m27s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
a14783abb3
Three gaps lane 4 found when it rewired the pf2e starter build through this
adapter.

**A creature lost half its stat block.** `body_sections` rendered strikes,
abilities and spells. Saves, skills, senses, languages and
immunities/weaknesses/resistances have no stat-schema field, so they survived
only inside `extra["system"]`, which nothing renders: a GM looking up an
imported creature saw what it hit with but not what it was immune to. Both
maps now emit two more sections before the strikes, in the order the book
prints them — Statistics (perception and senses, languages, skills) and
Defences (AC with the saves, HP with the IWR lists). AC and HP are repeated
from the envelope on purpose; a defence block listing Fort/Ref/Will but not
the AC beside them reads as though something went missing.

The 5e map cannot quite match it, and says so rather than pretending: that
system stores *proficiency*, not the computed bonus, so the rows name the save
and skill proficiencies instead of printing a number the file does not
contain. Deriving one from the challenge rating would be inventing data.

**Bare enrichers read as data.** `@Check[fortitude|dc:26|basic:true]` became
"fortitude, dc:26, basic:true". Most PF2e ability prose is made of these, so
every stat block read like a config file. The per-enricher renderer lane 4
wrote and then deleted when it adopted this adapter is recovered from
816a2e8 `scripts/content/_foundry_text.py` and folded into `_strip_enrichers`:
DC-and-statistic for @Check (including the pre-remaster `type:will` spelling),
"30-foot emanation" for @Template, formula-and-type for @Damage, the last
segment of a UUID path, and inline `[[/r …]]` rolls. Brackets are read by
counting rather than matched, because `@Damage[(4d6)[fire]]` nests.

**A bare Open5e array was still claimed as native.** 9deacde fixed this for
Foundry by declining on `_id`, but that is a rule about *someone else's*
format: an Open5e record has no `_id`, so a first record longer than the
512-byte sniff window — every real creature export — came straight back to the
native reader and imported as empty `other` records. Deciding by the absence
of other people's tells needs a new tell per format and fails silently each
time. The rule is now closed over our own shape: a truncated first element is
ours if `kind` is in the window, and `export_service._record_json` emits
`kind` first, which a test now asserts rather than trusts. The cost is a
hand-written array that buries `kind` past byte 512, refused loudly and fixed
with `format=questboard`; it is pinned as a test and documented in
CONTENT-PACKS.md beside the shape it constrains.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-09-08 07:41:03 +00:00
test(backend): the malformed-pack task fixture leads with kind (#553)
All checks were successful
CI / Docker image build (pull_request) Successful in 33s
CI / Bot/backend version sync (pull_request) Successful in 45s
CI / Backend lint (ruff) (pull_request) Successful in 50s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m35s
CI / Bot tests and audit (pull_request) Successful in 2m49s
CI / Frontend tests, audit, and build (pull_request) Successful in 3m7s
CI / Backend migration, tests, and audit (pull_request) Successful in 8m31s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 16m11s
f7a7c94910
a14783a closed the bare-array rule: a truncated first element is native only
when `kind` sits inside the sniff window. `[{"name": "Truncated"` therefore
became nobody's format, and the task test that stages it asserted the parse
error ("not valid JSON") while getting the detection one ("could not tell what
format"). The fixture now leads with `kind`, as the rule and the docs ask of a
native array, so the failure under test is the parse again. The detection
failure is covered by the test above it.

Found by lane 4 of #553 while rebasing onto this branch; the hunk is theirs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude-bot deleted branch fix/553-foundry-pf2e-body 2026-09-08 08:06:40 +00:00
Sign in to join this conversation.
No description provided.