feat(dataset): migrate game history and captures into a trainable corpus

Turn the data we already have into an open, educational dataset, so the trace we
are about to start collecting has somewhere to go.

`migrate.py` produces:
  * runs.jsonl          37 runs with outcome, killer, seed and final deck
  * decisions.jsonl     1052 decisions, each with its own outcome attached
  * states_index.jsonl  346 unique observations
  * states/             content-addressed gzipped blobs

Content addressing matters: measured, only 56% of captures are unique, so 44% of
storage is duplicates. 3.28 MB raw -> 0.42 MB stored.

The card-reward rows keep the REJECTED options, so this is a ranking dataset
rather than a classification one, and the per-fight `damage_taken` /
`turns_taken` pair is the dense reward signal a combat policy is judged on.

What it deliberately does NOT do: reconstruct per-step combat state/action pairs.
The session logs record the action but not the observation, and captures exist
only for combat, so a step has a state with no action or an action with no state
-- never both. Inventing them would poison the corpus. The gap is declared in
manifest.json instead, and collect.py will close it going forward.

Integrity checking is a separate entry point (`--check-only`) because `--verify`
alone rebuilds first and so can only ever see data that is correct by
construction -- a smoke test pretending to be a check. All six invariants were
verified by deliberately breaking the dataset and confirming the checker fails.
This commit is contained in:
0xrsydn 2026-09-22 06:06:21 +07:00
commit 8fae007e50
352 changed files with 2164 additions and 0 deletions

34
dataset/manifest.json Normal file
View file

@ -0,0 +1,34 @@
{
"counts": {
"capture_files": 616,
"decisions": 1052,
"runs": 37,
"unique_states": 346
},
"decisions_by_kind": {
"ancient": 43,
"card_reward": 323,
"enchant": 7,
"event": 120,
"potion_reward": 190,
"relic_reward": 162,
"remove": 10,
"rest_site": 97,
"shop_purchase": 19,
"transform": 8,
"upgrade": 73
},
"generated_by": "migrate.py",
"license_note": "Slay the Spire 2 is (c) Mega Crit. Game-derived identifiers are included for research and education.",
"limitations": [
"No per-step combat state/action pairs: the logs record the action but not the observation, and captures exist only for combat. collect.py records these going forward.",
"Per-map-point deck composition is not recorded by the game. The corpus stores observed deltas (cards_gained, cards_removed, upgraded_cards) and the final deck; folding them forward is left to the consumer.",
"0 wins in this corpus. The reward signal has no positive class."
],
"provenance": {
"game_build": "0.107.1",
"game_commit": "59260271",
"mod_commit": "55e0648"
},
"schema": "sts2.manifest/1"
}