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:
2026-06-19 06:24:28 +00:00
parent dfb6425395
commit 974ee33f71
8 changed files with 340 additions and 2 deletions
+59
View File
@@ -106,3 +106,62 @@ def test_log_stack_tool_handler(lyra):
assert "450" in out and "150" in out # confirms stack + live net
# graceful when there's no number
assert "number" in tools.dispatch("log_stack", {}, {}).lower()
# --- mental-game rituals ---
def test_ritual_tools_in_cash_only(lyra):
_, _, modes, tools = lyra
cash = _names(tools.specs(modes.CASH.tools))
talk = _names(tools.specs(modes.TALK.tools))
rituals = {"scar_note", "confidence_bank", "alligator_blood", "reset_ritual"}
assert rituals <= cash
assert not (rituals & talk)
def test_scar_and_confidence_capture(lyra):
_, poker, _, tools = lyra
poker.start_session(stakes="2/5", buy_in=500)
tools.dispatch("scar_note", {"content": "punted bottom set", "classification": "punt"}, {})
tools.dispatch("scar_note", {"content": "ran KK into AA", "classification": "cooler"}, {})
tools.dispatch("confidence_bank", {"content": "disciplined river fold"}, {})
scars = poker.list_rituals(kinds=("scar",))
assert len(scars) == 2
assert {s["classification"] for s in scars} == {"punt", "cooler"}
conf = poker.list_rituals(kinds=("confidence",))
assert len(conf) == 1 and "fold" in conf[0]["content"]
# bogus classification is dropped, not stored
tools.dispatch("scar_note", {"content": "x", "classification": "nonsense"}, {})
assert poker.list_rituals(kinds=("scar",))[-1]["classification"] is None
def test_alligator_toggle_and_state(lyra):
_, poker, _, tools = lyra
poker.start_session(stakes="2/5", buy_in=500)
assert poker.alligator_active() is False
tools.dispatch("alligator_blood", {"on": True}, {})
assert poker.alligator_active() is True
tools.dispatch("alligator_blood", {"on": False}, {})
assert poker.alligator_active() is False # latest toggle wins
def test_rituals_in_hud(lyra):
_, poker, _, tools = lyra
poker.start_session(stakes="2/5", buy_in=500)
tools.dispatch("scar_note", {"content": "overplayed top pair"}, {})
tools.dispatch("confidence_bank", {"content": "good value bet"}, {})
tools.dispatch("reset_ritual", {"content": "lost a flip"}, {})
tools.dispatch("alligator_blood", {"on": True}, {})
r = poker.hud()["rituals"]
assert r["alligator"] is True
assert len(r["scars"]) == 1 and len(r["confidence"]) == 1 and len(r["resets"]) == 1
def test_rituals_require_live_session(lyra):
_, poker, _, tools = lyra
# tools degrade gracefully (no exception) when nothing is open
assert "no live session" in tools.dispatch("scar_note", {"content": "x"}, {}).lower()
with pytest.raises(ValueError):
poker.log_ritual("scar", content="x")