Operator caught me re-investigating a feature we already shipped today. Root
cause was a self-contradicting knowledge base, not just my compaction: the
§Modes entry still said "Remote relay -- KNOWN BROKEN: the LAUNCH button can
never enable in this mode", while §Mode-specific traps 60 lines later documented
the same thing as FIXED. Reading the summary line first is exactly what a
future session does. Swept: that was the only stale copy.
Now recorded once, authoritatively (context/operator-console.md §Parked relay +
remote console, and a sysop recipe in docs/OPERATOR_GUIDE.md):
* THE PRODUCTION TOPOLOGY the operator actually wants: the relay parked
permanently on the home machine so no player ever edits join.bat, with the
console dialling in from anywhere (cell internet is fine -- outbound only).
* The exact standalone park command, and why the control port is what makes a
GUI-less relay usable at all (backgrounded, it has no stdin).
* The COMPLETE remote command set as a table, read out of _ctl_command /
_ctl_get_mission / _ctl_set_mission: launch, stop, rearm|newround, get,
set (the six CONTROL_SET_KEYS, written to the relay's OWN egg, effective
next round), ping, plus the log stream and the roster re-issued at AUTH.
Written down so the next session reads instead of re-deriving.
* The hard limits: remote mode ATTACHES -- it cannot start or restart the
relay, cannot resize the roster, cannot edit callsign/mech/colour.
* Multiple operators ARE accepted concurrently (ctl_conns is a list, each
AUTHing independently), so hand-off is seamless -- but nothing arbitrates
two people pressing LAUNCH.
Two live hazards found while verifying, both new:
1. THE SECRET'S CWD TRAP. control_secret() opens a bare relative
"operator_secret.txt" and SILENTLY GENERATES A NEW ONE if absent, so the
live secret depends on the relay's working directory. This repo really has
TWO secret files with DIFFERENT values (repo root and content\) -- a remote
operator holding the wrong one fails AUTH as a CONTROL_AUTH_TIMEOUT drop,
with no "wrong password" anywhere. Documented in both files.
2. operator_secret.txt was untracked but NOT ignored -- one `git add -A` from
publishing a live credential. Now in .gitignore.
Also corrected a nearby overreach: "the console must STAY CONNECTED" is about
the console channel to the PODS; an operator GUI dropping off a parked relay's
control port is harmless and the pods never notice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
246 lines
12 KiB
Markdown
246 lines
12 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.
|
|
|
|
### Running the relay PARKED (console from anywhere)
|
|
|
|
The recommended setup for a travelling operator: the relay lives permanently on the home machine,
|
|
so **no player ever edits `join.bat`**, and you drive it from a laptop — cell internet is fine,
|
|
since the console only makes an outbound connection.
|
|
|
|
**On the home machine, before you leave:**
|
|
|
|
```
|
|
cd C:\git\bt411\content
|
|
python ..\tools\btconsole.py --relay 1500 OPERATOR.EGG
|
|
```
|
|
|
|
Leave it running. Note the port it prints for operator control (**1507** = console port + 7) and
|
|
copy `content\operator_secret.txt` — that string is what any console needs to connect, and it grants
|
|
full mission control, so hand it out deliberately.
|
|
|
|
**From the road:** run `python tools/btoperator.py`, put the home address in Host, Console port
|
|
1500, paste the secret, and connect. You get launch, end, re-arm, mission changes (map / time /
|
|
weather / scenario / temperature / length, applied to the egg on the home machine, effective next
|
|
round) and live pilot lights.
|
|
|
|
**What still needs someone at the home machine:** starting the relay if it is not running,
|
|
restarting it, changing the **seat count**, and editing callsigns / mechs / colours — only the six
|
|
mission settings travel over the control link.
|
|
|
|
**Two traps worth knowing:**
|
|
- **The secret depends on the relay's working directory.** It is read as a bare `operator_secret.txt`
|
|
relative to wherever the relay was started, and if it is missing the relay *silently generates a
|
|
new one*. Always start the parked relay from the same folder (`content\`), or a console using
|
|
yesterday's secret will just be dropped on the auth timeout with no clear error.
|
|
- **Don't forward the control port to the open internet.** It is plaintext with a shared secret.
|
|
Keep the player ports (1500/1501) forwarded exactly as they already are and carry the operator
|
|
link over a private tunnel (Tailscale or similar) instead.
|
|
|
|
**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.
|