operator UI: a departed player no longer keeps their seat in the roster
Operator report: "if a player disconnects from the console, it leaves their name
up in the roster ... it should turn their seat back to empty." I had not noticed
or fixed this -- the earlier review raised adjacent roster issues (lights mapped
through the untrimmed tag list after a trim) but not this one.
CAUSE. The GUI only ever WROTE walk-up names into the table. On a disconnect
the relay pops seat_info, the write-back loop hit `if not info: continue`, and
the cell kept the departed callsign for the rest of the session -- while the
pilot light beside it correctly said "waiting". So the table and the light
disagreed and a free seat looked occupied.
Two changes, because the relay's two seat-clearing signals are NOT symmetric:
* SessionMonitor now pops seat_info when a pilot goes idle from EITHER signal.
"PLAYER n LEFT" is only printed inside the beacon-close branch gated on
`if seat not in self.by_host` (the relay's own comment: "never claimed:
player left"), so a player who was fully REGISTERED and then dropped
produced only `game[...] dropped` -- the common case, and the one that left
the name up. Keying "seat is empty" off LEFT alone could never have worked.
* _refresh_pod_status stashes the operator's configured callsign/mech PER
OCCUPANCY when a seat fills, and restores it when the seat empties.
Per-occupancy rather than once at egg load, so an edit the operator makes
while a seat is empty is respected instead of being overwritten by a stale
snapshot.
It restores the CONFIGURED PILOT, not a literal blank, deliberately: that cell is
editable and is what gets written to the egg on Save, so a placeholder like
"-- empty --" would end up in the mission file. The "unoccupied" signal is the
grey `waiting` light beside it.
NOT CHANGED, and explained instead: a vacated seat is held for its player for 90s
(seat_reclaim) so a crash or a reconnect returns them to the same seat and mech.
Assignment skips claimed/reserved/reclaim-held seats, so a brand-new joiner in
that window gets the next free seat, or ROSTER FULL if there wasn't one; after 90s
the seat is fair game. The operator said they were happy with players keeping
their seat/address, so this stays -- it is now documented in both docs rather than
silently removed.
VERIFIED: scratchpad/test_operator_roster.py (new, 14 checks) drives the REAL Qt
widget offscreen and feeds it the REAL relay log lines: a walk-up name appears;
after REGISTERED then `dropped` the light returns to waiting AND the seat shows
the configured pilot again; the seat is reusable by a different player and clears
for them too; the `PLAYER n LEFT` path clears it as well; an operator edit made
while the seat was empty survives an occupancy; and a neighbouring row is never
touched by another seat's traffic. Other suites still pass (rearm 19/19, net
17/17); checkctx CLEAN.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ab5828a866
commit
3a694485bf
@@ -94,6 +94,44 @@ host id, which is why a roster **trim remaps seat ids** and everything re-keys b
|
||||
**Launch with whoever connects:** the operator's LAUNCH can `_trim_roster` down to the seats that
|
||||
actually have live beacons or claims, so a 8-seat egg runs a 3-player round.
|
||||
|
||||
### When a player leaves
|
||||
|
||||
Relay side: the beacon's FIN frees the seat (`seat_beacons` entry deleted, and `seat_reservations` /
|
||||
`seat_prefs` dropped), but the TAG is **held for that player for 90 s** (`seat_reclaim`) so a crash
|
||||
or a reconnect returns them to the same seat with the same mech. Seat assignment skips seats that
|
||||
are claimed, reserved, operator-reserved, or reclaim-held — so a brand-new joiner in that 90 s window
|
||||
gets the next free seat instead, or `ROSTER FULL` if that was the only one. The hold expires on its
|
||||
own; nothing is held permanently.
|
||||
|
||||
**`PLAYER n LEFT` is NOT printed for every departure.** It is inside the beacon-close branch gated
|
||||
on `if seat not in self.by_host:` — the relay's own comment reads *"never claimed: player left"*. A
|
||||
player who was fully REGISTERED and then drops produces only `game[...] dropped` (+ PEER_DOWN). Any
|
||||
UI that keys "seat is empty" off the LEFT line alone will therefore miss the common case — which is
|
||||
exactly the bug below.
|
||||
|
||||
### The roster kept a departed player's name [FIXED 2026-07-26]
|
||||
|
||||
Operator report: *"if a player disconnects, it leaves their name up in the roster ... it should turn
|
||||
their seat back to empty."* The GUI only ever **wrote** walk-up names into the table: on a
|
||||
disconnect `seat_info` was popped and the write-back loop hit `if not info: continue`, so the cell
|
||||
kept the departed callsign for the rest of the session — while the pilot light beside it correctly
|
||||
said `waiting`. The table and the light disagreed, and a free seat looked occupied.
|
||||
|
||||
Two changes, because the two clearing paths are not symmetric:
|
||||
|
||||
1. `SessionMonitor` now pops `seat_info` when a pilot goes idle from **either** signal — the
|
||||
`game[...] dropped` line as well as `PLAYER n LEFT` — so a registered player's departure clears
|
||||
the identity too.
|
||||
2. `_refresh_pod_status` stashes the operator's configured callsign/mech **per occupancy** when a
|
||||
seat fills, and **restores it** when the seat empties. Per-occupancy (rather than once at egg
|
||||
load) means an edit the operator makes while a seat is empty is respected, not overwritten by a
|
||||
stale snapshot.
|
||||
|
||||
It restores the **configured pilot**, not a literal blank, on purpose: that cell is editable and is
|
||||
what gets written to the egg on Save, so a placeholder like "-- empty --" would end up in the
|
||||
mission file. The "unoccupied" signal is the grey `waiting` light next to it.
|
||||
Regression guard: `scratchpad/test_operator_roster.py` (drives the real widget offscreen).
|
||||
|
||||
---
|
||||
|
||||
## The round lifecycle (the part that used to strand the operator)
|
||||
|
||||
Reference in New Issue
Block a user