game room voice: gate on >1 cost-current depositor over the 7d initial period (not grace), not the phase — solo/CARTE-all-6 stays off — TDD
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed

Refines the room voice activation per user-spec: voice mirrors my_sea's window but over the seat's 7d INITIAL cost period (GateSlot.cost_current — within [filled_at, filled_at+7d), NOT the renewal grace). voice_active now needs the gate CLOSED (table_status set = ROLE_SELECT onset) + the viewer seated + MORE THAN ONE distinct gamer holding a FILLED, cost-current seat. A sole depositor — including a CARTE owner occupying all 6 seats (one gamer) — keeps voice OFF (no one to talk to); a 2nd qualifying gamer flips it on; it toggles back off once the cost period lapses into grace. Replaces the earlier phase-set gate (ROLE/SIG/SKY only) — voice now spans the whole 7d window incl. IN_GAME (the phase ceiling at SEA_SELECT made the 7d expiry unreachable).

TDD: RoomVoiceActivationTest reworked (13 ITs) — active w. 6 depositors across ROLE/SIG/SKY + persists in IN_GAME; inactive for a single depositor (CARTE-all-6), flips on once a 2nd gamer qualifies, inactive when filled 8d ago (grace), inactive before the gate closes, inactive for a non-seated viewer; room-id / muted-at passthrough + active-btn markup unchanged. 736 epic+drama ITs green.

[[project-my-sea-invite-voice-blueprint]]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-06-08 19:53:59 -04:00
parent b4ffab186e
commit 034639d335
2 changed files with 61 additions and 20 deletions

View File

@@ -820,18 +820,30 @@ def room_view(request, room_id):
seen_seated.add(seat.gamer_id)
seated_others.append(seat.gamer)
ctx["seated_others"] = seated_others
# Voice (Phase C, room path) — the burger fan's #id_voice_btn lights for a
# SEATED gamer across the whole character-creation arc: ROLE_SELECT onset
# (all tokens committed, seats pre-created by pick_roles) continuously through
# SKY_SELECT (which hosts the in-page DRAW SEA felt), going dark at IN_GAME.
# The room UUID is the WebRTC mesh key — RoomVoiceConsumer._can_join already
# gates an epic room on TableSeat membership (no consumer change), and
# voice-mesh.js / burger-btn.js bind the active click → join/toggle-mute.
# voice_muted_at carries the persisted mute so an in-arc nav/refresh rejoins
# MUTED (mirrors my_sea). [[project-my-sea-invite-voice-blueprint]]
VOICE_PHASES = {Room.ROLE_SELECT, Room.SIG_SELECT, Room.SKY_SELECT}
# Voice (Phase C, room path) — the burger fan's #id_voice_btn. Mirrors my_sea's
# voice window, but over the seat's 7d INITIAL cost period (GateSlot.cost_current
# — NOT the renewal grace, mirroring "lasts a day, off in grace" at 7d scale).
# It needs MORE THAN ONE distinct gamer holding a token-backed, cost-current
# seat: a SOLE depositor — including a CARTE owner occupying all 6 seats (one
# gamer) — has no one to talk to, so voice stays OFF; a 2nd qualifying gamer
# flips it on, and it toggles back off once the cost period lapses into grace.
# Gated on the gate being CLOSED (table_status set = ROLE_SELECT onset) + the
# viewer being seated. The room UUID is the WebRTC mesh key —
# RoomVoiceConsumer._can_join already gates an epic room on TableSeat
# membership (no consumer change); voice_muted_at carries the persisted mute so
# an in-period nav/refresh rejoins MUTED. [[project-my-sea-invite-voice-blueprint]]
active_depositors = {
slot.gamer_id
for slot in room.gate_slots.filter(
status=GateSlot.FILLED, gamer__isnull=False)
if slot.cost_current
}
is_seated = room.table_seats.filter(gamer=request.user).exists()
ctx["voice_active"] = is_seated and room.table_status in VOICE_PHASES
ctx["voice_active"] = (
room.table_status is not None
and is_seated
and len(active_depositors) > 1
)
ctx["voice_room_id"] = str(room.id)
ctx["voice_muted_at"] = (
request.user.voice_muted_at.isoformat()