Homebrew
Canonical homebrew authoring model and dual-edition projection pipeline.
Homebrew
Homebrew records are authored canonically and projected into edition-specific runtime shapes. The homebrew subsystem allows custom classes and subclasses to be added to Lorewright without modifying SRD source data, while ensuring homebrew content flows through the same generation and serving pipeline as official content.
Canonical Authoring
Authors create a single canonical JSON file per entity:
homebrew/classes/{index}.json— one file per homebrew class (e.g.,bloodrager.json)homebrew/subclasses/{index}.json— one file per homebrew subclass- Schema models in
engine/ingest/schemas.pyenforce structure viaCanonicalHomebrewClassandCanonicalHomebrewSubclass, discriminated by atypefield. Pydantic validation runs at load time inengine/ingest/homebrew_store.py, so malformed records fail immediately rather than producing corrupt downstream artifacts.
The canonical representation is edition-neutral — it describes the homebrew entity's features, mechanics, and level progression in a normalized form that the projection step maps onto each edition's conventions.
Projection Model
During db-refresh, the homebrew store reads all canonical files and writes projected rows into the same SQLite tables that hold SRD data:
db-refreshwrites projected rows for 2014 and 2024 compatibility. Each canonical homebrew record produces one or two rows depending on which editions it targets.- Merged views unify SRD and homebrew rows for generation and API retrieval. From the perspective of
generate-progressions, a homebrew class is indistinguishable from an SRD class — it appears in the same tables withsource = 'homebrew'instead of'srd'.
This projection model means the downstream pipeline (progression generation, manifest building, API serving) is source-agnostic. It never branches on whether content is SRD or homebrew; the source tag is metadata, not control flow.
Design Rationale
The canonical-then-project pattern was chosen over direct edition-specific authoring for several reasons:
- Single source of truth — authors maintain one file per entity, not one per edition. This eliminates the class of bugs where a 2014 version diverges from a 2024 version due to a forgotten edit.
- Schema enforcement at the boundary — Pydantic validation with
extra="forbid"catches typos and structural drift at load time, before any data reaches SQLite. - Separation of authoring and runtime concerns — the canonical format is optimized for human editability (flat JSON, clear field names). The projected SQLite rows are optimized for query efficiency. Neither format compromises for the other.
An alternative — letting authors write edition-specific files directly — was rejected because it would double the authoring surface and create synchronization hazards. The projection step is cheap and deterministic, so the indirection cost is minimal.
Assumptions & Constraints
- Homebrew must fit within SRD schema shapes. Since
CanonicalHomebrewClassprojects into the sameClassProgressionschema used by SRD content, homebrew cannot define entirely novel progression structures (e.g., a class with more than 20 levels, or a non-standard level progression curve). This is a deliberate constraint to keep the pipeline uniform. - Entity identity rules apply equally. Homebrew indices must not collide with SRD indices within the same
entity_type+srd_versionspace. The manifest enforces uniqueness, so a homebrew class named"fighter"would conflict with the SRD fighter. - Reviews are not gating. The review documents at
homebrew/reviews/are informational — they do not block generation or serving. A homebrew class withreview_status: "pending"still appears in the manifest and API. This is a conscious trade-off favoring iteration speed over quality gating. - File-based authoring only. There is no API endpoint for creating or editing homebrew records. Authors edit JSON files on disk and run
db-refreshto propagate changes. This keeps the authoring flow simple but means the system has no concept of draft vs. published homebrew state beyond the review documents.
Conceptual Model
Homebrew occupies a parallel authoring lane that merges into the main pipeline at the SQLite layer:
SRD JSON ──→ db-refresh ──→ 5e-database.sqlite ──→ generate-progressions
↑
Homebrew JSON ────┘
The merge point is the database. After db-refresh, the distinction between SRD and homebrew is purely a metadata tag on each row. This means every downstream system — progression generation, manifest building, API serving, the level-up engine — works identically for both. The only place homebrew-specific logic exists is in the ingestion layer (homebrew_store.py), which knows how to read canonical files and normalize them.
The review workflow is intentionally decoupled from the data pipeline. Reviews exist as file-based artifacts for human inspection, not as pipeline gates. This reflects the project's current stage where homebrew content is author-tested rather than formally reviewed.
Reviews
Review documents remain file-based at homebrew/reviews/{entity_type}/{edition}/{index}.json. Each review file is edition-specific (since projection may produce different feature distributions per edition) and carries a review_status field (pending, approved, flagged) along with per-level commentary.