Chocks Away is a flight simulator for Acorn Archimedes computers, written by Andrew Hutchings (interviewed here). It was published in 1990 by The Fourth Dimension (and followed by Extra Missions in 1991). It is fondly remembered by many because of its smoothly animated 3D graphics, simple controls and inclusion of a split-screen dogfight mode. The game world is drawn using a combination of flat-shaded polygon meshes, lines and points. Its 3D models of aircraft, buildings, bridges and other structures are primitive but I think they have a certain charm.
Some time in December 2017, I decided to reverse-engineer the 3D model file format for Chocks Away, starting from a disassembly produced by ARMalyser. I had already written a program to convert the 3D models for Star Fighter 3000 to Wavefront .OBJ format. I knew that Chocks Away had been written by the same author and used the same compression code (by Gordon Key) so I thought it might be possible to reuse a lot of my existing code. It turned out that Chocks Away had a lot of interesting and unique features.
The 3D models for Chocks Away are stored in a file named 'Land' (probably to hide them). The model data is indexed by an array of 200 (four byte, little-endian) addresses stored in a second file named 'Obj3D'. Consequently, it's possible for several indices to alias the same model. Strictly, one would need to know the address at which the model data will be loaded in order to correctly interpret the index file. In practice, the lowest address in the 'Obj3D' file is always the address of the start of the model data, which also coincides with the start of the 'Land' file.
Chocks Away: Extra Missions requires additional models for some maps. Instead of a single 'Obj3D' file, alternative index files numbered from 'Obj3D0' to 'Obj3DF' store between 109 and 159 model addresses. Model data in common between all maps is still stored in a single 'Land' file and any extra model data is stored in files numbered 'LandEx0' to 'LandExF'. Many of these files have length zero.
The rendering engine supports two levels of detail for every model, selected according to its distance from the camera. The distance beyond which a simplified model is used is part of the model data. Two models can be created from each set of data; vertices and geometric primitives defined nearer the start of the file are always rendered whereas those defined later in the file are only rendered at the high detail level. The vertex and primitive counts for the simplified model are stored separately from the counts for the complex model and should always be smaller.
If the chosen primitive count has the special value of 255 then the first vertex of the model is rendered as a point and any other vertices are ignored. It's unclear what use this is. Attempting to use it would probably crash the map view.
The vertex and primitive counts for the simplified model are ignored when drawing models on the map.
The near clip plane is an xz plane at a y position specified as part of the model data. If any vertex of one of the primitives comprising the model is closer than this plane (i.e. has a smaller y value) then that primitive will be clipped.
The last part of the per-model data controls plotting style. Lines can be drawn one or two pixels thick and polygons can be drawn with or without a blue or black outline. One word is overloaded to control both attributes: models with outlined polygons must have thick lines and models with plain polygons must have thin lines. Plotting style doesn't affect procedurally-generated primitives.
By default, thick lines and polygon outlines are disabled globally; enabling them (by pressing '5' in Extra Missions) only affects models configured to have those attributes in the first place. This attribute seems to have been set in a rather arbitrary way. For example, patrol boats can have outlined polygons and thick masts but gunboats cannot!
Plotting style is ignored when drawing the map: lines are thin and polygons have no outlines.
Offset | Size | Data |
---|---|---|
0 | 4 | Model simplification distance |
4 | 4 | Number of primitives -1, or 255 to draw 1st vertex |
8 | 4 | Number of vertices -1 |
12 | 4 | Simplified number of primitives -1, or 255 |
16 | 4 | Simplified number of vertices -1 |
20 | 4 | Ignored |
24 | 4 | Near clip plane distance |
28 | 4 | Plot style (0, 1 or 2) |
Value | Line style | Polygon style |
---|---|---|
0 | Thin | No outline |
1 | Thick | Black outline |
2 | Thick | Royal azure outline |
An array of vertex definitions follows the per-model data. 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. Any vertices required by a simplified model should be defined before those only required by the high-detail version of the same model, to minimize the number of coordinates processed.
It probably isn't practical to define more than 200 vertices in a single model because higher vertex indices cannot be used in a model that is drawn on the map (and some high indices trigger procedural generation when a model is rendered in 3D).
A left-handed coordinate system is used to specify vertex positions. Ground level is the xy plane with z=0 and all negative 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 vertex definitions. Each primitive definition is 16 bytes long, half of which is used to store vertex indices. Primitives are not shared between models.
The order in which primitives are defined is significant for several reasons:
Each vertex index occupies one byte. Vertices are indexed by their position in the preceding vertex array, starting at 1. A primitive must have at least two vertices and can have up to eight (an octagon). Primitives with fewer than eight vertices are terminated by a zero byte. Polygons with vertices specified in clockwise direction face the camera.
Some models are sufficiently complex that back-face culling is required to prevent near polygons being overdrawn by far polygons; others require polygons to be visible from both sides. For example, one airfield has two runways and one taxiway defined clockwise (facing up) but two other taxiways defined anti-clockwise! No switch to disable back-face culling is incorporated in the model data.
Beyond a specified distance, a polygon is replaced with a line between its first two vertices (as if the third byte of the definition were zero). This threshold applies to the distance of the whole model from the camera, not the distance of any individual vertex. A side-effect is to disable procedural generation (e.g. stripes or dashes) and force the specified colour and plotting style to be used.
The simplified version of a model may be selected for an object receding into the distance before its constituent polygons are simplified, or vice versa. A simplified model's vertex count need not include any vertices not required by polygons that are always simplified when that version of the model is selected.
The polygon simplification distance is ignored when drawing models on the map: if a polygon is drawn at all then all of its edges are drawn.
Offset | Size | Data |
---|---|---|
0 | 1 | 1st vertex index |
1 | 1 | 2nd vertex index |
2 | 1 | 3rd vertex index (or 0 to terminate a line, 253..255 for patterns) |
3 | 1 | 4th vertex index (or 0 to terminate a triangle, 248..255 for patterns) |
4 | 1 | 5th vertex index (or 0 to terminate a quadrilateral) |
5 | 1 | 6th vertex index (or 0 to terminate a pentagon) |
6 | 1 | 7th vertex index (or 0 to terminate a hexagon) |
7 | 1 | 8th vertex index (or 0 to terminate a heptagon) |
8 | 1 | Colour number |
9 | 3 | Ignored |
12 | 4 | Polygon simplification distance |
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)
The game uses procedural generation to create parts of its 3D models. Road and runway markings, battlements and trusses follow a regular pattern and can be described more accurately and concisely by algorithm instead of defining them by hand. Procedural generation is triggered by terminating a line or triangle with a special vertex number instead of with zero. This encoding scheme effectively limits the maximum number of vertices in a polygon mesh to 252, unless care is taken to ensure that vertices numbered 253..255 never appear as the third vertex of a triangle. (A stricter constraint applies to the fourth vertex of quadrilaterals and other complex polygons.)
Different special vertex numbers invoke different algorithms, using the previous vertices as their input. Special lines can be drawn with different thicknesses and numbers of dashes. Special triangles define parallelogram-shaped regions to be filled with parallel lines (for railway sleepers), zigzag lines (for trusses) or parallelograms (for stripes). Special triangles are also used to draw evenly-spaced points (for landing lights).
When drawing procedurally-generated primitives, the colour of the original primitive is ignored in favour of a special colour. Similarly, the plotting style of the model is ignored: polygons are drawn without outlines and line thickness depends on the chosen algorithm. Line thickness and colour may vary according to distance.
Back-face culling is not applied to procedurally-generated polygons so they effectively face both ways.
Procedural generation is disabled when drawing the map. Special primitives are drawn as ordinary triangles or lines, using the vertices and colour of the original primitive.
Vertex no. | Description | Colour | No. of primitives | Illustration |
---|---|---|---|---|
253 | Thin dashed line | White | 8 | |
254 | Thin dashed line | White | 16 | |
255 | Thick dashed line | White | 32 |
Vertex no. | Description | Colour | No. of primitives | Illustration |
---|---|---|---|---|
248 | Dotted line (ignoring 3rd vertex) | Orange | 32 | |
249 | Parallelograms | Dark grey | 16 | |
250 | Thick parallel lines | Peru | 64 | |
251 | Thin zigzag lines | Black | 16 | |
252 | Parallelograms if camera z <= -400 | Peridot | 8 | |
253 | Parallelograms if camera z <= -400 | White | 16 | |
254 | Parallelograms | Peridot | 8 | |
255 | Parallelograms | White | 16 |
Thick lines are not clipped correctly at the righthand edge of the screen, which means that up to one pixel can spill from the end of each raster line to the start of the next.
A subroutine used to do perspective projection when generating thick patterned lines (for roads and railways) corrupts the input depth value when the point to be projected is distant. The effect is to make distant lines thicker than those in the middle distance (and also brighter, in the case of parallel lines).
A patch to fix these bugs is available.
The following table summarizes the various magic numbers used in Chocks Away: Extra Missions.
The 'View target' column gives the values stored at
viewdata%!4
in PROCshowtargets
. The 'Hit
list offset' column gives offsets into a byte array of counts
of destroyed objects of each type, for the end-of-mission
summary in PROChitsummary
. The 'Message' column
gives indices within the game's message table. The 'Target
type' column lists values of the word at byte offset 24 in
the target object data. The 'Plane type' column lists values
of word at byte offset 140 in the plane object data. The
'Model' and 'Shadow' columns give indices within the 'Obj3D0'
file.
View target | Hit list offset | Message | Target type | Plane type | Model | Shadow | Hit points | Name |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 18 | 23 | TIGER MOTH | ||
1 | 1 | [[[[[ HELLO ERROR ]]]]] | ||||||
2 | 2 | 2 | 2 | 19 | 24 | FOKKER V7 TWIN | ||
3 | 6 | 3 | 3 | 29 | 26 | FOKKER EINDECKER IV | ||
4 | 4 | 4 | 4 | 31 | 27 | ALBATROS DIII SCOUT | ||
5 | 5 | 5 | 5 | 22 | 25 | GOTHA G IV BOMBER | ||
6 | 6 | 6 | 6 | 30 | 28 | FOKKER VIII TRIPLANE | ||
7 | 7 | 7 | 7 | 77 | 102 | FOKKER DE5 BIPLANE | ||
8 | 8 | 8 | 8 | 75 | 103 | FOKKER V3 TRIENGINE | ||
9 | 9 | 9 | 9 | 74 | 104 | CARGO AIRCRAFT | ||
10 | 10 | 10 | 108 | 103 | JET FIGHTER | |||
100 | 50 | 11 | 0 | 0 | 3 | GROUND GUN BASE | ||
101 | 51 | 12 | 1 | 1 | 10 | STORE BUILDING | ||
102 | 52 | 13 | 2 | 2 | 4 | TANK | ||
103 | 53 | 14 | 3 | 3 | 10 | HEAD QUARTERS | ||
104 | 54 | 15 | 4 | 4 | 10 | CONTROL TOWER | ||
105 | 55 | 16 | 5 | 5 | 10 | PATROL BOAT | ||
106 | 56 | 17 | 6 | 72 | 30 | AIRSHIP | ||
107 | 57 | 18 | 7 | 73 | 10 | BARRAGE BALLOON | ||
108 | 58 | 19 | 8 | 78 | 10 | CONTROL TERMINAL | ||
109 | 59 | 20 | 9 | 79 | 45 | OIL TANKER | ||
110 | 60 | 21 | 10 | 81 | 25 | GUN BOAT | ||
111 | 61 | 22 | 11 | 85 | 20 | TRAIN | ||
62 | 23 | 12 | 22 | 15 | GOTHA G IV BOMBER | |||
63 | 24 | 13 | 29 | 5 | FOKKER EINDECKER IV | |||
64 | 25 | 14 | 19 | 5 | FOKKER V7 TWIN | |||
65 | 26 | 15 | 75 | 5 | FOKKER V3 TRIENGINE | |||
66 | 27 | 16 | 74 | 18 | CARGO PLANE | |||
67 | 28 | 17 | 54 | 3 | YACHT | |||
68 | 29 | 18 | 87 | 30 | RAILWAY STATION | |||
69 | 30 | 19 | 46 | 50 | BRIDGE | |||
70 | 31 | 20 | 68 | 40 | FACTORY | |||
71 | 32 | 21 | 52 | 100 | AIRCRAFT CARRIER | |||
72 | 33 | 22 | 107 | 10 | JET FIGHTER | |||
35 | PLANE REPAIRED AND REFUELLED |
The following table summarizes the 3D models stored in the 'Land' file for Chocks Away: Extra Missions (i.e. the model data in common between all maps). Many of these models are not named targets or otherwise special to the game engine, therefore their appearance is subject to interpretation.
Number ranges ('..') indicate that more than one index aliases the same model data.
Number | Special? | On map | Appearance |
---|---|---|---|
0 | Yes | Hidden | Ground gun base |
1 | Yes | Hidden | Store building |
2 | Yes | Hidden | Tank |
3 | Yes | Hidden | Head quarters |
4 | Yes | Hidden | Control tower |
5 | Yes | Hidden | Patrol boat |
6 | No | Partial | Aerodrome |
7 | No | Partial | Aerodrome |
8 | No | Partial | Dark olive field |
9 | No | Partial | Dark green field |
10 | No | Partial | Avocado field |
11 | No | Partial | Peridot field |
12 | No | Partial | Harvest gold field |
13 | Yes | Hidden | Light grey cloud |
14 | Yes | Hidden | White cloud |
15 | No | Partial | Brown field |
16 | No | Partial | Royal azure lake |
17 | No | Partial | Aerodrome |
18 | Yes | Partial | Tiger Moth |
19 | Yes | Partial | Fokker V7 twin |
20 | No | Partial | Aerodrome |
21 | No | Partial | Aerodrome |
22 | Yes | Partial | Gotha G IV bomber |
23 | Yes | Partial | Tiger Moth shadow |
24 | Yes | Partial | Fokker V7 twin shadow |
25 | Yes | Partial | Gotha G IV bomber shadow |
26 | Yes | Partial | Fokker Eindecker IV shadow |
27 | Yes | Partial | Albatros DIII scout shadow |
28 | Yes | Partial | Fokker VIII triplane shadow |
29 | Yes | Partial | Fokker Eindecker IV |
30 | Yes | Partial | Fokker VIII triplane |
31 | Yes | Partial | Albatros DIII scout |
32 | No | Partial | Tree |
33 | No | Partial | Aerodrome |
34 | Yes | Full | Aerodrome |
35 | No | Partial | Rugby field |
36 | No | Partial | Low factory |
37..38 | Yes | Hidden | Rubbish (-ve primitive count) |
39 | No | Partial | Church |
40 | No | Partial | Terraced houses |
41 | No | Partial | Bridge |
42..43 | No | Partial | Grey block |
44 | No | Partial | Dark grey block |
45 | No | Partial | Church |
46 | Yes | Partial | Bridge |
47 | No | Partial | White house and outbuilding |
48 | Yes | Full | Island |
49 | No | Partial | Aircraft carrier |
50..51 | Yes | Full | Aircraft carrier |
52 | Yes | Partial | Aircraft carrier (canonical) |
53 | No | Partial | Lighthouse |
54 | Yes | Partial | Yacht |
55 | No | Partial | Blue block |
56 | No | Partial | Blue block |
57 | Yes | Full | Aerodrome |
58 | Yes | Full | Dark olive field |
59 | Yes | Full | Dark olive field |
60 | No | Partial | Dark olive field |
61..62 | No | Partial | Avocado square field |
63 | No | Partial | Black tower |
64 | No | Partial | Hangar |
65 | No | Partial | Grey house |
66 | No | Partial | Grey house with lean-to |
68 | Yes | Partial | Factory |
69 | No | Partial | Hexagonal road |
70 | No | Partial | Wall with battlements |
71 | Yes | Hidden | Airship |
72 | Yes | Hidden | Airship (canonical) |
73 | Yes | Hidden | Barrage balloon |
74 | Yes | Partial | Cargo aircraft |
75 | Yes | Partial | Fokker V3 triengine |
76 | Yes | Hidden | Dark olive field boundary |
77 | Yes | Partial | Fokker DE5 biplane |
78 | Yes | Partial | Control terminal |
79 | Yes | Hidden | Oil tanker |
80 | Yes | Partial | Oil rig |
81 | Yes | Hidden | Gun boat |
82 | No | Partial | Grey house |
83 | No | Partial | White house with outbuilding |
84 | No | Partial | Pier |
85 | Yes | Hidden | Train |
86 | No | Partial | Railway track |
87 | Yes | Partial | Railway station |
88 | No | Partial | Railway bridge side |
89 | No | Partial | Railway signal box |
90 | No | Partial | Railway signal |
91 | No | Partial | Level crossing |
92 | No | Partial | Street light |
93 | No | Partial | Rectangular signboard |
94 | No | Partial | Triangular signboard |
95 | No | Partial | Triangular signboard |
98 | No | Partial | Street light |
99 | Yes | Partial | Sea |
100 | No | Partial | Road |
101 | No | Partial | Road |
102 | Yes | Partial | Fokker DE5 biplane shadow |
103 | Yes | Partial | Fokker V3 triengine shadow |
104 | Yes | Partial | Cargo aircraft shadow |
105 | No | Partial | Brown field boundary |
106 | No | Partial | Runway lights |
107..108 | Yes | Partial | Jet fighter |
108 | Yes | Partial | Jet fighter |