Files
TeslaRel410/emulator/CAMERA-REVIEW-NOTES.md
T
CydandClaude Fable 5 b2f0becbd4 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>
2026-07-08 09:21:46 -05:00

193 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 |