setup_sig_session: wire deck contributions; _room_deck_variant replaces owner.equipped_deck

- setup_sig_session: drop _ensure_earthman() (deck seeded by migration); set
  deck_variant=earthman on all TableSeats; users get unlocked_decks add but
  equipped_deck=None (seat owns the deck); docstring documents role-pair mapping
- _room_deck_variant(room): new helper looks up deck from any seated deck_variant,
  falls back to owner.equipped_deck for legacy rooms
- sig_deck_cards / _sig_unique_cards: use _room_deck_variant instead of
  owner.equipped_deck — sig cards now work even when users have unequipped their deck
  after role confirmation

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-28 00:45:22 -04:00
parent e512e94056
commit eaff2a1edb
2 changed files with 40 additions and 32 deletions

View File

@@ -421,6 +421,19 @@ class SigReservation(models.Model):
# ── Significator deck helpers ───────────────────────────────────────────────── # ── Significator deck helpers ─────────────────────────────────────────────────
def _room_deck_variant(room):
"""Return the DeckVariant in use for this room.
Looks up the deck committed to any TableSeat in the room (all seats share the
same deck per game). Falls back to the room owner's equipped_deck for rooms
created before deck contribution was wired.
"""
seat = room.table_seats.filter(deck_variant__isnull=False).first()
if seat:
return seat.deck_variant
return room.owner.equipped_deck
def sig_deck_cards(room): def sig_deck_cards(room):
"""Return 36 TarotCard objects forming the Significator deck (18 unique × 2). """Return 36 TarotCard objects forming the Significator deck (18 unique × 2).
@@ -429,7 +442,7 @@ def sig_deck_cards(room):
NC/EC pair → MAJOR arcana numbers 0 and 1: 2 unique NC/EC pair → MAJOR arcana numbers 0 and 1: 2 unique
Total: 18 unique × 2 (levity + gravity piles) = 36 cards. Total: 18 unique × 2 (levity + gravity piles) = 36 cards.
""" """
deck_variant = room.owner.equipped_deck deck_variant = _room_deck_variant(room)
if deck_variant is None: if deck_variant is None:
return [] return []
wands_crowns = list(TarotCard.objects.filter( wands_crowns = list(TarotCard.objects.filter(
@@ -455,7 +468,7 @@ def sig_deck_cards(room):
def _sig_unique_cards(room): def _sig_unique_cards(room):
"""Return the 18 unique TarotCard objects that form one sig pile.""" """Return the 18 unique TarotCard objects that form one sig pile."""
deck_variant = room.owner.equipped_deck deck_variant = _room_deck_variant(room)
if deck_variant is None: if deck_variant is None:
return [] return []
wands_crowns = list(TarotCard.objects.filter( wands_crowns = list(TarotCard.objects.filter(

View File

@@ -2,8 +2,13 @@
Management command for manual multi-user sig-select testing. Management command for manual multi-user sig-select testing.
Creates (or reuses) a room with all 6 gate slots filled, roles assigned, Creates (or reuses) a room with all 6 gate slots filled, roles assigned,
and table_status=SIG_SELECT. Prints one pre-auth URL per gamer so you can deck contributions wired, and table_status=SIG_SELECT. Prints one pre-auth
paste them into 6 Firefox Multi-Account Container tabs. URL per gamer so you can paste them into 6 Firefox Multi-Account Container tabs.
Deck contribution by role pair (same segment, levity vs gravity pole):
PC & BC → Brands + Crowns (levity / gravity)
SC & AC → Blades + Grails (levity / gravity)
NC & EC → Trumps (levity / gravity)
Usage: Usage:
python src/manage.py setup_sig_session python src/manage.py setup_sig_session
@@ -15,7 +20,7 @@ from django.contrib.auth import BACKEND_SESSION_KEY, HASH_SESSION_KEY, SESSION_K
from django.contrib.sessions.backends.db import SessionStore from django.contrib.sessions.backends.db import SessionStore
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from apps.epic.models import DeckVariant, GateSlot, Room, TableSeat, TarotCard from apps.epic.models import DeckVariant, GateSlot, Room, TableSeat
from apps.lyric.models import User from apps.lyric.models import User
@@ -31,28 +36,6 @@ GAMERS = [
ROLES = ["PC", "NC", "EC", "SC", "AC", "BC"] ROLES = ["PC", "NC", "EC", "SC", "AC", "BC"]
def _ensure_earthman():
"""Return (or create) the Earthman DeckVariant with enough sig-deck cards seeded."""
earthman, _ = DeckVariant.objects.get_or_create(
slug="earthman",
defaults={"name": "Earthman Deck", "card_count": 108, "is_default": True},
)
_NAME = {11: "Maid", 12: "Jack", 13: "Queen", 14: "King"}
for suit in ("WANDS", "PENTACLES", "SWORDS", "CUPS"):
for number in (11, 12, 13, 14):
TarotCard.objects.get_or_create(
deck_variant=earthman,
slug=f"{_NAME[number].lower()}-of-{suit.lower()}-em",
defaults={
"arcana": "MINOR",
"suit": suit,
"number": number,
"name": f"{_NAME[number]} of {suit.capitalize()}",
},
)
return earthman
def _make_session(user): def _make_session(user):
session = SessionStore() session = SessionStore()
session[SESSION_KEY] = str(user.pk) session[SESSION_KEY] = str(user.pk)
@@ -71,7 +54,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
base_url = options["base_url"].rstrip("/") base_url = options["base_url"].rstrip("/")
earthman = _ensure_earthman() earthman = DeckVariant.objects.get(slug="earthman")
# ── Users ──────────────────────────────────────────────────────────── # ── Users ────────────────────────────────────────────────────────────
users = [] users = []
@@ -79,9 +62,11 @@ class Command(BaseCommand):
user, _ = User.objects.get_or_create(email=email) user, _ = User.objects.get_or_create(email=email)
user.is_staff = True user.is_staff = True
user.is_superuser = True user.is_superuser = True
if not user.equipped_deck: # Deck will be assigned to seat below; ensure it's in unlocked_decks
user.equipped_deck = earthman # but leave equipped_deck=None (seat assignment owns it)
user.equipped_deck = None
user.save() user.save()
user.unlocked_decks.add(earthman)
users.append(user) users.append(user)
# ── Room ───────────────────────────────────────────────────────────── # ── Room ─────────────────────────────────────────────────────────────
@@ -104,11 +89,21 @@ class Command(BaseCommand):
room.gate_status = Room.OPEN room.gate_status = Room.OPEN
room.save() room.save()
# ── Table seats + roles ────────────────────────────────────────────── # ── Table seats + roles + deck contributions ─────────────────────────
# PC/NC/SC → levity pole; BC/EC/AC → gravity pole.
# Each role pair shares a deck segment:
# PC & BC → Brands + Crowns
# SC & AC → Blades + Grails
# NC & EC → Trumps
for i, (user, role) in enumerate(zip(users, ROLES), start=1): for i, (user, role) in enumerate(zip(users, ROLES), start=1):
TableSeat.objects.update_or_create( TableSeat.objects.update_or_create(
room=room, slot_number=i, room=room, slot_number=i,
defaults={"gamer": user, "role": role, "role_revealed": True}, defaults={
"gamer": user,
"role": role,
"role_revealed": True,
"deck_variant": earthman,
},
) )
room.table_status = Room.SIG_SELECT room.table_status = Room.SIG_SELECT