KB: the parked-relay/remote-console mode is COMPLETE -- record it so it stops being re-derived
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
12d6862c1f
commit
9df7dff787
@@ -293,11 +293,75 @@ no UDP fan-out, no control channel). All sends now go through `_send_all_guarded
|
||||
- **Relay (internet)** — `btconsole.py --relay <port> <egg>`; pods dial out. The normal mode.
|
||||
- **Mesh (LAN legacy)** — `btconsole.py <egg> <host:port> ...`; the console dials OUT to each pod's
|
||||
`-net` port. The authentic 1995 cabinet direction.
|
||||
- **Remote relay** — the GUI drives someone else's relay over the control port instead of spawning
|
||||
a child. **Known broken:** the LAUNCH button can never enable in this mode.
|
||||
- **Remote relay / PARKED RELAY** — the GUI drives an already-running relay over the control port
|
||||
instead of spawning a child. **This is a COMPLETE, SUPPORTED deployment mode** (fixed 2026-07-26 —
|
||||
do not re-derive it; see §Parked relay below). The intended production shape: the relay lives
|
||||
**permanently on the operator's home machine** so every player's `join.bat` address never changes,
|
||||
while the console dials in from anywhere (a laptop on cell internet, another country).
|
||||
|
||||
**The console must STAY CONNECTED.** A disconnect trips the pod's console-loss path, which also
|
||||
closes its game listener — an engine bug, not ours.
|
||||
closes its game listener — an engine bug, not ours. (This is about the *console channel to the
|
||||
pods* — the relay holds those. An **operator** GUI disconnecting from a parked relay's control
|
||||
port is harmless: the relay keeps running and the pods never notice.)
|
||||
|
||||
---
|
||||
|
||||
## Parked relay + remote console [COMPLETE 2026-07-26 — do not re-derive]
|
||||
|
||||
The production topology: **the relay stays parked on one machine forever** (so players'
|
||||
`join.bat` never changes), and the operator console attaches over the control port from anywhere.
|
||||
Verified by reading the control handler (`tools/btconsole.py` `_ctl_command`, `_ctl_get_mission`,
|
||||
`_ctl_set_mission`) — this is the authoritative list, so nobody has to re-read it again.
|
||||
|
||||
**Park the relay** (standalone, no GUI — the control port is what makes this viable, since a
|
||||
backgrounded relay has no stdin):
|
||||
|
||||
```
|
||||
cd C:\git\bt411\content
|
||||
python ..\tools\btconsole.py --relay 1500 OPERATOR.EGG
|
||||
```
|
||||
|
||||
**What a remote console CAN do** (control port = console port + 7, i.e. **1507**; `AUTH <secret>`):
|
||||
|
||||
| Command | Effect |
|
||||
|---|---|
|
||||
| `launch` | the RunMission pair — a real mission start |
|
||||
| `stop` | End Mission (the stop latch) |
|
||||
| `rearm` / `newround` | the re-arm escape hatch (recover a dead LAUNCH between rounds) |
|
||||
| `get` | current mission settings + live seat count |
|
||||
| `set map=…;time=…;weather=…;scenario=…;temperature=…;length=…` | writes **the relay's own egg on the parked machine** (`CONTROL_SET_KEYS` whitelist), effective next round |
|
||||
| `ping` | liveness |
|
||||
| *(implicit)* | the full live log stream + the roster, re-issued from live state at every AUTH |
|
||||
|
||||
So a remote operator can run an entire night: stage, launch, end, re-arm, change the map/weather/
|
||||
length between rounds, and watch real pilot lights.
|
||||
|
||||
**What a remote console CANNOT do** (these need access to the parked machine itself):
|
||||
- **Start the relay** — remote mode *attaches*; it cannot create a relay.
|
||||
- **Restart it.** `Stop Session` only disconnects the operator; `Restart Session` just reconnects.
|
||||
- **Change the seat count** — the relay refuses (`roster changed N->M seats -- IGNORED`), and
|
||||
resizing needs a relay restart.
|
||||
- **Edit the roster** — callsign / mech / colour / badge / patch / experience are *not* in
|
||||
`CONTROL_SET_KEYS`; only the six mission keys travel over the wire.
|
||||
|
||||
**THE SECRET'S WORKING-DIRECTORY TRAP.** `control_secret()` opens the bare relative name
|
||||
`operator_secret.txt`, so **the live secret depends on the relay's cwd** — and if none is found it
|
||||
silently *generates a new one*. Confirmed hazard 2026-07-26: this repo had **two secret files with
|
||||
different values** (`operator_secret.txt` and `content\operator_secret.txt`), so a remote operator
|
||||
holding one of them would fail AUTH against a relay started from the other directory (symptom: the
|
||||
control connection is dropped on the `CONTROL_AUTH_TIMEOUT`, not a clear "wrong password"). Always
|
||||
park the relay from the **same** directory, and hand out the secret from *that* directory. The file
|
||||
is now `.gitignore`d — it was previously untracked-but-not-ignored, one `git add -A` away from
|
||||
being published in a shared repo.
|
||||
|
||||
**Multiple operators** are accepted concurrently (`ctl_conns` is a list, each AUTHing independently
|
||||
and receiving the same broadcast), which makes hand-off seamless — but nothing arbitrates two
|
||||
people pressing LAUNCH, so treat one-operator-at-a-time as an operating discipline.
|
||||
|
||||
**Exposure.** The control channel is plaintext TCP with a shared secret, so forwarding 1507 to the
|
||||
open internet puts that secret on the wire in the clear. Preferred shape: leave the **player**
|
||||
ports (1500/1501) forwarded exactly as they are — `join.bat` untouched — and carry only the
|
||||
operator control link over a private tunnel (mesh VPN).
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user