diff --git a/emulator/CAMERA-REVIEW-NOTES.md b/emulator/CAMERA-REVIEW-NOTES.md new file mode 100644 index 0000000..071e5a6 --- /dev/null +++ b/emulator/CAMERA-REVIEW-NOTES.md @@ -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=` 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/.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/.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 |