feat: live mental-game rituals in Cash mode
Brian's own rituals (mined from his logs) become first-class, live tools instead of post-hoc recap sections: - Scar Note — instructive mistakes with the punt/cooler/standard distinction. - Confidence Bank — good process, banked regardless of result. - Alligator Blood — invokable adversity state; she suggests it when he's card-dead/short/stuck, and her coaching register shifts while it's on (live state injected into context per-turn via chat._mode_state_note). - Reset — tilt circuit-breaker; mental marker only, stats stay continuous. poker_rituals table + log_ritual/list_rituals/set_alligator/alligator_active; 4 tools added to the Cash toolset and taught in the mode card; HUD gains a 🐊 banner + Confidence Bank + Scar Notes panels; recap grounded via _rituals_block. tests/test_modes.py +5 ritual tests; 41 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+102
@@ -108,6 +108,20 @@ CREATE TABLE IF NOT EXISTS poker_stack_log (
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_stacklog_session ON poker_stack_log(session_id);
|
||||
|
||||
-- Mental-game rituals Brian developed (scar notes, confidence bank, alligator
|
||||
-- blood, reset). Session-scoped events: capture entries (scar/confidence/reset)
|
||||
-- carry text; the alligator state is the latest alligator_on/alligator_off event.
|
||||
CREATE TABLE IF NOT EXISTS poker_rituals (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id INTEGER NOT NULL,
|
||||
kind TEXT NOT NULL, -- scar | confidence | reset | alligator_on | alligator_off
|
||||
content TEXT,
|
||||
classification TEXT, -- scar only: punt | cooler | standard
|
||||
hand_id INTEGER,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_rituals_session ON poker_rituals(session_id);
|
||||
"""
|
||||
|
||||
# Below this many observed hands, don't surface % stats (too small a sample).
|
||||
@@ -275,6 +289,62 @@ def stack_state(session_id: int | None = None) -> dict:
|
||||
}
|
||||
|
||||
|
||||
# --- mental-game rituals (scar notes / confidence bank / alligator blood / reset) ---
|
||||
|
||||
RITUAL_CAPTURE = ("scar", "confidence", "reset")
|
||||
|
||||
|
||||
def log_ritual(kind: str, content: str | None = None, classification: str | None = None,
|
||||
hand_id: int | None = None, session_id: int | None = None) -> int:
|
||||
"""Record a ritual event (a scar note, confidence-bank entry, reset, or an
|
||||
alligator on/off toggle) against a session. Returns the row id."""
|
||||
sid = _resolve(session_id)
|
||||
if sid is None:
|
||||
raise ValueError("no live session")
|
||||
conn = _c()
|
||||
with conn:
|
||||
cur = conn.execute(
|
||||
"INSERT INTO poker_rituals (session_id, kind, content, classification, hand_id, created_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(sid, kind, content, classification, hand_id, _now()),
|
||||
)
|
||||
return int(cur.lastrowid)
|
||||
|
||||
|
||||
def list_rituals(session_id: int | None = None,
|
||||
kinds: tuple[str, ...] | None = None) -> list[dict]:
|
||||
"""Ritual events for a session, oldest first; optionally filtered by kind."""
|
||||
sid = _resolve(session_id)
|
||||
if sid is None:
|
||||
return []
|
||||
sql = "SELECT * FROM poker_rituals WHERE session_id = ?"
|
||||
params: list = [sid]
|
||||
if kinds:
|
||||
sql += " AND kind IN (%s)" % ",".join("?" * len(kinds))
|
||||
params += list(kinds)
|
||||
sql += " ORDER BY id"
|
||||
return [dict(r) for r in _c().execute(sql, params).fetchall()]
|
||||
|
||||
|
||||
def set_alligator(on: bool, session_id: int | None = None) -> bool:
|
||||
"""Toggle Alligator Blood mode for the session. Returns the new state."""
|
||||
log_ritual("alligator_on" if on else "alligator_off", session_id=session_id)
|
||||
return bool(on)
|
||||
|
||||
|
||||
def alligator_active(session_id: int | None = None) -> bool:
|
||||
"""Whether Alligator Blood mode is currently ON (latest toggle wins)."""
|
||||
sid = _resolve(session_id)
|
||||
if sid is None:
|
||||
return False
|
||||
r = _c().execute(
|
||||
"SELECT kind FROM poker_rituals WHERE session_id = ? "
|
||||
"AND kind IN ('alligator_on', 'alligator_off') ORDER BY id DESC LIMIT 1",
|
||||
(sid,),
|
||||
).fetchone()
|
||||
return bool(r and r["kind"] == "alligator_on")
|
||||
|
||||
|
||||
def end_session(cash_out: float, mood: str | None = None,
|
||||
session_id: int | None = None) -> dict:
|
||||
"""Close a session: record cashout, compute net + hours. Returns the row."""
|
||||
@@ -552,6 +622,21 @@ def _hand_line(h: dict) -> str:
|
||||
return " | ".join(str(b) for b in bits if b)
|
||||
|
||||
|
||||
_RITUAL_LABEL = {"scar": "Scar Note", "confidence": "Confidence Bank",
|
||||
"reset": "Reset", "alligator_on": "Alligator Blood ON",
|
||||
"alligator_off": "Alligator Blood OFF"}
|
||||
|
||||
|
||||
def _rituals_block(rituals: list[dict]) -> str:
|
||||
lines = []
|
||||
for r in rituals:
|
||||
label = _RITUAL_LABEL.get(r["kind"], r["kind"])
|
||||
cls = f" [{r['classification']}]" if r.get("classification") else ""
|
||||
body = f": {r['content']}" if r.get("content") else ""
|
||||
lines.append(f"- {label}{cls}{body}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_recap(session_id: int | None = None, backend: str | None = None) -> dict | None:
|
||||
"""Generate Brian's .md recap from a session's structured data + conversation, store it."""
|
||||
backend = backend or "cloud"
|
||||
@@ -563,6 +648,7 @@ def generate_recap(session_id: int | None = None, backend: str | None = None) ->
|
||||
reads = [dict(r) for r in _c().execute(
|
||||
"SELECT seat, note FROM player_reads WHERE session_id = ?", (sid,)).fetchall()]
|
||||
stats = session_stats(sid)
|
||||
rituals = list_rituals(sid)
|
||||
|
||||
convo = ""
|
||||
if s.get("chat_session_id"):
|
||||
@@ -579,6 +665,9 @@ def generate_recap(session_id: int | None = None, backend: str | None = None) ->
|
||||
f"{stats.get('per_hour')}/hr | hands logged: {stats.get('hands_logged')} | tags: {stats.get('tags')}\n\n"
|
||||
"HANDS:\n" + ("\n".join("- " + _hand_line(h) for h in hands) or "(none logged)") + "\n\n"
|
||||
"READS:\n" + ("\n".join(f"- seat {r.get('seat')}: {r['note']}" for r in reads) or "(none)") + "\n\n"
|
||||
"RITUALS (use these for the Scar Notes / Confidence Bank / Mental Game sections — "
|
||||
"they are what actually happened, not to be invented):\n"
|
||||
+ (_rituals_block(rituals) or "(none logged)") + "\n\n"
|
||||
"CONVERSATION DURING SESSION:\n" + (convo or "(none captured)")
|
||||
)
|
||||
md = llm.complete(
|
||||
@@ -868,6 +957,13 @@ def hud(session_id: int | None = None) -> dict | None:
|
||||
# Context: how Brian runs at these stakes overall (closed sessions).
|
||||
ctx = running_stats(stakes=s.get("stakes")) if s.get("stakes") else {}
|
||||
|
||||
rituals = list_rituals(sid)
|
||||
by_kind = lambda k: [ # noqa: E731
|
||||
{"content": r["content"], "classification": r["classification"],
|
||||
"hand_id": r["hand_id"], "at": r["created_at"]}
|
||||
for r in rituals if r["kind"] == k
|
||||
]
|
||||
|
||||
return {
|
||||
"session": {
|
||||
"id": sid, "venue": s.get("venue"), "stakes": s.get("stakes"),
|
||||
@@ -882,6 +978,12 @@ def hud(session_id: int | None = None) -> dict | None:
|
||||
"hands": hands,
|
||||
"villains": _session_villains(sid),
|
||||
"notes": notes,
|
||||
"rituals": {
|
||||
"alligator": alligator_active(sid),
|
||||
"scars": by_kind("scar"),
|
||||
"confidence": by_kind("confidence"),
|
||||
"resets": by_kind("reset"),
|
||||
},
|
||||
"stats": {
|
||||
"hands_logged": stats.get("hands_logged", 0),
|
||||
"tags": stats.get("tags", {}),
|
||||
|
||||
Reference in New Issue
Block a user