Convert tabletop adventure module PDFs into playable osrlib Adventure documents.
Project description
osr-forge
Convert tabletop adventure module PDFs into playable osrlib Adventure documents: an LLM-assisted extraction pipeline, deterministic map-geometry synthesis, a human correction loop built on an overrides file, and validation against the real osrlib models.
Standalone package + CLI. Consumers need only its artifacts — adventure.json, report.json, overrides.yaml, SVG map previews — or its CLI, regardless of their own tech stack.
Status: released — install from PyPI, docs at https://mmacy.github.io/osr-forge/:
uv pip install osr-forge # or: pip install osr-forge
The package runs the whole pipeline — preprocessing, the survey (chunked past survey_max_pages) and content extraction stages, monster resolution against the osrlib catalog, deterministic geometry synthesis (doors and secret doors on the stating room's wall, stair and trapdoor transitions from keyed and level-shaped links, guessed landings flagged), and assembly — plus the human correction loop (overrides application, rerun/resume, the playability lint with its smoke delve, cost estimation) and the eval harness that keeps extraction quality a measured number (tools/eval/). The roadmap's phase plans live in docs/ beside the specification.
The library entry points are convert(), assemble(), check(), and estimate(); the osrforge console script wraps them:
osrforge estimate my-module.pdf # preprocess only; rough token/cost estimate
osrforge convert my-module.pdf # full pipeline into ./my-module.forge
osrforge assemble --workdir my-module.forge # stage caches + overrides → artifacts, pure
osrforge check --workdir my-module.forge # validate_adventure + the playability lint
osrforge preview --workdir my-module.forge # regenerate the SVG maps only
osrforge rerun assemble --workdir my-module.forge # resume any stage through assemble
Recording sessions and live verification runs are driven via tools/extract/run_extraction.py (see tools/extract/README.md); the on-demand eval harness — corpus, scorer, and scoreboard — lives in tools/eval/ (see tools/eval/README.md). The harness also measures your own retail modules: a private corpus directory side-loads with --corpus DIR (integrity via a local hash sidecar, truth authored per tools/eval/AUTHORING.md), and an explicit publish step copies aggregate-only scores — never module text — onto the committed BYOM scoreboard.
The correction loop
Conversion produces a draft — every gap and guess called out in report.json — and corrections live in overrides.yaml, never in hand-edits to generated output (a fresh convert leaves a commented template there). The loop:
- Read
report.json(flags, findings, the monsters summary) and eyeballpreviews/*.svgagainst the printed map. - Edit
overrides.yaml: monster remaps, per-area field replacement, area adds and removes, geometry (cells, edges, entrance, transitions), town/module metadata. Every entry carries areason, and every entry must take effect — a typo'd address fails assembly loudly instead of silently doing nothing. osrforge assemble && osrforge check— re-assembly is pure and instant (no model calls), andcheckexits 0 once validation passes and no error-severity finding remains.- Repeat until publishable.
Settings changes on an existing workdir go through rerun --set — for example, osrforge rerun preprocess --set 'blank_page_renders=[21]' blanks a render Azure's content filter rejects, or osrforge rerun assemble --set unresolved_fallback=omit flips the stand-in policy without re-rolling the model.
Development quickstart
Requires Python ≥ 3.14 and uv.
uv sync
uv run pytest
The full check suite, as CI runs it:
uv run ruff format --check && uv run ruff check && uv run pyright && uv run pytest
Tests use no network — model interactions replay from recorded fixtures. The only live-network activity in the repo is manual: the Foundry capability spike (tools/spike/) and the extraction runner (tools/extract/).
Pipeline settings
ConversionSettings holds the deterministic knobs, echoed into each workdir's run.json:
| Knob | Default | Meaning |
|---|---|---|
render_dpi |
150 | Page-render resolution (a legibility knob, not a cost knob — see docs/foundry-capabilities.md) |
max_pages |
200 | Source page-count guardrail |
max_source_bytes |
100 MiB | Source file-size guardrail |
blank_page_renders |
() |
Page numbers whose renders are emitted as blank white PNGs (text layer still extracted) — the content-safety-filter workaround; each blanked page is flagged page_unreadable in the report |
content_batch_pages |
8 | Content-pass batch size in pages (floor 2) |
survey_max_pages |
50 | The survey chunk size — the service's measured 50-images-per-request cap: a source at or under it surveys in one request; a larger source surveys in page windows of this size, merged before normalization |
monster_fuzzy_threshold |
0.85 | Monster resolution's fuzzy-tier auto-accept floor, pinned against measured catalog pairs |
monster_llm_top_k |
8 | Candidate templates offered per name in the monster-resolution LLM tier |
custom_monsters |
emit |
Whether the monsters stage runs the stat-block pass feeding custom-template emission: emit gives unresolved names the module's own creatures; off skips the per-unresolved-name model spend and keeps the draft SRD-catalog-pure (monsters-stage-owned — toggling it re-runs monsters, including its LLM tier) |
unresolved_fallback |
best-effort |
Where resolution or parsing came up empty and no usable stat block exists: flagged level-band monster stand-ins and unguarded-treasure rolls (best-effort), or leave the gap (omit) |
On an existing workdir, change a knob with rerun --set KEY=VALUE: the update lands in the run.json settings echo before the chain runs, and a knob owned by a stage upstream of the rerun stage is rejected with the stage to rerun instead.
Provider configuration
The Azure AI Foundry adapter reads its connection from OSRFORGE_FOUNDRY_* environment variables (an osr-forge-specific prefix, deliberately not AZURE_OPENAI_*, to avoid colliding with other tools' conventions):
| Variable | Azure meaning | Required |
|---|---|---|
OSRFORGE_FOUNDRY_ENDPOINT |
The Azure OpenAI resource endpoint (https://<resource>.openai.azure.com) |
yes |
OSRFORGE_FOUNDRY_DEPLOYMENT |
The model deployment name | yes |
OSRFORGE_FOUNDRY_API_KEY |
The API key. Omit to use Entra ID via DefaultAzureCredential, which needs the osr-forge[entra] extra |
no |
Licensing
Package code is MIT (see LICENSE). osr-forge ships no game content: osrlib's OGL data stays in osrlib, and test assets live under tests/assets/ outside the built distribution, each with its provenance and license documented in tests/assets/README.md.
These fences govern what this repository redistributes, not what you convert: bringing your own purchased, non-redistributable module is the primary use case. Conversion runs locally and everything derived from your module stays in your own workdir — nothing is shared unless you share it.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file osr_forge-0.1.0.tar.gz.
File metadata
- Download URL: osr_forge-0.1.0.tar.gz
- Upload date:
- Size: 124.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c0c77e1782d39169ea0622fa7b592503b72c79d3e98692bd6ee52765f82279c
|
|
| MD5 |
70712e7798f1d1d7f5d04b5541765c18
|
|
| BLAKE2b-256 |
b0d87c919b455c29d9b9663de29386317de84b8ae9dcd20047ec90fe60e2a4fc
|
Provenance
The following attestation bundles were made for osr_forge-0.1.0.tar.gz:
Publisher:
release.yml on mmacy/osr-forge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
osr_forge-0.1.0.tar.gz -
Subject digest:
0c0c77e1782d39169ea0622fa7b592503b72c79d3e98692bd6ee52765f82279c - Sigstore transparency entry: 2209651361
- Sigstore integration time:
-
Permalink:
mmacy/osr-forge@7e81dc32f7acf46e122f741d8ead5f65e9279069 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mmacy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7e81dc32f7acf46e122f741d8ead5f65e9279069 -
Trigger Event:
push
-
Statement type:
File details
Details for the file osr_forge-0.1.0-py3-none-any.whl.
File metadata
- Download URL: osr_forge-0.1.0-py3-none-any.whl
- Upload date:
- Size: 141.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aba42e7c12a2ff8ecaab60d38878f6ae0c31bfcc174be62870c86f668e625137
|
|
| MD5 |
2d8c25d6da2fe15bdcc864e360c9d935
|
|
| BLAKE2b-256 |
8173506eb31679d6dc2abfd30090155ea2322ff458112dc29cd6ed5c52daad6e
|
Provenance
The following attestation bundles were made for osr_forge-0.1.0-py3-none-any.whl:
Publisher:
release.yml on mmacy/osr-forge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
osr_forge-0.1.0-py3-none-any.whl -
Subject digest:
aba42e7c12a2ff8ecaab60d38878f6ae0c31bfcc174be62870c86f668e625137 - Sigstore transparency entry: 2209651389
- Sigstore integration time:
-
Permalink:
mmacy/osr-forge@7e81dc32f7acf46e122f741d8ead5f65e9279069 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mmacy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7e81dc32f7acf46e122f741d8ead5f65e9279069 -
Trigger Event:
push
-
Statement type: