Flight physics
The January 1998 software engine contains separate realistic and Arcade flight models. Its surviving debug information identifies the original source units, and the isolated reference harness executes their machine instructions. The C++ versions are in simulation/realistic_flight.cpp, rotor_dynamics.cpp, flight_systems.cpp and arcade_flight.cpp.
Units and state
The world uses 256 coordinate units per foot. Realistic flight keeps position, body-axis velocity, angular rates, engine state and control state as doubles in feet and seconds. Heading, pitch and roll are radians internally; the scene uses a full turn in 32 bits and Q31 rotation matrices. The aircraft model origin is nine feet above the flight-model position.
The simulation clock counts 200 ticks per second. Some response filters run once per update, while integration terms multiply by elapsed time. Matching the update sequence therefore matters as well as matching total elapsed time. The original x87 precision mode uses a 53-bit significand. Constants and their exact stored bytes are recorded in constants.json.
Realistic controls and angular motion
Four controls represent collective, tail rotor, longitudinal cyclic and lateral cyclic. Signed fixed-point input is normalized to doubles. On a carrier, cyclic and tail input are suppressed. Collective either integrates relative input at 50 units per second or follows an absolute throttle axis, with a range of 1 to 130.
Pitch demand depends on forward speed and pitch attitude, with a separate nose-down multiplier. The pitch rate moves one quarter of the way toward demand each update; roll and tail-rotor rates move halfway. Roll authority increases with forward speed. Tail authority falls with speed and is reduced on the ground. Bank contributes a coordinated turn above 17 ft/s; angular rates are converted to Euler-angle derivatives before integration. Released controls add pitch relaxation and a damped roll-restoring term.
The model treats ground pitch separately, relaxing toward its parked attitude as torque falls. Certain ground attitudes cause a hard-impact event. On carrier departure, the original truncates carrier heading and relative heading to whole radians before adding deck velocity; the reconstruction retains that defined calculation.
Lift, drag and translation
The model calculates an altitude-density factor, a ground-effect factor within 48 feet of the surface, speed-dependent torque demand and the difference between available rotor torque and required torque. Weight, attitude, density and ground effect all affect required torque. Engine failure increases the requirement; inverted bank increases it further.
Vertical velocity approaches a steady-state term derived from forward velocity, pitch, sideways velocity, bank and excess rotor torque. Forward acceleration combines pitch, gravity, thrust, drag and yaw coupling. Sideways acceleration combines bank, drag and yaw coupling. The model caps forward speed at -100 to 395 ft/s and sideways speed at -100 to 100 ft/s. Overspeed warnings begin above 340 ft/s; sustained overspeed or speed above 394 ft/s triggers a fatal event.
Body-axis movement is transformed through the attitude matrix. Wind is then applied in world coordinates while airborne. A random gust doubles wind strength for one update and restores it on the next. Terrain-following and hover modes modify the normal motion separately. Hover can engage below 17 ft/s, disengages at 18 ft/s, damps horizontal motion when level and adjusts collective against vertical motion.
Engines, fuel and failures
Collective, engine RPM, rotor RPM and descent determine torque and RPM targets. The engines approach their targets faster than the rotor spins down. Descent can maintain rotor speed with failed engines, with collective affecting that windmilling term. Left and right engine failures independently alter their displayed RPM and temperature.
Fuel consumption depends on rotor torque, aircraft weight and airspeed. Fuel loss also reduces weight. The low-fuel flag uses estimated endurance below six minutes. Exhaustion fails both engines. Overheat damage raises the affected engine temperature by 20 units per second until failure above 950. Excess torque raises temperature and accumulates a damage timer; recovery below the torque limit reduces that timer. Warning and sound events are emitted in the same order as the original routines.
Ground contact and landing
The landing evaluator combines forward speed and descent into abs(forward_speed) / 100 - vertical_speed / 22. Thresholds at 0.3, 0.6, 0.8 and 0.9 choose landing grades; values above 0.9 cause a hard-landing death. Surface types 1 through 3 cause a water landing unless the aircraft is on a carrier. Existing deaths take precedence. The surrounding flight update handles takeoff, carrier transfer, pull-up warnings and requests to evaluate mission completion after landing.
Verification
The subsystem suite compares 9,602 cases and 446,620 fields exactly, including binary64 values and ordered warning/audio events. Another 2,000 angular-rate and aerodynamic cases have exact integer/event fields and maximum double differences of 1.14e-13. Thirty-two continuous realistic-flight traces cover 76,832 states across all four warzones, including carrier departure, wind, failures, fuel exhaustion, hover, variable timing and hard landings. All 2,997,072 integer/event fields agree, including positions, attitude matrices and instruments. Of 8,451,520 double fields, 8,264,472 match byte for byte; the largest difference is 1.14e-13. These suites also pass under AddressSanitizer and UndefinedBehaviorSanitizer. Reports are under work/checks/cpp/.
Arcade flight
Arcade has its own model in models\arcade.c, with world-coordinate units rather than feet. Pitch input is squared while retaining its sign. Tail-rotor input uses the same shaping after reversing sign. Roll input is multiplied by 1.5; collective input is divided by three. Collective increments are applied per update and values below 0.1 in magnitude are cleared, which means a small input cannot gradually accumulate into a climb.
Pitch is limited to about 15 degrees and bank to 60 degrees. Releasing cyclic relaxes both toward level. Pitch drives forward acceleration; bank drives lateral motion and adds a speed-dependent turn. Forward speed is clamped to −76…255 ft/s. Climb is normally limited to ±40 ft/s, falling to 20 ft/s upward with one engine failed. Above 9,000 ft, positive climb is reduced toward zero at 10,000 ft. With neither vertical motion nor an engine failure, the model automatically holds height relative to the terrain.
Both engines normally report 100% RPM and 800 temperature units. Overheat damage raises the affected temperature until failure. With both engines failed, engine and rotor RPM fall by one per update, and collective is replaced by a descent term based on indicated airspeed and pitch. Fuel exhaustion invokes the same failure state. The landing evaluator is shared with the realistic model, but Arcade supplies indicated airspeed and its own terrain-adjusted descent term.
The Arcade initializer sets weight to 14,058 lb, calls a fuel setter that adds the fuel change to weight, then adds the fuel again. In a fresh 2,442 lb-fuel mission it therefore starts at 18,942 lb instead of 16,500 lb. The reconstruction retains that original calculation. Arcade's fuel formula otherwise closely resembles the realistic model's, with a base consumption term of 0.4 and its vertical-control state in place of rotor torque.
tools/check_cpp_arcade_systems.py compares 6,000 component cases with the original instructions, matching 337,859 fields exactly. tools/check_cpp_arcade_trace.py covers 28 uninterrupted scenarios and 67,228 states across all four warzones. All 2,285,830 integer/event fields agree; 4,207,780 of 4,235,364 double fields match byte for byte. The largest floating-point difference is 3.64e-12 and does not alter any checked position, matrix or instrument output. Both suites pass under AddressSanitizer and UndefinedBehaviorSanitizer. Reports are in work/checks/cpp/arcade-systems/, arcade-trace/ and their -sanitize counterparts.
Player collision dispatch
models/model.c:apache_collision_test_ tests a point nine feet below the pilot's position. If approximate travel since the previous update is below thirty feet, it uses point queries; at thirty feet and above it uses line queries. Mobile objects come first. The selected mobile must also contain the final point in its rotated bounding box or one of its attachments. If that second test fails, the routine proceeds to scenery, then persistent crater volumes. Consequently a line can cross a mobile object's coarse volume without producing a player collision when its endpoint is outside the detailed bounds.
A scenery collision requests death cause 2 and destroys the scenery object. Crater collisions report model 11. Mobile collisions retain the actual part's model and owner. An airborne player touching a registered carrier can land when either its present or previous collision height is at or above the deck. A player already attached to a carrier avoids the ordinary mobile-crash response. Landing attachment, heading adjustment and landing evaluation follow the contact check.
tools/check_cpp_player_collisions.py compares 24,000 dispatch cases against the original over all four warzones, with real scenery, rotated models, attachments and crater volumes. Another 6,000 cases compare carrier registration and deck-height predicates. All 222,000 fields match in normal and sanitizer builds, including 496 mobile contacts, 2,784 scenery contacts and 2,232 crater contacts. Both produce SHA-256 61d88c69ac364d26eb2513841a4defaf9c361db760af16e62d7894110ca4646f. Death/destruction handlers are observed in this sweep; carrier attachment and flight response are checked separately as they are integrated. Reports: normal, sanitizers.
Live mission flight and carrier response
The C++ flight model now owns the same player pose used by AI searches and collision queries. Starting on Tarawa reserves slot 7 where possible, swapping reservations with the aircraft already assigned there. The player remains a separately drawn mobile object while attached to the moving deck. Reservations survive takeoff, so an empty deck attachment can still belong to an airborne aircraft.
On landing, the original selects the nearest empty deck attachment in carrier coordinates, swaps reservations when necessary, and preserves the helicopter's relative position and heading. It clears the displayed pitch and roll before evaluating the landing. The realistic carrier path passes its forward velocity through an extra division by 256 when subtracting carrier speed; Arcade uses world units at that point. The reconstruction retains those defined calculations.
Eight continuous land/carrier flight traces agree over 9,600 updates and 8,127,587 fields in both normal and sanitizer builds. The collision-enabled suite adds 2,400 deliberate contacts and verifies 50 carrier attachments and their reservation changes. All 8,188,635 fields agree. Reports: flight, flight with sanitizers, contacts, contacts with sanitizers.