Files
RP412/docs/CONTROL-PRESETS.md
T
CydandClaude Opus 5 f7c7000694 The cut vehicles come back, and eight more besides
assets/RP411/RPL4.RES is now the 1.25MB resource from the airlock archive, a
2014 community build, replacing the 785KB file RP412 inherited. It verifies
as a strict superset of ours: nothing is lost but two unnamed Not Used
placeholders, the format version matches, all 26 base vehicles' L4 and
Thrustmaster mapping streams are byte-identical to the ones we shipped, vole
matches resource for resource, and it boots against our own GAUGE, VIDEO and
AUDIO with a log identical to the baseline.

Its L4GAUGE.CFG comes with it. That file is ours plus the new vehicles'
blocks and one fix: dragonInit gains twoBoosterInit, so the dragon's two
boosters finally have gauges - it always had them in its subsystem list and
the panel simply never drew them.

dark, blkspk and blktrn are back with the tables they shipped with in 4.10,
checked against the retail file: blkspk still puts its third booster on the
thumb-high in preset 4 alone, dark still spends preset 5's HORN slot on its
second demo pack. The archive also brings neut, a four-booster Lepton class
with an Eject subsystem, eight community Blacker variants and seven maps.

The black mystery turned out to be a renaming bug rather than a vehicle that
never existed. RP411's gauge config carried a blackInit block nothing could
select, because the lookup is <model>Init and no model is named black. The
airlock config calls the same block - byte-identical body - blktrnInit,
which is the model's real name. disk is now the only genuine orphan: a panel
layout with no vehicle behind it in any resource file, and no entry in the
console's own config either.

The roster page regenerates to 34 vehicles. Four of the eight community
variants have a ControlsMappings List but no streams of their own, pointing
at another vehicle's by id, so they carry no preset table and do not appear.

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

19 KiB
Raw Blame History

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.pccbutpres6.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 16 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 ModeInitialModeNonConfig | 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 — FirstMappableButtonLastMappableButton 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.

Every VTV in assets/RP411/RPL4.RES ships a full six-preset table, for both the RIO and the Thrustmaster — 34 of them as of the resource file promoted from the airlock archive (see §8). 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 ModeStandardRP_L4/RPL4MPPR.cpp:1826), the pod enters configuration state: the aux strips swap their legends from tool art to SET art (strptol1/2.pccstrpset1/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 16 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.

8. The promoted resource file, and the vehicles that came back

assets/RP411/RPL4.RES is no longer the file RP412 inherited. It is the 1.25 MB resource from assets/airlock_RP411/, a 2014 community build, which verification showed to be a strict superset of ours:

  • nothing is lost — the only resources ours has and it does not are two unnamed "Not Used" placeholders;
  • same format version (v1.3.0.2);
  • all 26 base vehicles' L4 and Thrustmaster mapping streams are byte-identical to the ones we shipped;
  • vole matches resource-for-resource, id for id and size for size;
  • it boots and runs against our own GAUGE/VIDEO/AUDIO, with a log identical to the baseline.

Its GAUGE/L4GAUGE.CFG was promoted with it. That file is ours plus the new vehicles' blocks, plus one fix: dragonInit gains twoBoosterInit, so the dragon's two boosters finally have gauges — it always had them in its subsystem list and the panel never drew them.

What the roster gained. The three vehicles cut after 4.10 are back — dark (Blacker Puck), blkspk (Black Speck) and blktrn (Black Tarantula) — with the same tables they shipped with: blkspk's preset 4 still puts its third booster on the thumb-high, dark's preset 5 still spends the HORN slot on its second demo pack. Beyond them the archive adds neut (Neutrino, four boosters and an Eject subsystem) and eight community "Blacker" variants, plus seven maps.

The black mystery was a renaming bug. RP411's gauge config carried a blackInit block that nothing could ever select, because GetGameModel() returns a model name and the lookup is <model>Init. The airlock config calls the same block — byte-identical body — blktrnInit, which is the model's real name. There was never a vehicle called "black"; the block had simply been renamed out of reach. disk remains the one genuine orphan: a panel layout with no vehicle behind it in any resource file, and no entry in the console's own config either.

Four of the eight community variants have a ControlsMappings List but no mapping streams of their own; they reference another vehicle's by resource id, so they carry no preset table of their own and are absent from the roster page.