Apache Longbow Research archive

Weapons, guidance and damage

Open the weapons viewer.

initialise_weapon_data_, at 0x00451ee0, fills 55 records of 104 bytes at 0x00665a70. Names have only 54 entries. Definition 54 has data but no allocated name slot; several others use the literal placeholder !!Erf!!. The viewer preserves their IDs instead of assigning names by appearance.

Distances use 256 world units per foot. Speeds are world units per second, acceleration is world units per second squared, motor times use the 200 Hz game clock, and angles use one unsigned 32-bit turn.

Definition layout

Offset Type Meaning
00 i32 Movement class: 1 unguided, 2 guided, 3 ballistic, 4 simple
04 i32 Guidance: 0 none, 1 laser, 2 radar, 3 infrared
08 u8 Firing flags, including burst handling
09 u8 Alternate penetration
0a u8 Burst size
0b u8 Weapon role used by selection/damage logic
0c u8 Damage
0d u8 Decoy probability denominator
0e 2 bytes Unresolved padding/fields
10 i32 Motor acceleration
14 i32 Motor duration in ticks
18 i32 Pause between bursts
1c i32 Distance before guidance begins
20 i32 Blast radius
24,28,2c i32 Minimum pitch, maximum pitch, heading limit
30 i32 Penetration
34,38 i32 Minimum and maximum targeting range
3c,40,44 i32 Minimum, maximum and launch speed
48 i32 Projectile model ID
4c i32 Target-box half-size
50 i32 Base fire interval
54 i32 Lateral acceleration
58 i32 Tracking-angle limit for moving rounds; impact dispersion for class 3
5c i32 Unresolved field
60 double Drag coefficient

Actor metadata has five weapon slots. Its last word, at offset 0x38 in the 60-byte actor record, selects the main weapon slot. The catalogue links those references to models.

Motion and expiry

set_round_velocity_ at 0x0044c408 applies acceleration while the motor flag is set. If age is strictly greater than motor duration, it clears that flag and removes the flame, but still applies acceleration in that update. Speed is capped at the weapon's maximum before drag is subtracted:

speed -= density(altitude) × drag × speed² × frame_time

The result is truncated to an integer and clamped at zero. Density is selected from a 71-entry table at 1,000 ft intervals. Sea level and below use 1; at and above 70,000 ft the last table entry is used. Guided turning has a separate lateral-acceleration limit. Powered movement, gravity and guidance are separate flags/paths; there is no perpetual cruise-speed floor in this velocity routine.

died_of_old_age_ at 0x0044e9f8 expires a round if it falls below minimum speed with the motor off, or travels more than 1.5 × maximum range. Collision code also checks terrain, map bounds, mobile objects and ground objects.

The two Hellfires have a five-second motor, 280 ft/s² acceleration and a 1,300 ft/s speed cap. The declared range is 18,000 ft for AGM-114A and 27,000 ft for AGM-114B. Both Hydra types have a three-second motor, 1,100 ft/s² acceleration and a 3,280 ft/s cap. Stinger has a three-second motor, 1,050 ft/s² acceleration and a 3,000 ft/s cap. Launch speed and the launcher's motion also affect the initial state.

Class 3 uses a different mechanism: the constructor chooses an impact position with random dispersion, then schedules damage at a future game-clock time. The delay is approximate range × 200 / maximum speed, plus a random 0–999 ticks. check_balistic_round_ applies that damage when the deadline arrives. These rounds do not run the ordinary movement, drag or collision loop.

Definition 20, Tank Gun, contains a signed maximum targeting range of −41,856 world units. The initializer explicitly writes 0xffff5c80 at 0x006662c8. Its class-3 impact scheduler does not read that maximum-range field. The battle-cell trace confirms the separate tank-versus-tank path bypasses that envelope: all three tested mission-1 tank models acquire a target, launch and apply their scheduled impact.

Seeker behaviour

check_round_target_lock_ at 0x0044d310 drops a destroyed target. It stores a selected target, lock bit and retry/decoy timeout separately.

The mission's weather values are Clear, Overcast, Light Fog and Thick Fog. The laser launch-solution test rejects both fog values, while the in-flight laser seeker explicitly rejects only Light Fog. This asymmetry is present in the original branches.

An enemy-launched missile tracking a target below 200 ft also performs a periodic distraction check. The native tests isolate that branch from actual chaff and flare deployment. A successful check temporarily clears lock; an unsuccessful one sets a one-second retry time.

calculate_guided_round_move_ at 0x0044c6f8 first flies straight until the arming/guidance distance. Thereafter, loss of an adjusted target pitches the round down. With a target, the routine predicts an intercept and caps its heading/pitch change using lateral acceleration and current speed. A low missile approaching a distant low target raises its aiming point to 100 ft. Tracking-angle failure can make it unguided; a target passing far behind can permanently break lock or trigger a nearby detonation.

Damage

calculate_damage_ at 0x00449ef8 starts with the larger of the weapon's blast radius and the current damage-radius override. Beyond twice that radius, damage is zero. Base damage is the larger of the definition byte and the current damage override.

Object-header flag 0x02 at offset 0xb8 chooses the alternate penetration byte; otherwise the main penetration word is used. Armour at object-header offset 0xa4 is subtracted from penetration. Beyond half the radius, damage is halved and effective penetration decreases by one. Beyond the full radius, the same reduction happens again. A penetration deficit reduces damage further by shifting right.

Role 2 weapons randomise damage before those reductions. Enemy force settings apply a damage modifier. Certain multiplayer modes instead use their own fixed random damage scheme. The C++ comparison covers all seven game-mode values, both allegiances, five difficulty multipliers and controlled random samples.

Fire timing uses the base interval, switching to the burst pause at a burst boundary. Force settings modify burst size and interval. The actual interval is then randomised between the selected interval and just under 1.5 times that value.

C++ reconstruction

The reconstruction contains all 55 typed weapon definitions and the original atmosphere and difficulty tables. Acceleration, drag, expiry, blast/armour damage, AI weapon selection, firing permissions and randomized acquisition/burst timing have original-execution comparisons. The full sweep matches 67,142 output fields, separately checks every definition field and passes under AddressSanitizer and UndefinedBehaviorSanitizer. Guidance, target acquisition, collision and damage are connected in the completed mission runtime. The later sections give their combined comparisons. Reports: normal and sanitizers.

Original execution checks

Numeric checks compare 8,640 speed cases, 2,160 lifetime cases and 6,912 damage cases against original execution. Seeker checks cover 49 cases, including exact designation-range limits, destroyed targets, weather, clouds, decoys, cooldowns and separate launch-site/missile line-of-sight checks.

Five integrated launch cases each repeat identically from fresh mission initialization. They exercise original construction, seekers, movement, collisions, damage and effects. The training target is held stationary, 2,000 ft ahead and 300 ft below the launch point; AI and player physics are not advanced during these cases.

Case Time to removal Result
Laser Hellfire 3.7 s Target destroyed
Radar Hellfire 3.7 s Target destroyed
Hydra 1.9 s Ground impact; target survives
Radar Hellfire, target marked destroyed after 1 s 3.1 s Guidance lost; ground impact short of target
Tank Gun 1.3 s Scheduled scattered impact; target survives

The weapon viewer includes trajectories and event times from these runs. Its separate fixed-altitude speed plots isolate acceleration and drag. The AI analysis covers weapon selection, acquisition, fire gates and battle-cell launches. The avionics journeys cover player selection and lock indications.

Collision volumes

Ground scenery uses authored model radii and heights. Point queries inspect only the current 64-by-64 sector cell, first the base scenery and then mission-specific additions. Mobile point queries use an approximate three-dimensional radius. A helicopter collision can then recurse through the object's attachment tree, testing the point against each rotated bounding box. Box faces are excluded in that recursive test, while the separate carrier/deck test includes them.

Line queries scan the rectangle of cells between the endpoints. They compare perpendicular distance to each object's centre and retain the smallest distance, with first-encountered ordering breaking ties. They do not select the earliest intersection along the segment. Craters use the long-lived crater buffer, with terrain-height and model-radius checks; the visual scaling of a crater does not enlarge its collision radius.

The perpendicular-distance calculation normalizes both vectors in fixed point, rejects points behind the start or beyond the end, and uses the original arccosine and sine tables. The intercept helper makes repeated travel-time estimates from the target's position, direction and speed. Its overflow guard returns the unadjusted target position.

The C++ checks cover 6,000 perpendicular-distance cases, 6,000 intercept calculations, and 24,000 cases each for scenery points, scenery lines, mobile points, mobile lines, recursive boxes, crater points and crater lines. They use all four warzones and include extra scenery, moving objects, boundary points and ignored objects. Sanitizer report.

Seeker reconstruction

The C++ laser, radar and infrared seeker checks now match 12,000 original cases, including exact timer/range/cloud boundaries and the order of visibility and random queries. Another 24,192 cases verify decoy probabilities across difficulty and multiplayer modes, including both random-state words. Both suites pass under AddressSanitizer and UndefinedBehaviorSanitizer. Reports: normal, sanitizers.

Projectile movement reconstruction

The C++ movement routines match 12,000 original cases across unguided, guided and simple projectiles. The checks cover arming distance, three-pass lead prediction, low-altitude aim correction, tracking windows, lateral acceleration, lost locks, gravity and 131 proximity detonations. Another 6,000 cases verify closest approach, and 12,000 terrain queries include 5,365 impacts. All 431,607 defined fields match in normal and sanitizer builds. Reports: normal, sanitizers.

The original closest-point helper projects in the start-minus-end direction. When its estimated closest point falls inside a segment, the resulting coordinate lies behind the start rather than along the missile's movement. The proximity explosion uses that coordinate. This is deterministic arithmetic in the original and is retained in the reconstruction. Signed angle windows also wrap for weapons with broad tracking limits, so the original ordered comparisons matter.

Ground interception first tests the endpoint. If that endpoint is below terrain, the engine divides the movement into eight steps, finds the first below-ground step and refines that interval twice more. It does not check an intervening ridge when the endpoint is above ground. The resulting bounded displacement retains its horizontal components and sets its vertical component to zero; the refined impact position is passed separately to the damage path.

Launch, lifetime and live-world queries

projectile_launch reconstructs all four movement classes. Visible rounds inherit source speed, enforce the launch-speed minimum, correct launch roll and attach a flame model when appropriate. Shells scatter their impact point using the original random sequence and schedule a future terrain impact. Weapon 28 uses cosine for both dispersion axes. The shell deadline is calculated from the slot's old launch timestamp before the caller writes the current time; later-fired shells can therefore be scheduled in the past. That defined calculation is retained.

The 256-slot round pool updates in slot order. Guided rounds resolve their target, send an incoming-missile notification when armed, move and test collisions. Collision priority is map edge, mobile object, scenery, lifetime and terrain. Proximity damage occurs before those checks. Powered survivors emit smoke, except weapon 29. Smoke density controls a power-of-two subdivision of the trail; simple rounds halve the density exponent. Lifetimes consume one random value per puff before the smoke allocator consumes its own.

Normal and sanitizer comparisons cover 11,999 construction cases (418,385 fields), 4,800 continuous updates and 1,200 launches. The continuous runs match 5,782 round snapshots, 370,922 smoke records and all 1,176 removals. They use real terrain, models, scenery and collision queries; guidance kinematics are supplied and damage calls are recorded. A separate capacity check fills all slots, rejects excess launches and checks reuse. Failed allocations do not change the C++ active count, correcting the original erroneous increment.

Live target descriptions now match 16,000 original cases, plus 4,000 equivalence and 4,000 sector-range cases. Ground targets use their cached horizontal position at zero height; actors and projectiles use their live transform. Classification is cached. Player speed uses the original airspeed * 432 conversion. Radar emissions, laser designation and chaff/flare activity follow the original actor fields. Countermeasure activity uses the decoy weapon's fire interval plus 1,000 ticks, rather than its motor lifetime.

Reports: construction, continuous pool, capacity, target descriptions. Their -sanitize counterparts also pass. The following sections cover the subsequent live-guidance, damage and AI-countermeasure integration.

Combined live missile tracking

The tracker now resolves launch targets and live designator targets, invokes the verified seeker with actual terrain visibility, and returns the selected or distracted target's movement state. Laser missiles follow their designator's current target unless the launch target is designated by ground forces. An adjusted target is a reference to a target record, not a fresh lookup of the designator's selection on every frame. Clearing or replacing that selection can leave the missile's old adjusted target intact until the next lock decision. Decoy references likewise retain their identity while their contents change.

The C++ implementation uses owned target records instead of raw pointers into original memory. A 960-sequence comparison covers 15,360 updates, 392 decoy-target states and 91 retained targets after selection disappears. Target identities, classification caches, lock flags, cooldowns, random values and movement properties match throughout, in normal and sanitizer builds. Normal report; sanitizer report.

Countermeasure release and warning response

A threatened aircraft estimates arrival time from approximate 3D range and missile speed. Below 1,201 clock ticks it requests chaff for radar guidance or a flare for infrared guidance. The player always receives the warning; automatic release requires the auto-chaff setting. A successful response also sets the retreat flag for 100 ticks if that flag was not already set.

AI aircraft can release only in classes 1 and 2. A request during cooldown recalculates and replaces the deadline rather than simply ignoring the request. The delay uses the difficulty-scaled fire interval, plus 100 ticks and a signed random remainder. get_drone_random_ returns the negated old random value, unlike the weapon and round RNGs, which return it unchanged. All three advance their state with the same two LCG steps.

Player release checks control ownership, cooperative key routing, ECM damage, ammunition and its own release deadline. Chaff waits the full fire interval; flares wait half. Both decrement the selected pylon and total stock unless infinite weapons are enabled. The actor's decoy-activity timestamp still uses the full interval for either type.

The original-code comparison covers 16,000 cases and 235,472 fields, including ordered launch, warning, cooperative-key and remote-notification calls. Normal and sanitizer builds agree. Normal report; sanitizer report. Projectile construction is checked separately; network and UI callbacks are observed here.

Applying damage and cockpit failures

Direct hits now use the original live-object rules around the damage formula: a horizontal integer distance for ground objects, a full 3D distance for mobiles, faction checks, accumulated damage and each model's destruction threshold. AI-originated friendly fire is suppressed unless the source is a network-associated actor or a mobile object that is not an actor. Model 64 receives an additional divisor against enemy fire outside game-flow state 7. Destroyed objects update the shared blast-damage and radius overrides before the surrounding explosion proceeds.

Player subsystem damage uses a separate random seed. Once new damage exceeds ten points, it makes difference / 10 + 1 random selections. Below three quarters of the model's resistance, selections 16 through 19 are ignored. At higher damage those can set engine fires, helmet-display failure or gun failure. No more than three MFDs can be broken by this routine. Repeated faults do not repeat their message or sound. The engine retains separate failure bits for the two FLIR-labelled entries.

The direct-damage and player-system checks cover 24,000 cases and 220,224 fields, including 2,669 destruction decisions and 12,287 feedback events. Kill/effect/log handlers are observed boundaries; player fault selection itself runs fully. Normal report; sanitizer report.

Blast-area iterators visit sectors in X/Y order, base scenery before mission scenery within each sector, and mobiles in their sector-list order. Membership uses strict approximate-distance comparisons. Ground searches reject an explosion higher than its search radius. The comparison covers 20,000 searches, 367,137 ground-object results and 101,119 mobile results across all four maps. Normal range report; sanitizer report.

Complete blast propagation

tools/check_cpp_blasts.py executes the original direct-hit wrappers and area-detonation routine over all four maps, with additional clustered mission scenery and mobile actors. The C++ comparison matches 640 calls, 1,215 destroyed scenery objects and 619 destroyed mobiles. State hashes, both combat random generators, the global random seed and callback order all agree in normal and sanitizer builds. Reports are in work/checks/cpp/blasts/ and blasts-sanitize/.

A free detonation creates its weapon effect before searching mobile objects at five times the weapon radius, then scenery at twice the radius. A direct hit damages its target first, searches scenery next and mobiles last. Its area radius starts at twice the weapon radius and can expand to twice the destroyed target's model explosion radius. Scenery has already changed to its crater replacement by that lookup. A surviving direct target receives a partial explosion only when the applied damage exceeds ten. The direct target is excluded from the subsequent area search.

The damage accumulator persists through a single blast: destroying an object can increase the damage/radius used for later objects. Search and callback order therefore affect the result. These checks run the real searches and arithmetic; kill handlers apply model/flag changes, while visual effects, scoring and network notifications are recorded at their call boundaries.

Combat effects and positional sound

tools/check_cpp_combat_effects.py compares the complete combat effect constructors against the original, using real terrain materials and model bounds. Both normal and sanitizer builds match 3,600 creation operations, 1,060 updates, 6,188 random placement checks and 2,400 volume/pan calculations. The snapshots contain 926,452 active crater/explosion records. Only the final audio playback call is intercepted. Reports are in work/checks/cpp/combat-effects/ and combat-effects-sanitize/.

The ground/air split is 5,120 world units (20 feet) above terrain. A weapon ground impact can add a crater using half its blast radius. Object explosions use a central burst capped at 38,400 units, then (radius / 38400 + 1)^2 smaller bursts for radii at or above that size. Smaller bursts use half the full radius, capped at 28,800 units. Their placement consumes the weapon RNG; crater heading consumes the shared global RNG between those samples. Model-based placement clips to the bounds, computes an approximate horizontal radius and rotates around the object's heading. Zero-radius cannon impacts still create the short-lived cannon mark.

Material 0 chooses the small or medium crater; the nominal large-crater function uses the same model as medium. Materials 1 to 3 create a splash. Materials 4 to 7 use the heave crater, except zero-radius hits which retain the cannon mark. Ground-object destruction switches to the authored replacement model before generating its cluster. Ground headings are passed as their raw 16-bit value, without the shift used by scenery rendering.

The mobile wrappers calculate forward velocity, but create_explosion_ discards it and uses a zero velocity for air sprites. Volume is based on approximate distance divided by 1.5 times the explosion radius, clamped to 10 through 63. Stereo panning follows the camera heading, folds the rear hemisphere, and clamps its angular displacement to 0x1f49f4ac.

The original random placement code can divide by zero for a degenerate model radius or height. Those cases are excluded from native comparisons; C++ uses a zero remainder. It does not reproduce the arithmetic fault.

Firing geometry and bomb release

The direct-fire routine uses horizontal approximate range and the weapon's angular envelope. Air-role weapons reject targets at or below 50 feet world altitude. Dual-role weapons below 50 feet reject targets at night or below 30 feet. These checks use world altitude, not clearance above terrain. The launch point is raised to at least 80 percent of the source model's height. Unguided fire leads the target with one to three intercept iterations, selected from launch altitude.

Visibility failure or an aim point outside the world returns a distinct lost-target result. For a fixed forward guided weapon, the direction calculation starts at a virtual origin advanced by its guidance-start distance. Once the angular test succeeds, the original replaces only the forward row of the launch matrix with the source direction. Heading and pitch allowances include the tracking angle, reduced to 70 percent for fixed-forward weapons.

Moving targets of class-3 shells receive a lead offset calculated from approximate three-dimensional range, shell maximum speed, target speed and target direction. The arithmetic uses the original wrapped integer intermediate products. Laser-guided bombs rotate the aircraft attitude down by 70 degrees and require both heading and pitch error within 40 percent of the bomb's tracking angle.

tools/check_cpp_firing.py compares all 55 angle envelopes, 6,000 direct aim cases, 2,000 ballistic leads and 2,000 laser-bomb release cases. All 53,831 fields agree in normal and sanitizer builds. The cases include 826 accepted direct shots and 40 bomb releases. Target properties and visibility are supplied inputs; real model metadata and trigonometry execute unchanged. Reports: normal and sanitizers.

Weapons in running missions

The mission weapon layer now connects actor and static-defence firing to the projectile pool, live target properties, guidance and countermeasures. Decoy references remain attached to their owning actor, so releasing or destroying a flare or chaff cloud updates the missiles following it. A missile that overshoots beyond its recovery envelope clears both its seeker state and its live guidance reference. Projectile removal releases the firing actor's outstanding-shot reference.

tools/check_cpp_mission_weapons.py runs eight continuous traces across all four warzones. All 7,843,193 fields match over 9,600 updates, with 3,882 launches, 5,537 incoming-missile notifications, 746 mobile hits, 461 scenery hits, 111 proximity detonations, 1,141 scheduled ballistic impacts and 19,095 active smoke records. Both builds produce SHA-256 80d831f07cabb3e19651a47bc915799cd20daeb00f86cb9f110c8a014b67ea5a. Reports: normal, sanitizers. Impact damage is observed without applying it in these traces; the original and C++ damage routines are verified separately.

Damage in running missions

tools/check_cpp_mission_battle.py runs the same mission loop with damage enabled. Projectile hits now change the shared actor and scenery state used by navigation, targeting and collision. Destruction removes actors from combat sectors, stops affected formations, replaces damaged scenery models, creates explosion clusters and craters, and requests positional sound and radio feedback. The weapon and war generators share the original global seed through nested damage/effect calls.

All eight traces match 7,629,707 fields across 9,600 updates: 3,438 launches, 74 combat casualties, 550 direct mobile hits, 64 direct scenery hits, 117 proximity detonations and 1,050 scheduled shell impacts. State snapshots include 89,652 actors, 16,131 smoke records and 29,582 explosion/crater records. Both normal and sanitizer builds produce SHA-256 f82d176e4fcc6fffdb21c6b6d851159fb4a74d5a5aab8a0356d6f586aa7075c2. Reports: normal, sanitizers.

Debrief-record callbacks are observed without modifying campaign accounting. These traces have no player-controlled aircraft; player flight, damage feedback and carrier interaction are being connected separately.

Player damage in a running mission

The C++ mission runtime now connects the verified subsystem-failure routine to the player's real flight model. Damage updates the same fault mask used by engines, controls and instruments, and forwards the original messages, radar toggles and impact sounds. Lethal hits use the normal player death request. Eight original-execution comparisons combine this with AI movement, weapons, collisions and effects for 9,600 updates; all 9,399,339 fields agree in normal and sanitizer builds. See the combined report and sanitizer report.

Player launch and warning integration

Player weapon control has now been reconstructed and checked against the original executable. Enter arms or selects the next available weapon; Ctrl+Enter safes it. Space while safe arms without launching, and firing while landed safes the weapons. Weapon cycling uses different orders for ground radar, air radar and neither. The gun has 1,200 rounds; each Hellfire rack holds four, each Hydra pod 19 and each Stinger pair two. Firing alternates paired stations. A depleted Hellfire is removed from its actual rack attachment.

The gun follows the optical/helmet direction or leads the selected target. Its mount and barrel have independent angular limits. Hydra elevation follows the selected target's centre height, or the manual elevation input, and is limited from ten degrees down to level. Launch transforms come from the live attachment tree, including each remaining Hellfire's own position. Guidance keeps a reference to the player's current laser-designated target.

Firing-solution indications distinguish safe, no target, too far, too near, obstruction, incompatible infrared target and excessive launch angle. Infrared and laser weapons have different fog and cloud rules. The impact-time display holds up to eight missiles and estimates arrival using motor duration and maximum speed. It removes entries when the corresponding missile is destroyed.

The chain gun's flash is the authored child model specified by the barrel metadata at header +0xb0. A firing-frame transition attaches or removes it and starts or stops the gun sound. Incoming missile warnings collect three entries and display at most two. The original shifts messages and missile references when inserting a nearer threat but does not shift the associated priority values. The reconstruction preserves this ordering, including equal arrival times and changing warning counts. These are valid operations within the allocated arrays.

Local C++ comparisons cover 12,000 weapon-control cases (679,852 fields), 6,000 mount/launch-transform cases (768,000 fields), 8,000 firing-solution cases plus 2,000 impact-queue operations (26,039 fields), and 1,500 incoming-warning frames (139,352 fields). The connected mission suite covers 3,200 updates across four warzones and both flight models, including 1,326 player-launched rounds. Reports are under work/checks/cpp/player-weapons, player-mounts, player-sight, player-warnings and player-launches.