Files
RP412/docs/CONTROL-PRESETS.md
T
CydandClaude Opus 5 cf59ac9f4b Every VTV and its presets, in a page that ships with the game
VTV-PRESETS.html joins the handbook in the dist: all 26 racable vehicles,
each with its hull plan view, what it carries, and the six control presets
on its stick. The data is decoded from RPL4.RES rather than transcribed -
each vehicle's own subsystem list names its systems, and
VTV::BasicSubsystemCount fixes list index i as subsystem 9+i, so every
subsystem id in all 26 mapping streams resolves with nothing left over.
The gauge config could not have been used for this: it is only artwork,
and it lies. The dragon has boosters and a chute with no art declared,
and the burro's chute sits in a different panel slot than sequence would
suggest. Both come out right this way.

The plan views are each vehicle's own damage-gauge silhouette, redrawn
from the three-colour original as an alpha mask so it takes the page's
colour in either theme. There is no per-vehicle art to use instead:
vehicles are grouped into hull families that share both the silhouette
and the mesh in VIDEO/, which is keyed by the same two-letter code. Each
card says which hull it is and who else races the same one.

The page does not mention the intercom PTT. Its hardware never went past
prototype cockpits, so naming a control nobody can press would only raise
questions; those cells are simply blank. CONTROL-PRESETS.md carries the
full account, because anyone re-decoding the resource will find message
ID 13 on the pinky and needs to know why the tables show it empty. Two
things in the shipped assets settle it: the tool panel's fourth quadrant
is bare where every other system has a legend, and the two finished
intercom station screens are referenced by nothing at all. The edge
strips are referenced - but gated on ModeIntercom, which nothing ever
sets, so they have never been on screen either. The block that would have
wired the buttons is inside #if 0 and still names L4ModeManager, a class
that no longer exists: cut before the RPL4ModeManager rename and never
revisited.

The doc also now explains the mode-mask gate itself, since that is what
makes the dead intercom legible: one 32-bit word, seeded 0x201, read by
both the controls dispatch and the gauge renderer's active/inactive
sorting. A drawable whose bit is never set is parked in inactiveList for
the life of the process and never complains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 13:10:53 -05:00

333 lines
17 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.
# Red Planet — the six control presets on the map screen
The six amber buttons down the right flank of the map display, labelled
PRESET 1 … PRESET 6 on the glass itself. They are not a display option and
they do not touch the map: **each one is a complete factory layout for the
four mappable joystick buttons**, authored per vehicle and shipped in
`RPL4.RES`. Pressing one swaps the whole stick over, live, mid-race.
```
map glass, right flank (vRIO 0x18..0x1D, amber)
┌──────────────┐
│ PRESET 1 ● │ Secondary7 press
│ PRESET 2 ○ │ Secondary8 │
│ PRESET 3 ○ │ Secondary9 ▼
│ PRESET 4 ○ │ Secondary10 VTVRIOMapper::SelectPresetMessageHandler
│ PRESET 5 ○ │ Secondary11 │ (keyboard 1-6 joins here)
│ PRESET 6 ○ │ Secondary12 ▼
└──────────────┘ L4VTVControlsMapper::PresetEnable(n)
├─ remove old ModePresetN from the
│ mode manager, add the new one
│ │
│ ▼
│ every control mapping whose modeMask
│ carries that bit goes live; the other
│ five presets' mappings go dead
└─ NotifyOfPresetChange(old, new)
lamp old→dim, new→on
```
## 1. What the player sees
The map is the pod's portrait-mounted secondary monitor. Its gauge group
paints the button legends itself — `tags` in
`assets/RP411/GAUGE/L4GAUGE.CFG:1611`, six 16×102 strips at x=464:
| legend | file | cell |
|---|---|---|
| PRESET 1 … PRESET 6 | `butpres1.pcc``butpres6.pcc` | one per legend cell down the right edge |
| ZOOM + / ZOOM / reticle / HORN | `butzmin`, `butzmout`, `butcross`, `buthorn` | left edge, top four cells |
The legend grid is not `height/6`: the first cell starts 13 rows down and
the six cells are 102 tall on a 105 pitch (13 + 6×102 + 5×3 = 640). vRIO's
`MFDSplitView::LayoutButtons` scales exactly that grid so each on-screen
button lands against its own painted label
(`MUNGA_L4/L4MFDVIEW.cpp:683`, constants at `:103`).
Addresses run **down** each column from the anchor — the map view is
created with `SideColumns, 0x10, 0x18` (`MUNGA_L4/L4VB16.cpp:4450`), so the
left column is Secondary1…6 top-to-bottom and the right column is
Secondary7…12, i.e. **PRESET 1 is the top button, PRESET 6 the bottom**.
That ordering is confirmed by the left column, where the code binds
Secondary1→zoom in, 2→zoom out, 3→reticle, 4→horn
(`RP_L4/RPL4MPPR.cpp:1857`) and the artwork paints ZOOM+/ZOOM/cross/HORN
in exactly that order, leaving the bottom two cells blank — the pod wires
only 6 of each column's 8 addresses; 0x16/0x17 and 0x1E/0x1F are Tesla
relays (`MUNGA_L4/L4CTRL.cpp:1901`).
Preset 1 is lit at construction, the other five dim
(`RP_L4/RPL4MPPR.cpp:1962`). These six lamps are driven explicitly by
`NotifyOfPresetChange`; unlike most panel lamps they are not linked to
their button's mapping state (`SetAutomaticOperation(False)` in
`CreateControlledLamp`, `RP_L4/RPL4MPPR.cpp:47`).
## 2. The mechanism — a preset is a mode-mask bit
The engine gates every control mapping behind a `ModeMask`. RP adds six
bits purely for this (`RP_L4/RPL4MODE.h:30`, `nextModeBit` = 0 so the
values are literal):
| mode | bit | value |
|---|---|---|
| ModeNonConfig / ConfigReady / ConfigOn | 02 | 0x001 / 0x002 / 0x004 |
| **ModePreset1 … ModePreset6** | **38** | **0x008 … 0x100** |
| ModeBasic / ModeStandard / ModeIntercom | 911 | 0x200 / 0x400 / 0x800 |
`PresetEnable` (`RP_L4/RPL4MPPR.cpp:685`) does one thing: removes the
outgoing preset's bit from the application mode manager and adds the
incoming one. `ControlsUpdateManager::Update` then only dispatches
instances whose `modeMask` intersects the live mask
(`MUNGA/CONTROLS.h:599` shows the same test in `GetMapState`), so five
sixths of the authored stick mappings are simply invisible at any moment.
Consequences that fall out of that design:
- Presets cost nothing to switch — no rebuild, no allocation, one mask word.
- They are exactly six because there are six switches: `presetCount = 6 //
limited to number of switches!` (`RP_L4/RPL4MPPR.h:119`).
- Nothing *but* control mappings is preset-scoped. No gauge, bitmap or
strip in `L4GAUGE.CFG` references `ModePreset*` — the mode names exist
in the lookup table (`RP_L4/RPL4MODE.cpp:23`) only so the resource
compiler can resolve them in the control-mapping source.
- Entering configuration state drops all six (`ModeAllNonConfig`) and
restores the previously selected one on exit
(`RP_L4/RPL4MPPR.cpp:483`).
Keyboard `1``6` calls `PresetEnable` directly
(`RP_L4/RPL4MPPR.cpp:1048`), the same entry point the flank switches use.
Both paths therefore move the lamps, because `PresetEnable` announces the
change itself through the virtual `NotifyOfPresetChange` (`:733`) rather
than leaving it to the switch handler — see §7.
### One mask, two consumers
The live mask is a single 32-bit word on the `ModeManager`
(`MUNGA/MODE.h:65`), seeded once with `ModeInitial` — `ModeNonConfig |
ModeBasic`, **0x201** — at `RP_L4/RPL4APP.cpp:539`, and changed only by
`AddModeMask` / `RemoveModeMask` / `ReplaceModeMask` (`MUNGA/MODE.h:26`).
Two subsystems read it, and both gate the same way — a bitwise AND, never
an equality, so any shared bit passes and `ModeAlwaysActive` (`-1L`, all
bits) always matches:
| consumer | object | gate |
|---|---|---|
| controls | `ControlsInstance::modeMask` | `MUNGA/CONTROLS.cpp:132` in `ControlsMappingGroup::Update` — decides whether a mapping dispatches at all |
| gauges | `GaugeBase::modeMask` | `MUNGA/GAUGREND.cpp:3625`, `:3782`, `:3852` — decides whether a drawable sits in the active list |
The gauge half runs in `GaugeRenderer::ExecuteForeground`
(`MUNGA/GAUGREND.cpp:3581`), which reads the mask once per pass, derives
`change_mode_mask = current ^ previous` (`:3597`), files newly created
gauges into the active or inactive chain (`:3625`), and — only when the
mask actually moved (`:3661`) — shuffles objects between them
(`ActivateGaugeBases` `:3782`, `DeactivateGaugeBases` `:3852`). Only the
active chain is executed, so an unmatched drawable costs one list entry
and nothing else. The lamp manager is handed the same word a few lines
earlier (`:3604`), which is how the preset and mode lamps are filtered.
A drawable whose bit is never set therefore never draws, and never
complains — it is simply parked in `inactiveList` for the life of the
process. That is exactly what happened to the intercom art (§3): its
gauges are authored, resolved and loaded, and then sit in the inactive
chain forever because nothing ever ORs `ModeIntercom` into the mask.
## 3. What is actually in a preset
Only the four mappable stick buttons are ever preset-scoped —
`FirstMappableButton`…`LastMappableButton` minus the hat
(`MUNGA_L4/L4CTRL.h:497`):
| element | button |
|---|---|
| 0x40 | trigger |
| 0x45 | pinky |
| 0x46 | thumb low |
| 0x47 | thumb high (by the hat) |
Everything else — throttle, pedals, stick axes, hat, the whole aux panel —
is bound `ModeAlwaysActive` or `ModeNonConfig` and is identical in all six
presets. The functions that can land on a stick button are the ones that
also have a panel button and a "configure" partner: booster fire, chute,
weapon trigger, LIFT CUT, SIDESLIP, HORN, and the intercom PTT — the last
of which never works on a shipped pod, see below
(`RP/VTVMPPR.cpp:144`, `RP/BOOSTER.cpp:50`, `RP/CHUTE.cpp:48`,
`RP/WEAPSYS.cpp:103`).
### The intercom PTT binding is dead on shipped hardware
The intercom press-to-talk needed a headset and a comms panel that never
went past prototype cockpits. `ActivatePTTMessageID` is still bound in the
authored data and still handled in the code, but no production pod can
trigger it, so **the buttons it occupies are dead, and are reported as
unbound in the tables here and on the roster page**. Anyone re-decoding
`RPL4.RES` will find message ID 13 sitting on the pinky and wonder why the
tables show it empty, hence this section.
PTT occupies 26 preset rows across 13 vehicles — always the pinky, always
presets 1 and 3, and only on lightly armed or unarmed vehicles, where the
pinky was free. Two independent things in the shipped assets confirm the
hardware never arrived:
1. **Its panel label was never drawn.** The tool panel (`auxUL2`) has four
button-pair quadrants and `common_setup` fills three of them — LIFT CUT
at (22,240), SIDE SLIP at (327,240), HORN at (327,54)
(`assets/RP411/GAUGE/L4GAUGE.CFG:767`). The fourth, (22,54), is blank —
and that is exactly the `AuxUpperLeft5/6` pair the PTT is bound to
(`RP_L4/RPL4MPPR.cpp:2000`, between the LIFT CUT/SIDESLIP pairs at
`:1978` and the HORN pair at `:2011`). Every other configurable system
has a legend on the glass; the PTT has bare panel.
2. **Its display art never reaches the glass.** `GAUGE/RPAICOM.PCX` and
`GAUGE/RPACOMM.PCX` are finished 640×480 intercom station screens that
the gauge config never references at all. The edge strips
`strpcom1/2.pcc` *are* declared — top and bottom of the centre aux
display, `port = auxC` (`assets/RP411/GAUGE/L4GAUGE.CFG:706`) — but
gated on `ModeIntercom`, and **nothing ever sets that bit**: there is no
`AddModeMask(ModeIntercom)` anywhere in the tree, and the config emits
no `enable` command for the data-driven path
(`MUNGA/GAUGREND.cpp:1791`) to pick up either. The one block that would
have wired the intercom buttons sits inside `#if 0`
(`MUNGA_L4/L4ICOM.cpp:903`) and still names `L4ModeManager`, a class
that no longer exists — so it was cut before the `RPL4ModeManager`
rename and never revisited. None of this art has ever been on screen.
Dropping it costs nothing on 12 of the 13 vehicles — presets 1 and 3 stay
distinct because their thumb-low binding differs. The exception is the
**quark**, whose presets 3 and 4 become identical, since the PTT was the
only thing separating them. Four other vehicles ship preset pairs that were
already identical before any of this: dragon, roach and spitter (3 ≡ 4) and
puck (2 ≡ 5).
**The roster page never mentions the PTT at all.** `docs/vtv-presets.html`
is the player-facing artifact; naming a control that cannot be pressed
would raise more questions than it answers, so those cells are simply
blank there.
## 4. The shipped tables
The mappings are streamed from the vehicle resource at mission start —
`LBE4ControlsManager::CreateStreamedMappings`
(`MUNGA_L4/L4CTRL.cpp:2153`), fed from the entity's ControlMappings list
resource by name: `"L4"` for the pod RIO, `"Thrustmaster"` for the stick
(`RP_L4/RPL4APP.cpp:761`). Each record is a `ControlsMapping`
(`MUNGA/CONTROLS.h:783`) carrying its own mode mask, so a single button can
appear six times with six different targets.
**All 26 VTVs in `assets/RP411/RPL4.RES` ship a full six-preset table, for
both the RIO and the Thrustmaster.** Decoded, the *lepton* (two boosters,
one chute, unarmed) reads:
| | trigger | thumb high | thumb low | pinky |
|---|---|---|---|---|
| PRESET 1 | booster 1 | booster 2 | chute | — |
| PRESET 2 | booster 1 | booster 2 | chute | **LIFT CUT** |
| PRESET 3 | booster 1 | booster 2 | **LIFT CUT** | — |
| PRESET 4 | booster 1 | booster 2 | LIFT CUT | chute |
| PRESET 5 | booster 1 | booster 2 | **HORN** | LIFT CUT |
| PRESET 6 | **LIFT CUT** | booster 1 | booster 2 | chute |
An armed vehicle substitutes the weapon trigger for the primary booster.
The *puck* (one booster, laser):
| | trigger | thumb high | thumb low | pinky |
|---|---|---|---|---|
| PRESET 1 | laser | booster | HORN | — |
| PRESET 2 | laser | booster | HORN | LIFT CUT |
| PRESET 3 | laser | booster | LIFT CUT | — |
| PRESET 4 | laser | booster | LIFT CUT | HORN |
| PRESET 5 | laser | booster | HORN | LIFT CUT |
| PRESET 6 | **LIFT CUT** | booster | — | laser |
The authored intent is consistent across every vehicle in the file:
1. **Presets 15 keep the primary weapon (or lead booster) on the
trigger** and shuffle the *secondary* duties — where LIFT CUT lives,
whether the stick carries HORN, and whether the chute/third system
stays on the stick at all.
2. **Preset 2 always puts LIFT CUT on the pinky** and **preset 6 always
puts LIFT CUT on the trigger** — verified true for all 26 tables.
Preset 6 is the outlier layout: everything shifts up a finger and the
trigger becomes a handling control rather than a fire control.
3. Vehicles carrying fewer systems leave cells empty, and some presets then
collapse into duplicates — on the puck, presets 2 and 5 are identical.
4. Heavily armed vehicles spend the freed cells on second and third
weapons instead of HORN (bttlbrg, gator, roadblk).
## 5. Editing the active preset in flight
The presets are also the storage for in-mission rebinding. With the CFG
button on the upper-right MFD (`AuxUpperRight1/2`, registered under
`ModeStandard` — `RP_L4/RPL4MPPR.cpp:1826`), the pod enters configuration
state: the aux strips swap their legends from tool art to SET art
(`strptol1/2.pcc` → `strpset1/2.pcc`, `L4GAUGE.CFG:715`), and every
mappable button gets a temporary mapping to the chosen system's "choose"
message (`RP_L4/RPL4MPPR.cpp:604`). Pressing a panel button arms a
function; pressing a stick button toggles it via `AddOrErase`, which
writes against **`previousPresetModeMask`** — the currently selected
preset, and only that one (`RP_L4/RPL4MPPR.cpp:1538`).
The `configMap` gauge on that display (`RPL4GAUG.cpp:3199`, placed at
`L4GAUGE.CFG:749`) shows the state of all four stick buttons against the
armed function, one icon each, using the same preset mask:
`cfgNone` / `cfgOther` / `cfgMe` / `cfgBoth` for
`unmapped` / `mappedByOthers` / `mappedByMe` / both
(`MUNGA/CONTROLS.h:315`).
Configuration is unavailable in Basic control mode — the CFG button and the
`configMap` gauge are both `ModeStandard`, which is set by Standard,
Veteran and Master but cleared by Basic (`RP_L4/RPL4MPPR.cpp:374`).
**Nothing is persisted.** Edits live in the `ControlsMappingGroup` chains
for the mission only; no code writes mappings back to disk, and the next
mission re-streams the authored table from `RPL4.RES`. (Porting a real
bindings file is still open — see the roadmap's Workstream A.)
## 6. The preset number goes out on the wire
`PresetEnable` sets `mustMatch = preset_number` unconditionally, before the
early-out, tagged `HACK - for backward watcher compatibility`
(`RP_L4/RPL4MPPR.cpp:700`). `mustMatch` is a replicated attribute of the
mapper (`RP/VTVMPPR.cpp:384`), so a watcher/spectator station sees which
preset a pilot is on. It is the only preset state that leaves the machine.
## 7. Defects found while reading
**Fixed (2026-08-06):** both lamp defects below.
1. **The preset loop wrote past `modeLamp`.** `modeLampCount` is 4, but the
preset pass stored six lamps into `modeLamp[i]`, i = 0…5. The members
are declared `configLamp[2], modeLamp[4], presetLamp[6]` in that order
(`RP_L4/RPL4MPPR.h:312`), so indices 4 and 5 landed in
`presetLamp[0..1]` and the whole thing was self-consistent by memory
layout — the preset press handler read the same out-of-range slots.
Two real consequences: an out-of-bounds write, and it **destroyed the
four control-mode lamps** created immediately above (`:1921`), so
`NotifyOfControlModeChange` drove PRESET 14's lamps on the map flank
and the Basic/Standard/Veteran/Master lamps on the upper-right MFD were
never lit at all. `presetLamp[]` was meanwhile never populated or read.
The preset pass now fills `presetLamp[]` (`:1960`), which is what the
array was always for.
2. **Keyboard preset switching desynchronized the lamps.** The lamp work
lived in `SelectPresetMessageHandler`, so only the flank switches moved
the lamps and keyboard `1``6` left the wrong one lit. It now lives in
the virtual `NotifyOfPresetChange`, announced by `PresetEnable` itself
(`:733`) — one place, every path. `VTVRIOMapper` overrides it to move
the six flank lamps (`:1651`); the base mapper and the Thrustmaster
mapper have no preset lamps and inherit the no-op (`:746`).
Verified by reading the commanded RIO lamp states out of the running
game (`PadRIO::lampState`, PadRIO + `TEST.EGG`, at rest in Basic mode).
Before and after are byte-identical except for lamp 0x33:
| lamps | before | after |
|---|---|---|
| 0x18 PRESET 1 / 0x190x1D PRESET 26 | 3c / 14 | 3c / 14 |
| 0x300x32 MASTER/VETERAN/STANDARD | 14 | 14 |
| **0x33 BASIC** | **14 (dim — never lit)** | **3c (lit)** |
Still open, by design rather than by accident: `PresetEnable` early-outs on
`preset_number == previousPresetNumber`, so re-pressing the lit switch is a
no-op. Correct for switching, but it also means a preset can never be used
as a "reset to authored bindings" after the player has edited it in
configuration mode.