# Sprite atlas and remaining HUD extraction

[Sprite browser](../analysis/interface/sprites/index.html) · [Indexed pixels and masks](../analysis/interface/sprites/sprites.json) · [Native selector checks](../analysis/interface/sprites/verification.json) · [Procedural HUD](../analysis/interface/sprites/procedural-hud.json)

## 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: false` and 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](../analysis/interface/sprites/sequence-verification.json). 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](effect-recipes.md) 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 `520A` restores the prior symbol then draws the selected one through `51B6`. Mask stream `5227` has seven records; on-source origin is (8,32), base/destination origin (120,198). Producer `576E–579B` calculates 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](../analysis/interface/sprites/17-callback-5227-6-0.png) 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 `52AB` uses field index 4543/4544/4545 to select one of three masks at `52E0`. Table `5356` supplies 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/5487` select 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](hud-and-formatted-text.md#digit-strips-and-weapon-icons).
- **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:

1. `7919–7959` selects CX=08A8h for the normal Caero cockpit: top 8, height 168.
2. `7974–7978` moves that top into the starting row-address high byte.
3. `23B1–2406` preserves the view height and enters `AF57`.
4. `AF57–AF60` rebuilds 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](dynamic-hud.md) 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.
