Add design doc and research notes
DESIGN.md covers the three-layer architecture (facts in code, Jev for tactics, gated escalation for macro). research/ documents the engine and mod surface, the Jev classifier's measured behavior, the STS2MCP HTTP interface, state shapes, failure modes, decision architecture, and a run log of the first four sessions.
This commit is contained in:
commit
fb32822468
9 changed files with 2341 additions and 0 deletions
168
docs/research/03-sts2mcp-interface.md
Normal file
168
docs/research/03-sts2mcp-interface.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# 03 — STS2MCP interface
|
||||
|
||||
A community mod by `kunology` that exposes the running game over a local HTTP
|
||||
API. Repository: `github.com/Gennadiyev/STS2MCP`, MIT.
|
||||
|
||||
It is the actuator for this project: it reads state and performs one action at
|
||||
a time. It does **not** alter gameplay (`affects_gameplay: false`).
|
||||
|
||||
## Endpoints
|
||||
|
||||
Base URL `http://localhost:15526`, no authentication.
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| GET | `/api/v1/singleplayer?format=json\|markdown` | Read state |
|
||||
| POST | `/api/v1/singleplayer` | Perform one action |
|
||||
| GET | `/api/v1/multiplayer` | Read multiplayer state |
|
||||
| POST | `/api/v1/multiplayer` | Multiplayer action |
|
||||
| GET | `/api/v1/profile` | Persistent profile progress |
|
||||
| GET | `/api/v1/compendium` | Compendium-shaped progress |
|
||||
| GET | `/api/v1/wiki?query=&item_type=&limit=` | Fuzzy card/relic lookup |
|
||||
| GET/POST | `/api/v1/profiles` | List / switch / delete profile slots |
|
||||
|
||||
All POST bodies carry an `"action"` field. Responses carry
|
||||
`{"status": "ok"|"error", "message": ...}`.
|
||||
|
||||
## `state_type` is the action enumerator
|
||||
|
||||
Every response carries `state_type`, and each type has a small fixed action
|
||||
set. **This is the action space.** There is no need to write one.
|
||||
|
||||
| `state_type` | Legal actions |
|
||||
|---|---|
|
||||
| `menu`, `game_over` | `menu_select` |
|
||||
| `monster` / `elite` / `boss` | `play_card`, `use_potion`, `end_turn` |
|
||||
| `hand_select` | `combat_select_card`, `combat_confirm_selection` |
|
||||
| `rewards` | `claim_reward`, `proceed` |
|
||||
| `card_reward` | `select_card_reward`, `skip_card_reward` |
|
||||
| `map` | `choose_map_node` |
|
||||
| `event` | `choose_event_option`, `advance_dialogue` |
|
||||
| `rest_site` | `choose_rest_option`, `proceed` |
|
||||
| `shop`, `fake_merchant` | `shop_purchase`, `proceed` |
|
||||
| `treasure` | `claim_treasure_relic`, `proceed` |
|
||||
| `card_select` | `select_card`, `confirm_selection`, `cancel_selection` |
|
||||
| `bundle_select` | `select_bundle`, `confirm_bundle_selection`, `cancel_bundle_selection` |
|
||||
| `relic_select` | `select_relic`, `skip_relic_selection` |
|
||||
| `crystal_sphere` | `crystal_sphere_set_tool`, `_click_cell`, `_proceed` |
|
||||
| `overlay`, `unknown` | none (transitions, or manual) |
|
||||
|
||||
`use_potion` and `discard_potion` work in any state where potions are
|
||||
reachable, including outside combat.
|
||||
|
||||
## VERSION DRIFT — release 0.4.0 is broken on this build
|
||||
|
||||
### Symptom
|
||||
|
||||
`GET /api/v1/singleplayer` returns **HTTP 500** as soon as combat starts:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Failed to read game state: Method not found:
|
||||
'Boolean MegaCrit.Sts2.Core.Combat.CombatManager.get_IsPlayPhase()'.",
|
||||
"exception_type": "System.MissingMethodException",
|
||||
"stack_trace": " at STS2_MCP.McpMod.BuildBattleState(RunState, CombatRoom)"
|
||||
}
|
||||
```
|
||||
|
||||
Menu, character select, map, rewards, and events all work. **Only combat
|
||||
fails** — the one state that matters most.
|
||||
|
||||
### Root cause
|
||||
|
||||
`CombatManager.IsPlayPhase` was removed in game `v0.107.1`. Verified:
|
||||
|
||||
```bash
|
||||
grep -c IsPlayPhase sts2.xml # -> 0
|
||||
```
|
||||
|
||||
Replacements present in `v0.107.1`:
|
||||
|
||||
- `MegaCrit.Sts2.Core.Combat.PlayerTurnPhase.Play` (per-player)
|
||||
- `PlayerCombatState.Phase`
|
||||
- `ActionSynchronizerCombatState.PlayPhase` / `.NotPlayPhase`
|
||||
- `CombatManager.DebugOnlyGetState().CurrentSide == CombatSide.Player`
|
||||
|
||||
### Timeline
|
||||
|
||||
| Event | Date |
|
||||
|---|---|
|
||||
| Release `0.4.0` (broken) | 2026-05-05 |
|
||||
| Fix commit `55e06485`, PR #123 | 2026-07-29 |
|
||||
| Newer tag | none |
|
||||
| CI build artifact | none |
|
||||
| Fork shipping a prebuilt DLL | none (checked 12 forks) |
|
||||
|
||||
The fix exists in source but was never released.
|
||||
|
||||
### Resolution
|
||||
|
||||
Build from upstream `main`. The fix is present and documented in the code:
|
||||
|
||||
```csharp
|
||||
// STS2 v0.107 removed CombatManager.IsPlayPhase. The turn phase now lives per-player on
|
||||
// PlayerCombatState.Phase
|
||||
internal static bool IsPlayPhase(Player? player)
|
||||
{
|
||||
if (!IsPlayerSideTurn()) return false;
|
||||
return player?.PlayerCombatState?.Phase == PlayerTurnPhase.Play;
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
nix shell nixpkgs#dotnet-sdk_9 --command bash -c '
|
||||
dotnet build STS2_MCP.csproj -c Release -o out/STS2_MCP \
|
||||
-p:STS2GameDir="$HOME/Library/Application Support/Steam/steamapps/common/Slay the Spire 2"'
|
||||
```
|
||||
|
||||
`Build succeeded. 0 Warning(s) 0 Error(s).` Result: 236,544 B DLL, replacing
|
||||
the 194,560 B release. The release DLL is kept at
|
||||
`vendor/STS2_MCP.dll.release-0.4.0`.
|
||||
|
||||
**Lesson:** the game moves faster than the mod's releases. Upstream open issues
|
||||
#131 and #132 already track `v0.111` compatibility. Expect this class of break
|
||||
to recur after any game update. Pin the mod build to the game build.
|
||||
|
||||
## Known doc drift inside the mod
|
||||
|
||||
`AGENTS.md` in the repo uses MCP **tool** names; `docs/raw-simplified.md` uses
|
||||
the HTTP **action** names. They do not match:
|
||||
|
||||
| AGENTS.md | HTTP action |
|
||||
|---|---|
|
||||
| `combat_end_turn` | `end_turn` |
|
||||
| `event_choose_option` | `choose_event_option` |
|
||||
| `rewards_pick_card` | `select_card_reward` |
|
||||
| `rest_choose_option` | `choose_rest_option` |
|
||||
| `proceed_to_map` | `proceed` |
|
||||
|
||||
Trust `raw-simplified.md` and the live API for direct HTTP use.
|
||||
|
||||
## Hard blocker: Timeline cannot be automated
|
||||
|
||||
After a run ends with a pending epoch, the mod refuses:
|
||||
|
||||
```json
|
||||
{"status":"error",
|
||||
"error":"Timeline has obtained epochs that still need to be revealed manually;
|
||||
not opening Timeline because this game state logs invalid unlock-state
|
||||
errors when entered through automation",
|
||||
"pending_epoch_ids":["NEOW_EPOCH"],
|
||||
"manual_action_required":true}
|
||||
```
|
||||
|
||||
This is a **deliberate guard**, not a bug. Entering Timeline through automation
|
||||
would corrupt unlock state. A human must open the Timeline and reveal the
|
||||
epoch. Until then the main menu offers only `settings` and `quit` and no run
|
||||
can be started.
|
||||
|
||||
## Mod config
|
||||
|
||||
The mod writes `mods/STS2_MCP.conf`:
|
||||
|
||||
```json
|
||||
{ "port": 15526 }
|
||||
```
|
||||
|
||||
It also injects an **"Instant Mode"** checkbox into the game settings, which
|
||||
shortens animations. Worth enabling for batch runs.
|
||||
Loading…
Add table
Add a link
Reference in a new issue