From 1c2b8f96ab102f520417d28b4a88fdea7293b028 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Tue, 28 Apr 2026 01:05:25 -0400 Subject: [PATCH] =?UTF-8?q?SIG=20SELECT:=20Nomad/Schizo=20locked=20by=20de?= =?UTF-8?q?fault;=20Note-unlock=20gate=20=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _filter_major_unlocks(cards, user): strips Major 0 (Nomad) and Major 1 (Schizo) unless user has matching 'nomad'/'schizo' Note; unauthenticated users see 0 majors - levity_sig_cards(room, user) / gravity_sig_cards(room, user): accept user param; default 16 court cards, up to 18 with both Note unlocks - View wires user into both calls; _sig_unique_cards / sig_deck_cards unchanged (game-table deck still includes all 18 unique) - _full_sig_setUp: seats now carry deck_variant=earthman - SigCardHelperTest: 4 new ITs (default 16, nomad +1, schizo +1); empty-deck test updated to clear seats + owner - SigSelectRenderingTest: 18-card test updated to 16-default + 3 Note-unlock ITs Pending: superusers auto-granted nomad + schizo Notes on creation (ask user) Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Sonnet 4.6 --- src/apps/epic/models.py | 25 +++++++--- src/apps/epic/tests/integrated/test_models.py | 49 +++++++++++++------ src/apps/epic/tests/integrated/test_views.py | 24 +++++++-- src/apps/epic/views.py | 4 +- 4 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/apps/epic/models.py b/src/apps/epic/models.py index 039cdfe..6565203 100644 --- a/src/apps/epic/models.py +++ b/src/apps/epic/models.py @@ -491,14 +491,27 @@ def _sig_unique_cards(room): return wands_crowns + swords_cups + major -def levity_sig_cards(room): - """The 18 cards available to the levity group (PC/NC/SC).""" - return _sig_unique_cards(room) +def _filter_major_unlocks(cards, user): + """Remove Nomad (0) and Schizo (1) unless the user has the matching Note unlock.""" + if user is None or not user.is_authenticated: + return [c for c in cards if c.arcana != TarotCard.MAJOR] + earned = set(user.notes.values_list("slug", flat=True)) + return [ + c for c in cards + if c.arcana != TarotCard.MAJOR + or (c.number == 0 and "nomad" in earned) + or (c.number == 1 and "schizo" in earned) + ] -def gravity_sig_cards(room): - """The 18 cards available to the gravity group (BC/EC/AC).""" - return _sig_unique_cards(room) +def levity_sig_cards(room, user=None): + """Cards available to the levity group (PC/NC/SC), filtered by user's Note unlocks.""" + return _filter_major_unlocks(_sig_unique_cards(room), user) + + +def gravity_sig_cards(room, user=None): + """Cards available to the gravity group (BC/EC/AC), filtered by user's Note unlocks.""" + return _filter_major_unlocks(_sig_unique_cards(room), user) def sig_seat_order(room): diff --git a/src/apps/epic/tests/integrated/test_models.py b/src/apps/epic/tests/integrated/test_models.py index 78fd41c..1138f1c 100644 --- a/src/apps/epic/tests/integrated/test_models.py +++ b/src/apps/epic/tests/integrated/test_models.py @@ -445,25 +445,44 @@ class SigReservationModelTest(TestCase): class SigCardHelperTest(TestCase): - """levity_sig_cards() and gravity_sig_cards() return 18 cards each. + """levity_sig_cards() and gravity_sig_cards() return 16 courts by default; + Nomad/Schizo added when the user has the matching Note unlock. Relies on the Earthman deck seeded by migrations (no manual card creation). """ def setUp(self): - # Earthman deck is already seeded by migrations + from django.utils import timezone self.earthman = DeckVariant.objects.get(slug="earthman") self.owner = User.objects.create(email="founder@test.io") - self.owner.equipped_deck = self.earthman - self.owner.save() - self.room = Room.objects.create(name="Card Test", owner=self.owner) + TableSeat.objects.create( + room=Room.objects.create(name="Card Test", owner=self.owner), + gamer=self.owner, slot_number=1, role="PC", + deck_variant=self.earthman, + ) + self.room = self.owner.table_seats.first().room + self._tz = timezone - def test_levity_sig_cards_returns_18(self): - cards = levity_sig_cards(self.room) - self.assertEqual(len(cards), 18) + def test_levity_sig_cards_returns_16_without_notes(self): + cards = levity_sig_cards(self.room, self.owner) + self.assertEqual(len(cards), 16) - def test_gravity_sig_cards_returns_18(self): - cards = gravity_sig_cards(self.room) - self.assertEqual(len(cards), 18) + def test_gravity_sig_cards_returns_16_without_notes(self): + cards = gravity_sig_cards(self.room, self.owner) + self.assertEqual(len(cards), 16) + + def test_nomad_note_includes_nomad(self): + from apps.drama.models import Note + Note.objects.create(user=self.owner, slug="nomad", earned_at=self._tz.now()) + cards = levity_sig_cards(self.room, self.owner) + self.assertEqual(len(cards), 17) + self.assertTrue(any(c.number == 0 and c.arcana == "MAJOR" for c in cards)) + + def test_schizo_note_includes_schizo(self): + from apps.drama.models import Note + Note.objects.create(user=self.owner, slug="schizo", earned_at=self._tz.now()) + cards = levity_sig_cards(self.room, self.owner) + self.assertEqual(len(cards), 17) + self.assertTrue(any(c.number == 1 and c.arcana == "MAJOR" for c in cards)) def test_levity_and_gravity_share_same_card_objects(self): """Both piles draw from the same 18 TarotCard instances — visual distinction @@ -475,11 +494,13 @@ class SigCardHelperTest(TestCase): sorted(c.pk for c in gravity), ) - def test_returns_empty_when_no_equipped_deck(self): + def test_returns_empty_when_no_deck_on_seats_or_owner(self): + """Falls back to empty list when neither seats nor owner have a deck.""" + self.room.table_seats.update(deck_variant=None) self.owner.equipped_deck = None self.owner.save() - self.assertEqual(levity_sig_cards(self.room), []) - self.assertEqual(gravity_sig_cards(self.room), []) + self.assertEqual(levity_sig_cards(self.room, self.owner), []) + self.assertEqual(gravity_sig_cards(self.room, self.owner), []) class TarotCardCautionsTest(TestCase): diff --git a/src/apps/epic/tests/integrated/test_views.py b/src/apps/epic/tests/integrated/test_views.py index ea8de8d..33b8a71 100644 --- a/src/apps/epic/tests/integrated/test_views.py +++ b/src/apps/epic/tests/integrated/test_views.py @@ -5,7 +5,7 @@ from django.test import TestCase from django.urls import reverse from django.utils import timezone -from apps.drama.models import GameEvent +from apps.drama.models import GameEvent, Note from apps.lyric.models import Token, User from apps.epic.models import ( Character, DeckVariant, GateSlot, Room, RoomInvite, SigReservation, TableSeat, TarotCard, @@ -1032,7 +1032,8 @@ def _full_sig_setUp(test_case, role_order=None): slot.status = GateSlot.FILLED slot.save() TableSeat.objects.create( - room=room, gamer=gamer, slot_number=i, role=role, role_revealed=True, + room=room, gamer=gamer, slot_number=i, role=role, + role_revealed=True, deck_variant=earthman, ) room.gate_status = Room.OPEN room.table_status = Room.SIG_SELECT @@ -1055,7 +1056,24 @@ class SigSelectRenderingTest(TestCase): response = self.client.get(self.url) self.assertContains(response, "id_sig_deck") - def test_sig_deck_contains_18_sig_cards(self): + def test_sig_deck_contains_16_sig_cards_by_default(self): + """Without Note unlocks the deck shows only 16 court cards (no Nomad/Schizo).""" + response = self.client.get(self.url) + self.assertEqual(response.content.decode().count('data-card-id='), 16) + + def test_nomad_note_adds_nomad_to_sig_deck(self): + Note.objects.create(user=self.gamers[0], slug="nomad", earned_at=timezone.now()) + response = self.client.get(self.url) + self.assertEqual(response.content.decode().count('data-card-id='), 17) + + def test_schizo_note_adds_schizo_to_sig_deck(self): + Note.objects.create(user=self.gamers[0], slug="schizo", earned_at=timezone.now()) + response = self.client.get(self.url) + self.assertEqual(response.content.decode().count('data-card-id='), 17) + + def test_both_notes_gives_18_sig_cards(self): + Note.objects.create(user=self.gamers[0], slug="nomad", earned_at=timezone.now()) + Note.objects.create(user=self.gamers[0], slug="schizo", earned_at=timezone.now()) response = self.client.get(self.url) self.assertEqual(response.content.decode().count('data-card-id='), 18) diff --git a/src/apps/epic/views.py b/src/apps/epic/views.py index a8c238b..61fb465 100644 --- a/src/apps/epic/views.py +++ b/src/apps/epic/views.py @@ -338,9 +338,9 @@ def _role_select_context(room, user): ctx["sig_reservations_json"] = json.dumps(reservations) if user_polarity == 'levity': - ctx["sig_cards"] = levity_sig_cards(room) + ctx["sig_cards"] = levity_sig_cards(room, user) elif user_polarity == 'gravity': - ctx["sig_cards"] = gravity_sig_cards(room) + ctx["sig_cards"] = gravity_sig_cards(room, user) else: ctx["sig_cards"] = []