# Script-linked presentations, proportional fonts and cockpit layouts

New browsers: [presentation clips](../analysis/presentations/index.html) and [fonts / interface](../analysis/interface/index.html).

## Presentation extraction

`tools/extract_presentations.py` associates 263 explicitly selected animation ranges with their images, palette history, layout, music selection and script offsets. It also exports 47 still pages, displayed in the same scene order. All frame references resolve. The browser groups these ranges under 124 record entries plus a synthetic title scene and exposes the ordered page chains, including entries with text but no animation. It embeds the original compressed streams for offline playback and frame stepping. **Next page** advances still pages, which remain on screen until requested. The first mission starts with both quotations over `00_022`, then the welcome page and animated briefing. **Play scene** runs all extracted ranges and still pages once in script order, updating the background, palette and music links at each transition; it can be paused and resumed. **Loop clip** repeats one selected range. Sequence playback stops on the final frame and can be restarted. For scenes with counted film captions, playback instead uses script-paced intervals with delays, animation waits, concurrent channels and subtitle expiry. Other scenes retain the convenience clip sequence.

The [scene and music catalogue](../analysis/presentations/music.html) maps fresh presentation entries to music groups and links each group to its complete intro/loop FLACs across the six exported playback modes. Gallery groups are one-based; script selectors are zero-based. For example, level 1’s opening quotations use group 6, before the welcome and main briefing select group 2. Startup and the menu use group 1; the committal ceremony and final presentation select group 6. Normal flight has no background music.

`tools/presentation_music.py` follows explicit next-page pointers after page stops, retaining music and presentation state within a record. `tools/build_presentation_music.py` generates the readable catalogue and JSON. A missing selection means inherited caller state, not necessarily silence; seamless continuation may bypass a briefing entirely.

Animation frame X coordinates are absolute screen positions; Y coordinates are relative to the scene image origin (`BFD3` patches the blitter at `DABA`). The web viewer applies that vertical offset, matching the game renderer: the first briefing portrait needs 24 additional source pixels downward, with no horizontal offset.

The browser is not a complete presentation virtual machine. Palette fades, user input and persistent state across records are not simulated. Captioned scenes follow delays and animation waits, including the single delayed pass of conditional-repeat commands; their animation channels advance together. Other scenes retain isolated-channel timing. Formatted briefing text is overlaid using the original bitmap masks and palette colours, with English/French/German selection and a visibility checkbox. Raw language cursors follow page commands and fresh-entry displacement words; margins, line steps, positioning, tabs and centring follow the game formatter. Pages are retained or cleared as instructed, and full-screen backgrounds remain beneath portrait image rectangles. Malformed or unsupported source text is labelled in the viewer rather than guessed. The browser hides the 18 direct-entry text records bypassed during seamless continuation (including levels 17 and 18) by default; a checkbox exposes them for script inspection. They remain explicitly labelled in the music catalogue. The page chain is an ordered script inspection, not a timed recording. Level 101’s film dialogue uses the three counted-message channels: original placement, nine-row backing strips, colour pairs and expiry are preserved. Empty message entries neither replace a caption nor add a delay. All three languages share the script timing. Music links are listening references rather than synchronised playback. Palette skips retain prior known entries within the record; unknown retained colours are magenta.

Recovered consumers:

| Consumer | Interpretation |
| --- | --- |
| `BFC0–BFDE` | Layout operands: text Y, height, width word, image Y, image X word. Height zero shortens the instruction to two operands. |
| `C07A–C09D` | Image resource is archive 03, slot `(2Fh + operand) & FFh`. Load also applies palette updates. |
| `C09E–C0C7`, `C0C8–C0D8` | Show/load background using the layout and image blitter. Palette-only resources do not replace image pixels. |
| `C0FB–C13F` | Animation resource uses operand high two bits as archive and low six as slot. Frame pointers append to a descriptor's table. |
| `DADA–DAFC` | Reset animation-load descriptors at SS:9D00 and SS:9EE3. The second table begins at global frame index 161. |
| `BFA5`, `C140–C16C` | Select one of twelve current/target animation pairs, set a range, wait for it, or disable it with A0h. |
| `DAA7–DAD9` | Draw active pairs in descending order, then move each current frame by one toward its target. |
| `D938–D9CC` | Presentation iterations use the record tick multiplier; clip preview interval is multiplier / 500 seconds. |

The frame decoder writes into the presentation page at Y+128; the browser uses the frame's stored display coordinates, removing that page offset. Backgrounds use script layout coordinates. Complete VGA page composition remains to be verified as a whole.

Thirty image-show events have exported layout associations. Eleven distinct nonempty image resources in these events have dimensions whose products exactly match their decoded pixel counts. These promote specific script-used dimensions beyond earlier visual guesses; they do not settle every image-sheet layout.

`analysis/presentations/clips.json` preserves script associations and layouts. `verification.json` records native decoding checks of the distinct first/last frames used by the clips, extending the earlier first-frame-only checks. This verifies frame pixels, not the complete compositor or timeline.

## Three fonts in resource 00/29

The startup loader at `03CA–03D2` loads resource 00/29 at **SS:2978**. Font setup `E29C` receives its base, glyph count and a byte that patches the per-glyph advance instruction.

| Inspection name | Runtime base | Resource offset | Glyph count | Setup examples | Advance |
| --- | --- | --- | --- | --- | --- |
| interface | 2978h | 0000h | 107 | `0A67–0A75` | width |
| compact | 4BE2h | 226Ah | 97 | `3CCA–3CD2`, `9CDF–9CE7`, `C041` | width + 1 |
| wide | 60D0h | 3758h | 96 | `C017–C020` | width + 1 |

Each font has N vertical-offset bytes, N width/height pairs, then four N-entry relative-pointer tables for X modulo 4. Each glyph row contains `ceil((width + alignment)/4)` bytes. Low nibbles are VGA plane-write masks; high nibbles supply the per-plane pattern used by the runtime colour lookup. Both are exported, along with the original bytes. The atlas uses inspection colours to distinguish the two patterns; it does not claim to reproduce runtime palette/latch state.

Characters start at code 33. Space advances four pixels. Extra glyphs retain numeric codes rather than assuming a Unicode mapping. Individual PNGs, atlases, exact metrics, source offsets and all alignment variants are in `analysis/interface/`. The font browser accepts sample text and exposes each glyph's dimensions and advance.

`tools/verify_fonts.py` executes **E29C and E1FE** in Unicorn for all **300 glyphs × four alignments = 1,200 cases**. All VGA write coordinates and final X advances match the exports. Runtime colour mapping is outside that assertion. All four decoded masks also agree after accounting for horizontal alignment.

## Cockpit image transfers

`BC50–BC82` selects the cockpit sheet from craft definition +0C. The five configuration values select resources **18, 16, 17, 18, 16** respectively; resource 15 is the sign-bit fallback.

`DB71` writes a relative CALL displacement into `DB9F`, so its BP parameter is not an absolute function address. For cockpit setup, `DBA1 + E3D1` wraps to **BF72**. That wrapper draws:

1. Source `(0,0,320,136)` to destination `(0,0)`.
2. Source `(0,96,320,104)` to destination `(0,136)`.

The overlap explains why a 320×200 graphics sheet should not simply be stretched to a 320×240 cockpit. The new static PNGs reproduce the transfers. The 3D viewport and instruments overwrite portions afterwards. Individual gauge frames, indicator selection rules, clipping and live HUD drawing remain to be reconstructed; these PNGs are not a finished cockpit renderer.

A further useful discovery is the text formatter at **B292**: controls 0–7 return, set position, change colour, newline, tab, set spacing, centre a line, and draw through an alternate path. Printable-run extraction alone does not recover these operations. Full multilingual message-command decoding and association with presentation events remain open.

## Reproduce

```sh
python3 tools/extract_interface.py
PYTHONPATH=/tmp/darker-python python3 tools/verify_fonts.py
python3 tools/interface_browser.py
python3 tools/extract_presentations.py
PYTHONPATH=/tmp/darker-python python3 tools/verify_presentation_assets.py
```

Pillow is required for font/cockpit PNG exports; native verification uses Unicorn. Original DOS files are untouched.

## Portable font exports (2026-09-23)

[Download and preview the portable fonts](../analysis/interface/portable-fonts/index.html). `tools/export_portable_fonts.py` produces OpenType/TrueType TTF and WOFF2 versions of all three fonts, plus coverage-layer companions. The outlines preserve source pixel boundaries and spacing; all 300 original glyphs have stable U+E000+byte addresses, with conservative ordinary Unicode aliases. Font raster checks cover all 600 glyph/layer combinations and their advances at 16 px. All six webfonts load in Chromium. [Usage and encoding details](../analysis/interface/portable-fonts/README.md).

Update: [masked HUD components and formatted multilingual text](hud-and-formatted-text.md) now provide descriptor-level cockpit assets and decoded text commands/message headers, with one flagged French source-data inconsistency.

### Repeated background selection retains the portrait

The background selector (`3B`) clears the page text, but native `BFE4–C007` compares the low six bits of the requested type against the retained type and skips loading/drawing the base image when they match. A clear-screen command sets that type to `3E`. In level 98, `D3A` repeats type 1 without another portrait draw: the `03_010` portrait loaded at `D2C` therefore remains behind the animation ranges at `D32` and `D3F`. Reloading `00_023` at that boundary incorrectly replaces the portrait with its black placeholder. Both the web preview and C++ renderer now retain it.

### Startup order and synthetic title

The browser lists the startup logo animation (`04_015 / 1`) first, then the synthetic DARKER title, then level 1’s quotations and briefing. The title is executable-driven rather than a resource-04 script: native `9CFE` uses image `00_014`, placed at (16,65), with the compact-font TM overlay at (256,84). The preview reproduces the 1,024-tick fade (2.048 seconds at 500 Hz), using the original sine table at `944A` and the `AFA2/AFAD` brightness/DAC arithmetic, followed by a 4,000-tick hold (eight seconds). It retains music group 1. This scene is explicitly labelled synthetic; the music catalogue still describes 124 actual script records. Browser animation consumes elapsed time rather than advancing only once per refresh.

### Message-only records and fixed-frame victory page

The Supa supply-pad record (`04_015 / 6`) and rolling-blackout record (`04_015 / 7`) have no presentation graphics. Their scene entries display ordered transcripts (21 and nine messages respectively), selectable in English, French or German. The canvas is hidden for these entries. They remain gameplay scripts, not timed movies: in particular, blackout messages are interleaved with world-state changes.

Nightmare victory (`04_015 / 4`) intentionally selects one fixed frame: `49 04` at `09BD`, followed by formatted text at `09BF` and a script stop. Its completed page combines `03_007`, frame 4 from `01_000` and the victory text. A fixed-frame selection before the formatter must use the completed page’s text, rather than an empty snapshot taken at the selection command. The viewer now displays the full page and waits for input.

## Language selection and credits

Retail credits are translated, rather than shared English artwork. The language
byte at `BAEB` selects a pointer from table `9D7A`: English at `9D80`, French at
`9EA6`, German at `9FE1`. The original `B292` formatter draws these pages over
the shared credits background and `00/28` logo. Names remain as authored;
headings and acknowledgements vary between languages. The French text even
omits Joan from the English acknowledgement, which is preserved rather than
silently corrected.

The original menu labels and scenario text also have three language variants.
`NIGHTMARE`, `STAR THREE` and the hidden menu's “What do you want?” are shared
English literals in the executable, not missing translations.

Confirmation shortcuts remain Y/N in every language: native code at `06FF–0707`
compares the same ASCII letters regardless of the translated Yes/No labels.

### German level 76 briefing corruption

The first German briefing page of level 76 (`04_009 / 3`) contains character
code 153 at resource offset `27EB`, outside the selected wide font's declared
range (33–128). Retail testing in DOSBox confirms garbled, overlapping text in
the second line, with the corrupt area flickering while the page remains on
screen. This is an original-game defect, not merely a missing web font glyph.
The flickering is consistent with the unchecked glyph lookup reading changing
memory; the precise memory contents have not been traced.

The presentation viewer continues decoding the surrounding text and marks the
undefined character with an amber outline box. It uses an explicit eight-pixel
replacement advance to keep the preview readable, with an on-page explanation.
This is an editorial annotation, not a corrected translation or an attempt to
simulate the original's unstable bitmap and text overlap. The original source
bytes remain unchanged. Later pages are decoded normally.

The playable reconstruction uses the selected font's question mark when an
undefined code would read bitmap data beyond the font resource. This prevents
the German level 76 briefing from throwing an exception. Its replacement width
is the question mark's original advance; the viewer's amber annotation remains
a separate inspection aid. Neither attempts to reproduce changing memory.
