console review round 2: six must-fixes from the adversarial pass (one of its fixes corrected)
The 4-dimension review of 4736cba..820caf8 returned 14 surviving findings. All six must-fixes plus the four deferables are in; one of the review's own prescriptions was wrong and is fixed differently (below). THE REAPER, FINAL DOCTRINE (blocker + major). Rev 2 -- "reap anything silent in the staging window" -- was still wrong twice over: a pad sends exactly ONE message in its life (the egg ACK, L4NET.CPP:1259) and is then silent FOREVER, so any manual-launch hold >180s reaped every healthy ACKed pad and force-relaunched their clients; and a REGISTERED game conn is quiet on TCP while loading, so with BT_RELAY_TCP_ONLY=1 (or blocked UDP) the reaper _abort_round()ed the whole night every ~3 minutes blaming a healthy player. The rule is now: an app-level deadline is valid only while a RESPONSE IS OWED. Only egg-sent-never-ACKed pads are reaped, with the debt clock starting at EGG SEND (a pad that sat through a long held-egg wait gets its full window -- an edge neither review round caught); game conns are never reaped at all. Half-open ghosts are TCP keepalive's job (~130s, faster than the deadline anyway). RE-ARM GUARDED ON EVERY CHANNEL (major). The launch_at guard lived only on the LAUNCH-press path; the ctl `rearm` command, stdin, and the always-lit GUI button reached _rearm_for_new_round unconditionally -- one press mid-mission zeroed the counters, permanently killing the mission clock AND making End Mission print "no mission is running": an unstoppable round, the exact class this feature exists to eliminate. Now refused (loudly) while a mission is running or a pair is in flight, and the button greys while launched. THE REVIEW'S OWN FIX WAS WRONG here: it prescribed refusing on `launches_sent >= 2`, but that stays 2 after a FINISHED round -- applying it verbatim resurrected the original dead-LAUNCH wedge, caught immediately by the regression suite. "Running" is `launches_sent >= 2 AND not stop_sent`. RE-ARM RE-KEYS SEATS (major). It restored the template roster but left seat_beacons/seat_prefs keyed by the trimmed round's positional ids, so round 2's trim minted a departed player's tag into the egg and trimmed a present player's out, shifting every callsign/mech a slot. The re-key block is factored out of _maybe_reset_round (_rekey_seats_to_roster) and shared. RESTART SESSION NO LONGER BAKES A WALK-UP'S NAME INTO THE EGG (major). _stop_session and _start_session now restore the stashed configured callsign/mech into the cells BEFORE _collect_egg can snapshot them (_restore_seat_defaults); previously the departed name went into the egg (name= plus rasterized bitmaps) and was then re-captured as the seat's permanent "configured default". LATE REMOTE OPERATOR GETS A ROSTER (major). The only line the GUI can adopt tags from was printed once at relay startup and aged out of the 400-line control history in ~33 minutes of stats chatter -- AUTH now re-issues the live roster line ahead of the replay, so a remote operator connecting at any point gets pilot lights and a working LAUNCH button. DEFERABLES, all four: _drop_game blames the tag stashed AT REGISTRATION (the live-roster resolve named the wrong tag for a trimmed-round conn dying after a re-arm restore -- and the tag-trusting GUI would clear the wrong seat); an operator-BLANKED callsign cell now restores (empty string is a real configured value; the falsy skip left the departed name up and re-captured it); a returning player displaces their own half-open registered ghost (same IP, no mission running) instead of eating ROSTER FULL until keepalive fires; udp_spoofed now rides the [relay-stats] line when non-zero and the warn set is capped at 256. VERIFIED: rearm suite grown to 25 checks (ACKed pad never reaped however silent; never-ACKed pad reaped; game conns never reaped; the egg-send debt clock; re-arm refused mid-mission, allowed after round end) -- plus net 17/17, roster 22/22, checkctx CLEAN. Live rig: mid-mission `rearm` refused with the message and the mission survived; End Mission -> re-arm -> full re-seat -> second mission launched. One bounded artifact observed and documented: each waiting pod bounces once (identity resync) after an explicit re-arm. Docs rewritten to the final doctrine (context/operator-console.md reaper + re-arm + roster-replay + udp_spoofed sections; OPERATOR_GUIDE + tooltip now say re-arm is between-rounds-only and warn about the one-bounce resync). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
820caf8765
commit
c9e561d9ba
+41
-5
@@ -511,7 +511,8 @@ class Operator(QMainWindow):
|
||||
"Clear the finished round's state and re-open the launcher, "
|
||||
"keeping everyone who is already connected. "
|
||||
"Use this if LAUNCH looks dead after a round instead of "
|
||||
"restarting the session.")
|
||||
"restarting the session. Between rounds only: refused while "
|
||||
"a mission is running or a launch is firing.")
|
||||
self.rearm_btn.clicked.connect(self._rearm_round)
|
||||
self.rearm_btn.setEnabled(False)
|
||||
self.launch_local_btn = QPushButton("Launch local instances")
|
||||
@@ -817,12 +818,35 @@ class Operator(QMainWindow):
|
||||
|
||||
# ------------------------------------------------------------- session --
|
||||
|
||||
def _restore_seat_defaults(self):
|
||||
"""Write every stashed configured (callsign, mech) back into its row and
|
||||
clear the stash. MUST run before anything snapshots or saves the table:
|
||||
Restart Session used to _collect_egg() with a departed walk-up's name
|
||||
still in the cell, baking it into the next session's egg (name= AND the
|
||||
rasterized bitmaps) and then re-capturing it as the seat's 'configured
|
||||
default' -- the reported roster bug returning through the egg file
|
||||
(review 2026-07-26)."""
|
||||
for tag, (callsign, mech) in list(self._seat_defaults.items()):
|
||||
for r in range(self.table.rowCount()):
|
||||
item = self.table.item(r, 0)
|
||||
if item is None or item.text().strip() != tag:
|
||||
continue
|
||||
cell = self.table.item(r, 1)
|
||||
if callsign is not None and cell is not None:
|
||||
cell.setText(callsign)
|
||||
combo = self.table.cellWidget(r, 2)
|
||||
if mech is not None and combo is not None:
|
||||
combo.setCurrentText(mech)
|
||||
break
|
||||
self._seat_defaults.clear()
|
||||
|
||||
def _start_session(self):
|
||||
self.end_sent = False
|
||||
# A session stopped while a seat was OCCUPIED leaves its stash entry
|
||||
# behind (the seat never emptied, so it was never popped); a new
|
||||
# session must not restore last session's snapshot.
|
||||
self._seat_defaults.clear()
|
||||
# behind (the seat never emptied, so it was never popped). Write those
|
||||
# configured values BACK into the cells before _collect_egg can bake a
|
||||
# walk-up's name into the egg, then start clean.
|
||||
self._restore_seat_defaults()
|
||||
relay_host = self.f_relayhost.text().strip()
|
||||
if (self.mode.currentIndex() == 0 and relay_host
|
||||
and relay_host.lower() not in ("localhost", "127.0.0.1")):
|
||||
@@ -916,6 +940,7 @@ class Operator(QMainWindow):
|
||||
self.pod_status.setText("remote link closed")
|
||||
|
||||
def _stop_session(self):
|
||||
self._restore_seat_defaults() # departed walk-up names must not outlive the session
|
||||
if getattr(self, "remote_link", None):
|
||||
link = self.remote_link
|
||||
self.remote_link = None # closed-signal becomes a no-op
|
||||
@@ -1130,8 +1155,16 @@ class Operator(QMainWindow):
|
||||
callsign, mech = info
|
||||
else:
|
||||
# Empty: restore the configured pilot and forget the stash, so
|
||||
# the next player to take this seat re-snapshots.
|
||||
# the next player to take this seat re-snapshots. Restore on
|
||||
# `is not None`: an operator-BLANKED cell is a real configured
|
||||
# value, and skipping it as falsy left the departed name up and
|
||||
# then re-captured it as the default (review 2026-07-26).
|
||||
callsign, mech = self._seat_defaults.pop(tag, (None, None))
|
||||
if callsign is not None and cell is not None and cell.text() != callsign:
|
||||
cell.setText(callsign)
|
||||
if mech is not None and combo is not None and combo.currentText() != mech:
|
||||
combo.setCurrentText(mech)
|
||||
continue
|
||||
if callsign and cell is not None and cell.text() != callsign:
|
||||
cell.setText(callsign)
|
||||
if mech and combo is not None and combo.currentText() != mech:
|
||||
@@ -1145,6 +1178,9 @@ class Operator(QMainWindow):
|
||||
(self.monitor.ready or (self.monitor.relay_mode and seated > 0))
|
||||
and not self.monitor.launched
|
||||
and have_relay)
|
||||
# Re-arm is refused by the relay mid-mission/mid-pair; grey it too so the
|
||||
# button beside END MISSION does not invite the press (review 2026-07-26).
|
||||
self.rearm_btn.setEnabled(have_relay and not self.monitor.launched)
|
||||
if self.monitor.stats:
|
||||
self.stats_label.setText(self.monitor.stats)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user