feat(backend): Open5e adapter and the starter-pack build scripts for content packs (#553) #564

Merged
claude-bot merged 4 commits from feat/553-open5e-adapter-starters into main 2026-09-08 08:34:51 +00:00
Collaborator

Phase 1 of #553, lane 4 of the content-packs spec. Stacked on #562 (the Foundry fixes); merge that first and this PR's diff shrinks to its own four commits.

The Open5e reader. Saved v2 API pages, bare arrays, and open5e-*.zip bundles map to the shared kinds, with the SRD 5.1 / 5.2.1 CC-BY-4.0 attribution carried as the licence. Registered last in DETECT_ORDER; the detection cases for kilobyte-record arrays now answer open5e thanks to #562's closed native-array rule.

Two starter-pack builds, as scripts, not bundled content. scripts/content/build_5e_starter.py (Open5e's SRD documents, third-party documents dropped, CC-BY notice to Wizards) and build_pf2e_starter.py (the Foundry pf2e system's ORC-licensed records only, flavour text removed, records re-stamped with the ORC Notice and Paizo attribution). The pf2e build stages an ORC-only, flavour-free copy of each pack file and maps it through the real Foundry adapter, so there is one pf2e mapping in the tree. Both are pinned to an upstream commit and byte-identical on a re-run (SOURCE_DATE_EPOCH); the packs they produce are release assets fetched on request, never shipped in the image. Adapter warnings reach counts.warnings and a SOURCES.md section.

Also. The formats-endpoint test is lane 3's registry invariant; the starter script tests pin the rendered body (Statistics and Defences before Strikes, enrichers as prose) so a regression in the Foundry maps shows up here too.

Tests: 368 across the Open5e, starter-script, detection, Foundry, 5etools, orcbrew, import, admin, export, task, URL-fetch, search, game-systems and stat-schema suites. Fixtures are invented; the starter scripts are exercised against fixture trees, not live downloads.

🤖 Generated with Claude Code

Phase 1 of #553, lane 4 of the [content-packs spec](docs/design/content-packs-spec.md). Stacked on #562 (the Foundry fixes); merge that first and this PR's diff shrinks to its own four commits. **The Open5e reader.** Saved v2 API pages, bare arrays, and `open5e-*.zip` bundles map to the shared kinds, with the SRD 5.1 / 5.2.1 CC-BY-4.0 attribution carried as the licence. Registered last in `DETECT_ORDER`; the detection cases for kilobyte-record arrays now answer `open5e` thanks to #562's closed native-array rule. **Two starter-pack builds, as scripts, not bundled content.** `scripts/content/build_5e_starter.py` (Open5e's SRD documents, third-party documents dropped, CC-BY notice to Wizards) and `build_pf2e_starter.py` (the Foundry pf2e system's ORC-licensed records only, flavour text removed, records re-stamped with the ORC Notice and Paizo attribution). The pf2e build stages an ORC-only, flavour-free copy of each pack file and maps it through the real Foundry adapter, so there is one pf2e mapping in the tree. Both are pinned to an upstream commit and byte-identical on a re-run (`SOURCE_DATE_EPOCH`); the packs they produce are release assets fetched on request, never shipped in the image. Adapter warnings reach `counts.warnings` and a SOURCES.md section. **Also.** The formats-endpoint test is lane 3's registry invariant; the starter script tests pin the rendered body (Statistics and Defences before Strikes, enrichers as prose) so a regression in the Foundry maps shows up here too. Tests: 368 across the Open5e, starter-script, detection, Foundry, 5etools, orcbrew, import, admin, export, task, URL-fetch, search, game-systems and stat-schema suites. Fixtures are invented; the starter scripts are exercised against fixture trees, not live downloads. 🤖 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>
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>
rbrooks force-pushed feat/553-open5e-adapter-starters from caa5ac2bdf
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 55s
CI / Backend lint (ruff) (pull_request) Successful in 33s
CI / Bot/backend version sync (pull_request) Successful in 20s
CI / Docker image build (pull_request) Successful in 21s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m32s
CI / Bot tests and audit (pull_request) Successful in 2m14s
CI / Backend migration, tests, and audit (pull_request) Successful in 8m17s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 15m30s
to 59f4ffc44d
All checks were successful
CI / Docker image build (pull_request) Successful in 28s
CI / Bot/backend version sync (pull_request) Successful in 43s
CI / Backend lint (ruff) (pull_request) Successful in 47s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 54s
CI / Bot tests and audit (pull_request) Successful in 1m47s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m34s
CI / Backend migration, tests, and audit (pull_request) Successful in 7m31s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 20m7s
2026-09-08 08:14:27 +00:00
Compare
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-09-08 08:14:34 +00:00
claude-bot deleted branch feat/553-open5e-adapter-starters 2026-09-08 08:34:51 +00:00
Sign in to join this conversation.
No description provided.