Apocalypse is a single-player 3D shoot 'em up for Acorn Archimedes computers, written by Gordon Key. The game world is drawn using a combination of flat-shaded polygon meshes and sprites. Most of the targets in the game are animated and have amusing names such as 'weirding flasher' and 'proton flapper'.
In March 2020, I decided to reverse-engineer the 3D model file format for Apocalypse, starting from a disassembly produced by ARMalyser. I had already written programs to convert the 3D models for Star Fighter 3000 and Chocks Away to Wavefront .OBJ format. Since Apocalypse also uses flat-shaded polygon meshes, I thought it might be possible to reuse a lot of my existing code. Apocalypse turned out to be a lot simpler than the other two games.
The 3D models for Apocalypse are stored in the same file as the executable code, so this game isn't easily moddable. The executable file contains two types of interesting data: object models and flats. Flats are individual polygons that have no colour and are defined in only two dimensions. I believe these are used for planet surface detail.
The game's assembled code and data is loaded at address
&8F00
. Flats are accessed via a table of 26
addresses located at address &18B64
. Object
models are accessed via a table of 200 addresses located at
address &19B6C
. Each address occupies 4
bytes, stored in little-endian byte order (i.e.
least-significant bits first). This mechanism allows fast
random access to data by the game.
Offset | Size | Data |
---|---|---|
0 | 4 | Number of vertices (v) |
4 | 12 × v | Vertex definitions |
4 + (12 × v) | 4 | Number of primitives (p) |
8 + (12 × v) | 8 × p | Primitive definitions |
8 + (12 × v) + (8 × p) | p | Primitive colours |
An array of vertex definitions follows the vertex count. Each vertex definition is 12 bytes long, comprising three coordinate values. Vertices are not shared between models.
The order in which vertices are defined is significant because their position in the array is used to refer to them in primitive definitions. It would be pointless to define more than 256 vertices in a single model because each vertex index is encoded as one byte in a primitive definition.
A right-handed coordinate system is used to specify vertex positions. Ground level is the xy plane with z=0 and all positive z coordinate values are above ground. Coordinates are stored as a signed two's complement little-endian number occupying four bytes.
Offset | Size | Data |
---|---|---|
0 | 4 | X coordinate |
4 | 4 | Y coordinate |
8 | 4 | Z coordinate |
An array of primitive definitions follows the primitive count. Each primitive definition is 8 bytes long, most of which is used to store vertex indices. Primitives are not shared between models.
Each vertex index occupies one byte. Vertices are indexed by their position in the preceding vertex array, starting at 0. A primitive must have at least three vertices and can have up to seven (a heptagon). Polygons with vertices specified in anti-clockwise direction face the camera.
Offset | Size | Data |
---|---|---|
0 | 1 | Number of edges |
1 | 1 | 1st vertex index |
2 | 1 | 2nd vertex index |
3 | 1 | 3rd vertex index |
4 | 1 | 4th vertex index |
5 | 1 | 5th vertex index |
6 | 1 | 6th vertex index |
7 | 1 | 7th vertex index |
An array of colours numbers follows the primitive definitions. Each byte specifies the colour of the primitive with the corresponding array index.
Colours are encoded using an additive RGB model with 4 bits per component. However, the 2 least-significant bits of each component (the 'tint' bits) must be equal for all three components.
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Role | Blue high | Green high | Green low | Red high | Blue low | Red low | Tint high | Tint low |
For example, colour 42 (binary 00101010):
This is similar to the SVG/HTML/CSS colour named 'Teal'. (0.0, 0.50, 0.50)
Offset | Size | Data |
---|---|---|
0 | 4 | Number of vertices (v) |
4 | 8 × v | Vertex definitions |
An array of vertex definitions follows the vertex count. Each vertex definition is 8 bytes long, comprising two coordinate values. Vertices are not shared between flats.
The order in which vertices are defined is significant because each vertex is implicitly joined to its predecessor by an edge. There isn't an enforced limit on the number of vertices (or edges).
Coordinates are stored as a signed two's complement little-endian number occupying four bytes.
Offset | Size | Data |
---|---|---|
0 | 4 | X coordinate |
4 | 4 | Y coordinate |
The following table summarizes some of the magic numbers used in Apocalypse.
Message | Model | Name |
---|---|---|
0 | 55 | Silicon Vat |
1 | 58..61 | Pumping Station |
2 | 62..65 | Seismic Hammer |
3 | 14 | Processing Factory |
4 | n/a | ESCAPE ABORTED |
5 | 66..69 | Krypton Breather |
6 | 80..87 | Rakonan GomJabba |
7 | 70,71 | Ground Transport |
8 | 72..74 | Wind Generator |
8 | 75..78 | Wind Generator |
9 | 40..42 | Ground Wasp |
10 | n/a | ENERGY UNITS RECOVERED |
11 | n/a | NO ENERGY UNITS RECOVERED |
12 | n/a | SHIELD ENERGY REPLENISHED |
13 | 56 | Static Release |
14 | 57 | Ground Scanner |
15 | 15, 7 | Obelisk |
15 | 167..166 | Obelisk |
16 | n/a | WITHDRAWAL ON GUILD ORDERS |
17 | 88..95 | Proton Flapper |
18 | 96..103 | Electron Grinder |
19 | 104..111 | Wave Generator |
20 | 112..119 | Postal Teleport |
21 | 120..127 | Climatic Ticker |
22 | 44..46 | Tilexu Floater |
23 | 26 | Snail Rider |
24 | n/a | Energy Bank Recharged |
25 | 128..135 | Weirding Flasher |
26 | 136..143 | Aldebran Linkbat |
27 | 144..151 | Argon Storehouse |
28 | 152..159 | Thermal Riser |
29 | 1, 6 | Guard Tower |
30 | 181..187 | Snailherd |
31 | 192..199 | Lhaktal Gourd |
32 | 23..25 | Simtaal Quak |
33 | 35,36 | Rakonan Barftub |
34 | n/a | SHIELD ENERGY OVERBOOSTED |