Progressions
Generated class and subclass progression artifacts and manifest conventions.
Progressions
Progressions are generated JSON artifacts written under progressions/. They represent the fully resolved, per-level capability profile of every class and subclass across supported SRD editions. The generation pipeline reads from the ingested SQLite database (data/5e-database.sqlite) and emits immutable JSON files that the API and UI consume directly — no runtime database queries are needed to serve progression data.
Output Layout
progressions/manifest.json— catalog of all generated entries, keyed by entity identity (entity_type+srd_version+index). The manifest carries schema version2.0and agenerated_attimestamp so consumers can detect staleness.progressions/classes/{edition}/{class}.json— one file per class per edition, containing aClassProgressionwith 20 levels of features, mechanics, spell grants, and spellcasting metadata.progressions/subclasses/{edition}/{class}/{subclass}.json— one file per subclass, containing aSubclassProgressionwith only the levels at which the subclass contributes features.
Contracts
- Data shapes are modeled in
engine/ingest/schemas.py.ClassProgressionandSubclassProgressionextend a sharedBaseProgressionthat enforcesentity_type,srd_version,source, and alevelsdict keyed by level number strings. EachProgressionLevelcarries optionalprof_bonus,ability_score_bonuses, typedMechanicRefentries,FeatureRefentries with descriptions, andSpellGrantentries. - API retrieval paths are documented in API Contracts.
Design Rationale
The progression system uses a generate-once, serve-many architecture. Rather than querying SQLite at request time and assembling progression data on the fly, the pipeline pre-computes every class and subclass file so the API layer can serve them as static lookups from the manifest. This was chosen for several reasons:
- Determinism — generated artifacts are versioned and reproducible. Running
generate-progressionsagainst the same database always produces identical output, making it easy to diff across pipeline runs. - Decoupling — the API never touches the ingestion database directly. This means the ingestion schema can evolve (new tables, renamed columns) without breaking the serving layer, as long as the emitted JSON conforms to the progression schema.
- Auditability — every progression file embeds a
generated_attimestamp and schema version. The manifest acts as a bill of materials for exactly which entities were generated and what file paths they map to.
An alternative approach — serving progressions dynamically from SQLite — was rejected because it would couple API latency to database query complexity, and would make it harder to guarantee consistent snapshots when multiple entities are fetched in a single page load.
Assumptions & Constraints
- Entity identity is a triple:
entity_type+srd_version+index. This triple must be unique across all manifest entries. File paths are derived deterministically from this identity, so renaming an entity's index requires regeneration. - Homebrew and SRD share the same progression schema. Homebrew classes appear in the manifest with
source: "homebrew"but use identicalClassProgression/SubclassProgressionshapes. This simplifies the API and UI but means homebrew cannot define progression structures that the SRD schema doesn't support. - Levels are string-keyed. The
levelsdict uses"1"through"20"as keys, not integers. This is a Pydantic/JSON serialization choice that must be respected in all consumers. - Regeneration is the only update path. There is no incremental update mechanism — any change to source data (SRD JSON, homebrew files, or ingestion logic) requires a full
db-refresh+generate-progressionscycle. This keeps the pipeline stateless but means small changes are expensive. - Spell grants are denormalized. Each
SpellGrantcarriesspell_index,spell_name,grant_kind, and asourcetag. This denormalization avoids join-time lookups but means spell metadata changes (e.g., a renamed spell) require regeneration.
Conceptual Model
The progression pipeline sits at the boundary between raw source data and application-consumable artifacts. Conceptually:
SRD JSON + Homebrew JSON
↓ db-refresh
5e-database.sqlite
↓ generate-progressions
progressions/*.json + manifest.json
↓ API / UI
Level-up engine, character tools
Each layer has a clear responsibility: ingestion normalizes heterogeneous sources into a uniform relational model; generation flattens the relational model into per-entity documents optimized for reads; and the API layer treats those documents as opaque blobs to serve. The manifest is the single coordination point — it tells the API what exists and where to find it, without the API needing to understand how generation works.
A key trade-off is storage vs. computation. By pre-generating ~100+ JSON files, the system trades disk space for zero-cost reads. The manifest file itself is small (a few hundred entries), so full scans for filtering by edition or entity type are trivially fast.
Operational Notes
- Regeneration procedures are in Rebuild Runbook.
- Validation coverage is in Testing Strategy.