Sky/Sea Select: ?seat-aware so a CARTE owner drives all 6 seats — TDD
A Carte Blanche gamer owns all 6 seats but could only cast sky / draw sea for ONE:
the sky/sea state IS per-seat (Character.seat), but the code resolved to the fixed
canonical (PC-first) seat, ignoring the ?seat switched to via the GATE VIEW pos-
circles — so the tray switched but the sky wheel / sea spread stuck on the
canonical seat + saves wrote back to it. Mirrors Sig Select's existing ?seat path.
- generalize `_acting_sig_seat` → `_acting_seat` (logic is sig-agnostic; 3 callers)
- `_role_select_context` SKY_SELECT branch keys off `selected_seat` (the ?seat-aware
seat, already computed) instead of `_canonical_seat`: user_polarity,
confirmed_char, user_seat_role, my_tray_sig, saved_by_position, saved_sea_spread,
sea_default_spread, hand_complete, sea_back_image_url
- sky_save / sky_delete / sea_save / sea_delete / sea_deck resolve the acting seat
via `_acting_seat(…, request.GET.get("seat"))`; sea_partial threads seat_param
- the sky + sea felts carry `?seat={{ current_slot }}` on their save/delete/deck/
sea_partial action URLs so the POSTs target the switched-to seat
- single-seat flow unchanged (no ?seat → canonical fallback)
- ITs: CARTE owner — ?seat switches the displayed spread; sea_save/sky_save target
the switched seat leaving the canonical seat's Character intact; felt URLs carry
?seat. 949 epic+gameboard ITs green.
; FLIP tint tweak (parallel edit): drop the polarity border, bump the flipped-back
overlay --quiUser/--terUser to 0.6 alpha
[[project-sig-select-seat-switch-open-problems]] [[project-deck-segment-model]]
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,11 +176,11 @@ def _canonical_user_seat(room, user):
|
||||
return room.table_seats.filter(gamer=user).order_by(_SIG_SEAT_ORDERING).first()
|
||||
|
||||
|
||||
def _acting_sig_seat(room, user, seat_param):
|
||||
"""The seat a SIG_SELECT action (reserve / ready / release) targets: the
|
||||
`?seat=N` override when N is one of the user's owned slots, else the
|
||||
canonical seat. Lets a CARTE multi-seat owner hold + ready a distinct sig
|
||||
per owned seat (one reservation per seat)."""
|
||||
def _acting_seat(room, user, seat_param):
|
||||
"""The seat an action targets: the `?seat=N` override when N is one of the
|
||||
user's owned slots, else the canonical seat. Lets a CARTE multi-seat owner
|
||||
act per-seat — a distinct sig reservation in SIG_SELECT, a distinct sky/sea
|
||||
Character in SKY_SELECT — across all six owned seats."""
|
||||
seat = _canonical_user_seat(room, user)
|
||||
if seat_param:
|
||||
try:
|
||||
@@ -630,7 +630,14 @@ def _role_select_context(room, user, seat_param=None):
|
||||
ctx["sig_cards"] = []
|
||||
|
||||
if room.table_status == Room.SKY_SELECT:
|
||||
user_role = _canonical_seat.role if _canonical_seat else None
|
||||
# CARTE seat-switch: the sky/sea state is per-SEAT (Character.seat), so key
|
||||
# this whole block off the seat the viewer switched to (`selected_seat` =
|
||||
# ?seat-aware), NOT the fixed canonical seat — without it the tray switched
|
||||
# but the sky wheel / sea spread stuck on the canonical seat, so a CARTE
|
||||
# owner couldn't drive his other 5 seats thru CAST SKY / DRAW SEA
|
||||
# (user-flagged 2026-06-07). Falls back to canonical if unset.
|
||||
_sky_seat = selected_seat or _canonical_seat
|
||||
user_role = _sky_seat.role if _sky_seat else None
|
||||
user_polarity = None
|
||||
if user_role in _LEVITY_ROLES:
|
||||
user_polarity = 'levity'
|
||||
@@ -639,15 +646,15 @@ def _role_select_context(room, user, seat_param=None):
|
||||
ctx["user_polarity"] = user_polarity
|
||||
confirmed_char = (
|
||||
Character.objects.filter(
|
||||
seat=_canonical_seat,
|
||||
seat=_sky_seat,
|
||||
confirmed_at__isnull=False,
|
||||
retired_at__isnull=True,
|
||||
).first()
|
||||
if _canonical_seat else None
|
||||
if _sky_seat else None
|
||||
)
|
||||
sky_confirmed = confirmed_char is not None
|
||||
ctx["sky_confirmed"] = sky_confirmed
|
||||
ctx["user_seat_role"] = _canonical_seat.role if _canonical_seat else ''
|
||||
ctx["user_seat_role"] = _sky_seat.role if _sky_seat else ''
|
||||
# Burger-fan Sky sub-btn: ACTIVE once the sky is saved (confirmed) — the
|
||||
# burger then pulses thrice (--priTk) on load + the sub-btn re-opens the
|
||||
# saved wheel. `saved_sky_json` primes that reopen so the felt draws the
|
||||
@@ -660,7 +667,7 @@ def _role_select_context(room, user, seat_param=None):
|
||||
)
|
||||
if sky_confirmed:
|
||||
# Fall back to seat.significator for Characters created before the sync was added
|
||||
ctx["my_tray_sig"] = confirmed_char.significator or _canonical_seat.significator
|
||||
ctx["my_tray_sig"] = confirmed_char.significator or _sky_seat.significator
|
||||
# DRAW SEA persistence — pre-render the saved Celtic-Cross hand from
|
||||
# the seat's Character.celtic_cross so a reload lands the filled
|
||||
# cross + the right spread (mirrors my-sea's saved_by_position, but
|
||||
@@ -684,7 +691,7 @@ def _role_select_context(room, user, seat_param=None):
|
||||
# NOT request.user.equipped_deck (which `select_role` nulls out once
|
||||
# the deck is contributed to the room → the back-img silently never
|
||||
# rendered in the gameroom + FLIP no-op'd). User-flagged 2026-06-07.
|
||||
_back_deck = _canonical_seat.deck_variant if _canonical_seat else None
|
||||
_back_deck = _sky_seat.deck_variant if _sky_seat else None
|
||||
ctx["sea_back_image_url"] = (
|
||||
_back_deck.back_image_url
|
||||
if (_back_deck and _back_deck.has_card_images) else ""
|
||||
@@ -1344,7 +1351,7 @@ def sig_reserve(request, room_id):
|
||||
|
||||
# CARTE per-seat sig: honor a ?seat=N override (carried on the reserve URL)
|
||||
# so the hold targets the SELECTED owned seat, not the canonical PC one.
|
||||
user_seat = _acting_sig_seat(room, request.user, request.GET.get("seat"))
|
||||
user_seat = _acting_seat(room, request.user, request.GET.get("seat"))
|
||||
if not user_seat or not user_seat.role:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
@@ -1440,7 +1447,7 @@ def sig_ready(request, room_id):
|
||||
return HttpResponse(status=400)
|
||||
# Per-seat ready: a CARTE multi-seat owner readies each owned seat's sig
|
||||
# independently (the ?seat=N rides the ready URL, like the reserve URL).
|
||||
user_seat = _acting_sig_seat(room, request.user, request.GET.get("seat"))
|
||||
user_seat = _acting_seat(room, request.user, request.GET.get("seat"))
|
||||
if user_seat is None:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
@@ -1745,7 +1752,7 @@ def sky_save(request, room_id):
|
||||
return HttpResponse(status=405)
|
||||
|
||||
room = Room.objects.get(id=room_id)
|
||||
seat = _canonical_user_seat(room, request.user)
|
||||
seat = _acting_seat(room, request.user, request.GET.get("seat"))
|
||||
if seat is None:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
@@ -1797,7 +1804,7 @@ def sky_delete(request, room_id):
|
||||
if request.method != 'POST':
|
||||
return HttpResponse(status=405)
|
||||
room = Room.objects.get(id=room_id)
|
||||
seat = _canonical_user_seat(room, request.user)
|
||||
seat = _acting_seat(room, request.user, request.GET.get("seat"))
|
||||
if seat is None:
|
||||
return HttpResponseForbidden()
|
||||
Character.objects.filter(seat=seat, retired_at__isnull=True).delete()
|
||||
@@ -1829,7 +1836,7 @@ def sea_save(request, room_id):
|
||||
if request.method != 'POST':
|
||||
return HttpResponse(status=405)
|
||||
room = Room.objects.get(id=room_id)
|
||||
seat = _canonical_user_seat(room, request.user)
|
||||
seat = _acting_seat(room, request.user, request.GET.get("seat"))
|
||||
if seat is None:
|
||||
return HttpResponse(status=403)
|
||||
try:
|
||||
@@ -1856,7 +1863,7 @@ def sea_delete(request, room_id):
|
||||
if request.method != 'POST':
|
||||
return HttpResponse(status=405)
|
||||
room = Room.objects.get(id=room_id)
|
||||
seat = _canonical_user_seat(room, request.user)
|
||||
seat = _acting_seat(room, request.user, request.GET.get("seat"))
|
||||
if seat is None:
|
||||
return HttpResponseForbidden()
|
||||
char = _confirmed_character_for(seat)
|
||||
@@ -1876,7 +1883,7 @@ def sea_deck(request, room_id):
|
||||
"""
|
||||
import random as _random
|
||||
room = Room.objects.get(id=room_id)
|
||||
seat = _canonical_user_seat(room, request.user)
|
||||
seat = _acting_seat(room, request.user, request.GET.get("seat"))
|
||||
if seat is None:
|
||||
return HttpResponse(status=403)
|
||||
|
||||
@@ -1909,7 +1916,9 @@ def sea_deck(request, room_id):
|
||||
def sea_partial(request, room_id):
|
||||
"""Return the rendered sea overlay partial for in-page injection after sky confirm."""
|
||||
room = Room.objects.get(id=room_id)
|
||||
ctx = _role_select_context(room, request.user)
|
||||
# ?seat threads through so the injected felt (CARTE seat-switch) reflects the
|
||||
# selected seat's spread + carries the right seat onto its own action URLs.
|
||||
ctx = _role_select_context(room, request.user, request.GET.get("seat"))
|
||||
if not ctx.get('sky_confirmed'):
|
||||
return HttpResponse(status=403)
|
||||
ctx['room'] = room
|
||||
|
||||
Reference in New Issue
Block a user