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 models on the map: lines are thin and polygons have no outlines.
|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|
|24||4||Near clip plane distance|
|28||4||Plot style (0, 1 or 2)|
|Value||Line style||Polygon style|
|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 2D map (and some high indices trigger procedural generation when a model is rendered in 3D).
A right-hand 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.
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 because later primitives are not rendered if a simplified version of the model exists.
Only the first primitive of most models is drawn on the 2D map. This explains why square brown field boundaries are drawn as straight lines (annoying for navigation). A small list of exceptions is hardwired into the game to allow certain models to be drawn on the map without simplification — for example cross-shaped aerodromes, which would otherwise appear to be missing a runway.
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 anti-clockwise direction face the camera.
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 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.
Primitives are drawn in the order they are defined without using a depth buffer. Some of the 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 anti-clockwise (facing up) but two other taxiways defined clockwise! No switch to disable back-face culling is incorporated in the model 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)|
|12||4||Polygon simplification distance|
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 models on 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|
|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|
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).
The following table summarizes the various magic numbers used in Chocks Away: Extra Missions.
HIT = byte offset in hitlist (from the 'PlaneNames' file).
VIE = value poked into
MSG = message number.
TGT = target object type (word at offset 24 in data).
PLN = plane object type (word at offset 140 in data).
GFX = plane model index (in 'Obj3D0' file).
SHD = ground shadow index (in 'Obj3D0' file).
|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|
|100||50||11||0||0||GROUND GUN BASE|
|62||23||12||22||GOTHA G IV BOMBER|
|63||24||13||29||FOKKER EINDECKER IV|
|64||25||14||19||FOKKER V7 TWIN|
|65||26||15||75||FOKKER V3 TRIENGINE|
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.
|0||Yes||Hidden||Ground gun base|
|8||No||Partial||Dark olive field|
|9||No||Partial||Dark green field|
|12||No||Partial||Harvest gold field|
|13||Yes||Hidden||Light grey cloud|
|16||No||Partial||Royal azure lake|
|19||Yes||Partial||Fokker V7 twin|
|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|
|37..38||Yes||Hidden||Rubbish (-ve primitive count)|
|44||No||Partial||Dark grey block|
|47||No||Partial||White house and outbuilding|
|52||Yes||Partial||Aircraft carrier (canonical)|
|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|
|66||No||Partial||Grey house with lean-to|
|70||No||Partial||Wall with battlements|
|75||Yes||Partial||Fokker V3 triengine|
|76||Yes||Hidden||Dark olive field boundary|
|77||Yes||Partial||Fokker DE5 biplane|
|83||No||Partial||White house with outbuilding|
|88||No||Partial||Railway bridge side|
|89||No||Partial||Railway signal box|
|99||No||Partial||Royal azure quadrilateral|
|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|