# Input and cockpit state

The original keeps device polling, held controls, command dispatch, cockpit controls and player movement separate. A modern input layer should produce the same command/control structure; it need not emulate DOS interrupts or analogue joystick calibration.

Development/repositioning controls now have [separate explanations and original-code captures](CHEATS_AND_DEVELOPMENT.md). [COMMAND_LINE.md](COMMAND_LINE.md) covers program arguments and recording/playback setup.

## Held keys

`tools/check_input.py` independently checks all 4,096 combinations of the twelve direction keys and 3,000 press/release events against `DBSIM 0x4c818` and `0x4c6ec`. Direction pairs come from the initialized original fixture. Results and all four command tables are in `analysis/interface/input.json`.

The fourteen held inputs are the eight perimeter keypad keys, M/J/K/I, Space and Enter. The first eight operate the movement pair, while M/J/K/I operate the separate aiming pair. Four diagonal movement entries are exclusive: the first such held entry replaces earlier movement and skips later movement keys. Only the first held dedicated aiming key contributes; these keys do not compose diagonal aiming. The normal step is 256 for walking and 64 for aiming; alternate control mode reduces movement to 64. Alt raises either to 256.

A press sets held and clears released. Releasing before an update has consumed that press marks it for one final update. Releasing after consumption clears it immediately. Consequently a short tap must not disappear between frames. Space and Enter have their own held flags; Enter is also allowed through to command processing.

The keyboard interrupt consumer at `0x7de20` establishes the modifier bits: Alt `0x200`, Ctrl `0x400`, Shift XOR Caps Lock `0x800`. Do not confuse these with SDL modifier values. Application command masking removes unrelated device bits. Native KEYJOY.CFG supports `TILT`, `BACKTURN` and `MISSILE`, each with DEFAULT/REVERSE; the supplied CD defaults explicitly reverse missile pitch.

## Commands and precedence

`0x59df8` offers a command to the cockpit first (`0x2d24c`), then the global handler, then the controlled actor's virtual handler. The HERC handler `0x11050` in turn offers it to the weapon list `0xd4b4` before its remaining commands. Thus an Escape or Tab has different effects according to the active panel/view. A single global hotkey switch would lose that precedence.

The exported tables preserve each numeric command and exact branch address. Established groups include:

- Cockpit 1–9: select the corresponding weapon control; selecting it again toggles its expanded control. F1–F6 route through the main view/display buttons; F7–F8 route through the wingmate panel. F9/F10 request side-view transitions. Tab cycles recognized targets when the current view permits it. R toggles the player's radar flag through `0x157f4`.
- Weapon Z: `0xd1c4` restores ordinary charge settings across the list (820 for enabled capacitor weapons, a no-op for ammunition weapons); B: cycle the three weapon-list modes; W/Alt+W: next/previous weapon. Minus/Equals and keypad minus/plus adjust the selected weapon through its virtual control. Alt+1–9 toggle the corresponding numbered mount in the current weapon group through `0xd5e4`.
- Global Backspace: request aim centering. Q: mission-result/exit dialog; Ctrl+Escape and Ctrl+Q: explicit exit dialog. P: pause dialog. Alt+P/F12: preferences/configuration. Insert: alternate joystick control mode when enabled. The view, recording and development commands remain exported with their exact consumers rather than guessed UI names.

`0x5a0c0` applies controls after command handling. Right-mouse aiming records an origin only when the initial press lies in the cockpit scene rectangle. Horizontal response is signed squared displacement divided by 64; vertical response is signed squared displacement divided by 32. Holding both mouse buttons resets torso yaw and elevation. These are positional, nonlinear controls, not raw mouse-delta steering. External camera and guided missile modes redirect the axes through other consumers.

## Live cockpit exchange

`tools/check_cockpit_state.py` executes the original `0x155a8` and its actual initialized widget methods for 768 supplied states. It compares independently calculated reactor reserve, front/rear shields, shield balance, current/commanded throttle, heading/torso/elevation and target ownership counts. It does not replace weapon-panel exchange or gauge setters with stubs.

- Reactor display: signed reserve × 1024 / 10000, truncating toward zero.
- Shields: front and rear charge × 1024 / capacity, plus the requested balance.
- Speed: signed current speed × 1024 divided by the appropriate forward/reverse profile limit. The throttle command flows from the widget to the actor normally; the actor's changed flag reverses this exchange for a keyboard/programmatic adjustment and then clears.
- Heading and torso angle remain frozen in the display when the actor is disabled; elevation still updates.
- Changing the displayed target decrements the previous target's assigned-attacker count, increments the new one's, clears the lock indication and marks the actor's target changed. The cockpit is therefore an active participant in target state, not only an observer.

`0x2cf28` updates the widget collection; `0x2b4a0` draws it. These calls also manage the dedicated instrument surface and the final forward overlay, so their ordering matters.

All eight 1,700-byte GAU files now agree byte-for-byte with the original loader after its selective coordinate rebasing. There are **nine**, not ten, stored weapon rectangles. Named layout blocks identify weapon controls, reactor, shields, view controls, throttle, heading/targeting, wingmates, and HERC identity/clock. The cockpit browser can overlay these bounds. Its static artwork remains a layout inspector; the reference renderer separately runs the actual live widgets.

Joystick configuration, interactive panels and portable widget drawing are integrated. Their original-code comparisons and host-window checks are described below and in MULTIFUNCTION_DISPLAYS.md and SHELL.md.

The native C++ held-key port now agrees with all 4,096 accumulation and 3,000 transition captures, including complete latch state. `check_input.py` retains these original states in `work/research/interface/native-keyboard.json` for fixture import. SDL routing, cockpit command precedence and mouse/joystick consumers are integrated; their comparisons are recorded below.

## Camera state

`check_camera.py` captures 960 original `0x4b8` updates and 1,200 original `0xa04` control updates; native C++ matches all returned state. Mode 0 is free movement, 1 is target-relative orbit and 2 is model-attached. Free/orbit pitch is limited to ±0x3000. Orbit distance stays within 2,500–30,000 units. A camera movement that falls below height 100 rolls back position/orientation unless increasing orbit distance, in which case the original inverse-sine lookup raises the camera. Distance and acceleration state are not rolled back along with the pose.

Attached mode uses the actor's real virtual node getter, combines its local matrix with the actor transform, then applies the short-coordinate camera offset. Its Euler extraction uses the original inverse-sine/atan tables, including the pole branch. When the actor has no attachment matrix, the camera instead follows its base placement and ignores the additional look angles.

HERC profile disk word 10 selects the camera part. `0x10ee0` supplies attachment offset `(0, word98, word100)` and orbit focus `(0, word102, word104)`. The offsets are local coordinates, not additional world heights. `0x13201` finds the part in the full model and returns the corresponding live hierarchical node transform.

## Player control routing

`capture_player_input.py` records 4,096 original `0x5a0c0` routing calls from the
initialized cockpit. Native C++ agrees on every movement/aim/camera/fire/target
request and retained state. Commands, widget events and the downstream movement,
aiming, camera and firing methods are explicit boundaries; those methods have
separate original-code comparisons.

Mouse-origin acquisition includes the viewport edges. The first right press
records the origin; subsequent updates use signed squared displacement, retaining
32-bit overflow and short axis addition. Both mouse buttons reset torso and
pitch angles without clearing the previous mouse response during that update.
Releasing right clears the response. Manual aiming cancels centering; otherwise
an enabled tracker follows a living selected target. A disabled HERC instead
requests centering. The target's virtual offset is added in world axes, rather
than rotated again. External camera mode redirects the main axes and suppresses
firing; controlled flyers use their separate control consumer. The third joystick button has a
rising-edge target-cycle request. Release settings can stop throttle or request
centering after this update's aim has already run.

## Player integration comparisons

The native mission now has three independent 720-frame comparisons that run
original player input, target/weapon exchange, live controllers, impacts, power
and perception together. Explicit widget requests select targets, pair weapons
and enable tracking; the manual-missile variant changes only the player's missile
ammunition selector to 3. All 41,040 actor observations and retained player weapon,
target, seeker and RNG states agree. The minimal headless widget adapter omits
painting and returns unchanged throttle; the live gauge owners have their own comparisons below.

Camera/manual-missile pointers in the original input structure can alias movement
or aiming. A steering missile clears that shared pair and the fire flag before
the HERC input consumer runs. Native code represents the chosen pair explicitly.
`capture_mission_herc_frames.py` and `player_world_capture.py` retain the exact
boundary definitions; commands are applied after group dispatch as in 0x59700.

## Native cockpit instrument drawing

Portable drawing now includes reactor reserve, forward/reverse speed lamps,
requested throttle, and shield balance. `check_reactor_gauges.py` captures 96
original full-paint rasters through all chassis-specific ENG_CLP masks.
`check_throttle_gauges.py` captures 864 complete native widgets, including the
Raptor's horizontal slider, clamping and inclusive LED boundaries. The current
speed lamps and requested-speed thumb are separate values; both use Q10 units.
THR_LED1/2 provide the original lamp strips, and PBUTTONS supplies the thumb.
The horizontal widget reserves twelve pixels even though the authored thumb
bitmap is wider. Index zero remains transparent when the button is painted.

`check_shield_instruments.py` captures 480 original shield widgets and all 1,025
charge levels for the six animated DAC entries, indices 80–85. Those entries
colour the existing cockpit shield artwork; the numerical displays instead
show the requested front/rear allocation, summing to 200 percent. CPGREEN is the
numeral font, confirmed by comparing its complete glyph payload with the live
initialized font. PBUTTONS entries 58–63 provide three faces for each button.
The original three palette dimming expressions are retained individually,
including their distinct thresholds and integer rounding.

`NativeRaster` runs original constructors, readers and VGA painters. UI bitmap
scratch copies require the original 640-pixel backing-page stride even when the
visible capture is only 320 pixels wide. Its real native control container owns
widget registration. Captures deliberately isolate full paints and supplied
widget state; they do not establish pointer-event behavior or full cockpit
composition by themselves.

## Cockpit pointer controls

`check_instrument_pointer.py` runs the original mouse event queue, hit tests,
press/drag/release methods and instrument callbacks for 1,248 sequential updates
across all eight cockpits. Native C++ matches the selected control, drag state,
button faces, pending and painted thumb positions, requested throttle and shield
balance after every update. Shield actions use their original simulation exchange.
The fixture replaces host mouse packets only and paints between packets; queued
packets within a single paint interval retain the previous painted geometry.

The throttle release getter reads that last painted thumb, not its pending mouse
coordinate. The vertical callback negates the axis value. Right releases still
pass through the shared event controller, but shield button callbacks require the
left-button bit. A simultaneous two-button release keeps the right-release word,
as in the original queue. Moving out of the root window skips ordinary button
hover processing, whereas an active slider continues clamping beyond its bounds.

The live mission now exchanges slider requests and shield allocations after the
simulation update. A nonzero keyboard throttle adjustment (even at the limit),
or an explicit stop, wins the exchange and clears its ownership flag. These
controls share the native painters' slider geometry and fixed-point rounding.

## Weapon readouts and group controls

The native implementation now reproduces all three original weapon rows: energy
charge and requested-charge slider, ammunition count, and ECM on/off. The
`check_weapon_instruments.py` captures compare 1,152 complete paints and input
rectangles across all eight cockpits. Each capture constructs and initially paints
the widget before changing state, so the original slider has valid backing pixels.
The capture then isolates the full paint on a known background. Charge lamps paint
only the charged interval; the remaining pixels belong to the cockpit artwork.

`check_weapon_display_state.py` compares 768 original row exchanges through
0xb5b8, 0xb67c and 0xb588. Energy settings use the original Q10/native-capacitor
rounding and keyboard ownership flag. Displayed ammunition counts descend toward
the actual count at the original frame-scaled rate. The ECM switch feeds both
native device flags. Weapon names combine the executable's weapon and seeker
name tables; UI initialization is separate from controller construction, as in
the original, and does not assign invented labels to unused seeker selectors.

`check_weapon_group_instruments.py` compares 72 original full group paints and
144 exchanges through 0xcf78. I/II/III selection uses the authored fonts; LINK and
TRACK use their original button faces. Mouse group and tracking requests are
exchanged after automatic weapon selection. A keyboard group-change latch takes
precedence over the mouse selection for that update and is then consumed.
The live cockpit preserves these phases and connects number keys, selection,
right-click membership, linked firing, tracking, charge sliders and ECM controls.

## HUD navigation and target rendering

`check_navigation_instruments.py` compares 1,024 sequential original paints of
the heading tape, torso indicator, elevation indicator, aiming cross and ATT
tracking label. Disabled HERCs retain heading and torso display state while
continuing to update elevation. The indicators consume angle differences, retain
clamping history and use different neutral thresholds. Heading strips retain the
original separate fixed-point compensation term and inclusive clipping boundary.

`check_target_instruments.py` covers 512 original target projections and complete
rasters, including behind-camera/near-plane rejection, both sides and missile
lock. The target box is derived from two vertical world points using the current
model radius; its apparent size is limited to 25–75 pixels. An acquired lock
replaces open corner lines with filled corner triangles. This capture isolates
the painter; target eligibility and acquisition have separate original tests.

`check_waypoint_instruments.py` covers 512 original route lookups and complete
arrow/text rasters. The HUD uses the point after the group's current route cursor,
omits absent or finished routes, and uses off-screen arrows outside its narrow
forward sector. The distance label uses the original approximate planar distance,
rounded down in world units before conversion to metres. These are not the
three-dimensional weapon range or the radar display's range calculations.

`capture_mission_instruments.py` retains every target's actual model radius,
position and side plus the player's next waypoint from all four original recorded
mission starts. These close the data connection between the isolated painters
and the native mission objects. Damage-induced widget flicker and interactive
MFDs have their own completed comparisons in MULTIFUNCTION_DISPLAYS.md.

## Cockpit clock and presentation programs

`check_clock_instruments.py` executes the original constructor, full painter and
update scheduler for 4,250 cases. The clock increments seconds on every other
colon phase, uses a strict `now > deadline` check, and reschedules from the
current tick without catching up missed intervals. A full repaint also toggles
the phase. The original wraps at **25:00**, not 100 minutes. Portable redraws
retain the current phase rather than advancing the clock on every host frame.

`check_cockpit_presentations.py` runs 2,560 original TTM updates across all ten
simulation scripts, including all eight cockpits, ambience and window cracks.
Both bytecode execution state and complete brush rasters match the native C++
player. This includes alternate named sequences, random waits, mirrored brushes,
waiting-frame redraws and label jumps. The Colossus program stops at its first
ENDLABEL under the ordinary automatic entry path. Its later animation remains
in the resource; the player does not silently skip that stop.

The TTM loader translates authoring `.BMP` names to ThreeSpace `.DBA` banks;
these are **not** an additional DGDS bitmap format. The portable parser also
reads all 123 original TTM programs, retaining every opcode/operand and named
label; its decompressed streams match the original-code-checked analysis. The
runtime implements the cockpit operations. The shell has a separate interpreter
for its own presentation operations; see SHELL.md for the integrated comparisons.

The separate presentation random generator is the original ANSI-style LCG at
0x7cd28, seeded from DOS time by the manager. It does not consume the gameplay
random stream. DBSIM creates the underlying timer with PIT divisor 0x26d3
(0x5a9dc), approximately 120 Hz; the clock and TTM callback counts advance
every second interrupt. The native inspection loop accumulates nominal 60 Hz
UI ticks separately from its 40 Hz physics updates.

## Joystick calibration and preferences

`check_joystick_pipeline.py` checks **3,840** original outputs against independent
arithmetic, plus all twelve device/pedal selections. `check_joystick_routing.py`
checks **2,304** subsequent application routes and six Insert-mode switches.
The machine-readable fixtures are `analysis/interface/joystick-pipeline.json`
and `joystick-routing.json`. Electrical gameport discovery is supplied; original
calibration, response curves, hat decoding, registration and application routing
execute unchanged.

Preference byte 7 selects none, standard, two standard joysticks, Thrustmaster
FCS, CH Flightstick or CH Flightstick Pro. Byte 8 selects the initial movement
or turret control arrangement; Insert toggles arrangements only when that
preference is enabled. Byte 9 enables separate rudder pedals. Two-stick mode
forcibly disables the separate pedal preference and consumes all four axes.
Byte 10 enables WCS throttle input. The four device responses are not universally
four flight axes: FCS uses its fourth analogue channel for the hat, CH uses it
for the slider, and the Pro decodes button combinations into hat directions.

The standard dead zone is 25 in signed 7-bit calibrated units, followed by a
fixed-point rescale. The usual response mode squares that rescaled magnitude,
retaining its sign, and applies another small-response dead zone. The pedal
specialization reverses the third axis. Calibration's lower clamp has an
original implementation error: its result is overwritten by the upper clamp,
so values below the recorded minimum can exceed the nominal response range.
This is recorded rather than silently repaired in the reference calculation.

Application routing selects a nonzero device axis in preference to the matching
keyboard axis. In movement mode the primary pair controls locomotion and pedals
control turret yaw; turret mode exchanges these roles. With two joysticks the
whole pairs exchange. WCS supplies commanded throttle after this selection.
The selected steering pointers alias the corresponding movement/aim pair; this
also matters to the already documented missile-control handoff. Device-axis
collection combines simultaneous sources with bitwise OR, not arithmetic sum.
The route fixture isolates these choices without held keys, external camera or
missile steering; those have separate original captures.

## Preference transactions

`check_preference_transactions.py` independently checks **800** original
transactions, recorded in `analysis/interface/preference-transactions.json`.
The 17-byte file has separate current, saved and previous-value arrays at
`0xbd562`, `0xbd574` and `0xbd586`, plus per-setting callback pointers at `0xbd598`.
Reading a missing file produces zeros; a successful read seeds all three arrays
and invokes every registered callback. Setting a value can invoke its callback
even when unchanged. Commit invokes changed callbacks and copies current values
to both saved and previous. Cancel restores saved values and invokes changed
callbacks, but does not overwrite the previous-value array.

Saving selected indices first reads the existing file and patches only those
bytes; if it does not exist, the current complete array is written. All writes
in this fixture are in memory. Callback invocations are recorded boundaries:
this check establishes transactions, not the downstream sound/display hardware
side effects of each preference.

## Original cockpit view transitions

`capture_cockpit_views.py` independently checks all **105** combinations of
view/request/pending state in `0x22634`, then captures **130** original VGA
presentations during F9/F10 side-view changes. F9 looks left from forward and
returns from the right; F10 looks right and returns from the left. A request
already pending prevents another from replacing it. The transition is completed
by `0x226f0`/`0x22904`, with page-dependent preparation and several hardware-pan
steps inside one presentation call. Treating the view as an instantaneous bitmap
swap loses this sequence.

`native_vga.py` now retains the attribute-controller address/data flip-flop and
fine horizontal panning. The original display driver also records its fine-pan
byte divided by two at `0x9e614`. `NativeScene.hardware_page()` combines the
programmed CRTC display start, fine pan and four planes; stable-page pixels agree
with the earlier surface-origin capture. Transition GIF cadence is a convenient
inspection interval, not a measured host-speed-independent animation duration.

### Original scrolling surface

capture_cockpit_pans.py retains the prepared 640×400 VGA surface and all 213
scrolling frames for forward/lower, both side panels and their returns. The
mouse cursor is disabled with original 66948 immediately before each
presentation; reticle and pointer drawing are separate consumers. No pan or
raster instruction is replaced. The exporter checks every captured indexed
pixel before emitting the native comparison fixture.

Downward scrolling uses five-pixel increments and briefly reaches 200 before
settling at the authored 197-pixel lower-panel origin. The upward path reaches
2 then 0 without overshooting. Horizontal paths use ten-pixel increments and
write their final origin twice. See analysis/cockpits/pan-frames/index.json.
All six paths and all 213 surface frames match the native implementation.

### Side panels retain the forward image plane

The original camera does not turn through 90 degrees for F9/F10. In 226f0,
the three additions to camera +44/+46/+48 all add literal zero; this is also
what the machine instructions do, not a decompiler omission.
capture_cockpit_view_camera.py executes the original command, camera-owner,
camera-update, drawing and presentation routines for both side panels and both
returns. All 24 frames retain the same camera position, angles and zero look
offset. The VUE projection origin instead moves the image centre to x=480
for the left panel and x=-160 for the right (forward x=160). This continues
the forward image plane into the adjacent cockpit panels.

The native cockpit resource reader already follows that projection rule.
Adding a quarter-turn camera rotation would be a departure from this original
behaviour. The observation is retained in analysis/cockpits/view-camera.json;
it does not assert pixel equality of the complete intermediate pans.

## Original modal dialogs

`analysis/cockpits/runtime.html` includes original pause, mission-status, exit and
preferences captures. `tools/capture_cockpit_dialogs.py` runs the actual command
dispatch at `59df8`, construction, painting, modal keyboard loop and destruction.
P, Q, Ctrl-Escape, F12 and Alt-P were exercised with cancellation and acceptance
(ten cases). Preferences initially focus Music: eight Tabs reach Done, while
Enter on Music changes that preference instead of dismissing the dialog.

The transport supplies keys only after a modal opens and retains preference
writes in memory. Every case returns modal depth `94d78` to zero and restores
`bdbda` to the cockpit focus `b5b6e`. The exit response is captured at the modal
boundary; the subsequent simulator-to-shell handoff is a separate check.
See `analysis/cockpits/modal-transitions.json` for keys, return values, resource
reads and before/after preferences. No installation files were changed.

The pointer-queue fixture now also compares the original double-click latch,
with 1,248 timed packets spanning the strict 60-tick boundary on the 120 Hz mouse
clock. Left and right releases keep separate previous timestamps, but either can
set the shared latch. Position is not part of that test. Simultaneous releases
still retain the right-release button word. The native lower map uses this latch
to transmit an already-selected, ready command on double-click.

The portable preference manager also matches all 800 original transactions,
including each current/saved/previous array, callback, and partial disk update.
The fixture now retains the selected-index inputs and previous values directly.
Saving settings remains separate from accepting edits.

### Portable mission alert rendering

`capture_alert_rasters.py` retains all nineteen original general alerts, their
bounds and the complete before/after indexed frames. Native cursor hide/show
operations isolate the dialog from the pointer. The portable renderer matches
all nineteen frames, including transparent artwork edges, original font metrics
and button borders. The original response table at `0x97158` distinguishes
a warning's Continue response from a terminal notification's Return response.

## Original controls and calibration windows

[The controls capture browser](../analysis/shell/controls/index.html) contains
original GO screenshots and reproducible policy/raster comparisons.
CTL_ALRT is the code-managed options screen 1320. Its seven buttons select the
joystick model, joystick role, WCS, RCS, mouse, calibration and Done.
Right-click reverses the two enumerated choices. Alt+Tab moves focus backward;
Escape accepts Done. Changing to two sticks temporarily hides RCS; applying
that selection also clears the saved pedal preference. Only preference bytes
7–11 are written by the controls apply operation.

Calibration uses CAL_ALRT, with original per-device instructions and a
sixteen-long calibration block (minimum, maximum, low centre, high centre for
four axes), written to DATA/JOY1.CAL and DATA/JOY2.CAL. The original selection
table enables the FCS hat, but neither CH choice enables its authored slider
calibration entry. With missing physical hardware the original reports that
no joystick is installed. A cancellation after sampling does not save changes,
but can leave the in-memory calibration partly changed: 0x7956 resets and clears
the device pointer before 0x80b8 attempts to restore the saved block.

The controls C++ port matches 1,944 changes and 72 complete original rasters.
Calibration matches 22 full original sequences, including missing devices and
cancellation after an endpoint, and 64 original rasters. Physical polling and
mature input-debounce counters are explicit capture boundaries.

## Live-input integration findings

The complete mixed routing capture now covers 4,096 combinations of actual
held keys, competing joystick axes/buttons, external-camera and missile modes,
pitch/backturn reversals, WCS and the button-2 mode-switch latch. The keyboard
alternate-speed choice is made before that button changes mode. These records
are in `work/research/interface/joystick-mixed-routing.json`; the low-level hardware
poll supplies a packet while the original routing executes unchanged.

The third joystick button (packet +0x17) triggers target cycling; keyboard
Enter is packet button 1 and also passes through the command chain. Copying
its held state directly into the third-button signal was a native integration
mistake: it bypassed tactical-panel handling. The fourth button reads the
current movement/turret mode to stop throttle or request centering.

`capture_input_options.py` retains 30 KEYJOY inputs and 80 composed hat-view
requests in `work/research/interface/input-options.json`. KEYJOY's original
`0x4d918` parser is a whitespace-token reader, not an INI reader. Spaces around
equals matter, # skips the rest of the line, comparison is case-insensitive,
and REVERSE sets a flag that later DEFAULT does not clear. DOS text EOF ends
the supplied file normally. An unrecognized token exposes a bug: the reader
uses the table's byte size (28) as an entry count, walking beyond seven token
pointers. These cases fail at the original compare's invalid memory access.
Native C++ safely reports malformed tokens.

The flag at 0x95814 is **MISSILE = REVERSE**, not DEFAULT; earlier mixed-fixture
field labeling was reversed even though the captured numeric behavior was
correct. All three raw initializer flags are zero. The shipped file sets only
MISSILE. Hat bytes request actions [1, 0, 4, 5] in order through 0x22634;
the first request valid for the current view wins, and a pending transition
blocks further requests. Existing view-transition captures retain the actual
hardware-pan presentation.

SDL platform mapping and its intentional distinction from DOS electrical
counts are documented in the native repository. The analysis captures continue
to operate on original gameport-style counts and original 64-byte calibration
blocks. No analysis files have been committed.

## In-flight preference controls and artwork

The policy in DBSIM 0x47a88 is now compared for 1,512 actions / 24 sequences.
Music and Sounds toggle; Radio cycles text only, speech only and text+speech.
Horizon toggles; Sky, Ground and Shapes change by 32 with the original byte-end
wrap behavior. Right-click reverses those detail changes. Done writes preference
slots 0–6 only, and does not commit the preference manager's saved/previous
arrays. The sound/display callbacks and the separately verified Controls child
are explicit boundaries for these policy fixtures.

The complete original PRF_ALRT painter also has 96 full raster comparisons,
including all value types, button faces and the vertically striped detail bars.
See [capture_mission_preferences.py](../tools/capture_mission_preferences.py)
and [the original captures](../analysis/cockpits/preferences/rasters.json).
The cursor is excluded because it is drawn independently of the dialog.


### Original DCI pointers and live native presentation

The small green diamond visible in the forward view is PCURSOR.DCI, selected
by 346a4 when the pointer is inside the authored HUD input rectangle. It is not
a world-space target marker. CURSOR.DCI is the ordinary white cross;
MCURSOR.DCI is the lower-map destination cursor. Original 345bc constructs the
input rectangle from GAU 48c plus its 450 parent. 2df04/2df6c manage primary and
secondary cursors, and the lower-cockpit transition switches ownership.
The shell uses res32/arrow.dci; simulator alerts have their own dci/ALERT.DCI.
The four directional DCI sprites and shell ICONS.DCI remain retained resources.

`capture_software_cursors.py` executes the original 66948 loader and VGA show
routine for all eleven DCI resources at ten pointer positions. All 110 native
rasters match, including hotspots, transparent pixels and clipped edges.
`check_native_cursors.py`, under a private X11 display, also checks actual SDL
pixels at scale two for aiming, normal controls, a clipped corner and a modal.
It complements the isolated raster fixture; it does not establish every possible
cursor-ownership transition. Raw native scene/replay images deliberately keep
the separately presented pointer out of retained cockpit pages.


## Integrated cockpit edges

Original 2dcc8/2dc2c and 2db1c establish the three inclusive screen-edge
controls, hover cursor selection and click requests. The capture in
work/research/cockpits/edge-controls.json covers seven sequential view states;
edge-controls.bin records 1,365 boundary cases. Native integration now routes
them after cockpit instruments, including corner precedence and directional
DCI pointers. This fixes the missing lower-panel transition in DEMO1.


## Retained external cameras and world draw ownership

All four complete demo comparisons now include 24 camera components on every
observed initial/frame/exit state: 7,032 states in total. ESRPLY09 stores these
expected results separately from the TAP inputs. They are checked after native
execution, never supplied to the camera. The captures are
demo1-camera-playback-v3 through demo4-camera-playback-v3, made with the unchanged
game and passive DOSBox observer, sound enabled and startup/radio clocks retained.

Original 5894c changes camera ownership after player controls and before the
camera transform. 58a44 retains an existing camera for its target. The first
orbit starts at distance zero and clamps to 2,500 on update; later cockpit/orbit
switches retain distance, orbit angles and rates. The previous integration
recreated it at distance 10,000 and switched one presentation too early.
Player control routing also finishes after a Q dialog accepts exit; the outer
frame then leaves before perception/power. This includes the final camera-rate
update even though no further camera transform or world draw occurs.

The external viewport is (0,30)-(319,170), with projection centre (160,105).
Its rectangular clip region selects row-based texture traversal, just like the
cockpit masks. Using column traversal caused visible texture and cloud
differences. Original 58b68 places GREEN6X8 labels at x=50/215, baseline 189,
using STRINGS0 groups 14 and 32. Both control-owner label rasters match.

Original 59c8f skips environment and scene rendering while lower-panel view 1
is stable, and resumes it during the prepared transition. Advancing hidden
clouds on those frames had displaced the entire cloud field by the time the
player returned to the forward view. Native camera and cloud diagnostics are
available through EARTHSIEGE_REPLAY_DIAGNOSTICS.

A projected folded quad exposed an additional original column-mapper boundary:
with a 40x40 texture it reads offsets 2,215 and 2,805, outside 1,600 bitmap bytes.
The actual mission uses the row mapper, which stays in range for this quad.
capture_folded_texture.py isolates both original modes with sentinel guard
bytes. The native safety rule leaves undefined samples untouched; all defined
pixels remain exact. Four captures test this boundary, in addition to the 600
ordinary texture rasters. This does not emulate arbitrary adjacent heap bytes.

The full sampled-frame investigation is complete: 114 of 120 images match
exactly after controlling presentation timing. The other six differ only at the
intentionally repaired malformed radar model, and match when the same repair is
applied to the original renderer. See [PIXEL_FIDELITY.md](PIXEL_FIDELITY.md) for
all 230 affected pixels, original instructions and reference boundaries.

## Retained cockpit instrument painting

Original 59c08 paints the widget container (2cf28) before 155a8 exchanges
reactor, shield and speed values for the next paint. The native renderer now
retains those values separately from simulation state. A second draw without a
simulation update uses the already exchanged values. Throttle hit testing also
retains the painted handle while a programmatic adjustment changes its pending
position.

The unchanged DEMO2 snapshot after frame 1020 shows shield values 374/374/512
with the old six DAC colours. Calling only the original widget painter changes
those colours without advancing simulation. DEMO1 frame 60 similarly retains
painted speed 921 while its newly exchanged value is 1024.

Speed lamps additionally retain their bitmap contents. The original incremental
bar setters paint the interval between old and new boundaries inclusively; an
increasing and decreasing speed can therefore colour the shared boundary pixel
differently. Raptor's horizontal full painter 3bda8 resets the forward bar cache
twice instead of resetting the reverse bar. Its resulting retained pixels and
next-update behaviour are reproduced, rather than recomputing a fresh bar.

The independent ESTHUP01 sequence fixture covers 2,720 original incremental
and full repaint cases across all eight cockpit layouts. Four replay fixtures
add 579 original reactor/throttle regions and shield-colour samples, excluding
13 regions near the original software cursor. Six shield points deliberately
avoid the separately clocked red lamps. These expected pixels never feed native
simulation or rendering.

Analysis evidence: `tools/check_throttle_updates.py`,
`tools/export_replay_instrument_frames.py`, `tools/inspect_panel_paint.py`,
`work/research/tapes/replays/demo1-speed-indicator/throttle-state.json` and
`work/research/tapes/replays/demo2-instrument-paint/instrument-paint.json`.

### Charge sliders retain their backing

capture_weapon_charge_retained.py executes the original energy-row constructor
3700c, setter 37194, full painter 37144 and incremental painter 3716c.
The child slider painters 389ac/38b30 save and restore the two-pixel-wide
thumb rectangle, including its two rows outside the LED bar. PBUTTONS frame 26
has a padded eight-pixel width, which is not the saved rectangle width.

On a full paint, a changed thumb position restores its old backing before the
LED boundary is reset. Reset 317a4 does not clear pixels. LED setter 31e5c
includes the old endpoint when charging and preserves the new endpoint when
discharging. Incremental slider updates always restore the saved rectangle,
then repaint only changed LEDs. The newly allocated saved backing in the
captured DOS startup is zero; moving a thumb before its first save therefore
leaves black corners on the cockpit artwork. Warmed full-painter captures had
previously hidden this behaviour.

work/research/cockpits/weapon-charge-retained.json and the native fixture
weapon_charge_retained.bin retain 1,280 sequential original cases, with
cold/full/incremental paints and changes of charge, requested position and
background. All match the C++ retained widget. The sixteen initial replay
corner differences are thereby eliminated without modifying weapon simulation.

### External-view return and physical VGA pages

The copy at 22b4d uses inclusive x=0..320. It is a 321-pixel copy between
physical pages in a 640-pixel-wide VGA backing surface. Driver 51cxx runs
three passes: partial left byte, all complete four-plane groups, partial right
byte. Copying page 0 to page 320 therefore wraps the extra column into the
first column of the source page's next row. The reverse copy has a different
overlap order. This is not a projection or clipping error.

The native port retains the same bounded storage writes. The actual driver
draw selector targets the other page of the +88/+8c pair before presentation;
vertical and horizontal pans accordingly use different starting pages.
tools/capture_cockpit_page_copy.py and work/research/cockpits/page-copy.json preserve
42 complete original VGA surfaces. This accounts for the ten alternate-frame
left-border pixels in DEMO1 after frame 1134.


## Expansion input differences

March expansion DBSIM 0x53218 adds `PEDALS` to the KEYJOY token table. Its
REVERSE flag negates the rudder-pedal axis before control routing. The other
three switches retain their meaning, and DEFAULT still does not clear an earlier
REVERSE. Unknown tokens still run beyond the original comparison table; the
C++ reader rejects them safely. `capture_expansion_input_options.py` checks 34
original inputs, including all switches, case handling and repeated assignments.

Expansion TAP frames add two held-button bits (weapon cycling and radar toggle)
and a separate queued-command list. Queued commands run before frame preparation;
the primary command keeps its later dispatch position. All 11,421 packets in
four recordings are decoded and original-parser checked. C++ runs each recording
to completion, including dialogs, audio and periodic drawing.

Expansion GO 0x09af4 saves 18 bytes of PREFS.CFG; DBSIM 0x4f5bc saves 22.
The latter's loader 0x4f504 zeroes its 22-byte buffer before reading, so a shell
file is valid simulator input. Slot 17 carries the battleset selection. It affects
combat music selection and projectile falloff. The native preference manager
accepts both lengths and preserves the extra simulator settings.
