sts2-bot/ui/JevOverlay/README.md
0xrsydn 108fd8bf80 docs(ui): document overlay setup and verification
Describe the compact panel, module boundaries, build and installation steps, read-only limits, and manual acceptance checks. Record user-confirmed in-game readability.
2026-09-23 13:40:02 +07:00

8.6 KiB
Raw Blame History

Jev decision overlay

A separate, read-only C# mod for Slay the Spire 2. It displays the bot's existing decisions.jsonl file inside the game. It does not depend on STS2MCP at runtime. The bot still uses STS2MCP to observe and control the game.

UI plan

The first version uses the compact panel selected during planning:

┌ Jev decisions · read only ───────── [] ┐
│ Read-only log · no new rows for 2s      │
│ #42 · JEV · combat                     │
│ Session: …                             │
│ Proposed: play_card                    │
│ Parameters: {"card_index": 0}          │
│ Proposal only; execution not recorded. │
│ Reason: …                              │
│ Recorded confidence: 0.81 · 720 ms      │
│ [Show questions and answers]           │
├ Recent decisions · select to inspect ──┤
│ #42  JEV       play_card                │
│ #41  CODE      use_potion               │
│ #40  FALLBACK  end_turn                 │
│ [Follow latest]                        │
└────────────────────────────────────────┘
  • The panel starts at the top right. F8 or the header button collapses it.
  • Selecting a history row stops automatic selection of new decisions.
  • Follow latest resumes automatic selection.
  • The details button shows recorded Jev questions, answers, model, and latency.
  • Text areas and history scroll independently. Evidence uses plain text, not markup.
  • Source names remain visible. Blue identifies Jev; amber identifies fallback.
  • Only the panel receives mouse input. There is no full-screen input blocker.
  • The panel uses Godot's default font and controls. It needs no asset pack.

This is a desktop mouse-and-keyboard first version. Dragging, position persistence, controller navigation, filtering, and a remappable shortcut are not included. The panel does not pause the game or bot. Collapse it if it covers a game control.

Module boundaries

ui/JevOverlay/
  Mod.cs                    mod startup, frame callback, background polling
  Core/
    OverlayConfig.cs        explicit absolute log path
    LogTail.cs              bounded, incremental JSONL reader
    DecisionHistory.cs      session/step joins and display text
  UI/
    OverlayView.cs          Godot controls and selection state
  Tests/
    Program.cs              temporary-file integration checks
  JevOverlay.csproj         game-facing assembly
  JevOverlay.json           game mod manifest

Core/ has no Godot or game dependency. UI/ has no file access or game API calls. Mod.cs connects them. All controls are built-in Godot nodes, so the mod needs no Godot editor project, custom node source generator, Harmony patch, or .pck file.

The game mod belongs under ui/, not a web frontend/ directory. Python policy, the runner, and vendored code remain unchanged.

Build

Use a .NET 9 SDK. No NuGet packages are needed; NuGet.Config clears package feeds. The game assembly paths are explicit. Builds do not install or start anything.

If dotnet is not on your PATH, a temporary Nix shell is sufficient:

nix shell nixpkgs#dotnet-sdk_9

A project flake and direnv configuration are not required for this version.

Run these commands from the repository root. On macOS arm64:

GAME_DATA="$HOME/Library/Application Support/Steam/steamapps/common/Slay the Spire 2/SlayTheSpire2.app/Contents/Resources/data_sts2_macos_arm64"
dotnet build ui/JevOverlay/JevOverlay.csproj -c Release \
  -p:STS2GameDataDir="$GAME_DATA"

On other platforms, set STS2GameDataDir to the installed game's directory containing sts2.dll and GodotSharp.dll. Use assemblies from the target game build.

Output: ui/JevOverlay/bin/Release/net9.0/. Do not distribute game assemblies with the mod.

Offline checks

The integration checks need .NET 9, but no game installation or connection:

dotnet run --project ui/JevOverlay/Tests/JevOverlay.Tests.csproj -c Release

The checks pass temporary files through the actual reader, history, and text formatter. They cover partial writes, UTF-8, malformed rows, session joins, action errors, rotation, truncation, bounded history, large logs, missing files, and recovery. They do not test Godot rendering or game input behavior.

Manual installation

Do this only when you intend to load the mod. Close the game first.

  1. Build against the installed game assemblies.
  2. Copy JevOverlay.dll and JevOverlay.json from the output directory into the game's mods/ directory.
  3. Copy JevOverlay.config.example.json there as JevOverlay.conf.
  4. Set log_path to the absolute path of the bot's decision log.
  5. Restart the game and enable mods if prompted.

On macOS, the mod directory is:

SlayTheSpire2.app/Contents/MacOS/mods/

Example configuration in JevOverlay.conf (JSON content, not a mod manifest):

{
  "log_path": "/absolute/path/to/sts2-bot/capture/decisions.jsonl"
}

Do not use ~ or environment variables in this value. For Windows JSON paths, use forward slashes or escape backslashes. Custom run.py --capture-dir locations need a matching log_path.

The reader waits if the file does not exist. Missing or invalid configuration appears in the panel. Configuration changes require a full game restart. The mod never creates or changes the configured log file.

To remove the mod, close the game and remove these three installed files. No saves, capture files, or existing mod files need to change.

Log meaning and limits

The viewer consumes the current run.py trace format:

Event Display behavior
decide Add a proposed action, identified by session and step
action_rejected Mark that action request as rejected
action_error Show the request error; completion remains unknown
session_end Show the session's stop reason, not action completion
Model/policy failures, no_decision Show a reader notice
Other events Ignore without treating them as malformed

The log does not record successful action results or a per-decision dry-run flag. The viewer cannot distinguish an unconfirmed live proposal from a dry-run proposal. It never marks a proposal as executed because the next decision arrived. A transport error does not prove that the game failed to execute a request.

The policy reason is recorded text, not private model reasoning. A jev response can accompany a fallback; the source field identifies the actual policy path. Recorded answer gates can differ from policy-specific final gates. Confidence is not proof of correctness. No Jev response means only that none was recorded.

The status reports file activity, not bot or game liveness. Existing history can be displayed when no bot is running. Sessions remain distinct, including when multiple runners append to the file. The latest appended decision is selected when Follow latest is enabled; there is no session filter in this version.

The reader polls every 500 ms on one background worker. It reads at most 256 KiB per poll and starts within the last 256 KiB of an existing log. It keeps at most 100 decisions and at most 128 KiB per incomplete line. Oversized or malformed lines are skipped. Display text is limited to 16,000 characters per text area. A displayed decision can leave history while it is selected.

Truncation and replacement are detected by file length, a prefix, and a checkpoint near the last read offset. A replacement with identical sampled bytes and no length decrease cannot always be distinguished from an append. This viewer is not a durable audit reader. Concurrent writers must still produce complete JSONL rows; the viewer cannot repair interleaved writes.

In-game acceptance check — requires explicit approval

No live game action or model call is needed to validate the panel itself. Point log_path at a disposable JSONL file and use synthetic records:

  1. Start the game with the mod installed and check the initial panel.
  2. Append a decide record with a session, step, and action.
  3. Confirm the panel updates and does not mark the proposal as executed.
  4. Expand the evidence area and select an older decision.
  5. Append another record and confirm the older selection remains selected.
  6. Test F8, collapse, scrolling, resizing, and clicks outside the panel.
  7. Switch game scenes and confirm there is exactly one panel.
  8. Remove or truncate the disposable file and confirm reader recovery.

The user confirmed that the installed panel appears and is readable in-game. Input routing, scene persistence, and the remaining acceptance steps are not yet verified. Building against game assemblies is not a substitute for these checks.