Document Live Cam + Mission Review architecture (CAMERA-REVIEW-NOTES.md)
Researched from the readable RP MUNGA source (BT shares the engine): host roles are assigned per-IP in the mission egg (hostType= on each pilot page); a CameraShip/MissionReview host gets a CameraDirector player that auto-cuts to the top-ranked pilot every 10s, with manual flight via the same cockpit hardware as pods (CameraRIOMapper) and fixed clamped trackside cameras authored in-game into MAPS/<map>.cam. Mission Review records raw NetworkPackets (SpoolFile -> last.spl) and replays them through a networkless playback app with timestamp bias -- the replay re-simulates the battle from replication traffic. Same BTL4OPT.EXE serves every role; standing up Live Cam on the emulated pod LAN is an egg edit, not a build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,192 @@
|
|||||||
|
# Live Cam & Mission Review — how the VWE games did it
|
||||||
|
|
||||||
|
Research notes, 2026-07-08. Sources: `CODE/RP/MUNGA` + `CODE/RP/MUNGA_L4`
|
||||||
|
(the readable Red Planet tree; BattleTech shares the MUNGA engine and has the
|
||||||
|
matching `CODE/BT/BT_L4/BTL4PB.HPP` + `CODE/BT/MUNGA/SPOOLER.HPP`), the
|
||||||
|
`sda4/BTLIVE/MAPS/*.CAM` files, and the mission eggs. Everything below is from
|
||||||
|
the code itself, not conjecture, except where marked.
|
||||||
|
|
||||||
|
Both features ride the same insight: the game is a **replicated-entity
|
||||||
|
network simulation**. Every machine on the mission LAN runs the full sim and
|
||||||
|
mirrors the others' entities from network traffic (master/replicant, see
|
||||||
|
`HOST.HPP`). A spectator view is therefore just *one more host* that renders
|
||||||
|
without piloting — and a replay is just *the same network traffic played back
|
||||||
|
from a file*.
|
||||||
|
|
||||||
|
## 1. Host roles come from the mission egg
|
||||||
|
|
||||||
|
`HOST.HPP:41`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum HostType {
|
||||||
|
GameMachineHostType, // 0 — a pod
|
||||||
|
CameraShipHostType, // 1 — Live Cam station
|
||||||
|
MissionReviewHostType, // 2 — replay station
|
||||||
|
ConsoleHostType // 3 — the ops console
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The console assigns a role to every IP on the mission when it authors the
|
||||||
|
egg. Each `[pilots] pilot=<ip>` page carries a **mandatory `hostType=` key**
|
||||||
|
(`MISSION.CPP:480` — boot aborts with *"ERROR: no host type in egg!"* if
|
||||||
|
absent). Our own `cavern.egg` shows a pod: `[200.0.0.113] hostType=0 ...`.
|
||||||
|
|
||||||
|
At player-creation time, `Registry::MakePlayer` (`REGISTRY.CPP:255`) switches
|
||||||
|
on the local host's type:
|
||||||
|
|
||||||
|
- `GameMachineHostType` → an ordinary `Player` (pod pilot);
|
||||||
|
- `CameraShipHostType` / `MissionReviewHostType` → a **`CameraDirector`**
|
||||||
|
player (the comment notes "Not a CameraShip to run on a POD!").
|
||||||
|
- Additionally, if the mission's `gamemodel` field is `"camera"`, the
|
||||||
|
player's *vehicle* gets `CameraShipPlayerFlag` — so a GameMachine host can
|
||||||
|
also fly a camera ("VTV, Mech, CameraShip w/controls" per the comment).
|
||||||
|
|
||||||
|
**The same game EXE serves every role** — pod, camera, review — chosen purely
|
||||||
|
by the egg. This is the actionable heart of it for us (§5).
|
||||||
|
|
||||||
|
## 2. Live Cam = CameraShip + CameraDirector
|
||||||
|
|
||||||
|
Classes: `CAMSHIP.HPP/CPP` (the flying camera vehicle, a `Mover` entity) and
|
||||||
|
`DIRECTOR.HPP/CPP` (the auto-director, a `Player` subclass).
|
||||||
|
|
||||||
|
- **CameraDirector** (`DIRECTOR.CPP:184 BeADirector`): every frame it checks
|
||||||
|
`timeLeftOnPlayer`; when it expires it finds the **top-ranked player**
|
||||||
|
(`FindPlayerByRank(0)` over the "Players" entity group) and, if the leader
|
||||||
|
changed, dispatches a `CameraShip::DirectionMessage{goalEntity,
|
||||||
|
timeOnCamera, focusOffset}` and resets the shot clock to **10 seconds**.
|
||||||
|
So the default broadcast is "follow the leader, re-cut every 10 s".
|
||||||
|
- **HUD** (`DIRECTOR.CPP:118 UpdateHUD` + `CameraShipHUDRenderable` in
|
||||||
|
`L4VIDRND.CPP:2199`): the feed overlays the followed player's identity and
|
||||||
|
a **ranking window** cycling 10 s on / 15 s off — always on during the
|
||||||
|
final 30 s of the mission, hidden once the mission stops.
|
||||||
|
- **CameraShip** (`CAMSHIP.CPP:340 FollowGoal`): aims at
|
||||||
|
`focusOffset × goal->localToWorld`, tracks the goal's linear position, and
|
||||||
|
asks the director for a new goal when `timeOnCamera` runs out. Camera
|
||||||
|
height is operator-adjustable (`ApplyControlledCameraHeight`).
|
||||||
|
|
||||||
|
### Manual camera operation
|
||||||
|
|
||||||
|
`CAMMPPR.HPP` (`CameraControlsMapper` subsystem) + `L4MPPR.HPP`: the camera
|
||||||
|
station reads **the same cockpit hardware as a pod** — there are three
|
||||||
|
concrete mappers: `CameraL4Mapper`, `CameraThrustmasterMapper`, and
|
||||||
|
`CameraRIOMapper` (a full RIO cockpit could fly the camera). Attributes:
|
||||||
|
stick / throttle / pedals / reverse-thrust / drive-camera / slide-or-clamp.
|
||||||
|
Buttons: **DirectorMode** toggle (auto-director vs manual),
|
||||||
|
**Increment/DecrementCamera** (cut between fixed cameras), and
|
||||||
|
**Create/Delete/OutputCameraInstances** — the camera-authoring workflow.
|
||||||
|
|
||||||
|
### Fixed trackside cameras — the `.CAM` files
|
||||||
|
|
||||||
|
`CAMINST.HPP/CPP` + `CAMMGR.CPP` (`CameraInstanceManager`): each map has a
|
||||||
|
notation file **`MAPS/<map>.cam`** (e.g. `sda4/BTLIVE/MAPS/ARENA1.CAM`)
|
||||||
|
holding `[cameraN]` pages:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[camera0]
|
||||||
|
cameraID=0
|
||||||
|
cameraType=DefaultCameraType ; or AlwaysSeesCameraType
|
||||||
|
tranx/y/z = position
|
||||||
|
quatx/y/z/w = orientation
|
||||||
|
minYawClamp/maxYawClamp ; pan limits (radians)
|
||||||
|
minPitchClamp/maxPitchClamp ; tilt limits
|
||||||
|
```
|
||||||
|
|
||||||
|
These are broadcast-style **fixed cameras with pan/tilt clamps**;
|
||||||
|
`CameraInstance::CalculateCameraRotation` pans them to track the goal within
|
||||||
|
the clamps. They were authored *in-game*: the operator flies to a spot,
|
||||||
|
presses CreateCameraInstance, and OutputCameraInstances writes the `.cam`
|
||||||
|
file back out (`CameraInstanceManager::WriteNotationFile`).
|
||||||
|
|
||||||
|
## 3. Mission Review = record the network, replay the network
|
||||||
|
|
||||||
|
### Recording (`SPOOLER.HPP/CPP`, `L4SPLR.HPP`)
|
||||||
|
|
||||||
|
- `SpoolFile` is a `MemoryStream` of **raw `NetworkPacket`s** (header +
|
||||||
|
payload, back to back). States: Empty → Spooling → Stored → Playing.
|
||||||
|
`SpoolPacket()` appends (hard-exits if the RAM buffer fills);
|
||||||
|
`SaveAs`/`Read` move it to/from disk. The canonical file name is
|
||||||
|
**`last.spl`** (`SPOOLER.CPP:251,274`) — same "last mission" convention as
|
||||||
|
`LAST.EGG`.
|
||||||
|
- `MissionReviewApplicationManager` owns a pool of `spool_count` RAM buffers
|
||||||
|
of `spool_size` each.
|
||||||
|
- `L4SpoolingApplication` (+ `L4SpoolingNetworkManager`,
|
||||||
|
`SpoolingInterestManager`) is the **recorder**: its
|
||||||
|
`ReceiveNetworkPacket()` override tees every packet arriving over the
|
||||||
|
mission LAN into the active spool *while also processing it normally* — so
|
||||||
|
the recording host fully participates (renders, directs cameras) as it
|
||||||
|
records. A `missionSpooled` flag tracks state; the spool is stored (→
|
||||||
|
`last.spl`) when the mission stops.
|
||||||
|
|
||||||
|
Because the recorder simply captures **received replication traffic**, the
|
||||||
|
recording is exactly what any spectator host saw: entity creations, state
|
||||||
|
updates, weapon events, kills — everything needed to re-simulate the battle,
|
||||||
|
at a tiny fraction of the size of video.
|
||||||
|
|
||||||
|
### Playback (`BTL4PB.HPP`, `L4SPLR.HPP`)
|
||||||
|
|
||||||
|
- `BTL4PlaybackApplication` (BT flavor of `RPPlaybackApplication`) is a full
|
||||||
|
game application whose network manager is `L4PlaybackNetworkManager` — a
|
||||||
|
`NetworkManager` with **no real network**: `CheckBuffers()` sources
|
||||||
|
packets from the `SpoolFile` instead of the wire.
|
||||||
|
- `SpoolerTask` (an `ApplicationTask`) walks the spool with `NextPacket()`
|
||||||
|
and `DispatchPacket()`s each one into the app, using **`timeBias`** to
|
||||||
|
shift the recorded timestamps onto the playback clock — packets are
|
||||||
|
released on the same relative schedule they were recorded, so the replayed
|
||||||
|
entities move exactly as they did live.
|
||||||
|
- The playback host's player is again a `CameraDirector` (host type switch,
|
||||||
|
§1) — so a review session *is* the Live Cam presentation (auto-cuts to the
|
||||||
|
leader, ranking overlays, fixed trackside cameras) running over recorded
|
||||||
|
traffic. This is what the debrief room / take-home tapes showed.
|
||||||
|
|
||||||
|
### What's NOT in the repo
|
||||||
|
|
||||||
|
The readable tree is partial: `L4SPLR.CPP` (SpoolerTask::Execute pacing,
|
||||||
|
exact spool start/stop triggers) and `BTL4PB.CPP` are missing — only their
|
||||||
|
headers survive. The compiled implementations are in the era binaries. The
|
||||||
|
trigger for "start spooling" (console message vs automatic at mission start)
|
||||||
|
is therefore unconfirmed; the message-handler names
|
||||||
|
(`Run/Stop/AbortMissionMessageHandler` on the spooling app) suggest it simply
|
||||||
|
records from RunMission to StopMission.
|
||||||
|
|
||||||
|
## 4. The physical setup at a center (period context, inferred)
|
||||||
|
|
||||||
|
A "camera ship" machine = one more networked PC with a Division board, its
|
||||||
|
out-the-window video feeding the spectator monitors (and the VHS deck for
|
||||||
|
take-home tapes). The mission-review station = the same machine (or another)
|
||||||
|
replaying `last.spl` into the debrief room after the mission. Both could be
|
||||||
|
driven hands-off (CameraDirector) or by an operator with a joystick/RIO.
|
||||||
|
|
||||||
|
## 5. What this means for OUR pod project
|
||||||
|
|
||||||
|
1. **No new binary needed.** BTL4OPT.EXE already contains all of it. A Live
|
||||||
|
Cam station = a second emulated machine on the pod LAN whose egg page
|
||||||
|
says `hostType=1`. Mission Review = `hostType=2` + a `last.spl`.
|
||||||
|
2. **The console app owns the roles.** When the TeslaSuite console port
|
||||||
|
authors eggs, adding a camera-host page is how you light this up — it's
|
||||||
|
an egg field, not a build variant.
|
||||||
|
3. **We can record spools ourselves.** The spool format is trivial (length-
|
||||||
|
prefixed file of raw NetworkPackets, `SPOOLER.CPP:50-95`); our emulated
|
||||||
|
NIC path (slirp/pcap) could tee mission traffic into a `last.spl`
|
||||||
|
independently of any spooling host — giving Mission Review of any pod
|
||||||
|
session after the fact.
|
||||||
|
4. **Camera authoring works from a cockpit.** `CameraRIOMapper` means the
|
||||||
|
vRIO can fly the camera ship and author `.cam` files for our arenas.
|
||||||
|
5. **Renderer note:** a camera/review host renders through the same Division
|
||||||
|
wire our bridge already decodes — the bridge should work unchanged for a
|
||||||
|
spectator host, modulo the `CameraShipHUDRenderable` overlays (2D HUD
|
||||||
|
layer, same 0x29/0x2b dpl2d path as the pod HUD).
|
||||||
|
|
||||||
|
## Quick reference — the cast
|
||||||
|
|
||||||
|
| Piece | File | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| `HostType` enum | `MUNGA/HOST.HPP` | pod / camera / review / console, set per-IP in the egg (`hostType=`) |
|
||||||
|
| `Registry::MakePlayer` | `MUNGA/REGISTRY.CPP` | host type → `Player` or `CameraDirector` |
|
||||||
|
| `CameraDirector` | `MUNGA/DIRECTOR.*` | auto-director: cut to rank-0 player every 10 s; ranking-window HUD |
|
||||||
|
| `CameraShip` | `MUNGA/CAMSHIP.*` | the flying camera vehicle (a Mover entity) |
|
||||||
|
| `CameraControlsMapper` | `MUNGA/CAMMPPR.*` | stick/throttle/pedals camera flight, director toggle, camera authoring buttons |
|
||||||
|
| `CameraL4/Thrustmaster/RIOMapper` | `MUNGA_L4/L4MPPR.*` | camera station input bindings (RIO cockpit supported) |
|
||||||
|
| `CameraInstance(Manager)` | `MUNGA/CAMINST.*`, `CAMMGR.CPP` | fixed clamped cameras; reads/writes `MAPS/<map>.cam` |
|
||||||
|
| `SpoolFile` | `MUNGA/SPOOLER.*` | NetworkPacket recording; `last.spl` on disk |
|
||||||
|
| `L4SpoolingApplication` | `MUNGA_L4/L4SPLR.HPP` | records the mission LAN while participating |
|
||||||
|
| `BTL4PlaybackApplication` + `SpoolerTask` | `BT_L4/BTL4PB.HPP` | replays the spool with `timeBias` re-timing; no real network |
|
||||||
Reference in New Issue
Block a user