rockets-engine
A Rust companion to ballistics-engine for powered, three-dimensional point-mass flight. It adds prescribed motor burns, changing mass, incremental RK4 integration and application-owned guidance hooks. The default adapter calls the existing ballistics-engine gravity/drag derivative function; it does not fork or modify that engine.
The crate is published on crates.io and consumes ballistics-engine 0.48.0 from crates.io. Rust 1.99.1 is pinned for development, matching the game workspace.
Use
[]
= "0.8"
Run
boost_coast prints CSV for a synthetic small rocket through ignition, powered flight and coast. Its numbers are illustrative. guidance_hook demonstrates externally supplied control acceleration without a target-tracking algorithm. staging flies a three-stage rocket through separations and inter-stage coasts. icbm_midcourse coasts a post-boost state around a central-gravity Earth to a reentry handoff; it needs no features and shows the planet-scale use of the integrator.
Command line
The cli feature builds a rockets binary:
simulate prints the trajectory as a table, CSV or JSON, with an optional ASCII altitude chart and an apogee/impact summary. Add --diameter-m and --cd to fly against the ballistics-engine atmosphere and drag kernel, --wind for moving air, and --elevation-deg/--azimuth-deg/--speed-mps to aim the launch. rockets help lists every option.
Two analysis commands build on the same options:
dispersionruns Monte Carlo samples with lognormal Cd-scale, per-axis wind and fractional speed perturbations (--runs,--seed,--cd-sigma,--wind-sigma,--speed-sigma) and reports apogee/range statistics and impact CEP. Deterministic per seed; no external RNG dependency.solve-range <metres>bisects launch elevation to hit a downrange ground target on the low-angle branch (--high-anglefor the other solution) and prints the elevation, range, apogee and impact speed.
--stage builds serial or parallel staged vehicles from the terminal: each --stage dry=kg,prop=kg,curve=t:N;t:N,ignition=s,sep=s adds a stage with its own motor schedule and optional separation. --gravity central|vacuum and --space-above-m cover central-gravity and atmosphere/space-switching flights.
Browser terminal
The same command surface runs at rockets.rs/sh/, compiled to WebAssembly. src/wasm.rs is a thin binding over the shared parser in src/command.rs, so the native and browser builds accept identical commands; scripts/build-wasm.sh rebuilds the module.
Basic use
use ;
let curve = new?;
let motor = new?; // 40 g propellant; ignition at t=0.2 s
let rocket = new?; // Dry mass includes the motor casing
let simulator = new?;
let state = FlightState ;
let result = simulator.step?;
assert_eq!;
# Ok::
To use the same aerodynamic force kernel as the game, replace UniformGravity with BallisticForces:
#
#
# Ok::
Model and units
- All values use SI units: metres, seconds, kilograms, newtons and m/s². Time is simulation time. Any game time compression belongs outside this crate.
- For local flight the fixed right-handed frame is X downrange, Y up, Z lateral. With
BallisticForces, position Y is absolute altitude above sea level; the weather station has its own altitude. Launch elevation is encoded in velocity and thrust direction, not a rotating coordinate frame. - For planet-scale flight, integrate in an Earth-centered frame with
CentralGravity: position measured from Earth's center, inverse-square gravity in both magnitude and direction.icbm_midcourseshows the pattern. The flat-frame models (UniformGravity,BallisticForces) are invalid at intercontinental ranges;CentralGravityis invalid (singular) at its own center and carries no atmosphere. - Thrust is piecewise linear in time since ignition. Curves must start at zero time, have at least two samples, strictly increase in time and have positive total impulse. Nonzero endpoint thrust is supported.
- Fuel consumption is proportional to delivered impulse. If
I(t)is accumulated impulse,I_totalis total impulse andmpis initial propellant mass, remaining fuel ismp × (1 − I(t)/I_total). This assumes constant effective exhaust velocityI_total/mp; the prescribed thrust already includes exhaust/pressure effects. There is no separate motor pressure compensation. - Mass is computed analytically at every RK4 stage rather than numerically integrating and clamping an independent fuel state. Dry mass is positive and fuel cannot become negative. At burnout thrust is zero and the empty motor remains attached.
- A
Rocketis a base mass plus any number ofStages, each with its own motor schedule and an optional separation time. Total thrust sums over attached burning motors, so serial and parallel (strap-on) staging share one model. At separation the stage's dry mass, motor and unspent propellant leave the vehicle; separating a burning stage (hot staging) ends its thrust.Rocket::newkeeps the 0.1 single-motor shape. - Ignition, every curve knot, burnout and every stage separation split the integration grid. RK4 uses the appropriate one-sided mass and force at discontinuities: a step closing on a separation still sees the departing stage, and the next step opens without it, including burns shorter than the maximum timestep.
- The default maximum internal step is 0.01 seconds, sized for motor burns. In vacuum coast (midcourse) raise it — seconds per step is plenty under central gravity; the Kepler-invariant tests integrate orbits at 1–2 s steps. This is fixed-step RK4, not an adaptive solver; choose a timestep suitable for the forces and confirm convergence for new vehicle models.
This baseline follows the thrust-curve, propellant-mass, gravity and drag structure illustrated by NASA's model rocket simulation. The force model is consistent with NASA's rocket thrust equation.
Guidance and other forces
Guidance::command(GuidanceContext) returns a ControlInput. A closure can implement the hook directly. The context contains the current flight state, motor output, mass, requested duration and configured steering limit.
- Guidance is called once per
step_guidedcall. Its command is held constant during that call's internal RK4 stages and substeps. thrust_directionis normalized. It describes externally supplied orientation; it does not model attitude, angular velocity, gimbal travel or turning rate.FixedDirectionprovides a simple unguided orientation.steering_acceleration_mps2is a world-frame acceleration supplied by an external actuator/control model. The solver caps its vector magnitude usingmax_steering_accel_mps2, which defaults to zero. It may act during coast; its physical availability and direction are the caller's responsibility.- The core has no target, seeker, interception or Godot entity types. Controllers can own their own state and inputs without changing the integrator.
ForceModel::accelerationsupplies environmental acceleration at each RK4 stage with that stage's current mass. A closure can implement this hook too. Keep it deterministic and free of side effects.BallisticForces::acceleration_in_windsupports wrappers that sample spatial or time-dependent wind.
Guidance and constraint composition
ProgradeDirectionfollows the velocity vector (gravity turns), with a fallback axis at rest.ThrustMisalignmentdecorates any guidance source with a fixed tilt/clock boresight error — the dominant real-world dispersion term for unguided rockets.SteeringLagadds a first-order actuator time constant to steering acceleration; its lagged state is snapshot-able for replay.RailForceswraps any force model and projects environmental acceleration onto a launch rail until exit; command the thrust axis along the rail and no steering until exit, then let tip-off take over.BoxedGuidance/BoxedForcesare type-erased handles for heterogeneous decorator/model chains.with_atmosphere_zonesandwith_wind_segmentsonBallisticForcesexpose the upstream downrange-segmented atmosphere and wind ladders, evaluated statelessly at each stage's downrange position.
The atmospheric adapter uses the body's own Cd-versus-Mach curve and updates both SI mass and the engine's imperial mass mirror. It includes gravity and drag; bullet-specific reference-BC and postprocessing effects (Litz spin drift) are disabled. Standard atmosphere delegates to the upstream altitude model: humidity affects density, while this derivative path uses dry-air sound speed. Uniform atmosphere obeys the upstream direct-mode limits (0 ≤ density < 2 kg/m³, sound speed > 200 m/s); a zero density retains gravity but removes drag. UniformGravity(Vec3::zeros()) gives an inertial vacuum.
Reduced-order dynamics (weathercock and roll)
Weathercock is a second-order body-axis model in the guidance layer: the axis relaxes toward the relative wind with natural frequency ωₙ² = q·A·Cnα·(CP−CG)/I and damping 2ζωₙ, and the filtered axis becomes the commanded thrust axis. Positive static margin stabilizes; negative margin diverges. WeathercockParams takes the Cnα and CP Mach tables, a two-point CG model (dry + propellant references, mass-weighted as propellant burns), diameter, scalar pitch inertia and damping ratio. Axis and rate are snapshot-able state, updated once per public step, so choose the cadence well below the natural period. Pair with with_body_lift to close the loop through angle of attack. CLI: --weathercock with --cna, --cp, --cg-dry-m, --cg-prop-m, --pitch-inertia, --damping-ratio.
RollDynamics is the scalar roll channel: fin-cant drive against roll damping, with an explicit equilibrium rate. Feed the resulting effective twist to BallisticForces::set_twist between public steps (pair with with_spin for length and handedness). Still not 6-DOF: no quaternion, no inertia tensor, no gyroscopic coupling.
Geometry to aerodynamics
A subsonic Barrowman modeler, implemented against the OpenRocket technical documentation and its FinSetCalc/BarrowmanCalculator sources. RocketGeometry (nose cone + body tube + trapezoidal fins) yields analyze(): Cd/Cnα/CP curves and the reference area, with forces() and weathercock_params() convenience builders. CP is the component moment sum (nose volume fraction + fin quarter-MAC); drag is Schoenherr/roughness-limited skin friction with fineness and fin-thickness corrections, base drag, and conical nose pressure. Fin normal force uses the Diederich formula with body and fin-count interference factors.
Limits, deliberately: subsonic only (Mach-1 clamp), conical/ogive/elliptical/parabolic noses, trapezoidal fins, no boat-tails or transitions, no induced-drag-from-AoA model (induced_k stays zero — supply your own if needed). CLI: --geometry nose=shape:L:D,tube=L:D,fins=N:cr:ct:s:m:t with m the leading-edge sweep and t the fin thickness (metres).
Model rockets
The model feature (default on) adds hobbyist conveniences:
MotorFileparses RASP.engmotor text — manufacturer, designation, diameter/length, delays, propellant and total masses, and the thrust samples.motor(ignition_s)schedules it.--motor-engtakes inline text;--motor-filereads from disk in the native CLI.RecoveryForceswraps any force model and swaps in recovery drag once the vehicle is descending, with an optional absolute-time deployment gate (--recover <cdA>,--recovery-after-s <t>). Terminal velocitysqrt(2mg/(ρ·CdA))emerges naturally. Deployment is a pure function of state, so integration stays deterministic.Airframe::sport_24mm()andAirframe::high_power_54mm()are illustrative presets (not measured kit data): diameter, Cd, Cnα, CP, CG, pitch inertia, induced-k and recovery Cd·A in one bundle.--airframe sport24orhp54fills the same options and composes with--weathercock.
Modified point-mass
BallisticForces climbs one rung above point-mass through three opt-in builders, all evaluated in-loop by the upstream kernel:
with_spin(twist_rate_in, body_length_m, right_hand)— Magnus force and yaw of repose for a spinning body.twist_rate_inis rifling language (inches per turn); for a fin-canted rocket it is the roll rate the cant produces per unit velocity. The correlations are McCoy bullet shapes, so treat magnitudes as approximate for blunt or finned bodies; signs and scaling are physical.with_coriolis(latitude_deg, shot_azimuth_deg)— rotating-frame deflection for planet-scale arcs, exact−2Ω×v. Do not combine with an inertial-frameCentralGravitymodel, which already accounts for Earth rotation by not rotating.with_tipoff_yaw(yaw_rad, decay_distance_m)— rail tip-off: an initial yaw decaying exponentially with downrange distance, perturbing the early angle of attack.
The CLI exposes the same surface: --twist-in/--body-length-m/--twist-left, --coriolis lat:az, --tipoff yaw:decay.
Angle-of-attack aerodynamics
with_body_lift(cna_vs_mach, induced_k_vs_mach) adds quasi-static body lift without integrating attitude. The solver now hands every RK stage's commanded thrust axis to the force model through ForceModel::acceleration_with_axis (a provided method — orientation-blind models are bit-identical and existing implementations keep working). The adapter measures the angle of attack between that axis and the wind-relative velocity and adds:
- Normal force
N = q·A·Cnα(Mach)·α, perpendicular to the relative wind on the axis side — a guided turn at angle of attack turns harder because the airframe works. - Induced drag
Cd += k(Mach)·α²— the same turn costs speed.
Cnα is per radian (body + fins), k per radian squared; both are piecewise-linear Mach tables needing two or more ascending points. Reference area derives from the diameter. CLI: --cna mach:cna,... --lift-k mach:k,.... This is still prescribed-attitude point-mass — the axis is commanded, not earned; trim, weathercock lag and stability margins belong to a future reduced-order layer.
The upstream ICAO atmosphere is defined to about 80 km; do not fly BallisticForces above it. Wrap it in AltitudeSwitch with a vacuum model above a cutoff (UniformGravity for flat-frame flight, CentralGravity for Earth-centered arcs) when a trajectory leaves the atmosphere.
Save, replay and host integration
Simulator does not hold evolving flight state. Each step returns a new FlightState, end-of-step motor/mass telemetry and its internal step count. An error leaves the caller's state untouched. Invalid numeric inputs and non-finite results return errors, and each call has a bounded substep budget.
The default serde feature serializes FlightState and MotorPhase. To resume a flight, restore the state with the same motor schedule, vehicle, environment and solver settings. Motor fuel/phase are derived from simulation time, so they cannot drift apart in a snapshot. Save any stateful guidance controller separately. Invalid deserialized states are rejected when stepped. A controller's mutations are not rolled back if integration fails after the guidance call.
For deterministic replay, retain the same fixed public update cadence, control inputs and environment history. Internal grids are anchored to each public step; changing how calls are partitioned can change floating-point results. Cross-platform bit-for-bit identity is not promised.
For Counterbattery, a future CbBallistics adapter can repeatedly call step to precompute an unguided powered arc, or call step_guided at the simulation cadence for live control. Convert world coordinates once at that boundary, especially absolute altitude and side-facing X. The host must detect terrain and unit collisions over each returned movement interval; the library does not stop on a ground plane. No game integration is included in this crate.
Scope of 0.2
The engine models translation with prescribed motor burns across serial or parallel stages, in flat or Earth-centered frames. It does not yet model throttle or restart within a stage, launch rails, changing drag geometry, body attitude, lift coefficients, actuator dynamics, seekers, Earth rotation (Coriolis and launch-site velocity are the caller's responsibility in an ECI frame), orbit perturbations such as J2, or terrain collision localization. Guidance hooks provide the boundary for future control models; they are not a built-in autopilot.
Validation
The tests check the ideal rocket equation and analytic position in vacuum, RK4 convergence, delayed ignition, nonzero endpoint thrust, short burns, zero-thrust intervals, fuel bounds, wind/mass/drag response, control limits, invalid inputs, work limits, and snapshot replay. Unpowered trajectories are compared against the upstream RK4 solver across multiple vehicle, wind and atmosphere configurations. Staging tests cover exact mass drops at separation, hot-staging thrust overlap, inter-stage coast phases, parallel boosters and two-burn Δv against Tsiolkovsky — including a regression for one-sided mass at a step closing exactly on a separation. Central-gravity tests assert Kepler invariants: a circular orbit closes to millimetres and an ICBM-scale midcourse ellipse conserves specific energy and angular momentum to better than 1 part in 10¹⁰. These checks establish numerical behavior and compatibility, not empirical validation of a particular real motor or airframe.
ballistics enables the companion adapter and its ballistics-engine dependency. serde enables snapshot serialization. Disabling both leaves the integrator, motor, guidance and custom force hooks usable on their own.
Licensed under MIT; see the repository's LICENSE file.