# Flight controls: executable versus manual

2026-09-18. Canonical unpacked image offsets. Compared with the supplied manual,
printed English pages 17–19 and 22 (PDF pages 10, 11 and 13).

The event dispatcher at `B780–B7B6` indexes an **83-entry** table at `B7FA`
with `(scan - 1) * 2`. **32 entries** have a nontrivial handler. Continuous
controls also read the key bitmap at `15A7`; they need not appear in this table.
The following covers these flight input paths, not every menu or configuration
screen. Availability depends on craft, equipment, mission and cutscene state.

## Main controls and manual coverage

| Key | Flight action | Code evidence | Manual coverage |
| --- | --- | --- | --- |
| Left / right arrows | Bank left / right | Held-key steering, `7AD6` onward | p.17. |
| Up / down arrows | Nose down / up | Same | p.17. |
| Ctrl + arrows | Increase control force | Modifier read from `15AA`; manual p.17 | p.17. |
| Opposing arrows together | Reduce force in first direction | Manual p.17; steering consumes both bits | p.17. |
| A | Toggle automatic altitude adjustment, Caero fighter | `B97A`, flag `7ED2` | p.17. |
| E | Engine toggle; craft-dependent behavior | `B8CE`, flag `4552` | p.17: Caero explicit; other-craft function only hinted. |
| Enter | Boost; craft-dependent behavior | `B956`; energy deduction and message | p.17: Caero explicit; other-craft function only hinted. |
| Number-row − / = (manual says − / +) | Other-craft speed settings | `B913/B918`, values 248/500 to `7F20`, gated by craft | p.17: keys hinted; speed settings unspecified. |
| Backspace | Brake | Input mask at `7F59`, used `7F55`, `7FE0`, `81D4` | p.17. |
| Caps Lock | Clear/reacquire target lock | `797B` calls `CFCB` | p.17. |
| Space | Primary fire | Held-action mask; configurable | p.19. |
| Alt | Secondary fire | Held-action mask; configurable | p.19. |
| 1, 2, 3 | Select available primary weapon | `C8A0`; mode-dependent | p.19: keys listed; later weapon names left blank. |
| 5, 6 | Select available secondary ground weapon | `C885` | p.19: keys listed; names left blank. |
| 8, 9, 0 | Select available secondary air weapon | `C885` | p.19: keys listed; names left blank. |
| Insert / keypad 0 | Hold enlarged radar overlay | `55A5`, default mask `1000` | p.18 explicitly documents keypad 0 and magnification; p.22 identifies Insert equivalence. |
| Tab | Hold third-person look-around mode; steering device controls view | `7872`, default mask `0180` includes joystick button; third-person transition confirmed during playtesting | p.18: look-around documented; third-person transition not explicit. |
| F1 | Cockpit; missile-follow view in missile mode | `B8AC`, view index 0 | p.18. |
| F2 | Behind craft/missile | Same, index 1 | p.18. |
| F3 | Level behind craft/missile | Same, index 2 | p.18. |
| F4 | Full-screen inside / missile-eye view | Same, index 3 | p.18. |
| F5 | Drop camera, follow craft/missile | `B8A0`, index 4 | p.18. |
| F6 | Drop camera, fixed view | `B8A0`, index 5 | p.18. |
| F7 | Drop camera on object pointed at | `257D` | p.18. |
| M | Toggle missile-view mode | `B8C1`, XOR `54CA` with 7; disabled for mode 2 | p.18. |
| Comma / period (manual labels < / >) | Move following camera closer / farther | `B8B2/B8B5`, six-position index `B8B8`, consumed `2554` | p.18. |
| Pause / Num Lock | Pause until another key | Scan `45`, `B7BB` | p.19. |
| Escape | Quit/confirmation UI | `B7CE` | p.19. |
| Q after Escape | Confirm quit | `B7DA`; requires nonzero `3EA5` | **Not listed** in the reviewed control section. |

These are scan codes, not character comparisons. Shift is not necessary for
comma/period zoom or the `=` event. Dedicated versus keypad equivalents depend
on the keyboard/emulator's extended scan-code delivery. The supplied batch file
runs `X Lang=0 Mus=1` without control remappings.

The manual documents mouse/joystick steering and firing as well. It lists command-line
options for changing action masks, but this executable differs from the manual's
fire-option names and has an apparent mask-inversion defect. See the complete
[command-line reference and native parser checks](command-line-options.md) before using remapping recipes.
The mask's keyboard half is: Tab `0100`, Backspace `0200`, equals `0400`,
minus `0800`, Insert `1000`, Caps Lock `2000`, Space `4000`, Alt `8000`.
Lower bits represent mouse/joystick buttons. The bitmap aggregation is `B9F3–BA20`.

The enlarged radar is therefore **documented**, though easy to miss: printed p.18 explicitly calls it an overlaid, magnified display. The undocumented bindings in the extras table are distinguished from the merely hinted craft-specific functions above. The Skimma’s repeated 1–3 enable/disable behaviour is not explained in the reviewed control section.

## Caero weapon number keys

The manual leaves the later weapon names blank. The following mapping is recovered
from the executable's number-row bindings and mission-script weapon introductions.
Selections work only when the mission has made that weapon available; these names
apply to the **Caero**, not the Skimma's craft-dependent 1–3 controls.

| Number-row key | Weapon | Fire action |
| --- | --- | --- |
| **1** | Pinner Direct | Primary |
| **2** | Pinner Mimic | Primary |
| **3** | Brent Forbes Dual Launch | Primary; automatically selects its follow-up homing unit after the capsule launches |
| **4** | No direct weapon binding | — |
| **5** | Forbes Diffuser | Secondary; automatically alternates gas capsule and trigger |
| **6** | Brent Ground | Secondary |
| **7** | No direct weapon binding | — |
| **8** | Caero Weapon | Secondary |
| **9** | Pinner/Forbes Chargeable | Secondary; hold to charge, release to launch at a valid target |
| **0** | Brent Hunter | Secondary |

Default keyboard fire actions are **Space** (primary) and **Alt** (secondary).
The paired weapons use internal selection IDs 4 and 7, but pressing those number
keys does not select the corresponding components. In particular, **3 selects
both stages of Dual Launch through its automatic sequence**; its follow-up remains
on the primary-fire path.

Evidence: dispatch `B793–B7B8`, key table `B7FA`, selection handlers `C8A0/C885`,
weapon indexing `C9D1–C9D8`, and mission availability handler `C281`.
See [weapon identities and behaviour](weapon-behaviour.md) for the slot mapping,
mission evidence and special firing/impact rules.

## Extras and hidden controls

| Key | Finding | Evidence and limits | Manual coverage |
| --- | --- | --- | --- |
| Z | Toggle player-ship freeze after `Lyndon's little snooze` | Playtesting confirms enemies continue, firing and external views work; callback swap at `B938` | **Not listed** in the reviewed manual control/configuration sections. |
| X | Immediately start next-level transition after `Level X` | Playtesting confirms cutscene then progression; `B926` | **Not listed** in the reviewed manual control/configuration sections. |
| Grave / backtick | Alias for F7's object camera action | Both table words point to `257D`; not listed in English controls | **Not listed** in the reviewed manual control/configuration sections. |
| F9 | Switch Gouraud-shaded polygons to the flat-colour path and back | `33B4` changes relative jump operands at `FC13/FE13`, selecting `30EB/30A1` versus `312A/30BD`; extra bytes are vertex shades, not UVs; see model-colours.md | **Not listed** in the reviewed manual control/configuration sections. |
| J | Reset joystick sampling/calibration state | `B7F4` calls `171C` and `1731`; not a demonstrated mouse/joystick selector | **Not listed** in the reviewed manual control/configuration sections. |
| Keypad minus | Cycle paired-view camera separation through 8,16,24,32,40; no effect on the ordinary single-view path | `B7E5` changes immediate `2B69`; `2B51` gates the paired camera offsets on display mode `3CFD >= 1`. Stereo-separation interpretation follows from the two displaced scene draws; full alternate-display output not tested | **Not listed** in the reviewed manual control/configuration sections. |

Shift also participates in a camera-state latch (`3E20`, `257E`); this has not
been assigned a separate player-facing action. No active event-table bindings
for F8, F10, number-row 4 or 7 were found. This does not make them unavailable
in other screens or through a different input device.

Keypad minus has **five states**, with an initial parameter of 16: successive presses select 24, 32, 40, 8, 16. At `2B51`, mode `3CFD < 1` jumps straight to the ordinary scene draw at `2BC1`, bypassing this parameter entirely. Otherwise the renderer saves the camera position, applies a half-parameter offset (`2B6E`), draws one view, applies a negative full-parameter offset (`2B7C`), restores the saved camera values, switches drawing destination and draws the other view. `2B97` projects these offsets using camera-orientation coefficients. This strongly identifies a stereo/paired-view separation adjustment: look for changed relative parallax between the two views, not a detail or shading change. No visible change is expected in the standard single-view mode, consistent with the test. Hardware/display-mode activation and full output remain unverified.

## Reproduction

`python3 tools/inspect_flight_keys.py` writes all 83 entries, including no-ops,
and the relevant held-key bit locations to `analysis/flight-keys/bindings.json`.
This is static extraction plus manual comparison, not a claim that every key
has been exercised in a complete playthrough. The existing hidden-command
harness separately executes the Z/X handlers and their gates.

## Halon / Skimma differences

Playtesting confirms that after level 99 the player moves into the Halon city and
flies a Skimma, and that **E switches its shields on/off**. Bank 31's role is
therefore identified by gameplay, not merely guessed from its geometry.

Craft configuration is separate from city-bank selection. Loader `BC50–BC66`
reads 24-byte records at `1B66 + 24 * byte[F003]`:

| Configuration index | Control discriminator `B91F` | City bank | Initial callback |
| --- | --- | --- | --- |
| 1 | `19` | 30 | `7E7F` (Caero) |
| 2 | `1A` | 31 | `8108` |
| 3 | `1B` | 31 | `7E49` |
| 4 | `19` | 32 | `D510` (underground) |

Playtesting confirms that the higher Enter speed is turbo on the upgraded Skimma.
This identifies the `1B` behavior as the upgraded configuration; the complete
mission-to-configuration schedule remains to be mapped.

| Control | Caero (`19`) | Halon configuration `1A` | Halon configuration `1B` |
| --- | --- | --- | --- |
| E | Engine toggle | Shield toggle | Same non-Caero shield branch |
| Number-row − | No speed-setting write | Set speed parameter 248 | Set speed parameter 248 |
| Number-row = | No speed-setting write | Set speed parameter 500 | Set speed parameter 500 |
| Enter | Spend 32 boost reserve; activate boost | No action in this handler | Set speed parameter 640 |
| A | Altitude-hold toggle | Normal `8108` flight path ignores this toggle | Needs configuration-specific callback assessment |
| 1–3, where available | Select weapon | Select/toggle weapon enable | Select/toggle weapon enable |

The speed keys select discrete parameters; they do not increment/decrement a
throttle value. The numbers are internal parameters, **not established mph**.
Holding the brake bypasses the selected parameter in the `81D1–81E0` path.
The manual's printed p.14 mentions cruising/turbo speeds and upgrades, but does
not supply these key assignments. Playtesting has now confirmed − / = as speed
down/up, and Enter as the upgraded Skimma’s higher maximum speed (turbo).

For non-Caero weapons (`C8B6–C8DB`), a repeated press on an already selected
available weapon toggles its low enable bit. Changing selection also clears
other enable bits (`C8DD`). This differs from ordinary Caero selection. The
isolated probe verifies key 1 twice yields **1,0** in the Halon configurations,
versus **1,1** for Caero. Availability is still gated by the equipment mask.

### Shields and landing

E sets a startup deadline at `57B8 = timer[0BF8] + 06FF` when enabling shields.
The display logic at `57B7–5828` uses that deadline and the resource at `810A`.
Thus the code distinguishes enabling shields from their later displayed state;
no conversion of the timer interval into real seconds is claimed.

With the shield flag enabled, `84E7–84FC` subtracts incoming damage from `810A`
and sets a different damage status when depleted. With shields off, it instead
sets damage status 4. This identifies the earlier “incoming quantity” test more
precisely for the Halon craft: it is shield-resource depletion, not a universal
health field. A 21-point probe takes reserve 100 to 79 with shields enabled.

The supply-pad approach path (`C6FB–C76E`) requires shields **off**, object flag
`10` clear, bank mode 1, and suitable movement/position conditions. It tests the
cell's type for **3**, then installs automatic landing callback `7D9A` and sets
object flag `10`. E refuses to leave shields enabled while that flag is set.
This matches the manual's p.15 requirement to disable shields before landing.
It also identifies **bank 31, type 3** as the supply-pad geometry used by this
landing check.

`tools/verify_flight_controls.py` executes 16 original-handler probes covering
speed/boost differences, E's landing-state restriction, shield damage and repeated
weapon selection. Sound/UI notification and target-refresh helpers are stubbed;
this is not a substitute for testing each late-game configuration in DOSBox.


### Manual shield timing observation

We measured shield charge/startup as approximately 4 seconds with manual
reaction-time uncertainty. The PIT setup subsequently traced to divisor 2,386
predicts about 500.075 ticks/second, putting the 06FFh deadline at 3.58146 seconds.
The approximately 0.42-second difference is retained rather than declaring an
exact confirmation or changing the clock rate to fit it. The display routine
57B7–5828 has several phases based on the remaining deadline and shield reserve;
which visible transition was timed is not established. No extra fixed delay
has been established by this inspection. Video timing of consistent start/end
landmarks would resolve the comparison more tightly.

See [recorded measurement](../analysis/flight-physics/user-shield-calibration.json).
