# Procedural cockpit graphics: ring, attitude and enlarged contacts

[Interactive cockpit](../analysis/interface/dynamic/index.html) ·
[Extracted data](../analysis/interface/dynamic/procedural.json) ·
[Browser checks](../analysis/interface/dynamic/procedural-browser-verification.json)

This pass adds the **Skimma weapon ring**, **Caero attitude line and fixed vector
surround**, and **enlarged-radar contact symbols**. The attitude line is in the
Caero (`B91F = 19h`) branch, not the Skimma branch. The controls supply drawing
inputs directly; they do not run a flight or targeting simulation.

## Skimma weapon ring

`5D47` draws the aiming outline at X=164. `5D56–5E0E` selects weapon state and
radius; `5D83–5DDC` places the individual masked sprites. Unlike the earlier
speculative description of a rotating ring, this inspected loop starts at a
fixed sine-table position on every call. It changes radius and remaining/spent
indicators, not a continuously advancing rotation phase.

| Weapon index | Sine byte increment (`5E16`) | Ring positions / working capacity (`5E19`) |
| --- | --- | --- |
| 0 (weapon 1) | 146 | 14 |
| 1 (weapon 2) | 255 | 8 |
| 2 (weapon 3, upgraded Skimma only) | 205 | 10 |

These are **working shot counts**, distinct from the reserve/reload capacities
5, 3 and 4 at `5E1C`, and from field `454E`'s selected reserve display.

The loop reads signed high bytes of sine/cosine words. For raw radius `r`, its
unmasked destination origin is:

```
x = FCCD + ((sine_high * r) >> 8) - 2
y = baseline - ((cosine_high * r) >> 8)
```

The inspection uses `FCCD = 160` and normal-play baseline 88. Nonzero `4540`
selects baseline 118. Radius is consequently approximately half the raw input
in screen pixels. Mask `6010` comprises four `(skip,width)` rows:
`(5,2), (4,4), (4,4), (5,2)`. The visible sprite extends beyond the unmasked
origin; it must not be recentered as a generic circle.

Before each sprite, the low count byte is decremented. A nonnegative result
selects source row 47 (remaining); a negative result selects row 52 (spent).
Destination X modulo four selects source X `8 + 5*alignment`. The export keeps
all four alignment variants for both states and both Skimma sheets: 16 masked
RGBA PNGs, with original source coordinates in the JSON.

### Reload and radius input

The native producer compares clock `BF8` with deadline `C51C` using a signed
16-bit difference. When the difference is negative, it constructs radius from
that difference, suppresses drawing below raw radius 15, and supplies a **zero
remaining count**: the expanding reload ring uses spent indicators.
`5E31–5E40` sets the deadline 1024 ticks ahead when refilling the working count.

Outside that interval, the producer uses `(word[5E00] << 6) >> 8`, preserving
16-bit wrap, clamps the raw radius to at least 15, and uses the selected working
count at `5E10 + index`. Weapon enable bit 0 at `4543 + index` gates both paths.
The later smoothing/update through `7CDB` is not reproduced in this preview.
The browser's radius slider therefore remains an explicit drawing input.

## Caero attitude and fixed vector graphics

`5E6F–5ED3` computes a line from the sine-table offsets stored at `3E43` and
`3E49`. The main loop carries these as view-angle parameters. Multiplication
uses signed high-word products with scale 224, preserving asymmetric integer
rounding. The probe supplies the two offsets directly; mapping every camera
mode and player attitude into them remains separate work.

`5F5D` orders endpoints and enters the original line rasterizer at `A77B`.
The export executes that rasterizer for **289 combinations**: pitch and roll
indices −128 through +128, step 16 (−45° through +45°, step 5.625°). Each frame
stores lossless horizontal pixel runs, not a browser-generated approximation.
The deliberately limited inspection grid avoids asserting unrestricted camera
angle/viewport behaviour.

`5ED6–5F0D` adds the fixed vector graphics using streams `5F67` and `6009`.
Their pixels are also captured from native drawing code. The inspection uses
zero for the self-modified colour parameter at `5F01`; live feedback is not
simulated. The line uses a fixed palette index 14 in the browser. Separately,
252 native cases check its actual endpoint and colour arithmetic, including
player pitch-high-byte inputs and both branches of `7ED2`.

Coordinates are captured in a viewport with centre Y=84. The browser adds the
established Caero eight-pixel cockpit offset, placing that centre at screen
Y=92. Skimma ring sampling does not receive this Caero offset.

## Enlarged-radar contacts

`5BCA/5BE2` renders a 12-pixel rounded diamond, shared by the enlarged-radar
contact callers. The exported mono shape is relative to incoming BX/DI:

```
  ##
 ####
 ####
  ##
```

Its occupied coordinates are X=1..4, Y=1..4 within that local anchor. The native
routine loads colour through VGA latches; subsequent framebuffer writes carry
plane masks, not literal pixel colours. The capture records the selected
colour at `DBB8` and reconstructs the planar coverage. Stereo duplication is
explicitly excluded.

The normal radar's object traversal and coverage predicate are reused. Enlarged
callbacks at `5C31` and `5C34` multiply the rotated fixed-point offsets by three
before reducing them to pixels. They reject squared distance **>=3965**. The
screen anchor is X=76 and a Y supplied through patched word `5C46`; the original synthetic checks initially used Y=96. The assembled preview now
uses native local Y=100 plus the Caero eight-pixel row offset, giving screen
Y=108. Its contact checks now supply local Y=100 too. Each shape anchor is offset −3 on both axes.

Colour depends on distance and which callback is selected. For squared distance
`d`, it is `7 - ((d - 3965) >> (8 + shift))`, reduced to a byte, with shift 0
for group A and 1 for group B. These group labels avoid assigning an unverified
allegiance. Coverage is assumed in the probes and controlled explicitly in the
browser, as for the existing small radar.

The browser now offers the assembled mono enlarged radar, as described below.
Stereo placement and the complete navigation-contact traversal remain separate.

## Evidence and reproduction

Native probes use the captured runtime image from
`analysis/hidden-commands/ui/after-command-memory.bin.gz` (image segment 01A2),
with synthetic display state. Drawing code and geometry execute in Unicorn;
coverage and selected hardware operations are intercepted as described above.
No original installation files are modified.

- 140 native ring-placement cases, including empty/full working counts and
  four radii, match an independent integer reference.
- 54 producer cases cover reload suppression/expansion, enable state and
  ordinary radius selection. Execution stops before the subsequent smoothing
  update; no claim is made about full target acquisition.
- 252 native attitude endpoint/colour cases match an integer reference;
  289 rasterized angle frames are exported directly.
- 144 native enlarged-contact cases match the browser projection, including
  headings, boundary rejection and both colour groups.
- Browser checks compare ring placement and enlarged projection against those
  native results, exercise both craft families, enforce ordinary Skimma's two
  weapons, and check reset behaviour. Screenshots are retained alongside data.

```sh
PYTHONPATH=/tmp/darker-python python3 tools/extract_dynamic_hud.py
NODE_PATH=/tmp/riotfire-browser/node_modules node tools/verify_procedural_hud.js
```

The standalone `tools/procedural_hud.py` rebuilds its own JSON and ring PNGs;
use `extract_dynamic_hud.py` to update the assembled browser too.


## Enlarged-radar surround and height-coded contact follow-up

[Additional graphics data](../analysis/interface/dynamic/radar-graphics.json)
is generated by `tools/radar_graphics.py` and incorporated by
`tools/extract_dynamic_hud.py`.

The original `59CB` path now runs through its mono background and heading
rasterisation in the probe. The circle at `A5D6` and planar span writer `A712`
produce the actual outline/coverage; no browser circle approximation is used.
The base capture contains **12,572 pixels** including the centre marker and
number-panel clearing strip, stored as lossless horizontal runs. VGA plane
masks are combined with the selected colour latch rather than treating dummy
framebuffer-write values as palette indices. Stereo flags are disabled.

The original caller at `55B5` sets local Y to `100 + word[5458]`. In the normal
Caero view that word is zero, and the row table adds eight pixels. The resulting
screen centre is **(76,108)**. The previous centre (76,96) was explicitly an
inspection fixture, now superseded in the assembled display.

The heading line is captured at **256 heading states**, matching the browser
slider's 256-word increment. Native endpoints and rasterised pixels are retained
per state. The real routine can consume finer headings; the export is a sampled
inspection control, not a claim that all in-game headings quantise that way.
The five large glyph copies are observed at local X=56,64,72,80,88 and Y=33;
the Caero offset puts them at screen Y=41. They show row, separator, column
using the existing extracted large digits. Font copying is intercepted in the
native capture and assembled from those PNGs in the browser.

`5B92` supplies the height-coded navigation glyph. It first restores a six-row
background mask at source X selected by destination alignment: 96,109,102,115.
Its source row is 8 in the actual sheets for all three cockpit resources. Twelve
masked PNGs preserve those backgrounds and alignments. It then draws the shared
12-pixel contact shape with a computed colour.

The colour uses a **signed eight-bit difference** between object height byte
`+0D` and reference byte `2E37`, followed by arithmetic division by two. For a
negative result, the magnitude operation is bitwise complement, not absolute
value: `-1` maps to magnitude zero. Magnitude saturates at ten. The two branches
start at palette indices 151 (nonnegative) and 183 (negative), then subtract
that magnitude. This behaviour is preserved, including wrapping differences;
no metre conversion is inferred.

**144 native cases** verify colour and background-source selection across
heights, reference values and all four horizontal alignments. The browser
compares its colour arithmetic with those fixtures and offers a fixed-position
sample on each craft. This is not yet the whole navigation radar: list selection,
projection/range clipping through `5B76`, and the live reference-height producer
remain separate. Coverage/interference and live contact allegiance also remain
outside the assembled enlarged-radar preview.
