Masked HUD components and formatted multilingual text
HUD component browser · Formatted text browser
HUD directories and masks
The five-entry directory at 460B selects 4D70, 4615, 4D70, 4D70, 4615 by craft configuration. Each directory contains a count byte, eleven callback pointers, then that count of masked-component pointers. Directory 4615 has nine components (Caero/underground sheet 00/16); 4D70 has four (sheets 00/17 and 00/18). The export contains 13 component descriptors, 127 mask strips and 364 on/base PNGs, including the shared geometry rendered with the two different non-Caero sheets.
The updater at 4554–45A6 compares current and prior display-state bytes. Callback-driven instruments occupy 4540–454A; masked components follow at 454B onward. A component descriptor holds an on-source packed Y/X-half word and a base/destination word, followed by variable-length strip records. Each strip begins with byte length and vertical offset, then one (horizontal skip, width) pair per scanline. The byte length is 1 + 2 * rows; it excludes the length byte itself. One descriptor has two additional tail bytes, retained rather than silently interpreted.
51B8 selects a starting strip by stepping those lengths, then emits the requested number of strips. Source X and destination X are twice their packed high bytes. The packed low byte is Y; the strip's vertical offset applies to both. The emitter's AX points at the row pairs, SI/CX specify source Y/X, DI/DX destination Y/X and BL gives the number of rows.
The dynamic inner call at 51E8 is patched: its untouched-image destination is not the running sprite blitter. Paths at 52C5/52D9 select specialised drawing and restore the E0BC path. The masked copying implementation around DFA8–E11E supports clipping and duplicated screen paths. The component PNGs preserve the masks; they do not invent rectangular opaque replacements.
The prior static background-transfer reconstruction supplies the source surface. The HUD browser overlays all on strips of a selected component on that surface and outlines their destinations. In-game values may select only some strips, and instrument callbacks draw additional graphics. Individual component images retain transparent pixels outside the row masks.
Caero identities corrected by comparison with the English manual, pages 6–8 (these supersede the initial descriptive guesses):
| Display field | Descriptor | Manual identity / role |
|---|---|---|
| 454B | 463E | Altitude Monitor |
| 454C | 46F4 | Damage Report System: peripheral impact-damage meter |
| 454D | 4888 | Damage Report System: inner damage lights |
| 454E | 4A2E | Caero Power Storage Device: power-cell indicators |
| 454F | 48CA | Caero Power Storage Device: charging meter for the next cell |
| 4550 | 4A8C | Caero Power Monitor: incoming beacon power; engine status belongs to this instrument |
| 4551 | 4BE6 | Weapon Status Display: weapon-charge meter, above primary/secondary weapon icons |
| 4552 | 4D1C | Engine-state indicator from the earlier control trace; part of the Power Monitor |
| 4553 | 4D38 | Nayas Receiver Unit |
The Power Monitor's engine light dims during a stall. The Damage Report System's peripheral indicator fills with impact damage; when full, an inner light illuminates. The manual describes repair, but the exact runtime reset/repair rules remain open. In gameplay terms, the overall display behaves as a health bar.
For the Nayas Receiver Unit, the right light indicates that the unit is on; the left flashes during reception/transmission and continuously while the hangar is open to return to base. These are gameplay/manual identifications, not newly verified state equations. The 4551 mask set is the meter component, not the complete weapon-icon renderer. The earlier “accumulated energy reserve” name was misleading: its gameplay association is weapon charge, distinct from stored boost cells.
The four-component Skimma directory reuses state-byte addresses with different meanings. Its labels remain separate. Neither these identifications nor the mask exports complete live radar, compass/attitude drawing, all procedural callbacks or the full cockpit renderer.
Formatted text
The export covers 372 language sections (124 records × English/French/German), including 27 empty sections. 371 sections parse completely, producing 546 formatted pages and 1,696 counted messages. All original bytes are accounted for, including one undecoded tail.
A formatted page uses B292 commands:
| Byte | Operation |
|---|---|
| 00 | Return/end |
| 01 | Position: Y byte, X byte |
| 02 | Colour word through E11F |
| 03 | Newline using current margin and line increment |
| 04 | Tab to next 16-pixel boundary relative to margin |
| 05 | Set left margin and line increment |
| 06 | Centre a nested terminated run |
| 07 | Runtime number drawing |
| 20+ | Font glyph |
Only text, newline, spacing, position, colour and end commands occur in the recovered page set. Counted in-flight messages use a width byte followed by length and that many glyph bytes; a zero width is a one-byte empty entry. The count is consumed by B294, not drawn as a character. C406/B125/B132/B147 manage message placement, expiry and progression.
The number of introductory pages varies. The extractor searches only formatter-return boundaries and requires a complete valid counted-message suffix. This is a structural page/message partition, not a trace of message scheduling or every scene transition. Raw bytes, resource offsets, control events, message widths and lengths are preserved in formatted-text.json.
The text browser displays original custom glyph codes using the recovered Compact font; it preserves explicit line breaks. It does not claim the exact font choice, placement, colours and timing of every runtime screen. The scenario inspector now uses the cleaned English records and links to the language-aware browser. Unicode interpretation of every accented/special glyph remains separate; source codes are authoritative.
Apparent original French data error
In 04_009 / 1, French, a message at resource offset 10F4h has width 103 and stored length 21, but appears to contain 22 glyphs before the next valid header at 110Ch:
Retournez $ aHemmersan
Here $ is a custom font glyph, not a literal dollar sign in the displayed sentence. The a before Hemmersan appears extraneous. Following the stored count leaves the final n to be consumed as the next width, with 9Eh then interpreted as the next length/relative step. This is an original-source inconsistency, not a printable-run filter error. Its exact in-game symptom has not been tested.
The browser provides a clearly labelled candidate recovery using all 22 glyphs and resynchronising at 110Ch for the following message. The binary, stored length and main decoded sequence are not repaired silently.
Verification and reproduction
verify_hud_text.py executes original x86 routines in Unicorn:
- 254 HUD descriptor cases: all 127 strips with both source selections; verifies emitted source/destination coordinates, mask pointer and row count. The inner VGA blitter is intercepted, so this is descriptor-traversal verification, not full pixel-compositor verification.
- 546 formatted pages and 1,696 counted messages: verifies native glyph order and exact consumed byte counts in B292/B294. Glyph drawing and colour calls are intercepted; font rasterisation was independently verified in the earlier font work.
- Export checks concatenate all parsed records and preserved tails back to each source language section exactly.
python3 tools/extract_hud.py
python3 tools/extract_formatted_text.py
PYTHONPATH=/tmp/darker-python python3 tools/verify_hud_text.py
python3 tools/interface_browser.py
python3 tools/mission_browser.py
Browser checks exercise all 13 HUD descriptors and their images, all language selections and the flagged French recovery. Original DOS installation files remain unchanged.
Cockpit observations and inspection controls
For the Skimma directory (4D70), visual inspection identifies 454D as speed/engine-output setting and 454C as the directional shield-strength display. 454B is possibly the missile-lock warning; 454E remains unidentified. These observations are recorded separately from native-code behavioural proof.
The browser now has Show yellow bounds, off by default, independent of Apply all on strips. Its state persists while switching components and source sheets.
Visual inspection shows that the Caero preview's masks look wrong and sit slightly off vertically, while the Skimma previews look correct. This remains open: the existing 254 checks establish descriptor traversal and arguments to the inner blitter, not full VGA pixel composition. Do not treat them as proof that the Caero presentation is correct. The row-offset initializer AF52 builds a linear 240-row table, so that initial table alone does not explain a Caero-only offset. No speculative coordinate correction has been applied. A useful next check is full masked-blitter pixel capture with the actual background surface and display-mode patches.
Altitude source-sampling investigation
Visual inspection identifies Caero field 454B as the altitude monitor, and refines the visual symptom: destination placement looks right, but artwork is displaced within the mask, consistent with sampling too high in the source surface by roughly 10–14 pixels. This supersedes the earlier interpretation of a destination-placement error.
tools/verify_hud_sampling.py now executes 51B8 through the actual E0BC inner blitter and records VGA source addresses and destination write masks, rather than intercepting the inner call. All 254 on/base strip cases and 31,448 pixel transfers agree with the current coordinate formulas under the AF52 linear row-table setup. E0BC adds horizontal offsets of 12 and 308 for its two display copies; it does not add a vertical source offset. The first altitude strip samples source rows 40–50 (descriptor source Y=0, strip offset=40). An isolated BF72/DD37 run with the decoded 00/16 image also matched the existing cached-background image exactly.
This narrows the unresolved discrepancy to assumptions outside those isolated paths—for example, additional source-surface preparation, runtime state or display setup. It does not establish that the current Caero preview matches the game. No guessed 12-pixel correction has been applied. The HUD browser now includes an expandable Source surface and sampling locations view to expose precisely which artwork each strip reads. The upper destination preview remains unchanged.
Windscreen/top-bar hypothesis
One hypothesis is that the cockpit's full-width top bar introduces a fixed vertical offset for content inside the windscreen, while the cockpit texture itself is placed without that offset. A source-surface preparation or display-coordinate adjustment may therefore be missing from the preview. This remains a hypothesis: the isolated blitter trace establishes sampling under its supplied row table and source surface, not every earlier runtime transformation. Check view-rectangle setup, row-table changes and writes into the cached source surface; do not shift the destination masks, which appear correctly positioned on inspection.
Caero preview calibration: +8 source pixels
Following the screenshot identifying the grey top-bar gradient leaking into an on-strip, Caero (directory 4615, sheet 00/16) previews now sample eight pixels farther down the source surface. This moves the sampled artwork upward within the existing mask. All nine Caero components receive the same on-source adjustment. Base/restoration sampling, masks, destination positions and all Skimma previews are unchanged.
The original source_on descriptor stays intact; preview_source_offset and preview_source_on make the calibration explicit. The source-sampling diagram and readout use the adjusted preview coordinates. This is a visual calibration from the screenshot, not a newly established native instruction or a claim to have resolved the runtime cause. The previous native-coordinate checks still describe the unadjusted descriptors.
Regeneration changed 72 Caero on-strip PNGs. Every base PNG and every Skimma PNG remained byte-identical. Browser checks confirm the +8 annotation is restricted to Caero.
Subsequent visual inspection confirmed that the adjustment fixes the alignment perfectly, including within the masks. The visual correction is therefore confirmed; its original runtime cause remains a separate investigation.
Digit strips and weapon icons
The Caero sheet's two digit strips serve the map-grid position readout, rather than a clock:
| Strip | Native consumer | Use |
|---|---|---|
| X=308, 4×5 pixels per glyph | 5429/542C |
Two small two-digit fields above the normal cockpit radar, at X=44/56, Y=185 |
| X=312, 8×7 pixels per glyph | 5472 via 5A5D–5A72 |
Larger coordinate readout on the enlarged radar, with separator glyph 11 |
Hold Insert / keypad 0 to invoke the enlarged radar (55A5, input mask 1000). Source Y uses the mutable origin at 5458; it is not necessarily the top row of the sheet. This section identifies consumers statically, not a complete live radar reconstruction.
The coordinate provenance is explicit: 5C65 reads player position words SS:D98E/D990, uses their high bytes to index the world-segment A800 lookup, and returns the two mapped cell coordinates. 573C–574C validates the result, adds 0101 and stores it in display fields 4545/4546. The renderer removes that encoding increment and divides by nine, then constructs the two decimal glyph indices. For normal Delphi positions this displays grid numbers 1–15, corresponding to the energy-tower lattice. Reproducing startup's byte-wrapped lookup generator (0396–03A9) gives cell 0 → 1, cell 9 → 2, cell 63 → 8 and cell 126 → 15; the nearest-tower boundary between the first two lies between cells 4 and 5. This readout has no elapsed-time input on this path.
The top-middle weapon sprites are ordered by weapon selection ID. Callbacks 5484/5487, assigned to fields 4543/4544, select an 8×12 source rectangle at X = 140 + 8 × selection_id and place the primary/secondary icons at X=260/268, Y=195. This source formula identifies the ten actual sprites at IDs 1–10. ID zero means no weapon icon: applying the formula to zero mistakenly samples altitude-indicator artwork. The viewer shows the empty black panel for that choice. Thus the ten entries are:
- Pinner Direct (key 1)
- Pinner Mimic (key 2)
- Brent Forbes Dual Launch primary capsule (key 3)
- Forbes Diffuser trigger (automatic stage)
- Forbes Diffuser gas capsule (key 5)
- Brent Ground (key 6)
- Brent Forbes Dual Launch follow-up (automatic stage)
- Caero Weapon (key 8)
- Pinner/Forbes Chargeable (key 9)
- Brent Hunter (key 0)
The order therefore resembles number-key order, but 4 and 7 represent automatically selected stages rather than independent bindings. Selection writes at C885–C8AF and the weapon-specific transitions establish the connection; see weapon identities and behaviour. These icon callbacks are separate from the masked weapon-charge meter at field 4551.
Shared particle-effect artwork
Visual inspection identifies the shaded ball sprites filling most of the remaining blue source area as the explosion, smoke and flame animation steps at different scales. In-game, these effects reuse the same ball artwork. The cockpit sheet therefore also acts as a shared effect-sprite atlas; these images are not cockpit instruments.
This identification comes from gameplay observations. Exact frame boundaries, scale indexing, animation order, colour treatment and the native consumers for each effect remain to be traced. The visual resemblance to Magic Carpet's ball effects is an observation, not evidence of shared code or assets.
Follow-up: sprite atlas and native source-offset resolution
Sprite and procedural HUD extraction exports the particle atlas, weapon icons, coordinate digits and callback-owned Skimma masks. Native view setup now explains the Caero +8 offset via the first 168 row-table entries; this supersedes the earlier unresolved-cause notes above. It also replaces the tentative Skimma 454B missile-lock identification with the traced low-altitude producer and exports the Caero compass marker geometry.
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.
Script associations are now available separately in the scenario viewer and message-condition analysis. The structural page/message partition above is preserved; uncertain runtime associations remain labelled.
German glyphs beyond the declared font directory
Two German pages contain codes beyond the interface font's declared 107 glyphs:
04_009 / 3, offset 2796h, contains 99h; 04_010 / 6, offset 3A09h,
contains 97h. The original does not validate those indices. E1FE reads the
following font-table bytes, and its four specialised drawing loops consume at
most four planar bytes per row, irrespective of the resulting advance width.
Thus an out-of-directory glyph can advance much farther than the image it draws.
Native raster checks now cover these two glyphs at all four horizontal alignments, and all 546 formatted pages pass the layout comparison without rejecting either German page. This reproduces source behaviour rather than substituting a guessed character. The French counted-message length anomaly above is a separate issue.