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>
211 lines
11 KiB
Markdown
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.
|