First static city-model extraction
Update: animated geometry and linked model states 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. 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. 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.
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 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.