Files
BT411/docs/OPERATOR_GUIDE.md
T
arcattackandClaude Opus 5 c9e561d9ba 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>
2026-07-26 10:11:07 -05:00

211 lines
11 KiB
Markdown

# BattleTech 4.11 — Operator Console Guide
How to run a games night: set up a mission, get pods seated, launch rounds, and recover when
something sticks. Written for the sysop at the console, not for players.
*Engineering detail (ports, protocol, state machine) lives in `context/operator-console.md`.
Player-facing instructions ship in the zip as `README.txt`.*
---
## 1. Starting the console
```
cd C:\git\bt411
python tools\btoperator.py
```
A window titled **BT411 Operator Console** opens. That is the console.
> **One thing worth knowing:** there are two programs. `btoperator.py` is the window you use.
> `btconsole.py` is the headless relay it starts for you in the background — all the actual
> networking. You never launch that one directly; its output is what scrolls in the log pane at the
> bottom of the window. When this guide says "the relay said…", that is where to look.
---
## 2. Set up the mission, then start the session
Work top to bottom:
1. **Pick the mission settings** — map, mission length, weather, experience level, and the seat
(pod) count. Every dropdown is populated live from `BTL4.RES`, so anything offered is valid.
2. **Set the seat count to roughly who you expect.** It does not have to be exact — you can launch
with fewer (see §4) — but a wildly oversized roster makes the staging messages confusing.
3. **Choose the mode**: **Relay (internet)** is normal — players dial out to you, so *they* need no
port forwarding. **Mesh (LAN)** is the original cabinet direction for a local network.
4. **Press Start Session.** The relay comes up and begins accepting pods.
**For internet play, you must be reachable.** Forward/allow these TCP ports to this machine
(default console port 1500):
| Port | Why |
|---|---|
| 1500/tcp | the console channel (hands out the mission egg, sends the launch) |
| 1501/tcp | game traffic between pods |
| 1501/udp | the fast update channel (position/motion) |
Port 1507/tcp is the operator control channel — only needed if someone drives this relay remotely.
Players give their client `BT_RELAY=<your address>:1500`; `players/join.bat` handles that.
---
## 3. Watch the pods arrive
Each pilot row lights up as they progress. In order:
**waiting****seated** (claimed a seat, callsign and mech shown) → **egg sent**
**registered****ready** (mission loaded) → **LAUNCHED**.
You will see this in the log and it is **normal, not an error**:
```
console conn 1.2.3.4:5678: egg HELD (3/8 pods present)
```
The mission file is deliberately held until every seat is present, so that every pod's copy
contains everyone's callsign and mech choice. If you are not waiting for the rest, just launch —
see next.
---
## 4. Launch a round
Press **🚀 LAUNCH MISSION**.
- If everyone has loaded, the round starts within a few seconds.
- **If some seats are empty, launching is still correct** — the relay shrinks the session to the
players who are actually here and starts. You do not need to edit the roster down first.
- The relay holds the start until every pod reports **ready**, so nobody loads into a mission
already in progress. If one pod is slow you will see who, every 10 seconds; after 3 minutes it
starts anyway rather than letting one wedged client hold up the night.
The mission then runs on its own clock (the mission length from the egg). **⏹ END MISSION** stops
it early.
---
## 5. Running back-to-back rounds
When a round ends, players' clients relaunch themselves and reconnect. Once they are back, press
**LAUNCH** again. You can change mission settings between rounds and press **Apply** first.
### If LAUNCH looks dead — press ↻ Re-arm
This is the fix for the most annoying failure this console had. If a round has ended and the LAUNCH
button is greyed out, or pressing it does nothing:
> **Press ↻ Re-arm, then LAUNCH.**
Re-arm clears the finished round's state and re-opens the launcher **while keeping everyone who is
already connected**. You do **not** need Stop Session / Start Session, which disconnects everybody.
Re-arm is **for between rounds only**, and the console enforces that: while a mission is running it
is greyed out (and the relay refuses it with *"press End Mission first"*), and while a launch is in
the middle of firing it is refused with *"launch sequence in flight"*. So you cannot break a live
round with it. Expect each already-waiting player's client to bounce once (a few seconds) right
after a Re-arm — that is the seat re-sync, not a crash.
**Why this happens:** the console used to only consider itself ready for a new round if *every*
player from the last round came back, or if *all* of them left. In between — the normal case when
one person closes their window or their client crashes — it got stuck, and said nothing. That is
fixed (an explicit LAUNCH now re-arms by itself), and Re-arm is the belt-and-braces button. If you
ever see the old behaviour, it is a bug worth reporting.
---
## 6. Known limitations — buttons that are not what they appear
Read this once; each of these is a control that looks live but does nothing in that mode.
**Mesh (LAN legacy) mode: the mission buttons do not work.** LAUNCH, END MISSION and Re-arm all log
">> operator … sent" and are then **silently discarded** — the mesh console has no command channel.
`Apply mission settings` is also inert (mesh reads the mission file once at startup). Mesh launches
its rounds by itself once the pods have the mission. **If you want to control launches, use Relay
mode.**
**Remote relay mode** (driving another machine's relay) is now a full console — LAUNCH, END MISSION,
Re-arm, Apply and Launch-local all work, and the pilot lights populate from the relay's own roster
when you connect. Two things still differ: **Stop Session only disconnects you** (the remote relay
keeps running, which is usually what you want), and **Restart Session** likewise just reconnects the
control link rather than restarting that relay.
**Other sharp edges:**
- **Start Session overwrites your egg file** on disk, without asking. Keep a copy of any mission you
care about under a different name.
- **`Apply` only pushes the six mission settings** (map, time, weather, scenario, temperature,
length). Callsign / mech / colour / badge / patch / experience edits need **Save**. Changing the
**seat count** mid-session is refused outright — that needs a session restart.
- **The Colour dropdown offers colours the game cannot paint.** Only Black, Brown, Crimson, Green,
Grey, Tan and White work; anything else silently comes out grey. The relay warns in the log.
- **If the relay dies on its own**, `Launch local instances` stays clickable and will start clients
against nothing. Press Start Session first.
- **`Public host`** is used only when exporting player scripts; it does not affect what the relay
binds or advertises.
## 7. When a player leaves
Their pilot light goes grey (**waiting**) and the seat's callsign reverts to whatever you had
configured for that row — so a free seat no longer looks occupied by a departed player.
**Their seat is held for them for 90 seconds.** That is deliberate: someone who crashes or gets
dropped comes straight back into the same seat with the same mech. During that window a *brand-new*
player is given the next free seat instead, or told the roster is full if there wasn't one. After 90
seconds the vacated seat is fair game for anybody.
So if a new arrival can't get in right after somebody left, wait a minute and have them retry — or
raise the seat count if you're simply out of seats.
## 8. Troubleshooting
| What you see | What it means | What to do |
|---|---|---|
| `egg HELD (n/N pods present)` | normal staging — waiting for the rest of the seats | wait, or press LAUNCH to start with who is here |
| LAUNCH greyed out after a round | the launcher is still holding the last round | press **↻ Re-arm** |
| `LAUNCH pressed but NO players are seated yet` | nobody has claimed a seat | check the players are pointed at the right address/port |
| `LAUNCH pressed but NOT all seats have ACKed` | some pods are still taking the mission file | wait a few seconds; if one is never coming, press **Re-arm** (between rounds only — it is refused mid-launch/mid-mission) |
| `launch HELD -- still loading: PLAYER n` | that pod has not finished loading | wait; it force-starts after 3 minutes |
| `*** WARNING: ... EMPTY seat(s) ... the mission will STALL` | **can be a false alarm** after the roster is trimmed | if the round starts and plays fine, ignore it |
| `seat RECLAIMED by identity` | a player who dropped got their seat back | nothing — this is working as intended |
| `game[...] dropped: closed` | a pod disconnected | normal at round end; mid-round it means they crashed or lost connection |
| A player gets **ROSTER FULL** | no free seat in the current round | Re-arm between rounds so the roster re-opens, then let them join |
| Console window stops responding | the relay stalled on a dead connection | Stop Session, Start Session; please keep the log (below) |
| A player says they cannot connect at all | ports, or wrong address | confirm 1500/1501 TCP + 1501 UDP reach this machine |
### The one thing to do before restarting a session
**Copy `content\operator_relay.log` somewhere first.** Start Session **overwrites** it — so
restarting to clear a problem also destroys the evidence of that problem. If something misbehaved,
save that file and hand it over; it is timestamped line-by-line and it is what makes these bugs
diagnosable.
---
## 9. Match reports
At the end of each clean round, every player's client **uploads its own match report to you
automatically** (relay mode only). They land in `matchlogs\` named by the sender's address. You do
not need to ask players to send anything.
Exceptions worth knowing: a client that **crashes mid-round** loses its report, and players on
**solo** or **Steam** have no relay to upload through. In those cases ask them for
`content\matchlog_*.txt` directly. Their crash log, `content\join.log`, is **never** uploaded — ask
for it explicitly when something went wrong on their end.
---
## 10. Local test instances
**Launch local instances** starts game clients on this machine against your own session — useful
for testing alone or filling a seat. **Stop games** closes them. In relay mode the session must
already be started, or a local instance has nothing to dial.
---
## 11. Which build everyone needs
**All pods must run the same build as each other.** Build **4.11.554** changed what pods tell each
other about scores, so a mixed session shows disagreeing KILLS/DEATHS columns (nothing crashes —
the remote numbers just do not move). When you hand out a new zip, make sure everyone takes it
before the session.