# 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: ```text ┌ 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 ```text 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: ```sh 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: ```sh 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: ```sh 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: ```text SlayTheSpire2.app/Contents/MacOS/mods/ ``` Example configuration in `JevOverlay.conf` (JSON content, not a mod manifest): ```json { "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.