# First static city-model extraction

Update: [animated geometry and linked model states](model-animation-and-states.md)
now implements gate/fountain transforms, exports 27 additional linked definitions,
and adds state/animation controls. Earlier references below to those models being
unresolved describe the previous extraction pass.

2026-09-18. Open [the interactive type atlas](../analysis/models/index.html).
Each selectable type has a rotatable preview and an OBJ download. Pair bank 30
with map 68, bank 31 with map 69, and bank 32 with maps 70–77. This supplies a
visual key; most semantic building names have not been assigned. Bank 31 type 3 is
identified by the automatic supply-pad landing code.

**362 of 391 city type records**, plus **99 special-object entries**, export successfully. These are type records, not
461 distinct meshes: aliases and variants share geometry. Eight city records
encounter unsupported commands, and 21 city records produce no polygon faces on this path.
The manifest records every success/failure without substituting guessed shapes.

The first bank includes recognizable wall sections/junctions, towers and other
city structures. The provided city-reference map can help label landmarks, but
its labels have not yet been registered to grid coordinates.

## Bytecode, not a conventional vertex array

Rendering starts at model-header offset `+11` (`2CB6–2D11`). A byte chooses an
entry inside the executable's `FCxx` dispatch page; `FExx` supplies the clipping
path. Some byte values enter the middle of a shared instruction sequence.

Examples from the far/render path:

| Opcode | Static interpretation |
| --- | --- |
| `0D` | Return from drawing subroutine |
| `3E` | Call relative subroutine; signed displacement relative to its own word |
| `4D` | Relative jump |
| `25`, `2C` | Set/reset output vertex cursor |
| `8E` | Read signed 16-bit C, A, B components; emit vertex |
| `91` | Replace A and B, retain C; emit |
| `7F`, `88`, `94` | Replace C, A or B respectively; emit |
| `73`, `76`, `79` | Negate C, A or B respectively; emit |
| `5E`, `61`, `64` | Zero C, A or B respectively; emit |
| `52/56/5A`, `67/6B/6F` | Zero/negate a component, then continue without emitting |
| `02`, `03` | Triangle/quad, colour byte followed by vertex indices |
| `01` | Explicit count-minus-one, colour and indices |
| `0E`, `0F`, `11` | Gouraud triangle/quad/explicit count; each vertex index has a shade byte |
| `16` | Camera-facing test and conditional skip |
| `1A` | Distance test and conditional skip |
| `1E` | Coloured line between two indexed vertices |

Thus a symmetric shape can generate mirrored points with one-byte instructions
instead of repeating all three coordinates. Signed relative calls also let
multiple model records share a vertex-generation subroutine.

## Export semantics and limits

The exporter executes the supported static command stream, retaining coordinate
state across calls. Faces snapshot the vertices they reference, so later cursor
reuse does not overwrite previously recorded geometry. It chooses fall-through
for visibility/distance tests, intentionally retaining surfaces normally culled.
This can combine mutually exclusive details and does not reconstruct painter
ordering. It stops on unknown instructions and does not emit a partial mesh
under a successful filename.

Coordinates remain in native integer units; OBJ axes are `(A, -C, B)` with Y up.
The viewer now uses recovered palettes and face/vertex shades; neutral artificial
shading remains an optional inspection mode. OBJ/MTL exports preserve the flat
fallback colours. See [colours and damage states](model-colours.md). Game fades,
exact rasterization, animated/procedural commands and special-object naming
remains incomplete. Links to exported damage models are navigable; some
variants have no separate directory type and are not yet exported.

## Verification

`tools/verify_model_vertices.py` runs the **original x86 coordinate interpreter**
in Unicorn for every successfully exported type. It extracts the vertex-command
sequence, omitting drawing and control flow, and configures two independent
axis projections. All **32,900 vertex events**, across **461 exported entries**, agree
with the decoded coordinates in both projections.

This checks the coordinate-command semantics, including component reuse and
negation. It does **not** independently validate polygon topology, shade
interpolation, visibility branching or axis orientation against gameplay. The
standalone viewer also loads without browser JavaScript errors; thumbnails
and the large preview were visually inspected.

```sh
python3 tools/extract_models.py
PYTHONPATH=/path/to/unicorn python3 tools/verify_model_vertices.py
python3 tools/model_viewer.py
```

Outputs: `analysis/models/manifest.json`, `vertex-verification.json`,
`bankNN_typeNNN.obj`, and standalone `index.html`.

## Ships and the light tower

The special-object directory is now exported as a separate atlas category.
Bank 30 special object 25 is the Caero player model; bank 31 special objects
26/27 supply ordinary/upgraded Skimma slots and share geometry. See
[ships and towers](ships-and-towers.md) for pointers and aliasing limits.

Opcode `22` is now decoded as a camera-facing screen-space disc at the most
recent output vertex. It decrements the output cursor, reads a colour and an
unsigned radius numerator, and passes numerator / projected depth to the disc
rasterizer. Its metadata is preserved, but it is omitted from OBJ polygon
surfaces and the viewer. This unlocks the polygon bodies of six city types,
including bank 30 type 1, the energy tower. Unsupported transform commands
`32` and `3B` remain explicit export failures.
