Effects and interface bitmaps
The .BMP files in 3DDATA are raw row-major palette indices, with no Windows bitmap header. Index zero is masked out by the renderer. The images use the live game palette; the extracted previews use the original daylight palette after cockpit initialization, including its upper 32 entries.
| Family | Shipped frames | Runtime frames | Dimensions |
|---|---|---|---|
GNDEXP |
33 | 33 | 64 × 32 |
AIREXP |
20 | 15 | 64 × 64 |
SMOKE |
9 | 8 | 32 × 32 |
AIREXL |
20 | None found | 64 × 64 |
FLKEXP |
20 | None found | 64 × 64 |
NEWGND |
33 | None found | 64 × 32 |
initialise_explosions_ and initialise_smoke_ load the active sequences. Each texture descriptor has three words: texture type, height, and pixel pointer. The type selects row stride: type 0 is 16, type 1 is 32, and type 2 is 64. Thus a ground explosion is type 2 with height 32, not a square 32-pixel texture. All 56 active buffers are compared byte for byte with original execution by tools/effects.py.
Runtime animation
Explosion storage holds 128 records of 36 bytes. An explosion advances by one frame on each visual update: 33 frames for a ground explosion, 15 for an air explosion. Moving explosions also integrate their velocity using elapsed time. Ground explosions are placed on the terrain. Air explosions are clamped above it using their radius, including their first animation frame.
Smoke storage holds 2,048 records of 32 bytes. Its age uses the visual clock; it expires when age reaches its lifespan. The image advances through eight phases of that lifespan. Its size grows from the base size to five times that size, and its quad is rotated relative to the camera. Rendering uses masked indexed pixels, not alpha blending. The viewer's rate control plays the source frames directly; the full smoke lifetime and billboard motion belong to the simulation.
Interface pieces and controller guides
Four 16 × 16 .BM files have original sprite constructors: CLOSEON, CLOSEOFF, RESIZING and RESIZE. The constructors add four to every palette index. Both their descriptor dimensions and remapped buffers are checked against original execution.
BUTTON, STOP, MOUSEPTR and WAITPTR are additional raw interface files. Their preview dimensions follow their file lengths and image layout; no reference from the game executable has been found. The two Gravis controller guides are ordinary 640 × 480 PCX files with embedded palettes.
Reconstruction checks
The C++ renderer matches 1,824 original billboard frames (140,083,200 indexed pixels), covering every active image, camera roll and clipping. Ground-explosion textures retain their original 64 × 32 aspect ratio. The state comparison exercises 4,635 operations and matches 411 snapshots, including replacement after both circular buffers fill. Smoke creation consumes one value from the game random stream only when its position is inside the world.
The combined scene comparison adds 6,144 smoke and explosion records to 96 terrain/cloud/sky views. All 7,372,800 pixels match, including effects on either side of the cloud-height limits. Smoke and explosions are hidden inside the cloud layer and in infrared mode 2. Their drawing distance uses strict near/far depth comparisons, then the ordinary scene-distance queue.
Reports: billboards, state updates, assembled scenes. The same checks have sanitizer runs alongside them.
Craters and splashes
Craters are model instances, separate from the bitmap explosions. The engine keeps two circular pools of 128 entries. The long-life pool holds ordinary craters; the short-life pool holds cannon marks and splashes. Only splash model 0x180 expires with time: its animation bit is set for ages 0, 1 and 2, cleared for ages 3 and 4, and the entry disappears on the fifth visual update. Cannon marks remain until overwritten.
The small-crater wrapper selects model 11, both medium and large select 257, and heave selects 385. Cannon marks use 62. Position is snapped to the original interpolated terrain height; heading comes from the shared random sequence, except for cannon marks which use the firing object's heading.
The scalable creator quantizes its radius in units of 256. Below its size threshold it uses an integer reciprocal; larger ordinary craters combine a capped integer scale with half-sized fractional increments. The implementation reproduces those steps exactly. The simple short/long creators leave their slot's scale fields unchanged. They start at zero in the original and survive slot reuse, whereas the scalable creator writes all three scale fields.
State checks cover 4,168 operations, including size boundaries, circular replacement and clearing/reuse. Drawing checks match 932 crater/splash images and 81 combined scene frames. The remaining comparison cases encounter the original missing-normal reads already documented for models. Reports: crater state, crater frames, combined scenes.
Detached fragments
fragment_3d_object_ (0048fa58) creates one piece for the object and then visits its attached children in order. Each piece holds its own world pose. The parent fragment retains no attached children, so a broken rotor or other part is not drawn twice.
The pool has 64 entries and replaces them in a ring. Eight calls to the engine's linear-congruential generator choose a normalized rotation axis, signed angular velocity, normalized upward-biased movement direction and speed. Each visual update rotates the piece, subtracts 8192 × delta_time from vertical speed and advances its position using the new speed. Angles and integration use the original rounded Q31 products.
On reaching terrain, the piece stops rotating and moving and rests at ground height. At zero elevation, the terrain-material query distinguishes land from water. Surface values 1, 2 and 3 remove the fragment and create a splash if its position is within the main map. Other surfaces retain the resting fragment until its slot is reused. Material lookup checks normal features first, then the super-feature layer. It returns the first enclosing authored polygon's surface code.
The normalization helper (00474f50) halves all three components if any exceeds the signed quarter-range. It calculates a double-precision Euclidean length, truncates it and adds one before dividing the components into Q31. Arbitrary-axis rotation (00475070) uses a rounded fixed-point Rodrigues matrix. These details are checked independently before testing their use in debris.
The C++ comparison covers 3,268 operations and 792 state snapshots, including 36,638 fragment records, 3,180 splash records and 18,023 resting-piece records. An additional 10,162 vector/rotation cases and 44,384 surface queries agree exactly. Integrated drawing matches 83 complete scenes; 13 further cases encounter previously identified missing normals in the original models. Reports: fragment state, fragment scenes, vector math, surface queries.