my-sea voice: cap visitors at 5 (6 seats) + free a seat on leave — TDD

Phase 5a of the my-sea voice batch (user-spec 2026-05-29). The owner holds 1C;
at most 5 visitors fill 2C–6C, which also caps the voice mesh (voice requires a
deposited seat, so seat-capping caps membership).

- SeaInvite: MY_SEA_MAX_VISITORS=5 + present_count(owner) / table_has_room(owner)
  classmethods (present = ACCEPTED + deposited + not LEFT).
- my_sea_visit_insert_token: a fresh deposit into a full table is bounced
  (?full=1, no token spent, no seat); a visitor who BYEs frees their seat
  (is_present → False) for the next visitor.
- my_sea_visit_gate:  context → the gate shows 'TABLE FULL' + inert
  rails instead of INSERT TOKEN for a not-yet-present visitor.
- 6 capacity ITs (count/room, full-table bounce, leave-frees-seat, gate flag,
  already-seated not blocked). 291 gameboard ITs green.

Remaining Phase 5 (live-verify / needs a spec call): disconnect visuals
(--priRd/.fa-ban, item 7) + the true Web-Audio equalizer (item 5) + consumer-
level voice-member enforcement + multi-seat (3C–6C) spectator viz.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-29 21:35:22 -04:00
parent da97c623c9
commit f0b9f02c7c
4 changed files with 117 additions and 3 deletions

View File

@@ -41,8 +41,8 @@
{# `_room.scss`. Layout mirrors the room gatekeeper. #}
<div class="gate-top-row">
<div class="gate-main-panel">
<div class="token-slot{% if not deposit_reserved %} active{% else %} pending{% endif %}">
{% if not deposit_reserved %}
<div class="token-slot{% if not deposit_reserved and not table_full %} active{% else %} pending{% endif %}">
{% if not deposit_reserved and not table_full %}
<form method="POST" action="{% if spectator %}{% url 'my_sea_visit_insert_token' visit_owner_id %}{% else %}{% url 'my_sea_insert_token' %}{% endif %}" style="display:contents">
{% csrf_token %}
<button type="submit" class="token-rails" aria-label="Insert token to play">
@@ -51,6 +51,8 @@
</button>
</form>
{% else %}
{# Reserved (owner two-step) OR the spectator table is #}
{# full (owner + 5) — static rails, no submit. #}
<div class="token-rails">
<span class="rail"></span>
<span class="rail"></span>
@@ -58,7 +60,7 @@
{% endif %}
<div class="token-panel">
<div class="token-denomination">1</div>
<span class="token-insert-label">INSERT TOKEN TO PLAY</span>
<span class="token-insert-label">{% if table_full %}TABLE FULL{% else %}INSERT TOKEN TO PLAY{% endif %}</span>
<span class="token-return-label">PUSH TO RETURN</span>
</div>
{% if deposit_reserved %}