Terrain, scenery and map colours
The reference has four warzones: Fort Hood, Cyprus, Korea and Yemen. Each combines a height field, terrain textures, flat feature polygons and placed 3D objects. The mission files add moving units and their routes.
Height field
GRIDPOST.BIN contains 257 × 257 eight-byte posts, in X-major order. The post at (x,y) starts at (x*257+y)*8. Each cell spans 1,048,576 world units. Its four corner heights define two triangles.
| Bytes | Meaning |
|---|---|
| 0–1 | Little-endian height, multiplied by 256 |
| 2 | Lighting input |
| 3–4 | Colours for the two triangles |
| 5 | Flags; bit 6 selects the diagonal |
| 6–7 | Texture selectors; low seven bits index the orientation-specific table |
get_terrain_elevation_ interpolates within the selected triangle using integer arithmetic. The Python decoder agrees with the original executable for 572,308 queries: every cell corner and centre, random positions, and map boundaries. The report is native-height-check.json.
Textures and palette
The terrain loader builds 256 texture slots per warzone. TEXT64.RAW supplies 97 textures for each diagonal orientation, and TEXT64SP.RAW supplies 31 special textures for each. The lookup tables select the appropriate half-square texture. UV coordinates cover a 64 × 64 square. The triangle corner order and UVs were checked through 2,048 calls to the original renderer dispatch. See native-rendering-check.json.
The displayed palette is more than the contents of 3DPAL.RGB and TERRAIN.RGB. During flight, update_palette_registers_ replaces colours 83–114 with a grassy ramp for Fort Hood and Korea. Cyprus and Yemen retain the dry terrain ramp. The previews use the resulting original palette and intensity lookup tables. They currently show unlit terrain without the game's fog and day/night adjustments.
Roads, coastlines and other flat features
FEATURES.BIN is a counted offset table. FEATURE1.LYR places its records in a 64 × 64 grid. Each placement is four terrain cells wide. Record headers point to vertices, faces and simplified distant faces. Variable-length vertex coordinates combine coarse, medium and fine components. The records contain polygon fills and occasional line segments, rather than independent texture images.
SUPER1.LYR places a second set of much larger features around the main map. Its 16 × 16 grid uses 67,108,864-unit sectors with an origin offset of -402,653,184. These provide the distant surroundings. The neighbourhood preview includes these surrounds at the map edges, below the detailed terrain and its road/coastline features. Line-only feature records are drawn as lines.
A face can name the same vertex more than once. The original constructs a linked list in the projected vertices themselves, so later references overwrite earlier links. The decoder preserves the authored list and also exports the resulting traversal as render_indices. Concave and self-crossing outlines need more than a triangle fan; the preview uses ear clipping and an even-odd fallback.
All 156,159 feature vertices and 73,884 normal and simplified face submissions agree with execution of the original loaders and scanners. See native-feature-check.json.
Placed scenery
GNDOBJS.BIN begins with 4,096 sector offsets. Each points to a 16-bit count followed by 12-byte placements: model, local X, local Y, heading, animation state and status, each a 16-bit value. X and Y combine the four-cell sector origin with a local value multiplied by 256. Headings cover one turn in 65,536 steps. Grid objects keep their authored orientation; ordinary models receive the placement rotation.
The four maps contain 1,518, 7,390, 8,977 and 24,432 placements respectively. The local browser links each placement back to the model catalogue.
Tools
tools/terrain.py decodes posts and placements. tools/features.py decodes feature records. tools/export_terrain_native.py runs the original loader to obtain texture tables and triangle dispatch. The accompanying check_*_native.py programs compare the independent decoders with original x86 execution. All inputs are read from the retained reference files; no media are modified.
Line of sight
The visibility routines sample the height field along a ray. Both return clear immediately if both endpoints are above the maximum terrain elevation. Weighted visibility at precision p uses 2^p fine samples near the start and 2^p − 1 coarser samples farther along. At the usual precision 3, these are distances 0 through 7/64 of the segment, followed by 1/8 through 7/8. The exact endpoint is not sampled. A point at the terrain height is clear; one below it is blocked.
The plain routine has a different loop: it checks p samples, spaced by 1/2^p, rather than checking the whole segment. The position-returning weighted variant retains the last fine-phase position even when an obstruction is found in its coarse phase. These are defined original behaviours. If the first sample is already underground, both position-returning variants use uninitialized output coordinates; that output is excluded from reproduction. A bounded implementation can return the start point in this case.
tools/check_los_native.py compares 1,400 visibility cases and 986 defined position results, including the exact query sequence and early exits. See native-los-check.json.
Software terrain detail and fog
The terrain renderer has seven paths. Distant triangles use a constant colour, with paired palette entries producing a dithered fog transition. Closer triangles can interpolate the grid-post lighting across either untextured colour pairs or a 32 × 32 texture. At the highest texture setting, the source images are 64 × 64.
The intermediate texture setting uses a different close-range representation: it divides each triangle into 64 smaller triangles and assigns one colour to each. 3DDATA/TERRTEXT.CMP contains the colour sequences, arranged as four sets of 97 × 64 bytes for the two diagonals and their two halves. Each small triangle uses its three vertices' average intensity. It is not a small texture mapped onto each triangle.
The highest setting instead corrects texture perspective across 16-pixel spans. Edge interpolation uses reciprocal depth. The renderer calculates corrected U, V and intensity at each span boundary, then uses integer increments within the span. This retains the original rounding at span boundaries, including the shorter remainder at the end of a row.
Four fog intensity tables use strengths 0.04, 0.26, 0.47 and 0.74. The code narrows each calculated slope to a 32-bit float before expanding the palette rows. Fog distances also select between paired colours and the four tables. These details are now reproduced in C++, with all 262,144 fog-table bytes and 32,040 shading decisions matching original x86 execution. A further 7,000 complete terrain polygon frames match pixel for pixel, including clipped and subdivided polygons. Reports: terrain shading and terrain frames.
Super-feature drawing omission in the reference engine
The January 1998 Windows software engine loads SUPER.BIN and SUPER1.LYR, visits the visible super-sectors and projects their vertices. However, draw_super_feature_ at 0x004a7184 returns immediately after either vertex scanner. Unlike draw_feature_ at 0x004a70f0, it never calls either face-list scanner. The instruction listing confirms both returns; this is not a decompiler omission.
Consequently, those large exterior landscape polygons are available in the data browser but do not appear in the faithful reconstruction's main scene. An original-code comparison exposed this near the coast of Cyprus: drawing the stored faces added 1,660 pixels absent from the original frame. This is a defined omission, not an unsafe memory effect. Earlier executable variants have not yet been checked for this particular omission.
Visibility reconstruction
The C++ uniform and weighted visibility samplers match 12,000 cases and 139,307 height queries. Uniform sampling checks precision steps, not 2^precision. Weighted sampling checks 2^precision fine steps followed by 2^precision - 1 coarse steps. A coarse obstruction retains the last fine position. Heights equal to terrain remain visible. Both endpoints above the terrain's maximum height bypass sampling. For an obstruction at the first sample, the original return position is unset; C++ returns the start position.
Complete line-of-sight checks then query mobile and ground collision volumes. Their treatment of a scenery obstruction differs depending on whether a mobile object was also hit, so the implementation preserves the original decision order. Across all four warzones, 24,000 cases agree (12,873 clear and 11,127 blocked), including ordered queries and selected-object identities. Normal report; sanitizer report.