Commit Graph
2 Commits
Author SHA1 Message Date
arcattackandClaude Opus 5 820caf8765 console review follow-up: two edge cases in my own roster fix, found and fixed
Self-review of 3a69448 (plus an adversarial pass still in flight) turned up two
real defects in the work I just shipped:

1. THE STASH LIFECYCLE LEAKED ACROSS SESSIONS AND EGGS.  _seat_defaults was
   keyed by ROW INDEX and initialised once in __init__ -- never cleared on Start
   Session, on Open/New egg (which rebuilds the table), or on a seat-count
   change.  Concrete harm: stop a session while a seat is OCCUPIED (the stash is
   only popped when a seat empties, so it survives), reconfigure or open a
   different egg, start again -- and when that seat next empties the GUI
   restores LAST SESSION'S snapshot over the new configuration.  Fixed by (a)
   keying the stash by TAG, the seat's actual identity, so a changed roster
   makes stale keys inert instead of landing on whatever row now sits at that
   index, and (b) clearing it at both natural boundaries: session start (both
   local and remote paths -- the clear sits before the remote branch) and the
   table rebuild in _load_egg_into_ui.

2. POSITIONAL hostID->tag MAPPING IS WRONG AFTER A LAUNCH TRIM -- and 3a69448
   widened its blast radius.  The GUI resolves REGISTERED/dropped lines via
   host_tag(), position into the UNTRIMMED egg roster; after a trim remaps seat
   ids (e.g. the lone returning player reclaim-held at seat 3 becomes host 2),
   those lines light the wrong row -- and, with the new seat_info pop on
   dropped, CLEAR the wrong seat's identity, leaving the real one's name up:
   the exact reported bug, resurfacing in the trimmed case.  Fixed on both
   sides: the relay's REGISTERED and dropped prints now carry tag='...' from
   its LIVE (possibly trimmed) roster via a new _tag_of() helper, and the GUI
   regexes prefer the explicit tag with the positional fallback kept for old
   logs.  SEATED/READY/LEFT already carried tags; only these two were
   positional.

VERIFIED
  * test_operator_roster.py grown to 22 checks, all passing: post-trim lines
    light and clear the RIGHT row while the wrong row stays untouched; the
    legacy no-tag format still resolves positionally; a stopped-while-occupied
    session cannot leak GHOST into the next session's empty seat; a stale
    foreign-tag stash never touches a live row.
  * Live 2-pod rig: both new formats confirmed on the wire
    (REGISTERED (1/2) tag='...' / dropped: closed tag='...'), mission launched
    and stopped cleanly, round RESET intact.  rearm 19/19, net 17/17.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 08:54:13 -05:00
arcattackandClaude Opus 5 3a694485bf 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>
2026-07-26 08:43:18 -05:00