Add co-op dungeon system (party runs, voting, betting, gifts, items)

Multi-day party runs with funding decisions, TwinBee-narrated floor events,
spectator parimutuel betting, basket/mimic gift system, and weighted-roll
item distribution including masterwork drops at T4-T5.

Schema (5 tables, 2 fewer than spec by computing helpfulness on-the-fly and
skipping the loot-pending state):
- coop_dungeon_runs / _members (daily funding as JSON column)
- coop_dungeon_events (votes as JSON, used to derive TwinBee helpfulness)
- coop_dungeon_bets / _gifts

Mechanics:
- 2-4 player parties, 24h invite window, locks consume one combat action
- Per-floor success roll: base + sum(funding) + level/pet bonuses + event
  vote modifier + active gift modifiers, clamped to 5..95
- Funding tiers (none/min/std/agg/all_in) with liability cap at +8% for
  under-leveled players
- TwinBee narrates events from existing flavor pool; 20 authored events with
  per-option modifiers and embedded recommendation
- Parimutuel betting with 10% rake; odds line shown on lock/daily/status posts
- Gift modifiers symmetric at 50/50 sender mix so no dominant strategy
- Item drops on success via weight-by-contribution roll; T4 25% / T5 100%
  masterwork chance (random pick from existing T4/T5 defs)

Balance via Monte Carlo (coop_dungeon_balance_test.go):
- All tiers exceed 1.5x solo daily income at average party profile
- Optimal funding strategy walks up tiers correctly (Min/Std/Std/Mixed/Agg)
- All-In stays -EV at every tier (boost lever, not optimal play)

Tests: parsing, liability cap, JSON roundtrips, vote tally with leader
tiebreak, event meta consistency, parimutuel payouts, gift EV symmetry,
weighted-roll distribution (Monte Carlo), masterwork tier gates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-04-26 07:58:13 -07:00
parent 16d64323d9
commit 8ad31a0009
12 changed files with 3757 additions and 0 deletions

View File

@@ -1373,6 +1373,75 @@ CREATE TABLE IF NOT EXISTS holdem_tip_audit (
CREATE INDEX IF NOT EXISTS idx_tip_audit_user ON holdem_tip_audit(user_id);
CREATE INDEX IF NOT EXISTS idx_tip_audit_action ON holdem_tip_audit(action, street);
-- Co-op Dungeon (party multi-day runs)
CREATE TABLE IF NOT EXISTS coop_dungeon_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tier INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'open', -- open, active, complete, wiped, cancelled
leader_id TEXT NOT NULL,
current_day INTEGER NOT NULL DEFAULT 0,
total_days INTEGER NOT NULL,
base_difficulty INTEGER NOT NULL, -- per-floor failure % at zero modifier
gold_pool INTEGER NOT NULL DEFAULT 0, -- accumulated funding
reward_total INTEGER NOT NULL DEFAULT 0, -- final reward (set on completion)
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
locked_at DATETIME,
completed_at DATETIME
);
CREATE INDEX IF NOT EXISTS idx_coop_runs_status ON coop_dungeon_runs(status);
CREATE TABLE IF NOT EXISTS coop_dungeon_members (
run_id INTEGER NOT NULL,
user_id TEXT NOT NULL,
turn_order INTEGER NOT NULL,
total_contributed INTEGER NOT NULL DEFAULT 0,
is_liability INTEGER NOT NULL DEFAULT 0,
daily_funding TEXT NOT NULL DEFAULT '{}', -- JSON: {"1":"standard","2":"all_in",...}
PRIMARY KEY (run_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_coop_members_user ON coop_dungeon_members(user_id);
CREATE TABLE IF NOT EXISTS coop_dungeon_events (
run_id INTEGER NOT NULL,
day INTEGER NOT NULL,
category TEXT NOT NULL, -- obstacle, opportunity, crisis, encounter
event_index INTEGER NOT NULL, -- index into the category flavor pool
recommended TEXT NOT NULL, -- A, B, or C — TwinBee's pick
votes TEXT NOT NULL DEFAULT '{}', -- JSON {"@user:host": "A"}
winning_vote TEXT DEFAULT NULL,
outcome TEXT DEFAULT NULL, -- 'success' or 'failure' (after resolution)
modifier_applied INTEGER DEFAULT 0,
post_event_id TEXT DEFAULT NULL, -- Matrix event ID for live edit
PRIMARY KEY (run_id, day)
);
CREATE INDEX IF NOT EXISTS idx_coop_events_outcome ON coop_dungeon_events(outcome);
CREATE TABLE IF NOT EXISTS coop_dungeon_bets (
run_id INTEGER NOT NULL,
player_id TEXT NOT NULL,
position TEXT NOT NULL, -- 'success' or 'failure'
amount INTEGER NOT NULL,
placed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
payout INTEGER, -- NULL until run resolves
PRIMARY KEY (run_id, player_id)
);
CREATE INDEX IF NOT EXISTS idx_coop_bets_run ON coop_dungeon_bets(run_id);
CREATE TABLE IF NOT EXISTS coop_dungeon_gifts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id INTEGER NOT NULL,
sender_id TEXT NOT NULL,
day INTEGER NOT NULL,
gift_type TEXT NOT NULL, -- 'basket' or 'mimic'
votes TEXT NOT NULL DEFAULT '{}', -- JSON {"@user:host": "open"|"leave"}
vote_result TEXT, -- 'opened' or 'left'
outcome TEXT, -- 'boost' or 'reduction'
modifier INTEGER NOT NULL DEFAULT 0,
post_event_id TEXT,
sent_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
resolved_at DATETIME
);
CREATE INDEX IF NOT EXISTS idx_coop_gifts_run_day ON coop_dungeon_gifts(run_id, day, vote_result);
`
// SeedSchedulerDefaults inserts default scheduler jobs if they don't exist.