Skip to content

Repository files navigation

STS2 WebSocket Control

A Slay the Spire 2 mod that exposes live game state and accepts normal-play gameplay actions over a local WebSocket (ws://localhost:15527), plus a TypeScript/Node CLI that drives the game from a terminal.

  • Scope: read full state + take legal in-game actions (play card, use potion, end turn, navigate, claim rewards, events, shop, rest sites, selection, run/menu management). No god-mode/debug mutation of HP/gold/energy/deck/relics.
  • Build target: Slay the Spire 2 v0.107.1 (Windows-native, .NET 9.0.7, Godot 4).

Why did I build this?

I built this to test out the Oh-My-Pi coding harness and experiment with the latest GLM 5.2 model from z.ai. This took about 800k worth of tokens to get functional, but I'm pretty impressed with how the agent learned during the troubleshooting process. I learned a lot about session context management and setting clear concise goals when talking with the agent.

I originally thought about wiring this up to a twitch stream to provide a TwitchPlays Slay The Spire 2. However, in the course of researching planning material I came across a fully featured MCP and twitch channel already doing this. If anything, I may vibe code a webUI front-end so that I can play from my phone browser.

Another idea was to take the data from SpireBird and see if that information can be used to train a model to be 'quantifiably good' at Slay The Spire. However, I don't have the tokens to burn currently and model training/tuning is not something I understand quite yet.

Demo Video (v.0.2.0)

Slay the Spire 2 TUI - Youtube

Status

Scope State
WS server + get_state (menu/combat/all screens) ✅ verified
Combat: play_card, use_potion, end_turn + energy check ✅ verified
Full map grid, enemy intents, card descriptions/cost, power stacks ✅ verified
Nav/reward/event/shop/rest/treasure/relic + card_select/confirm (incl. potion overlays during combat) ✅ verified
cancel_card_select — go back from a card-select/confirm screen (smith upgrade, transform, removal, …) 🔧 built, needs runtime verify
Menu/run: start_new_game, abandon, return_to_main_menu, continue_game ✅ verified
Pause detection + save_and_quit (soft rollback) ✅ verified
Deck/draw/discard/exhaust pile inspection ✅ verified
discard_potion (engine DiscardPotionGameAction) — CLI discardpotion, TUI d 🔧 built, needs runtime verify
Incoming-damage turn summary (Σ intent damage − block) — CLI + TUI ✅ client-side, unit-tested
Mid-combat curse surfacing (run.player.curses diff) — mod + CLI + TUI 🔧 built, needs runtime verify
TUI pile-view (v) modal viewport clipping ✅ regression-tested
TypeScript CLI (REPL + ASCII map + combat/pile renderers) ✅ typechecks clean
Selection-mode combat actions (combat_select_card, etc.) ❌ not implemented

Build (containerized — zero host toolchain install)

Everything builds in rootless Podman. The only host install is Podman itself; the .NET 9 SDK and Node come from the images. The game DLLs are mounted read-only; the only host write is the repo (build outputs).

# From the sts2-ws-control\ repo root:
pwsh .\podman-build.ps1          # build mod + CLI in the container, stage build/mods/
pwsh .\install.ps1               # copy staged {dll,json} -> <game>\mods\STS2_WSControl\  (only host write outside repo)

Or build the mod directly (fastest iteration, no Node layer):

podman run --rm `
  -v "${PWD}:/work" `
  -v "C:\Program Files (x86)\Steam\steamapps\common\Slay the Spire 2\data_sts2_windows_x86_64:/game/dlls:ro" `
  mcr.microsoft.com/dotnet/sdk:9.0 `
  dotnet build /work/mod/STS2_WSControl.csproj -c Release -p:Sts2DataDir=/game/dlls -p:GameDir=/game

Launch the game, enable STS2 WebSocket Control in the mod manager, agree to load mods. On success the log shows [STS2 WebSocket Control] server started on ws://localhost:15527/.

Smoke test (no Node needed)

scripts\ws-probe.ps1 is a pure-.NET WebSocket client (pwsh 7):

pwsh .\scripts\ws-probe.ps1                                   # get_state
pwsh .\scripts\ws-probe.ps1 -Action play_card -ArgsJson '{"card_index":0,"target":"1"}'
pwsh .\scripts\ws-probe.ps1 -Action end_turn

CLI (TypeScript)

podman run --rm -v "${PWD}:/work" -w /work/cli node:22 sh -c "npm install && npm run dev"

CLI (Mise)

If mise is installed navigate to /cli and run mise run dev

Networking note

The mod binds loopback (localhost/127.0.0.1). A CLI running in a container cannot reach the host's loopback by default (rootless Podman + the podman-machine WSL2 boundary). For now, drive the game from the host (pwsh ws-probe.ps1, or run the CLI on a host with Node). To run the CLI in-container later, rebind the mod to http://+:15527/ and connect via host.containers.internal — a documented plan delta.

TUI (Go + Bubble Tea) — proof of concept

A full-screen terminal client in tui/, built with Bubble Tea and lipgloss. It auto-reconnects, polls get_state, renders every screen (combat with intents + HP bars, the map grid, rewards, shop, events, rest sites, relic/treasure selects, pause), and drives the game from the keyboard. Like the TS CLI it runs on the host to reach the mod's loopback socket.

cd tui
# build a Windows binary via the container toolchain (no host Go install):
podman run --rm -v "${PWD}:/work" -w /work docker.io/library/golang:1.24 `
  sh -c "GOOS=windows GOARCH=amd64 go build -o sts2-tui.exe ."
.\sts2-tui.exe                     # connects to ws://localhost:15527/ (override with -url)

With Go 1.24+ on the host: go build / go test ./... from tui/. Press ? in-app for the full key reference; see tui/README.md.

Protocol

JSON, one message per WS text frame:

client → server : { "type":"request",  "id":int, "action":string, "args":{…}? }
server → client : { "type":"response", "id":int, "ok":bool, "result":{…} | "error":string }

get_state returns { state_type, run?, combat?, … }. state_type ∈ menu | monster | elite | boss | hand_select | combat_rewards | card_reward | map | rest_site | shop | event | card_select | relic_select | treasure | game_over | overlay. Poll-based: call get_state, act, re-get_state.

How this was built (no external mod reference)

The mod is implemented against the game's own public API, verified by reflecting over the shipped sts2.dll (scripts\probe-api.ps1 / probe-types.ps1) and the XML doc. Key confirmed entry points: RunManager.Instance / .DebugOnlyGetState(), CombatManager.Instance.DebugOnlyGetState(), LocalContext.GetMe(...), and combat dispatch via PlayCardAction/UsePotionAction/ EndPlayerTurnAction enqueued to RunManager.Instance.ActionQueueSet.

Layout

mod/                     the .NET 9 mod (STS2_WSControl.{csproj,json} + WsControlMod*.cs + Protocol.cs)
cli/                     TypeScript CLI (src/{protocol,client,format,repl,index}.ts)
tui/                     Go + Bubble Tea full-screen TUI client (main.go, client.go, model.go, view.go)
scripts/                 probe-api.ps1, probe-types.ps1, ws-probe.ps1 (reflection + smoke-test tools)
Containerfile, compose.yaml, podman-build.ps1, install.ps1   container build/install
archive/                                                     superseded planning docs (PLAN, TODOS, etc.)

About

A websocket server and cli to play Slay The Spire 2 over a terminal.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages