Terrain, scenery and mission resources
The map and scenery browser opens on the game’s regional terrain, with separate Terrain and Lighting selectors. It also exposes all 39 stored grids, with the additional grids labelled as archived inspection data. These include retained variants; the original runtime selects regional geometry separately from its weather configuration, as described in mission world selection. Click a map cell to inspect its tile, assembled model parts and original authoring metadata. The right-hand view renders a configurable neighbourhood; drag to orbit and use the wheel to zoom. It also exports the selected neighbourhood as OBJ and can overlay the decoded rectangular collision tests.
Selecting a mission chooses its regional terrain, lighting configuration and start position, then overlays its player/enemy waypoint routes, mission targets and flak rectangles. Individual routes can be hidden. The complete mission script and waypoint commands stay beside the map: route drawing does not claim to execute enemy AI or mission conditions.
Finding missions and briefings
The map and waypoint browsers now group missions by Training, Flashpoints, Tour of Duty, Arcade and Simulator. Subgroups identify each Flashpoint location, aircraft/squadron tour and simulator location. The leading number is the position in that script sequence; Mission 501, for example, remains the original resource number. Simulator entries are weather alternatives rather than successive campaign stages.
The metadata links 128 missions to literal mission assignments or numeric launches in the retail parser. The other 73 retained scripts remain separately labelled; absence from this graph does not prove they are unused. A further 26 compiled waypoint programs have no linked mission script. Shared programs retain their individual mission/environment choices.
Search includes mission numbers, places, aircraft and briefing text. The mission panel shows available original briefings/translations, nearest pre-mission text, opening radio messages and links to the mission source, campaign statement and precise presentation clip. There are 83 missions with linked briefing or pre-mission text; these excerpts are not invented mission titles. The metadata retains source paths, hashes and association evidence; map_missions.py regenerates it.
The PC Plus graph references 28 missions, of which 20 have a conservative branch path from its entry: five training missions and fifteen Yugoslav Flashpoints. Its eight Atlantic entries remain in a function without such an entry path. The demo filter uses the linked flow; retained demo references are labelled on their mission cards. This reachability check does not simulate successful gameplay. Demo training order is recorded separately: retail hop 4 (504) is the demo's second hop.
Weather-named grids are not the terrain loaded by retail missions. In particular, the old preview’s floating motorway slopes at yugos_n cell (137,113) came from retained definitions that refer to mountain models. The playable yugoslav grid uses flat motorway variants there. See the selection evidence.
Grid layout and coordinates
Each .lev and .le2 file contains 40,000 bytes. Combine the two bytes at each position:
tile identifier = LEV byte | (LE2 byte << 8)
source position = X * 200 + (199 - Z)
world cell centre = ((X * 2 + 1) * 1024, (Z * 2 + 1) * 1024)
The engine's loader at executable file offset 0xb0c39 uses column-major storage and a reversed Z row. World positions are converted to cells with a shift by 11, giving 2,048 units per cell. Source rows cannot simply be interpreted as conventional row-major X/Z positions. The browser normalises them to rows of increasing native Z; it does not assign a geographical north label to that display.
Mission 830 provides a useful independent cross-check. Its native start, 125952,landed,48128, is cell (61,23) with half-cell offsets: the corrected grid selects the 18_rway1 runway assembly. Targets (112,57), (133,64), (167,106), (158,113) and (113,131) select factory assemblies; (125,68) selects an oil plant; (140,76) and (103,134) select military installations.
A .dat terrain table begins with a little-endian 16-bit tile count. Each terrain tag contains a 16-bit rotation, a 16-bit variant count, then that many NUL-terminated SSD names. These names are alternatives, not components to draw together. The native installer at 0xb0708 obtains a random value at 0xb07a1, reduces it modulo the variant count at 0xb07ae, then instantiates exactly one SSD at 0xb07df. Individual SSDs can themselves contain multiple model parts. Rotations use the engine's full-circle 16-bit angle convention. The .asc authoring counterpart records the artist's directory path, rotation and additional nine-value metadata. Paths such as n:\3dshapes\fws.pc\sh\missions\id\rwy\mil\36\ distinguish authored runway, city, forest, mountain and industrial groups.
Terrain heading is reduced to one of four quarter turns by the loader. The placement callbacks at 0xb481c, 0xb4838, 0xb485c and 0xb4888 map local (X,Z) to (X,Z), (Z,-X), (-X,-Z) and (-Z,X), respectively, and supply the same heading to the model renderer. Positive heading therefore turns +X towards -Z. The map viewer now applies these exact transforms to geometry, part anchors and collision boxes. Its previous opposite turn direction left 90°/270° tiles facing 180° away from their authored placement; rotating every tile by another half turn would incorrectly change the already-correct 0°/180° tiles. All 32 native placement captures match the shared browser transform in native-checks.json.
The browser records these choices as variants, and previews one alternative per cell. Selection is repeatable across clicks and neighbourhood-radius changes: it uses the native generator’s reset seed 0x31415926 and the loader’s row-major, reversed-Z visitation order. This is an inspection choice; the full game’s SSD initialisers can consume additional random values, so it is not asserted to reconstruct every live installation exactly. 64 native generator/modulo captures match the selection arithmetic in native-checks.json.
The original schematic is a sprite whose four contiguous pixel planes are interleaved during extraction. Its stored image Y runs opposite native Z; the map browser reverses its rows so scenery, mission markers and routes share the same X/Z coordinates. On the Yugoslav grid, 5,946 of 5,947 cells tagged with the sole sea variant fall in the schematic’s water region after this correction, versus only 1,162 before the row reversal. The schematic is an authored overview, not a pixel-exact terrain classification.
The environment .txt files select palettes, altitude transitions and related settings. The browser offers their literal contents and chooses an initial palette from those references, with a regional fallback. The full runtime palette transitions are not executed by the inspection viewer.
Two retained grids reference values outside their associated tile table: colum_d and somal_st. Those values remain highlighted in magenta for raw inspection. Original-code captures now establish that retail missions select the valid regional columbia and somalia grids through world, while those type names choose lighting/weather settings. There is no need to invent a tile remapping or infer a retail mission defect; see mission world selection.
Night city and runway lights
The amber night lights are authored inside the terrain models, rather than added by a separate SSD effect actor or a dynamic light source. The environment parser sets bit 0x0080 in the shape status word at code-object offset 0x47c82 when the configuration contains night. Model opcode 63 tests that mask and enables additional fixed screen-dot commands. Twilight configurations explicitly request night too. All 33 decoded native environment captures agree with this flag; the capture tool previously read the same offset in the wrong object and incorrectly reported every environment as daytime.
For example, near-detail city1a, city3a and city5a enable 50, 48 and 50 light dots respectively; runway marking mr_mrk18 enables 100. City colours use the original warm palette entries 16–25, from pale yellow through amber to brown. Opcodes 51/52 draw one-pixel and two-by-two-pixel marks at authored positions, commonly along the ground-level road layout. They do not illuminate nearby polygons. These commands differ from the world-sized smoke/fire circles.
The map viewer now places the lights with their scenery parts, anchors and tile rotations, and enables Night lights automatically for night/twilight environments. The checkbox permits comparisons; palette selection remains independent. Fixed dots scale with the preview's equivalent 320 × 200 pixel size. Buildings and hills occlude them through the depth buffer, with a small depth offset to keep ground-level dots from flickering against coplanar terrain. The inspection viewer still omits the game's distance fades and full runtime palette transitions.
Scenery/entity definitions
An .ssd is a supershape definition: it associates models with parts and contains initialisation, drawing, damage and state scripts. The authoring name table contains 921 quoted names. The archive has 704 structural/named SSD candidates; 703 have valid headers and entry tables. One candidate under an unknown path fails entry-point validation and remains flagged in the decode report.
The 10-byte header is:
| Offset | Size | Meaning |
|---|---|---|
| 0 | 1 | Bytes of custom actor storage |
| 1 | 1 | Number of model parts |
| 2 | 2 | Native range/scale-related field; exact interpretation still being traced |
| 4 | 2 | Definition flags |
| 6 | 2 | Cached runtime handle; normally 0xffff on disk |
| 8 | 1 | Entry-point count |
| 9 | 1 | Anchor-coordinate count |
Signed XYZ anchor triples follow, six bytes each. Then come little-endian entry-point words, each a signed displacement in words from its own address. Entry 0 initialises the object. Parts use 29 bytes each at runtime; custom storage follows them.
The decoder recovers 2,287 initial model assignments across 695 definitions with parts. Drawing entry 1 supplies the part/anchor associations used by the viewer. Multi-part city and industrial tiles therefore show their component buildings at authored positions rather than piling every model at the origin. Other entries and calls are retained as original instructions; runtime conditions and ongoing damage animations are not simulated.
Useful opcodes, established from the dispatch table at file offset 0xb537c:
| Opcode | Interpretation |
|---|---|
| 0 | Return |
| 1 / 2 | Relative jump / call, with word displacements |
| 3 | Assign a model from the name table to a part |
| 5 | Assign a model with orientation metadata |
| 6 / 8 / 46 | Draw a part at an authored anchor, through different native paths |
| 74 | Draw an anchored part, with an optional damage overlay at the same anchor |
| 7 | Draw a part at the object origin |
| 10 / 11 | Set a run of custom actor words / one custom word |
| 12 / 13 | Rectangular collision test, with different responses |
| 20–27 | Install per-part state/animation programs |
| 47 / 48 | Alternate native collision-test path; primitive interpretation still under study |
Assembled scenery uses one common world scale, including part anchors and collision coordinates. Shape camera multipliers are inverted against world scale 1, rather than retaining each shape's standalone inspection scale. For example, mr_rwy multiplies camera translation by 10: its raw 800 × 4,000 coordinate span therefore represents an 80 × 400 world-unit runway segment. The four detailed runway pieces at yugos_n cell (61,23) span Z = −800…800 inside their 2,048-unit tile. Previously their independent inspection scales enlarged the details tenfold, extending them into neighbouring mountains. The same correction applies to other scenery shapes using camera multipliers and to assembled-object previews and exports.
SSD drawing opcode 74 draws its first part at the second operand's anchor, then may draw the third operand's overlay part when the first is damaged. The initial assembly retains the anchored intact part. Opcode 46 also takes a part and anchor. Previously these paths lost the anchor, piling airfield buildings and runway segments at the tile origin. Native captures execute the intact 18_rway1, m_rway2 and rig_1 draw programs and verify all 33 part/anchor calls. The simpler m_rway variants used by yugos_m do not follow the detailed runway's combination of scaled components and opcode-74 attachments.
Opcode 5's XYZ-sized triple is orientation metadata, not a translation. A runway marking with (0,-32768,0) must rotate through half a turn; interpreting it as a position would move it far above the runway. The decoder retains this distinction, and uses the original component matrix generator's sine table when applying it.
Both rig_1 and searig_1 initialise custom word 0 to zero, then compare it with one through opcode 37. Following that known condition recovers their two smoke parts and two parameter streams apiece. Complete native initialiser executions match the decoded results. The assembly now has 2,291 initial parts and no unresolved initialiser warnings; see the checks.
Collision, attributes and preview scope
The entry-program walk exposes 2,131 collision-test records. These are authored tests, potentially repeated across event paths; they are not necessarily 2,131 unique solid volumes. Coordinates, source opcode and byte offset remain visible. The overlay draws the rectangular tests from opcodes 12/13 as wire boxes. Tests in the alternate collision path are retained as metadata rather than presumed to be the same primitive.
Seven .sta tables contain 250 tag words with 5×5 tag patterns related to the nine-value ASC metadata. These are likely terrain-editor stamp definitions; native use and placement rules remain unverified. See the patterns and evidence.
The assembled viewer uses decoded polygon programs and a WebGL depth buffer. Near and far planes follow the camera distance and assembled bounds; the former fixed ten-tile far plane incorrectly clipped the default view. Concave outlines use ear clipping, with an even-odd strip tessellation for non-simple outlines such as crossed bridge faces. A ground backdrop one native unit below Y = 0 fills the space under field/desert markings. It uses inspection palette index 32 and is drawn first without writing the depth buffer, preventing it from fighting distant water and flat terrain. Authored scenery retains normal depth testing. The backdrop is omitted from OBJ exports; it is a visual inspection aid, not an extracted terrain mesh or a reconstruction of the native horizon renderer. Native Y is negative upwards; the WebGL conversion reverses Y and Z together to preserve lateral handedness. It supplies original base palette colours, component rotations and placement, and draws resolved atlas textures with affine UVs and discrete palette indices, but omits native lighting/fog, view-dependent runtime actor state and procedural jitter. Unresolved runtime texture bindings remain visible as placeholders. The model VM's facing rules are disabled when collecting a neighbourhood mesh so that orbiting the camera does not leave holes from the initial viewing direction. The original renderer's conditional ordering remains a separate subject for comparison.
Coplanar overlays in the map preview
City ground patches, road markings and other overlays deliberately share planes. The original painter’s renderer resolves their overlap by drawing order; interpolated WebGL triangle depths previously produced striped z-fighting as the inspection camera rotated. The map viewer now intersects each pixel’s camera ray with a canonical face-plane equation, so coplanar faces receive identical fragment depths and retain their existing submission order. Depth remains enabled for buildings, hills and other genuinely separated surfaces. The depth buffer stores linear camera distance to preserve precision across large neighbourhoods; the small existing depth allowance for light points is converted to that same representation. Original vertices, triangulation, colours, affine texture sampling and exports are unchanged.
GPU readback checks compare actual city ground and horizontal/sloped overlay fixtures against depth-disabled painter rendering over 72 views. Sixteen additional cases check separated surfaces in both drawing orders, including a one-native-unit separation at distance. Hidden points/lines stay hidden behind a roof, while a light on its surface remains visible. These are browser regression checks, not DOS raster equivalence claims. The canvas also keeps a fixed CSS aspect ratio, avoiding the previous gradual size change caused by including its border in drawing-buffer dimensions on every rotation.
See model-format.md for the polygon interpreter and decode-report.json for extraction counts and rejected definitions.
Part animation streams
The assembled-definition browser exposes 296 initial parameter streams in 92 definitions, alongside parts, anchors, collisions and SSD instructions. Parameters can be edited per part; streams can be advanced one tick at a time or played at an inspection rate of 10 ticks/second. While playing, native vertex jitter also animates, and sized particles use their authored radii and drawing patterns. See the model parameter and effect notes for their model-specific meanings and ranges. That preview rate is not a measured native scheduling rate.
The definition browser follows the native whole-part draw queue, including each shape’s signed header depth bias. In rig_1 and searig_1, the water’s +3000 offset puts it behind the structure; the smoke’s −100 offset brings the effects forward. SSD call order alone is not the final paint order. See model depth ordering and the native queue captures.
Each runtime part begins with eight parameter bytes and has eight stream cursor words at part offset 0x0b. Stream words encode a command in the low byte and an immediate value in the high byte. The native interpreter is at file 0xb6ce0:
| Command | Effect |
|---|---|
| 0 | Set the parameter to the immediate byte, advance and end this tick |
| 1 / 2 | Increment / decrement the parameter, wrapping to a byte, then end this tick |
| 3 | If parameter equals the immediate, skip the next displacement word; otherwise take the relative jump |
| 4 | Unconditional word-relative jump |
| 5 | Stop this stream |
| 6 | Invoke an SSD event using the following word |
A jump changes the cursor by one plus the signed following displacement. Comparisons and jumps can run within the same tick until a value-changing command or stop is reached. Four representative streams, including wrapping and loops, match the native interpreter through 126 completed ticks; the comparison fixtures retain the expected values.
The synthetic jump-to-self test does not return after its sixth completed update. The earlier harness counted its unchanged parameter byte after instruction-budget exhaustion as another successful tick. The corrected capture records 126 completed native updates across the four tests and marks this non-returning case explicitly; a stable memory value alone is not proof of a completed native call. This synthetic case is not a shipped-game defect.
The preview deliberately stops event-calling streams with a visible note, because executing the event requires broader actor/game state. It uses the initial draw assembly, rather than claiming to execute conditional SSD drawing or damage branches. Within each part it applies the model facing tests and preserves the selected drawing sequence across polygons, lines and point effects. The game's definition flag 0x0100 selects updates of all eight parameter streams per part; otherwise its common update path advances selector 0. Raw definition flags and every installed stream remain visible for inspection.
Animation timing
The model's drawing program selects geometry for a supplied parameter value; it does not contain a universal frame duration. SSD parameter streams decide which values to supply. Their shared scheduling gate has now been traced through the September launcher and game executable, with isolated native captures from check_animation_timing.py.
The launcher's fast timer setup at START.EXE file 0x5591 writes PIT control 0x36 and divisor 0x5555, or 21,845. Its IRQ handler at file 0x5533 increments a 32-bit counter once per interrupt; function 0x19 at file 0x556f returns that counter's address. The game reads it through 0xa6134 and measures elapsed ticks at 0x74654. With the nominal IBM PC PIT clock of 1,193,182 Hz, this is 54.6204 timer ticks/second. The hardware clock value is also recorded in the DOSBox-X timer implementation. The music driver's separate 50 Hz sequencing value must not be substituted for this simulation clock.
At flight-loop file 0x7205c, elapsed simulation ticks accumulate in data word 0xe74c. If the total is greater than 7, the loop subtracts 7 and sets animation flag 0xac90; otherwise it clears the flag. The actor update at 0xb4f44 advances one value-changing stream command when that flag is set. The resulting nominal rate is 54.6204 / 7 = 7.8029 animation ticks/second, approximately 128 ms per tick. The first tick from an empty accumulator needs 8 timer ticks because the comparison is strict.
This is a nominal scheduling rate, rather than a guarantee that every visible change is 128 ms apart. Updates occur on displayed-frame boundaries, and the loop allows at most one animation update per frame. It retains a backlog, but clears it if the remainder exceeds 100; it does not perform multiple catch-up updates in that frame. A slow frame rate therefore slows these streams: with 12 timer ticks per frame, the captured gate produces approximately 4.55 updates/second. The player timing routine at 0x8c43c also clamps and adjusts elapsed simulation ticks, so speed-up modes and scheduling choices matter. Individual drawing states can span several parameter values; event handlers can replace streams or change the model. Actor visibility and update eligibility still require full-mission tracing.
Representative authored sequences, executed through the original parameter interpreter:
| Model and controller | Values supplied | Visible timing at the nominal rate |
|---|---|---|
fire_10, part 24 of ss/fact_1.ssd, parameter 7 |
Increment 1–12, then stop at 12 | Smoke builds over roughly 1.54 s, then holds. The stream does not loop the fire's growth or reach the viewer's extra state 13. |
samdish, part 6 of ss/defop1.ssd, parameter 0 |
Loop 0–18 | 19 ticks, approximately 2.44 s per turn. Drawing state 19 exists but is not visited by this stream. |
airaew, ss/airaew.ssd, parameter 6 |
Loop 0–36 | 37 ticks, approximately 4.74 s per turn. Values 35 and 36 draw the same last pose, giving it a two-tick hold. |
herky, entry 5 of ss/herky.ssd, parameter 1 |
Set 0, increment through 255, then request SSD event 19 | Door poses start at 0, 1, 6, 11, 16 and 21: later steps take five ticks, approximately 0.64 s. The open door holds through 245; crate departure begins at 246, about 31.5 s after the initial value. Later event behaviour is outside this isolated capture. |
ian1, parameter 0 |
Drawing states 0–19 are recovered; its active gameplay controller remains unidentified | No verified real-time rate for this particular resource. The ejection GIF uses the requested inspection rate of 10 steps/s. |
Vertex jitter is generated during drawing, but its frame seed is tick-gated. Opcode 83 advances the shared generator for each jittered vertex. The frame setup at 0xc488d also uses animation flag 0xac90: on a flagged frame it saves the current seed words; on every frame it restores those saved words before drawing. With an unchanged visible drawing order, intervening frames therefore repeat the same jitter. Flame flicker, smoke movement and parachute flutter normally advance with the nominal actor-animation tick even when the model parameter is held. Camera-dependent drawing order and other models’ consumption of the shared generator can change that appearance. The model viewer’s isolated inspection rate is still a playback convenience rather than a reconstruction of the full shared renderer. Eight native frame-setup cases verify the saved/working seed behaviour in timing-checks.json.
The checks execute 64 native IRQs, 1,120 native scheduler passes, 496 updates of authored streams and eight frame-seed resets, validating the counter, strict threshold, frame limit, wrapping, held endpoints and the captured value sequences. The numerical rates above are derived from the timer setup and those isolated handlers, not measured from a complete running mission.
The archived ssinfo.h also preserves some authoring enum labels such as CRUISEPOS, LIVES, KILL and CLOSEDOOR. Portions of that header are incomplete, so labels are supporting research material rather than an authoritative complete schema for all definitions. The game-logic notes connect these definitions to the collision and mission systems.
Compiled routes and AI sequences
The compiled-program notes and route viewer decode all 192 binary route programs, including actor groups, surface units, player points, radio text and command operands. The map's compiled-actors checkbox adds authored command paths for the selected mission. The compiled-route viewer also overlays its records directly on the corresponding terrain or schematic, with selectable actor/command markers and a link back to the selected scenery cell. These are separate from the 182 text authoring files. Eighteen CBN resources contain timed AI control inputs and SSD events, exposed in the manoeuvre viewer.
Environment palette captures show native palette bindings and distance fields for 33 configurations. Six authoring/base grid names do not have usable configurations; their native errors are retained rather than silently pairing them with a different environment.
The complete native 0xb0708 loader visits stored rows before columns when
assigning random scenery variants. The files themselves are column-major.
A complete mission 501 launch exposed this distinction: swapping the loop
order preserves the final RNG seed but assigns different variants to cells.
The research viewer now follows the actual loader order.