From b0987eb229fd0c50b1d484a4bc091ea49fadebad Mon Sep 17 00:00:00 2001 From: prosolis <5590409+prosolis@users.noreply.github.com> Date: Thu, 14 May 2026 00:02:09 -0700 Subject: [PATCH] Combat: add combat_session schema for turn-based fights Schema-only: persistent per-fight session table so manual elite/boss combat can resume or be auto-finished by the timeout reaper from exact mid-state. State machine and accessors land in a later PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/db/db.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/internal/db/db.go b/internal/db/db.go index 80d93ba..f52028a 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -1884,6 +1884,43 @@ CREATE TABLE IF NOT EXISTS space_inviter_prompts ( PRIMARY KEY (user_id, prompt_sent_at) ); CREATE INDEX IF NOT EXISTS idx_space_inviter_user ON space_inviter_prompts(user_id); + +-- ── Turn-based combat — persistent per-fight session ─────────────────────── +-- One row per manual elite/boss fight. Persists across bot restarts and +-- player away-from-keyboard so a fight can resume (or be auto-finished by +-- the timeout reaper) from exact mid-state. At most one row per user with +-- status='active' (enforced in code, not by constraint). +-- +-- encounter_id: room/node id within the run this fight belongs to. +-- enemy_id: bestiary stat-block id for the elite/boss. +-- phase: intra-round state machine position. +-- statuses_json: serialized player + enemy status effects (poison, etc.). +-- turn_log_json: per-round event stream, consumed by live narration. +-- expires_at: started_at + 1h; reaper auto-plays the fight to a real +-- win/loss from persisted state once this passes. +CREATE TABLE IF NOT EXISTS combat_session ( + session_id TEXT PRIMARY KEY, + user_id TEXT NOT NULL, + run_id TEXT NOT NULL, + encounter_id TEXT NOT NULL, + enemy_id TEXT NOT NULL, + round INTEGER NOT NULL DEFAULT 1, + phase TEXT NOT NULL DEFAULT 'player_turn', -- player_turn|enemy_turn|round_end|over + player_hp INTEGER NOT NULL, + player_hp_max INTEGER NOT NULL, + enemy_hp INTEGER NOT NULL, + enemy_hp_max INTEGER NOT NULL, + statuses_json TEXT NOT NULL DEFAULT '{}', + turn_log_json TEXT NOT NULL DEFAULT '[]', + status TEXT NOT NULL DEFAULT 'active', -- active|won|lost|fled|expired + started_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + last_action_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + expires_at DATETIME NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_combat_session_active + ON combat_session(user_id, status); +CREATE INDEX IF NOT EXISTS idx_combat_session_expiry + ON combat_session(status, expires_at); ` // SeedSchedulerDefaults inserts default scheduler jobs if they don't exist.