Apache Longbow Research archive

AI, formations and ground battles

Apache's units are called drones in the original code. They share task and route structures, but their behaviour depends on their class, orders, weapons and side. The player is also represented by a drone, with a separate flight model.

Combat update order

update_war_model_ (0x463478) updates ordinary drones, armed scenery and battle cells in that order. A drone must be alive, armed and on side 1 or 2 to enter its combat update. The ordinary task scan skips fortifications and network-controlled units.

An existing target can be fired upon each update, subject to the weapon deadline. New target acquisition is staggered by drone_serial % 4 == frame_number % 4. The work is distributed across four rendered frames, so acquisition cadence depends on the frame rate. Helicopters and aeroplanes use their task-specific acquisition routines. Tanks, vehicles, ships and trains use the air-defence path. Artillery has its own fixed-point and recon orders. Ground battle cells supply the separate tank-versus-tank path.

A destroyed target is released. Release clears the current target, the attack deadline, and flags 0x11a; fixed-point target records are returned to their pool.

Searching the war map

The war map is a 64 by 64 grid. Each cell spans 0x400000 world units, or 16,384 feet. Each side has its own optional cell record with eight air-target slots and eight ground-target slots. Counts can exceed eight; the explicit candidate list remains eight entries. Overflow removal can rescan the sector to refill it.

the_loop_ (0x463f38) converts minimum and maximum range to whole sectors. It rounds upward only when the remainder exceeds half a sector. The maximum search radius is three cells. It checks the observer's own cell first when minimum range rounds to zero. Otherwise it scans square rings, stopping at the first ring containing candidates. Each edge includes both corners, so a corner can occur twice in the candidate list. A random candidate is selected from that first occupied ring.

This is a coarse search, not a circular distance test. Exact weapon-selection limits are applied later. target_in_sector_range_ uses the same rounded sector radius independently on X and Y, without checking height.

The random air selector chooses one of the first eight contacts, then rejects it if its world Z is below 7,680 units, or 30 feet. It does not try another contact in that cell. The closest selector checks each available contact above that threshold, comparing max(abs(dx), abs(dy)) + min(abs(dx), abs(dy))/4 with integer truncation. These functions read world altitude, not height above the local terrain.

Orders and attention

The task names displayed in the browser come from _task_names. They include air interdiction, strike, escort, CAP, three bombing modes, ground vehicles, tank formations, artillery, boats and fortifications.

initialise_drone_attention_level_ (0x427840) gives ordinary mobile tasks a four-mile attention range, counter-air and air-defence tasks seven miles, and sweep/CAP tasks ten miles. Artillery, unknown and fortification tasks receive zero here. Enemy attention is multiplied by the Forces setting's range percentage; friendly attention is unchanged. Distances in this paragraph are statute miles.

Basic aircraft acquisition caps search range by both attention and available air weapons. A candidate must lie near the route and pass the original terrain line-of-sight query. Helicopters on this path accept other helicopters. A successful interception sets an attack period of 60 to just under 70 seconds. Strike and bomber orders use different route-relative target and release rules.

Strategic and tactical bombers select their bomb slot before the pass is complete. Their release test compares horizontal range with aircraft speed multiplied by integer height divided by the weapon's maximum speed. Laser-guided bombing uses its own release path. These are explicit game rules, rather than a general flight simulation for AI pilots.

Choosing and firing a weapon

The ground selector prefers the gun when both gun and secondary ammunition remain and the target is strictly inside the gun envelope. It then tries the secondary envelope. Single-weapon and bomb-run paths bypass these individual range tests. The air selector first checks the aggregate air envelope, then considers slots 4, 3 and 2, allowing lower numbered slots to replace higher ones. A gun with ammunition and a valid envelope takes precedence.

The fire gate checks a target, a nonzero weapon, the armed flag, ammunition and the selected slot's deadline. CAP cannot fire at a fixed-point target. Role-3 weapons are prohibited by actor flag 0x8000; infrared guidance is rejected in weather conditions 2 and 3. The gate itself has no additional range test.

For burst weapons, the burst size is the authored byte multiplied by the Forces percentage, truncated and clamped to at least one. Whenever remaining ammunition is a multiple of that size, the next interval uses the weapon's burst pause. Otherwise it uses the ordinary fire interval. The selected interval is multiplied by the Forces fire-interval percentage, then a random amount from zero to just under half that interval is added.

Acquisition adds a separate delay. Friendly units always use Forces level 9 for this delay, which has a zero base. Both sides add 0 to 999 ticks, just under five seconds. Enemy units use the selected Forces base. The level-5 base is 500 ticks, higher than level 4's 400; this small reversal is present in the table.

The firing report includes every Forces row and per-weapon timing. The game clock runs at 200 Hz.

Tank battles and the negative range

A battle cell links two opposing tasks and their formations. The authored attack waypoints identify the battle positions. When a formation has fewer than five sixteenths of its original units alive, it is assigned the losing end condition; the opposing formation is assigned the winning condition. Those conditions choose the previous or next waypoint. Targets are periodically released and selected again.

Tank Gun, weapon 20, has a shipped maximum-range value of −41,856 world units. Its aggregate ground envelope becomes zero. That does not disable scripted tank battles: battle_drone_aquire_target_ (0x465c7c) selects a surviving member of the opposing task directly and creates a scattered fixed-point target. make_drone_fire_ passes through the weapon and deadline checks without applying that range.

The battle check uses the M1A1, T-62 and dug-in T-62 from mission 1. All three acquire a target, fire, reduce ammunition and schedule another shot. Their original class-3 shell routines apply the impact at the target point after a calculated travel delay plus random delay. Two fresh runs agree, including every impact deadline. See the battle trace.

Evidence

The checks run the original January 1998 executable in an isolated reference harness. Controlled inputs are recorded in each report. Complete moving battles, radar coverage and task manoeuvres remain under investigation.

Moving missions

tools/capture_world_journeys.py runs missions 1, 22 and 44 for ten simulated minutes at half-second updates. Each is repeated from a fresh original-engine initialization. Full-state sequence hashes and individual launches, kills and battle endings are recorded in journeys/index.json. Mission 1 has 2,622 launches, 55 kills and four battle endings; mission 22 has 702, 49 and two; mission 44 has 1,250, 25 and two. These are repeatability references for the reconstruction, with all original AI movement and combat active.

Reconstructed ground movement

update_drones_ starts each group at its authored time, except stationary tasks. Ground vehicles normally move as a formation: the leader advances along the route, while members keep their own heading vectors and adjusted speeds. At a waypoint, the leader carries any unused movement into the next segment. Members are repositioned using the formation offsets and use the remaining fraction of the update.

Road and raised-waypoint sections switch the group to individual route following. The switch considers the destination surface and the heights of the next waypoint and two preceding waypoints. On returning to formation movement, the original pairs ascending formation offsets with its reversed linked list of members. This ordering is preserved. Trains update wagons from front to back and stop at the first destroyed wagon.

A battle waypoint of type 4 stops a formation and clears its advance/retreat flags. Retreat follows the previous waypoint; an advance follows the next. Task 21 loops back to the beginning. Ordinary routes stop at their final waypoint. The stopped formation-member distance is uninitialized in the original; the reconstruction assigns zero without emulating the stale stack value.

Eight C++ traces, spanning all four warzones, match 212,080 recorded states and 4,843,050 defined fields through 19,200 updates. They include variable frame durations, 5,594 waypoint crossings, destroyed members and retreat/advance orders. Both normal and sanitizer runs pass.

Aircraft and ship steering

drone_fly_to_ steers AI aircraft and ships independently of the player's flight model. Above the ground, heading changes follow a smoothed and limited bank angle. On the ground, heading changes directly at a limited rate. Pitch aims toward the destination, with separate lookahead checks for scenery and rising terrain. Forward movement uses the speed at the start of the update; acceleration changes the speed used next time.

Helicopters ease into larger speed changes and display a separate acceleration-dependent pitch. Near a landing waypoint, horizontal movement is bounded by the remaining displacement and descent changes from five to two-and-a-half feet per second for the final thirty feet. Climb limits are fifteen or seven-and-a-half feet per second. Carrier approaches add the carrier's speed.

Ships call the same steering routine. The shipped water-update function looks up the actor's aircraft subtype rather than its ship subtype, so ordinary ships use row 4 of the water characteristics table. The reconstruction follows that defined lookup.

The C++ steering core matches 6,000 original-execution cases covering aircraft, helicopters, ships, terrain and obstacle avoidance, attack speeds, landing bounds and 86 world-edge departures. Both normal and sanitizer runs pass. On aligned or no-pitch paths, the original can derive its turn-limitation flag from unset local variables; the reconstruction uses zero corrections. Those undefined flag bits are excluded from comparison.

Mission movement in C++

The mission movement layer now combines ground formations, trains, aircraft, helicopters and ships. It preserves task start times, formation traversal order, patrol loops, retreat randomness and carrier attachments. Eight continuous traces span all four warzones and 19,200 updates. They compare 348,486 actor states and 9,025,846 defined fields, with 5,594 waypoint crossings, 904 aircraft patrol loops and twelve carrier takeoffs. Player flight, target acquisition/firing and scenery obstruction queries are separate boundaries in these traces.

Carrier takeoff contains a cross-reference in the original: drone_take_off_from_carrier_ reads the aircraft waypoint's X coordinate but the mission planner's currently selected waypoint Y coordinate when calculating the stored route distance. The relevant loads are at 0x0042afbc and 0x0042afce. The reconstruction retains the selected planner waypoint and reproduces this calculation. This distance does not steer the aircraft on the tested routes.

The test sends resume orders without resetting aircraft initialization. Resetting an airborne helicopter after a patrol loop reaches waypoint zero makes the original dereference a missing previous waypoint. That invalid-memory path is not reproduced.

Reports: normal movement, sanitizer movement.

Complete firing dispatch in C++

The actor firing layer now joins weapon selection, aiming, launch requests, ammunition, randomized deadlines and target release. The original reselects the weapon when reading its remaining ammunition after decrementing it. Exhausting one magazine can therefore select another slot before the new deadline is stored; that deadline still uses the weapon that just fired. The reconstruction preserves this order.

Visible projectiles without weapon flag 2 wait for the actor's previous round to disappear. A failed visibility or world-bound check clears the target and attack flags. A merely blocked shot keeps the target. Laser-guided bombs use the player's designator, announce the release and remove the projectile's flame attachment. Ordinary AI guided rounds have no designator. Class-3 scheduled shells consume ammunition even if allocation fails.

The four aggregate range helpers run in their original order. The air maximum picks the highest occupied AA slot and compares it with the gun; the air minimum picks the first occupied AA slot and then compares it with the gun. The original leaves the minimum's local variable unset when only a gun remains. C++ uses that gun's minimum instead. No stale-memory value is simulated.

tools/check_cpp_actor_firing.py compares 8,000 supply changes and 8,000 complete firing calls. All 407,655 defined fields match, including 1,334 shots, 227 laser-bomb releases, 91 target releases, 4,133 weapon-slot changes and ordered launch/audio notifications. Target kinematics, visibility and projectile allocation are supplied inputs; the full selection, aiming and ammunition/timing path runs unchanged. Reports: normal and sanitizers.

Reactions, warnings and destruction

Units can turn on an attacker when their task, allegiance, ammunition and range allow it. An existing target receives task-specific priority: several aircraft orders only switch to an air attacker, while ground orders only switch to a ground attacker. Bombers keep their assigned work while bombs remain. A successful switch clears the old attack state, selects the new weapon, waits two seconds before firing and sets a two-to-twelve-second attack period. Aircraft additionally set the pursue flag.

Being fired upon can trigger a short evasive period through the war-model RNG. The chance is one in five normally and one in fifteen during retaliation. A successful retaliatory acquisition then replaces that evasion state. Friendly ships use a separate five-minute warning interval and a one-in-five chance to report incoming fire. The player can receive a ground-fire warning with a two-in-fifty chance.

Targeting warnings use the weapon RNG and separate ground-radar and bandit deadlines. Ground-radar warnings wait thirty seconds; bandit warnings wait ten. The bandit clock direction is calculated relative to the player's viewpoint, with separate samples for above and below. The original divides the unsigned bearing by one twelfth of a turn, then subtracts one before selecting the sample: the first thirty-degree sector yields no spoken direction, while the nominal twelve-o'clock sample occupies only the last few integer angle values. That defined selection is preserved.

Destroying an actor removes it from its mobile sector, marks it dead and decrements its task's survivor count. A formation leader outside individual movement mode also stops its followers. Their velocity vectors are left unchanged; their scalar speeds become zero. Repeated destruction of an actor already marked 1 has no further effect. Target release clears the attack/evasion deadline and flags.

tools/check_cpp_actor_reactions.py compares 24,000 acquisition, notification and destruction calls. All 392,727 fields agree in normal and sanitizer builds, including 468 acquisitions, 930 sound choices, 534 visibility queries and 2,064 removals. Target properties and visibility are controlled, while the original task logic, random generators, timers, weapon reselection and follower changes run normally. Reports: normal and sanitizers.

C++ target-search comparison

tools/check_cpp_target_search.py compares 19,000 cases and 140,735 fields against original execution. Normal and sanitizer builds produce SHA-256 1065f785b28dd7e8a2c173214d6e40bc0b888bc036b77d211b0d35ea6c80cbbb. Reports are in work/checks/cpp/target-search/ and target-search-sanitize/.

The sector search rounds a range to four-mile cells, rounding upward only beyond the half-cell boundary, and caps its radius at three cells. It visits the current cell first when the minimum range allows it, then each square ring in the original edge order, including duplicated corners. A random choice is made from the first occupied ring.

The air and ground contact lists hold eight references but retain an eight-bit count of all occupants. A full air list gives the player its first slot. Removal swaps in the last stored contact; overflow removal requests a sector rescan. Closest air selection filters low targets before measuring distance, while random air selection chooses once and rejects a low target without retrying. Combined air-defence selection has different preferences: closest selection favours static installations, random selection favours mobile units.

The comparison covers all ten sector selectors, insertion/removal, overflow requests, random draw counts and exact ring visitation. Target properties and overflow rebuilding are supplied inputs. Invalid sector coordinates and empty removal use bounded handling.

Task acquisition and target retention

The C++ task-targeting layer implements ordinary air acquisition, strategic and tactical bombing, laser-bomb support, ground strikes, mobile air defence, artillery and air-target retention. tools/check_cpp_actor_targeting.py compares 55,660 cases and 777,931 fields, including 14,002 target changes and 5,584 changes to the weapon RNG. The output hash is b900b4602df96bf827db388d776e59a1e7c9c479c6c9e73f5ee060001e90a587. Sector-search results, target properties and visibility are supplied; geometry, task decisions, weapon reselection and deadlines execute normally. CAP selection is a separate boundary.

Attention ranges are four, seven or ten miles according to task; several artillery and static tasks use zero. Enemy ranges are multiplied by the difficulty's range percentage. Air engagements last sixty to seventy seconds before another retention decision. Ground strikes use thirty seconds. Close targets trigger short evasive periods; accumulating more than ten damage points since the last check breaks off the attack.

Several instruction-level details affect these decisions:

Reports: work/checks/cpp/actor-targeting/report.json and actor-targeting-sanitize/report.json.

Radar-directed combat air patrol

Patrol scans visit the 64 by 64 sector grid in X-major order. Both sides must have a war-model record in a sector. Each side considers up to eight opposing aircraft there, checks its radar status and detection floor, and requires the sector's allegiance to match its own. Each global contact list stops at forty entries. Selecting a target refreshes the scan only after more than 2,000 ticks have elapsed, then chooses the nearest contact within twice the patrol's route attention range. Stopped patrol aircraft do not receive an assignment.

Patrol acquisition tries ordinary local air targets first. A radar assignment sets an intercept deadline from integer travel time multiplied by 1,000 ticks. When the target drops beneath radar coverage beyond the weapon's extended range, the patrol retains a fixed last-known position. It can recover the live target when radar or proximity permits, or abandon the search on reaching that position without recovering contact. Destroying the reporting radar can also turn a live target into a fixed position.

The acquisition routine calculates its approximate three-dimensional range with (dx, dy, dy), duplicating the Y difference instead of using altitude. The preceding nearest-contact selection uses the proper (dx, dy, dz). The C++ routines preserve these separate calculations. An empty contact list leaves the stored previous patrol assignment untouched, though the selection call itself returns no target.

tools/check_cpp_patrol.py compares 500 scans, 5,000 selections and 5,000 acquisition calls. All 531,792 fields match in normal and sanitizer builds, including 13,239 contacts, 200 full lists, 271 last-known positions and 609 recovered contacts. Radar properties and local acquisition are controlled inputs. Output SHA-256: 8caf507ce2b8558b5d4cd8b20af8a649c52d774b6501211a2125c917dee158b7. Reports: work/checks/cpp/patrol/report.json and patrol-sanitize/report.json.

Ground battles and formation outcomes

Battle cells pair an allied task with an enemy task. Their initial strengths count every leader and formation member, and each side stores the other side's attack waypoint. The survivor test uses sixteenths of the initial strength; a value below five triggers retreat. A losing formation selects its previous waypoint, while a winning formation advances to its next waypoint. Followers receive new formation positions and velocities in their original linked order.

Units acquire new battle targets once every four frames, staggered by serial number. Most choose a random surviving opponent directly. Weapon 20 instead creates a dispersed ground aim point using four draws from the actor's random generator. That generator returns the negative previous value, unlike the weapon and war-model generators. A newly selected weapon with no deadline receives a random delay of up to ten seconds before firing.

The scheduler initializes its allied-survival flag once per update, outside the loop over battle cells. An allied retreat in an earlier cell can therefore cause the enemy in a later cell to advance. This is a defined control-flow quirk and is preserved. Stopped followers' unset route distances are replaced with zero rather than reproducing uninitialized memory.

tools/check_cpp_battles.py matches 30,000 isolated cases and 890,008 fields, including target choice, random state, aiming, deadline updates and ordered callbacks. tools/check_cpp_battle_missions.py checks all 110 missions, 121 battle cells and 968 outcome transitions. All 6,600 initialization fields and 3,460,099 defined actor fields match across 139,152 states. Both suites pass with AddressSanitizer and UndefinedBehaviorSanitizer. Reports: battle logic, mission transitions, sanitized logic, sanitized transitions.

Radar coverage and the war-model grid

Each four-mile sector has a separate record for each side. It stores one radar reference, a three-by-three grid of detection floors and two contact lists. The lists retain eight aircraft and eight ground units, with an eight-bit count of all occupants. Removing a stored contact from a full list rebuilds that list from the mobile scene in its actual linked order. The player replaces the first stored air contact when the list is full.

Radar models 46 and 111 have range 10 and a 500-foot base detection floor. Models 77 and 526 have range 5 and a 50-foot floor; model 328 has range 5 and a 300-foot floor. Coverage propagates through square rings of radar patches. Each patch takes the greater of the radar's base floor, the conservative terrain height and the propagated floor from the preceding patch toward the radar. The terrain query takes the greatest of the four surrounding terrain posts. Elevated shadows can grow with distance; their scaling performs integer division before multiplication.

Overlapping radars replace a patch only when the new floor is lower. Updating any patch also replaces the sector's single radar reference, so different patch floors in the same sector can have come from different radars. Coverage queries do not automatically discard floors when a radar is destroyed; status is queried separately by consumers such as combat air patrol.

tools/check_cpp_war_grid.py matches 1,056,156 fields in normal and sanitizer builds. It covers 192 cumulative radar insertions, 2,304 radar queries, 8,000 terrain queries and 4,608 contact insertion/removal operations across all four warzones. Rescanning executes the original mobile-list traversal. Reports: normal, sanitizers. C++ clips coverage to the 192 by 192 patch grid instead of reproducing the original unchecked upper-edge writes.

Stationary defences and combat update order

Stationary SAMs and anti-aircraft guns retain a live target while it is undamaged and within their weapon's approximate three-dimensional range. Acquisition searches up to four miles, capped by the weapon range. The original staggers this by record address modulo four; the records are all aligned, so they acquire on the same frame slot. They turn toward their target even while waiting for their firing deadline. A successful shot increments the burst counter and selects the next interval using the weapon RNG and difficulty modifiers.

Only sectors near the player helicopter receive these updates, using _model_vp. The loop starts two sectors below the helicopter and stops before the upper bound two sectors above it. Its clamped upper bound is 63, also excluded, so the final map row and column are never visited by this scan.

Mobile combat acquisition is staggered by actor serial number. If a formation member's acquisition runs and finds no target, the remainder of that formation is skipped for that update. Stationary leaders, network-controlled leaders and the player have separate exclusions. Strategic and tactical bombers use a horizontal release envelope calculated as (altitude / weapon maximum speed) * aircraft speed, with integer division first. Tactical bombers clear their attack timer after a release attempt within that envelope.

tools/check_cpp_combat_update.py compares 30,500 original calls and 280,158 fields. Both normal and sanitizer builds match, including headings, RNG state, ordered callbacks, 653 static shots, 326 new static targets and 1,919 actor fire calls. Searches, target properties and the shot callback are supplied; timing, geometry, dispatch and traversal execute normally. Reports: normal, sanitizers.

Mission scenery and sector ownership

Mission files assign sector allegiance and add anti-aircraft guns, SAM sites, tanks, APCs, reconnaissance markers, laser-designation markers and protected/extra objectives. Each sector has fixed capacities for these records. The loader rejects gameplay placements above zero terrain elevation and stores coordinates at a 256-unit granularity. Unassigned sectors start with side 3.

Front-line distances start at zero wherever an interior sector borders a different side. Repeated relaxation propagates Manhattan distances through the grid. Added gun, missile, tank and APC models depend on side and warzone; each sector sorts them by descending model height, preserving insertion order on ties. Self-target lookup takes the first exact coordinate match in that sorted list. Laser markers select the nearest unmarked scenery object in their own sector, searching the static layer before the dynamic layer.

tools/check_cpp_mission_scenery.py compares all 110 missions, including 8,266 gameplay records and 8,044 dynamic scenery placements. All 22,025,840 fields match in normal and sanitizer builds. Output SHA-256: 6e51ad6250347108b4539d1c3976260da9f874a88799e32111e3c78cffcd6686. Reports: normal, sanitizers.

Mission radar startup

Radar coverage is constructed after gameplay scenery and before drones receive their starting positions. Static radar references cache the sector origin plus the packed local coordinate, without the usual factor of 256 applied to that local coordinate. Mobile radars use their first waypoint to choose sector allegiance, but their still-zero world position to build the coverage patch. Their subsequent movement does not rebuild that coverage. These are defined behaviors of the reference engine.

The war-model random generator advances twice during startup. Actor initialization then continues from the resulting global seed. Mobile lists place the leader first, then its followers in reverse formation order. Carrier-attached leaders stay out of the mobile map until takeoff; class-6 objects allocate a combat sector without entering either contact list.

The drawing program's animation flags are at model-instance offset +0x10; faction, destroyed and collision-exclusion state live separately at +0x40. The C++ model now keeps both fields throughout scene and combat integration.

tools/check_cpp_mission_radar.py compares all 110 missions. All 1,839,213 fields match, including 57,166 sector records, 21,989 actors, coverage sources/floors, ordered contact slots and RNG state. Both normal and sanitizer builds produce SHA-256 56aee2bab4390c4cf8b0e374a7485c9d388692aa6259cb3b33cd619eeae4e60a. Reports: normal, sanitizers.

Moving actors and contact lists

The live mission registry now shares the actor model objects with rendering, collisions and target lookup. A sector crossing first removes the object from its old mobile list, then updates its position, then inserts it into the new list. Contact overflow rescans run after removal while the old position is still available. Moving within a sector retains list order. Carrier takeoff inserts the aircraft; landing removes it before attaching it to the ship. Ordinary ground formations move followers in reverse formation order, while trains use ascending order.

tools/check_cpp_mission_spatial.py runs eight continuous traces across all four warzones, with real terrain and scenery collision avoidance. All 4,592,517 defined fields match across 19,200 updates, including 72,300 actor states, 83,676 combat cells, 9,295 mobile lists, 3,423 removals and 3,379 placements. The traces include 147 retreat orders, 56 kills and sixteen battle outcomes. Both builds produce SHA-256 3404fee084bf76180843b7ed44222b85b74e4ebac4b0e81c783f00f8642d99fd. Reports: normal, sanitizers. Shooting and waypoint target searches remain separate from this comparison.

Target searches in complete missions

The mission target layer resolves actor and scenery references directly against the scene and combat grid. It uses the same objects for target positions, classification, speed, allegiance, damage, radar emissions and laser designation. Actors crossing a sector boundary immediately become available to queries in their new sector. Static defence queries retain the mission's gun/SAM order.

tools/check_cpp_mission_targets.py compares 110,000 queries across every shipped mission, including 4,840 actor relocations. All 1,055,152 fields match, with 42,656 successful selections, complete target properties and exact random state. Both builds produce SHA-256 ed880b372151a9ff13346ecc1839645399abffa2647342d5cea83127968aaac1. Reports: normal, sanitizers.

Continuous mission combat

Movement, target searches, task-specific acquisition, retaliation, radar patrols, stationary defences and ground battles now run together against the same live actor and scenery records. Actor serials are local to their task and determine the four-frame acquisition schedule. Attack movement calls the weapon selector before calculating its arrival radius and interception point, so a changing target range can change the weapon even before a firing attempt.

The war-model and weapon generators retain separate random values, but both update the same global seed. Nested retaliation calls can advance both generators. Battle artillery uses the actor generator for dispersion and the war-model generator for opponent selection and deadlines.

tools/check_cpp_mission_combat.py runs eight continuous traces across all four warzones, including casualties and moving the player-relative activation point. All 6,538,521 fields match over 9,600 updates: 89,652 actor states, 233,397 actor firing calls, 17,578 static firing calls and 56 casualties. Both normal and sanitizer builds produce SHA-256 d657a929a881a584c221652f32089b4c881ce778bd33962782bfdcedc3fc06ec. Reports: normal, sanitizers. Firing is observed at the allocation boundary in this trace; projectiles, impacts and player flight are checked separately.

Parked A-10s in Cyprus quickstart

MISSDATA/QS_C.DTA gives four friendly A-10s individual routes that leave the airfield, fly a strike sortie, return, taxi and stop near the player's final waypoint. Their model is archive record 3.

The January 1998 reference engine creates these aircraft without landing gear. Its attachment trees contain the fuselage, wings 1216/1218 and eight pylon models 1101. The three wheel assemblies exist separately as a10lgr (1214), a10rgr (1217) and a10_ngr (1219), but the constructor leaves their variable attachment slots empty. The high-detail fuselage and wings have no animation-state branches to lower gear. stop_air_drone_ (0x42d7f0) stops and levels the aircraft without changing its model tree or animation flags.

Parked aircraft also continue to release countermeasures. notify_drone_missile_locked_ (0x42875c) checks the incoming missile's guidance and estimated time to arrival. It requests chaff for radar guidance and flares for infrared guidance below 1,201 ticks, about six seconds. make_drone_chaff_ (0x427fec) and its flare counterpart check aircraft classification and the release deadline. They do not check altitude, speed or whether the aircraft has landed. Laser-guided missiles do not request either decoy. The ordinary gunfire notification is a separate reaction; it does not itself release a decoy.

The focused original-execution report records all four attachment trees, executes the stop routine, and verifies radar/infrared responses from a stopped A-10 at ground level. Both releases occur in the original machine code. Run tools/check_parked_a10_native.py to repeat the check. The full sortie is not advanced by this focused test.