Files
project-lyra/tests/test_roster.py
T
serversdown 9b844bc356 feat: live table roster — seat_players / unseat_player + HUD card
The missing backbone for read tracking: a place for "who's at the table" to live.
When Brian reads the table off Bravo (handles like TAG), Lyra registers them as
seated this session; reads/TAGs then attach to those players by handle instead of
spawning duplicates or getting missed.

- session_players table; seat_player/seat_players/unseat_player/session_roster;
  _resolve_or_create_player (shared name/descriptor resolution, dedupe guard).
- tools seat_players (accepts objects or a plain name list) + unseat_player.
- HUD gains `roster`; Session page shows a 🪑 Table card (seat, handle, category,
  read count, last read).
- Cash card: capture the roster when he names the table; a Bravo handle like TAG
  is a PERSON, seated as a player — never the tight-aggressive style.

5 tests. Full suite 162 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 03:41:20 +00:00

80 lines
2.8 KiB
Python

"""Live table roster: seat players, attach reads by handle, roster on the HUD."""
from __future__ import annotations
import importlib
import numpy as np
import pytest
def _fake_embed(texts):
out = []
for t in texts:
v = np.zeros(64, dtype=np.float32)
for w in t.lower().split():
v[hash(w) % 64] += 1.0
out.append((v if v.any() else np.full(64, 1e-6, dtype=np.float32)).tolist())
return out
@pytest.fixture
def mods(tmp_path, monkeypatch):
monkeypatch.setenv("LYRA_DB_PATH", str(tmp_path / "test.db"))
from lyra import llm
monkeypatch.setattr(llm, "embed", _fake_embed)
import lyra.memory as memory
importlib.reload(memory)
import lyra.poker as poker
importlib.reload(poker)
import lyra.tools as tools
importlib.reload(tools)
return poker, tools
def test_seat_players_builds_roster(mods):
poker, _ = mods
poker.start_session(venue="Meadows", buy_in=300)
n = poker.seat_players(["TAG", "Jonathan", {"name": "Wheelz", "seat": "3"}])
assert n == 3
roster = poker.session_roster()
names = {r["name"] for r in roster}
assert names == {"TAG", "Jonathan", "Wheelz"}
assert next(r for r in roster if r["name"] == "Wheelz")["seat"] == "3"
def test_read_attaches_to_seated_player_by_handle(mods):
poker, _ = mods
poker.start_session(venue="Meadows", buy_in=300)
poker.seat_players(["TAG"])
poker.add_read(note="limped A4o from the SB, UTG straddle", name="TAG")
roster = poker.session_roster()
tag = next(r for r in roster if r["name"] == "TAG")
assert tag["reads"] == 1 and "A4o" in tag["last_note"]
# No duplicate TAG spawned — the read landed on the seated player.
assert sum(p["name"] == "TAG" for p in poker.get_villain_file()) == 1
def test_seat_players_tool_and_roster_in_hud(mods):
poker, tools = mods
poker.start_session(venue="Meadows", buy_in=300)
out = tools.dispatch("seat_players", {"players": [{"name": "TAG"}, {"name": "JD"}]}, {})
assert "TAG" in out and "JD" in out
assert len(poker.hud()["roster"]) == 2
def test_unseat_player_removes_from_roster_keeps_history(mods):
poker, _ = mods
poker.start_session(venue="Meadows", buy_in=300)
poker.seat_players(["TAG"])
poker.add_read(note="showed a bluff", name="TAG")
assert poker.unseat_player(name="TAG") is True
assert poker.session_roster() == [] # off the table
assert poker.player_profile("TAG")["reads"] # history intact
def test_seat_players_accepts_plain_name_list_via_tool(mods):
poker, tools = mods
poker.start_session(venue="Meadows", buy_in=300)
tools.dispatch("seat_players", {"players": "TAG, JD, Wheelz"}, {})
assert {r["name"] for r in poker.session_roster()} == {"TAG", "JD", "Wheelz"}