Sprite atlas and remaining HUD extraction
Sprite browser · Indexed pixels and masks · Native selector checks · Procedural HUD
Extracted assets
965 PNG previews (including the empty weapon panel) preserve source-sheet associations rather than claiming 965 unique artworks:
- 880 particle instances: four sheets (00/15–18), five sizes, 22 phase selectors and two horizontal alignments.
- 10 Caero weapon sprites plus an empty-panel preview for selection zero. Selection zero has
draw_sprite: falseand no sprite source rectangle; its preview samples the black cockpit panel, not the altitude artwork at X=140. - 22 coordinate glyphs: ten small digits; ten large digits plus blank and separator.
- 52 additional Skimma callback-owned masked sprites: seven bearing symbols with on/base sources and three weapon-status masks with four source choices, across sheets 00/17 and 00/18.
JSON preserves palette indices, source rectangles, coverage masks, source palettes, drawing references and RGBA pixel hashes. Particle transparency follows the native scanline masks, not colour-key guessing. Weapon/digit rectangles retain their copied background pixels. Existing 364 main-directory masked HUD PNGs remain in analysis/interface/hud/.
Particle size and phase selection
Native 6AA6–6B32 selects these tables by projected depth:
| Pixel size | Table | Depth interval, upper bound excluded |
|---|---|---|
| 16×16 | 6B9C | 0–1752 |
| 12×12 | 6B79 | 1752–2454 |
| 8×8 | 6B5E | 2454–3510 |
| 6×6 | 6B47 | 3510–4914 |
| 4×4 | 6B34 | 4914–8190 |
Depth below 32 is rejected by the ordinary projected-object entry at 6A6F; the table's first interval describes selector arithmetic, not visibility. At depth 8190 and above, 6A59 selects a single-pixel colour from the 22-byte table at 6A43, subject to bounds checks. That colour table is retained in JSON.
Each size table has two five-byte source records (phase parity), a radius byte and 2*radius skip/width row pairs. Source X additionally uses a phase-pair lookup at 6BC7 and a variant selected by destination X bit 1. This is why atlas extraction must retain the two alignment variants. These are precomputed scaled sprites, not a single bitmap resized during ordinary rendering.
The source rows are normalised to the physical cockpit cache. 6B19 subtracts the viewport top before accessing a row table whose origin includes that same top; the offsets cancel. Sources therefore remain stable between normal cockpit and external views.
Effect aging at 6C01–6C28 and 6C65–6C8E derives a phase from remaining lifetime: remaining = (flags & 127)*64 - elapsed_ticks; negative remaining expires the effect. For ordinary bounded lifetimes, the phase is remaining >> 6. If flag bit 7 is set and that phase is at least ten, six is added to it. This establishes a reversed aging progression and an alternative bright phase range, not a single universal 0→21 animation. The browser now offers the two native-order sequences: fire 21→16, then 9→0, and smoke 15→0, plus a separate ascending atlas inspection order. Fire/smoke names come from visual identification; the splice at phase 9 is confirmed by the native bright-flag branch. Both aging implementations pass 68 synthetic lifetime/expiry checks; see sequence verification. Preview steps use approximately 128 ms (64 timer ticks), without claiming the exact first-frame duration or scheduling of each spawned effect. Follow-up: effect recipes now exports all 21 referenced recipe entry points, emitter timing/motion, attached sound records, building bindings and the separate damage-trail constructor. Composite spatial drawing and full scheduling remain separate. Another drawing path at 689F supplies the selector through the self-modified immediate at 6AA7.
Additional HUD selectors
The existing descriptor directories do not contain every mask:
- Skimma bearing indicator: callback
520Arestores the prior symbol then draws the selected one through51B6. Mask stream5227has seven records; on-source origin is (8,32), base/destination origin (120,198). Producer576E–579Bcalculates a bearing from a selected cell, subtracts/combines player heading and quantises it to the seven visible states; zero suppresses the indicator. Exact identity of the selected cell in each gameplay situation remains to be traced.- Original artwork/mask artefact: the rightmost bearing symbol (state 7, mask record 6) includes a stray dark-blue pixel at its bottom-right corner. Its final row copies three pixels starting at offset 32; the last samples source (42,42) and lands at cockpit (154,208). This was observed in retail play and is present in both Skimma sheets (00/17 and 00/18). The extracted lit sprite retains it for reference. The faithful reconstruction preserves it; Redux shortens this final mask row to two pixels, leaving the underlying cockpit pixel untouched.
- Skimma weapon-status sprites: callback
52ABuses field index 4543/4544/4545 to select one of three masks at52E0. Table5356supplies four source origins: (272,194), (120,31), (40,31), (80,31). State zero samples the restoration source. The browser retains numeric state IDs rather than assigning unverified colour-state semantics. - Caero weapon icons: callbacks
5484/5487select an 8×12 rectangle for IDs 1–10 from X=140+8*selection; native cockpit sampling gives physical Y=8. Destination is (260,195) or (268,195). Selection IDs are documented in the HUD reference. - Coordinate digits: physical source origin Y=8; small digits at X=308 are 4×5, large digits at X=312 are 8×7. Both display lattice coordinates, not elapsed time.
The Skimma field 454B is now identified more precisely: producer 579C–57B6 emits value 2 if the player's height high byte is below 4, otherwise zero. A nonzero 7E61 additionally gates it with timer bit 0100h to blink. This is a low-altitude indication, superseding the tentative missile-lock label. The threshold corresponds to native height 1024; it is not converted to metres. Field 454E remains unresolved.
Caero source alignment: native cause established
The successful +8 correction is explained by the original view setup:
7919–7959selects CX=08A8h for the normal Caero cockpit: top 8, height 168.7974–7978moves that top into the starting row-address high byte.23B1–2406preserves the view height and entersAF57.AF57–AF60rebuilds the first 168 row addresses starting at physical row 8; entries 168–239 retain their original unshifted addresses.
HUD source artwork lies in the shifted range while instrument destinations/restoration rows lie below it. Consequently only source artwork needed the +8 correction. Skimma cockpit and ordinary external modes use top zero. Four original-code setup probes verify these complete row tables. The exporter metadata now records a native viewport offset rather than an unexplained visual calibration.
Procedural HUD coverage
The Caero field 4542 is derived from heading at 56C7–56CF. Callback 5384–53D2 clears five previous marker pixels and draws five new ones around (54,215). The original coordinate-generation code has been executed for all 136 heading states; JSON preserves erase/draw coordinates and colour tokens. The five explicit drawing colour indices at 53D8 are 9E,9C,9C,9C,9E; their sheet-palette RGB values exactly match the five red-highlight pixels at heading state zero. The five erase indices at 53D3 are zero. The browser now clears that initial highlight and draws the selected heading using these colours. This reproduces the marker rather than leaving a fixed red mark plus a white inspection overlay. It does not simulate radar contacts.
The saved DOSBox memory capture identifies the patched drawing call at 53CC as targeting DBC1. That routine reads the colour cache using AH and writes a pixel; it does not sample the five initial compass pixels. Thus the proposed sampled-highlight interpretation predicts the correct colours, but the actual implementation uses a hardcoded five-colour sequence.
Remaining procedural work is explicitly separate from missing sheet sprites: live radar contacts and projection, target/lock markers, the flashing missile-view indicator, complete instrument-update timing and full effect recipes. The current exports do not constitute a live cockpit renderer.
Verification and reproduction
PYTHONPATH=/tmp/darker-python python3 tools/extract_hud_procedural.py
python3 tools/extract_interface_sprites.py
PYTHONPATH=/tmp/darker-python python3 tools/verify_particle_sequences.py
PYTHONPATH=/tmp/darker-python python3 tools/verify_interface_sprites.py
python3 tools/extract_hud.py
python3 tools/interface_browser.py
Verification executes 440 native particle selections (all size/phase/alignment choices at both viewport origins), 293 native HUD selections and four complete viewport row-table initialisations. The particle blitter and HUD copy calls are intercepted at their argument boundaries; these checks do not emulate final VGA pixel transfers. Existing masked-blitter verification remains separate. Browser checks covered all four sprite categories, five particle sizes, both alignments, phase sweep, image loading and the compass slider with no JavaScript errors.
Selection-zero correction: visual inspection identified the earlier X=140 crop as part of the altitude indicator. Directly supplying zero to the isolated coordinate setup did not establish a valid icon. The export now explicitly represents no sprite for zero; exact native clear/skip handling remains to be traced.
Follow-up: dynamic cockpit inspection combines the assets, verifies radar projection and strip transitions, exports native target outlines, identifies Skimma 454E as the selected weapon reserve/reload counter, and recovers the engine’s separate dim-state source patch.
Incoming-missile markers and flight quit confirmation
The Caero's central HUD includes nearby hostile-projectile markers, independently
of the four aircraft-pursuit lamps. Native 5CE0 → 5E6A → 5A75 invokes this in the
cockpit and forward full-screen view. 5AC9 scans the hostile projectile list,
rejects hidden records and uncovered radio cells, then rotates their horizontal
positions by player heading. 5B76 magnifies these radar coordinates eightfold
and clips to squared radius 0x0c30. The preceding coordinate window extends
seven city cells either side of the player, with an exclusive positive edge.
This is a local overhead warning display, not a perspective marker on the
missile mesh: shots behind the player appear below the sight centre.
The six-pixel navigation mask at 6B52 uses the alignment-specific sheet columns
at 5CDC and row 8. 5B92 overlays a twelve-pixel centre whose colour reflects
signed height difference from the camera. Radio outages suppress these warnings;
tunnels have full coverage. The scan includes missiles aimed at scenery as well
as the player. The Skimma and external/missile-follow views do not call this path.
Escape's native handler B7CE toggles the in-flight quit prompt and records the
low byte of the game clock. 3E9F displays it for 170 ticks of every wrapping
256-tick period, while flight continues. Q's handler B7DA only requests exit
when confirmation is active; a second Escape cancels. The prompt is drawn at
physical row 120, with the original compact font and colour pair 165F.
Its English, French and German text and authored horizontal positions come from
E3F6, E5E7 and E822 respectively.