tSC-DataModel is the foundation module for The Spire Chronicles suite.
It provides shared combat/run telemetry models, aggregation, persistence, and a stable contracts API for other modules and modders.
Every card, relic, and status effect in a fight is dissected and attributed, then exposed as an auditable data surface the rest of the suite reads from. DataModel is the only module that touches the game's internals; everything else is a consumer.
- Slay the Spire 2:
v0.107.1
A single damage number feels like truth, but it is a measurement, not an achievement — and once it becomes the scoreboard it actively misleads. It erases the Vulnerable that added 50%, the energy handed to a teammate, the block that kept the party alive. This framework replaces the tyranny of that one number with a principled, auditable, event-level accounting of what each player actually contributed, so support play finally shows up in the record it earned.
The full argument — with the psychology and the citations behind it — is in docs/motivation.md. The technical how is in the Integration Guide.
Primary in-repo consumers:
InsightsDamageMeterActBossAward
DataModel captures game events, attributes and aggregates them, and publishes a single stable contract. Feature mods never touch the game or the internal stores — they resolve one API object and read from it.
flowchart TD
STS2["Slay the Spire 2 (Harmony hooks)"] --> CAP["DataModel capture & attribution"]
CAP --> STORE["Internal stores<br/>LiveCombatStatsAggregator · CurrentRunStatisticsStore"]
STORE --> API["TheSpireChronicles.Contracts<br/>IChroniclesDataApi · ChroniclesMetricCatalog · Event bus"]
API -->|"ChroniclesApiRegistry.TryGet(out api)"| DM["tSC-DamageMeter"]
API -->|"ChroniclesApiRegistry.TryGet(out api)"| IN["tSC-Insights"]
API -->|"ChroniclesApiRegistry.TryGet(out api)"| AB["tSC-ActBossAward"]
API -->|"ChroniclesApiRegistry.TryGet(out api)"| TM["tSC-Telemetry"]
API -->|"ChroniclesApiRegistry.TryGet(out api)"| EXT["your mod"]
subgraph consumers["Consumers (contract-only, read-through)"]
DM
IN
AB
TM
EXT
end
Consumers reach the data three ways, all off the same api root:
api.Combat— live, in-fight KPI mapsapi.Run— cumulative run aggregates, snapshots, source leavesapi.Events— push notifications (lifecycle by default; opt in to high-volume streams)
- Stable contract API under
TheSpireChronicles.Contracts(ChroniclesApiRegistry,IChroniclesDataApi, DTO snapshots). - Persisted run/combat data models and schema-aware storage flow.
- Core telemetry aggregation and shared runtime helpers used by the rest of the suite.
- No checkout-time requirement for
assets/characters,assets/sounds, or award KPI folders — seeassets/README.md(KPI icons ship with tSC-ActBossAward).
Contract boundary details live in:
- Integration Guide — API surface, bootstrap, event bus, metric catalog
- Motivation — the design rationale behind attribution
Steam Workshop (recommended): subscribe to tSC-DataModel (3757787104).
Manual:
- Download a release zip from GitHub Releases.
- Extract into
Slay the Spire 2/mods/tSC-DataModel. - Ensure the folder contains
TheSpireChronicles.dllandTheSpireChronicles.json.
- Reference the contracts namespace (
TheSpireChronicles.Contracts) instead of internalDatahelpers when possible. - Use
ChroniclesApiRegistry.TryGet(out IChroniclesDataApi api)at runtime and gate features if unavailable. - Metric wire ids should be resolved through
ChroniclesMetricCatalog, not hardcoded.
Full walkthrough — bootstrap, compile-time dependency probes, the event bus, and the metric catalog contract — is in the Integration Guide.
Resolve the API, then read a run aggregate. Query with the typed ChroniclesMetricId and
resolve snapshot keys through the catalog — never hardcode wire ids:
if (!ChroniclesApiRegistry.TryGet(out IChroniclesDataApi api))
return; // DataModel not loaded — disable gracefully
if (api.Run.TryGetSnapshot(out RunAggregateSnapshot snap)
&& ChroniclesMetricCatalog.TryGetWireId(ChroniclesMetricId.DamageRaw, out string wireId)
&& snap.KpiByMetricId.TryGetValue(wireId, out var damageByPlayer))
{
foreach (var (playerKey, amount) in damageByPlayer)
Log($"{playerKey} dealt {amount} raw damage this run");
}The RunAggregateSnapshot you get back is a flat, auditable record — keys are catalog wire
ids, inner keys are player index keys (P:<netId>):
Because a supporter's support.energy.provided.to.teammates sits next to a striker's
damage.raw, both contributions show up in the same record — the whole point of the
framework.
REST upload is owned by the separate tSC-Telemetry module. DataModel captures and persists combat/run data locally; Telemetry subscribes to finalized combat snapshots and POSTs opt-in upload envelopes.
See Telemetry/README.md in the Telemetry mod project for upload configuration.
- Build version comes from
Directory.Build.props(Version,CompiledAgainstSts2Versions,BuildArtifactVersion). - Compiled-against StS2 version is listed in Built against above; update both when recompiling against a new game build.
- The module logs compatibility warnings for unknown game builds and continues.
- Artifact naming:
TheSpireChronicles_<mod_version>_StS2_<compiled_versions>.zip
Repo-root files follow GitHub conventions; everything else lives under docs/ and is
also published to the Project Wiki.
DataModel/
├─ README.md ← you are here
├─ CHANGELOG.md
├─ CONTRIBUTING.md
├─ THIRD_PARTY_NOTICES.md
├─ LICENSE.txt
└─ docs/
├─ motivation.md ← why the framework attributes every contribution
├─ integration.md ← technical how-to for consuming the contract
└─ reference/
├─ captured-cards.md ← cards tSC recognizes for attribution
└─ captured-relics.md ← relics tSC recognizes for attribution
- Motivation · Integration Guide
- Captured cards · Captured relics
CONTRIBUTING.md·CHANGELOG.md·THIRD_PARTY_NOTICES.md
This is an independent community project and is not affiliated with or endorsed by Mega Crit.

{ "RunId": "seed-8F3A...", "CombatsCompleted": 6, "TotalCardsPlayed": 214, "SchemaVersion": "3.x", "KpiByMetricId": { "damage.raw": { "P:1": 1840, "P:2": 990 }, "support.energy.provided.to.teammates": { "P:2": 12 } } }