Ballistics Engine
A high-performance ballistics trajectory calculation engine with comprehensive physics modeling, automatic zeroing, and statistical analysis capabilities.
Project Website: https://ballistics.rs/
Features
- Full 3D Trajectory Integration - Six-state ballistic modeling with adaptive RK45 and fixed-step RK4 integration methods
- Advanced Drag Models - Full standard-projectile family (G1, G2, G5, G6, G7, G8, GI, GS, and the British RA4 reference function), each backed by its own real Mach-indexed table with automatic transonic corrections, plus user-supplied custom Cd(Mach) drag tables (
--drag-table, used as-is with endpoint hold outside their measured domain, no transonic correction applied — see CLI_USAGE.md;bc_valueis ignored while a custom table is active), with an optional--cd-scale <FACTOR>whole-curve truing multiplier (Hornady AFF / AB CDF style;1.0= neutral, typical range 0.90-1.10; requires--drag-table— see CLI_USAGE.md).true-velocity/plan-truing's forward model is deliberately G1/G7 only. GL is out of scope — its only public source is velocity-domain data, which doesn't fit this engine's Mach-indexed table format. - Automatic Zeroing - Calculate sight adjustments and apply zero angles automatically;
trajectoryechoes the solved bore angle (degrees, additive across table/JSON/CSV) whenever auto-zero ran, andzero --from-angle <DEGREES>solves the zero RANGE(S) a previously solved/stored bore angle produces — a bore angle generally implies two zeros (the classic 25/300-yard relationship), so both are reported rather than one being silently picked — so the angle can be captured once and reused later, independent of the day it was solved — see CLI_USAGE.md - Canted-Rifle Modeling - Model a rifle zeroed level but fired canted (
--cant <DEGREES>, alias--cant-angle, ontrajectory/monte-carlo); clockwise cant shifts point of impact right and low downrange for a rifle with an upward zero correction — see CLI_USAGE.md - Deliberate Zero POI Offset - Record that the rifle is deliberately zeroed off — e.g. 0.1 in high / 0.2 in left at the zero range (Kestrel ZH/ZO semantics) — and shift the whole solution by the equivalent angular bias (
--zero-poi-up/--zero-poi-right, inches imperial / cm metric, ontrajectoryand every zero-solving subcommand; solve-jsonshot.zero_poi_up_m/zero_poi_right_m; saved-profile fields;.a7pzero click counts convertible viaprofile import --zero-click) — see CLI_USAGE.md - Lateral Sight Offset - Model offset-mounted optics (
--sight-offset, inches imperial / mm metric, positive = sight right of bore): the bullet starts that far left of the sight line and the windage zero converges it onto the sight line at the zero range — physical mount geometry, distinct from and additive with the zero POI offset (solve-jsonrifle.sight_offset_lateral_m; saved-profile field; also onmonte-carlo/WEZ mirroring--cant) — see CLI_USAGE.md - Multiple Named Zeroes / Per-Load Offsets in Profiles - Store alternate zero conditions on a saved profile (Lapua Sight-In POI / ATrag zero-zone class): each named set carries an optional zero distance plus constant per-load dial corrections in mils (
profile zero-set add|remove|list;--zero-set NAMEontrajectory/come-ups/wind-card/range-table/dsf/plan-truing; corrections are added to total-correction dial outputs before the tracking-CF division; profile-CSVV_OFFSET_MIL/H_OFFSET_MILcolumns and.a7p --zero-clickimport feed sets automatically; unknown names fail loudly listing the available sets) — see CLI_USAGE.md - Drops Reference: LOS vs Target Plane - Reference sampled drops to the line of sight (default, unchanged) or to the vertical target plane on steep inclined shots (
trajectory --drops-reference target: drop ÷ cos(shooting angle), JBM's "target plane" checkbox; sampled table/CSV column relabeledDrop (target)/drop_target_in; solve-jsonshot.drops_reference) — see CLI_USAGE.md - Scope Tracking Correction Factors - Compensate turrets that don't track their nominal click value: derive
CF = actual/dialedfrom a tall-target test (tall-targetsubcommand, pure arithmetic), then--elevation-cf/--windage-cf(or validated saved-profile fields) DIVIDE every dial-unit output once at the shared conversion boundary — come-ups, range/compare/wind cards, mover lead/Ring, PDF dope card, zero MOA/mrad — never raw inches (an under-tracking scope, CF < 1, needs more dial);true-velocitymultiplies dialed observations by the CF (scope-dial to true angular) so scope error is not baked into trued MV/BC — see CLI_USAGE.md - Equivalent Horizontal Range (BDC shoot-to) - Inclined zeroed shots print the flat range whose angular correction against the same zero matches the inclined solution (SIG AMR / Leica EHR / Gunwerks style, angular-match inversion over one flat re-solve — not the rifleman's-rule cosine), so fixed BDC turrets/reticles can dial as if flat (
trajectorysummary line; solve-jsonsummary.equivalent_horizontal_range_m; publicTrajectorySolver::equivalent_horizontal_range) — see CLI_USAGE.md - Reticle Hold Points - Place a firing solution where you actually read it: a point in your own reticle, FFP/SFP aware with second-focal-plane subtensions rescaled for the magnification in use (
reticle hold, native + browser terminal + a new appended C ABI export;reticle generate mil-grid|tree|bdcbuilds descriptions in one shared serde schema that saved profiles,--reticle-jsonand solve-json's optionalreticleblock all speak). Horus/TREMOR grid layouts, wind-dot calibration and any vendor catalog are deliberately excluded — see CLI_USAGE.md - Reticle/BDC Inverse Solvers - Three read-only solvers over an existing load, sharing one drop-vs-range root find:
mark-to-rangemaps each reticle subtension to the range where it lands (Nightforce / Nikon Spot On / Swarovski / TRACT), reporting marks the load cannot reach rather than dropping them;bdc-matchfits the magnification that makes an SFP BDC reticle match the load (Zeiss Rapid-Z; closed-form least squares, with a residual warning when nothing fits);optimal-zeromin-max searches the one zero that minimizes the largest hold a whole target list needs and reports whether a dead-center hold keeps each inside its vital zone (GeoBallistics HDZ). CLI-only this train — see CLI_USAGE.md - Robust Hold Corridors - Solve a bounded set of NAMED segmented-wind scenarios at once (
hold-corridor --scenarios set.json --ranges 200,400,600 [--target rect:WxH|circle:D]) and get, at every range, each scenario's hold, the min/max corridor they span, the minimax (Chebyshev-center) hold, the worst-case miss from it, and whether one hold keeps every scenario inside the target — with a versionedRobustHoldReportV1JSON form. Caps (≤8 scenarios, ≤64 ranges) and malformed segments are structured errors raised before any solving, and reordering scenarios cannot change the answer. No probabilities are assigned anywhere: the corridor is the span of the hypotheses you supplied, not a confidence interval — see CLI_USAGE.md - Moving-Target Lead - Wind-aware hold tables for targets moving at a constant speed/angle, with iterative intercept-range correction for non-perpendicular motion (
leadsubcommand; publicballistics_engine::calculate_leadAPI) — see CLI_USAGE.md - Mover Ring - Field-tested alternative for engaging movers: a per-point ring radius (
target_speed × time-of-flight) falls out of an already-solved trajectory with no second command or re-entered ballistic data (trajectory --target-speed, additive across table/JSON/CSV output);leadalso gainedtrajectory's powder-temperature flags for muzzle-velocity parity between the two — see CLI_USAGE.md - Side-by-Side Load Comparison - Compare 2-8 loads at identical conditions with per-load independent zeroing (
compare --load "NAME:DRAG:BC:MASS:VELOCITY[:DIAMETER]", mixable with saved profiles); JSON/CSV output carries per-row deltas against the first load — see CLI_USAGE.md - Powder Temperature Command - Resolve the temperature-adjusted muzzle velocity standalone, without a trajectory solve (
powdersubcommand): linear fps-per-degree model or a measured temperature→velocity curve, optional--sweepvelocity ladder and muzzle energy; shares the solvers' exact resolution code (publicresolve_powder_adjusted_velocityAPI) — see CLI_USAGE.md - Unit Conversion - Seamless switching between Imperial (default) and Metric units
- BC Segmentation - Velocity-dependent ballistic coefficient modeling with automatic estimation
- Atmospheric Modeling - Temperature, pressure, humidity, and altitude effects with ICAO standard atmosphere; also accepts a single density altitude reading (
trajectory --density-altitude, feet imperial / meters metric) as a direct alternative to entering altitude/pressure/temperature separately — back-solves an ISA-equivalent atmosphere (preserving Mach/lapse-rate/segmented-atmosphere behavior, not a density-only shortcut) and supersedes--altitude/--pressure/--pressure-typeentirely, with an explicit--temperaturestill honored for correct powder-temperature sensitivity — see CLI_USAGE.md - Clock-Position Wind Entry - Enter wind direction as the dominant field convention: marked clock positions (
--wind-direction 3oc,10h30, or10:30; 12 o'clock = headwind, minutes count 0.5°) alongside plain degrees on every wind-direction flag and the WASM terminal; inside--wind-segmentthe colon-free forms apply (10:3oc:400) while10:30:400keeps its numeric SPEED:ANGLE:DIST meaning; bare numbers stay degrees everywhere — see CLI_USAGE.md - Earth-Fixed Compass Wind Bearings - Store wind as absolute compass bearings and let the solver re-reference them against the shot azimuth (
--wind-ref compass+--shot-directionontrajectory/monte-carlo; covers the single direction, location-CSV WIND_DIR, and every--wind-segmentangle; Monte Carlo converts before dispersion sampling; solve-jsonwind.wind_reference; WASM buildersetWindReference/setShotDirection; wind FROM north on a shot due north = pure headwind, pinned) — see CLI_USAGE.md - Wind Effects - 3D wind calculations with altitude-dependent wind shear modeling, downrange-segmented wind (
--wind-segment SPEED:ANGLE:DIST[:VERTICAL], repeatable — model wind that varies along the path, e.g. muzzle plus downrange sensor readings), and vertical wind (--wind-vertical <SPEED>ontrajectory/monte-carlo, or the segment's optional 4th field; positive = updraft, raises point of impact) — see CLI_USAGE.md - Oblique Wind-Drift Cards - Wind dope cards at any wind-FROM angle, not just full-value 90° crosswind (
wind-card --wind-angle <DEG>or--wind-angles <CSV>for one card per angle); each cell is a real trajectory solve, default (no flags) unchanged from the classic full-value 90° card — see CLI_USAGE.md - Monte Carlo Simulations - Statistical analysis with parameter uncertainties
- BC Estimation - Estimate ballistic coefficients from trajectory data
- Advanced Physics:
- Spin Effects: Magnus effect and empirical Litz spin drift
- Earth Effects: Coriolis effect with latitude-dependent calculations
- Angular Motion: Gyroscopic precession and nutation physics
- Transonic Analysis: Pitch damping coefficients and stability warnings
- Trajectory Sampling: Regular interval data collection for analysis
- Form Factor Corrections: Bullet-specific drag adjustments
- Multiple Output Formats - JSON, CSV, formatted tables, and printable PDF dope cards
- Terminal Chart - Inline drop, drift, velocity, and energy vs. range charts right in the terminal (
trajectory --plot, Unicode braille-dot canvas by default,--plot asciifallback); pure Rust, zero new dependencies, no ANSI colors — see CLI_USAGE.md - Profile import:
ballistics profile import file.a7p— imports ArcherBC2.a7pprofiles (rifle, bullet, atmosphere, zero) with a full mapping report;--dry-runpreviews without saving
Installation
From crates.io
From Source
The binary will be at: target/release/ballistics
Feature Flags
| Feature | Default | Description |
|---|---|---|
online |
✅ Yes | HTTP client for API integration (--online flag) |
To build without network capabilities:
Quick Start
Basic Trajectory (Imperial Units - Default)
# .308 Winchester, 168gr bullet at 2700 fps
# With automatic zeroing at 200 yards
Metric Units
# Same bullet in metric units
Unit Systems
The engine supports two unit systems, selectable with the --units flag:
Imperial (Default)
- Velocity: feet per second (fps)
- Mass: grains
- Distance: yards
- Diameter: inches
- Temperature: Fahrenheit
- Pressure: inHg
- Wind: mph
Metric
- Velocity: meters per second (m/s)
- Mass: grams
- Distance: meters
- Diameter: millimeters
- Temperature: Celsius
- Pressure: hPa (millibars)
- Wind: m/s
Commands
Trajectory Calculation
Calculate ballistic trajectory with environmental conditions:
# Imperial units (default)
Auto-Zero Feature
Automatically calculate and apply the zero angle for a specific distance:
# Zero at 200 yards and show trajectory to 500 yards
# Custom sight height for auto-zero
Zero-Day Conditions (zero shift)
A rifle's zero is a fixed barrel angle set on the day you sighted in. If you later shoot
in different weather — or with a different muzzle velocity (e.g. a cold vs. warm powder
temperature) — the point of impact shifts. By default --auto-zero solves the zero angle
using the same conditions you pass for the shot, which assumes you zeroed in today's
conditions. The --zero-* flags let you decouple the two: the zero angle is solved
under the conditions the rifle was actually zeroed in, while the trajectory itself runs
under the current shot-day conditions.
# Zeroed on a cold morning (28 F) at 2600 fps; shooting this afternoon at 85 F / 2700 fps.
# The zero angle is solved for the cold/slow load, then the warm/fast trajectory is
# computed against it — so the dope correctly shows the point of impact drifting high.
Available overrides (each independently optional; any omitted flag falls back to the shot-day value, so leaving them all off reproduces the previous behavior exactly):
| Flag | Meaning | Units (imperial / metric) |
|---|---|---|
--zero-velocity |
Muzzle velocity on the zeroing day | fps / m·s⁻¹ |
--zero-temperature |
Air temperature on the zeroing day | °F / °C |
--zero-pressure |
Barometric pressure on the zeroing day | inHg / hPa |
--zero-humidity |
Relative humidity on the zeroing day | percent |
--zero-altitude |
Altitude on the zeroing day | feet / meters |
Powder Temperature
Propellant temperature changes muzzle velocity. Two models are available:
Linear — a constant sensitivity (fps or m/s per degree) applied relative to the temperature the load was chronographed at:
Measured curve (non-linear) — real powders aren't perfectly linear (temperature-
stable powders flatten; others steepen when hot). If you've chronographed the load at
several temperatures, pass the points directly and the muzzle velocity is interpolated
at the powder temperature (clamped at the endpoints — no extrapolation). This
overrides --powder-temp-sensitivity when supplied:
Powder temperature vs air temperature. The curve maps powder temperature to
velocity, while --temperature drives air density. These are decoupled: the curve is
looked up at --powder-temp when given, otherwise at --temperature (powder assumed at
air temperature). So a load left in a hot chamber or a cold pocket:
# 85 F air (density), but the powder is at 60 F (velocity from the curve at 60 F)
Both powder models compose with --auto-zero, symmetrically. For the linear model,
--zero-temperature resolves zero-day velocity relative to the reference --powder-temp.
For a curve, --zero-powder-temp overrides the powder lookup; otherwise an explicit
--zero-temperature is used, or the shot-day --powder-temp is inherited when no zero-day
temperature was supplied. Zero-day atmosphere flags still drive air density independently.
An explicit --zero-velocity takes precedence over either powder model.
Bore Height and Ground Impact
Control bore height above ground and ground impact detection:
# Set bore height for prone shooting position (2 feet)
# Disable ground impact detection for full trajectory to max range
Bore height defaults: 5 feet (imperial) / 1.5 meters (metric) - standing position.
Advanced BC Modeling
Enable velocity-dependent BC modeling for more accurate long-range predictions:
# Enable BC segmentation (velocity-based BC changes)
Advanced Physics - Magnus and Spin Drift
Enable advanced gyroscopic and aerodynamic effects:
# Magnus effect and spin drift calculation
# Coriolis effect for extreme long range
Zero Calculation
Calculate the sight adjustment needed to zero at a specific distance:
# Calculate zero for 200 yards
# With custom sight height (default is 0.05 yards / 1.8 inches)
# Metric example
Output includes:
- Zero angle in degrees
- Adjustment in MOA (Minutes of Angle)
- Adjustment in mrad (milliradians)
- Maximum ordinate (highest point of trajectory)
Monte Carlo Simulation
Run statistical analysis with parameter variations:
BC Estimation
Estimate ballistic coefficient from observed trajectory data:
True Velocity (Velocity Truing)
Calculate the effective muzzle velocity that produces a measured drop at a known range. This helps "true" your ballistic system by identifying discrepancies between chronograph readings and real-world performance.
# Basic offline calculation
# With chronograph comparison
# Chronograph measured downrange, not at the muzzle (MBA-1377): most
# chronographs read 10-15 ft (or 25 m) downrange, so --chrono-distance
# back-solves the true muzzle velocity from the raw reading
# With BC5D tables for improved accuracy
# Joint MV + BC calibration from multiple observed impacts
Use case: A shooter measures 5.1 MIL of drop at 600 yards. Their chronograph showed 2822 fps. The command calculates the effective velocity is actually ~2740 fps, suggesting a -82 fps adjustment for accurate ballistic predictions.
Downrange chronograph correction (MBA-1377). Most chronographs (and radar units) read some distance downrange rather than at the muzzle — 10-15 ft is typical for optical screens, 25 m for Lapua/JBM's reference distance — so the raw reading is a few fps low. --chrono-distance back-solves the true muzzle velocity from that reading via secant iteration on the same forward drag model (BC/--drag-model/atmosphere) the rest of the command uses; zero or absent is an exact no-op. It's a pure display-side correction (--chrono-velocity never feeds the drop-based solve either way) and validates its input to a sane 1-98 ft / 0.3-30 m band (100 ft is out of range) rather than silently extrapolating a bad distance into a bad velocity.
Joint velocity + BC truing. With two or more --observed RANGE:DROP impacts spanning supersonic to transonic ranges, true-velocity fits both muzzle velocity and ballistic coefficient against the real trajectory solver. When the observation set is too short/closely-spaced to separate the two, it refuses the joint fit, trues velocity only, and says so — no false-precision BC. See CLI_USAGE.md for details.
Plan the observations before shooting (MBA-1346). plan-truing evaluates a
discrete set of ranges with the same forward model and finite-difference Jacobian
used by the fitter, then chooses an exact-size, minimum-separation-compliant design.
It reports information gain, singular values, conditioning, rejected/unreachable
candidates, and an explicit MV-only recommendation when the available facility
cannot identify BC:
--measurement-resolution is the assumed independent one-standard-deviation
impact-reading error, not a tolerance or extreme bound. A saved scalar G1/G7
profile may replace the explicit load flags (--profile NAME); velocity-banded BC
profiles and custom drag curves are rejected because they do not have one scalar BC
parameter to identify.
Quantify what the observations actually learned (MBA-1353). Add
--observation-sigma to opt into a weighted joint MV/BC MAP fit and local Gaussian
uncertainty report. A third --observed RANGE:DROP:SIGMA field overrides the default
for one reading. Optional priors are always visible and explicit; predictive output
separates uncertainty in the modeled drop from the wider interval for a future
reading:
The report includes MV/BC 95% intervals, covariance and correlation, chi-square, effective degrees of freedom, prior-domination/weak-identification warnings, and propagated drop bands. Declared sigmas are treated as absolute known errors, so the covariance is not rescaled by residual RMS. With no uncertainty flags, the existing point estimate and output schema are unchanged.
Wind-Call Truing (true-wind)
true-velocity trues the vertical axis; true-wind (MBA-1392) trues the other one.
Give it where your groups actually landed left/right of the aim point and it back-solves
the crosswind that reproduces that miss through the real forward model, plus a wind-call
correction factor against the wind you called:
A horizontal miss is not purely wind, so the command separates it: --twist-rate is
required and gyroscopic spin drift is always modelled and subtracted (a 1:11" .308
drifts ~3.5 in right at 700 yd — read as wind, that alone is several mph of error), and
--latitude with --shot-direction adds Coriolis. Anything the model had no data for
stays absorbed in the solved wind and is named in the report, so a contaminated number is
never presented as pure wind. Signs are documented in one block: --miss positive =
impact right of aim, solved wind positive = wind from the shooter's left
(9 o'clock) pushing impacts right. --miss values are linear inches off the target, not
dial readings, so scope tracking correction factors deliberately do not apply.
See CLI_USAGE.md.
DSF (Drop-Scale-Factor) Truing
Second stage of the Applied Ballistics-style two-stage truing workflow (MBA-1357).
Once true-velocity has fixed the supersonic (Mach > 1.2) muzzle velocity/BC, the
drop discrepancies that grow through the transonic region and into subsonic flight are
no longer fixable by a single MV correction — the residual is a slowly-varying function
of Mach. dsf records observed-drop/predicted-drop ratios at specific Mach <= 1.2
ranges and keys them, one saved profile at a time, to a Mach-indexed table:
# Stage 1: true the muzzle velocity from a supersonic-range drop reading (as above).
# Stage 2: record a subsonic/transonic drop observation on the trued, saved profile.
dsf takes no ballistic parameters of its own — it solves the named saved profile's
own trajectory (no CLI overrides) and derives everything else from --range and
--observed-drop (mil, moa, or in, no separator between number and unit). An
observation whose target-range Mach exceeds 1.2 is rejected outright, pointing back to
true-velocity. Up to 6 distinct Mach-keyed points accumulate per profile; a new point
within 0.05 Mach of an existing one supersedes it (announced on stdout); a 7th distinct
point is rejected, naming --clear-dsf to make room. trajectory --saved-profile and
come-ups --profile then auto-apply the table as a drop-only correction — velocity,
energy, and time of flight are byte-identical to the untrued solve — printing a
table-output-only note; JSON/CSV carry the corrected drop numbers with no equivalent
text. profile save NAME ... --clear-dsf removes an existing table. See
CLI_USAGE.md for the full staging-gate
reference.
Advanced Features
Online Mode (API Integration)
The CLI can query a remote ballistics API server instead of calculating locally. This enables access to enhanced BC data, ML-augmented predictions, and doppler-derived drag curves.
Important: The
--onlinefeature connects to a proprietary cloud service that is not covered by the MIT license. When using--online, trajectory parameters and your IP address are transmitted to our servers. See ONLINE_SERVICE.md for full terms, privacy policy, and data handling practices.
# Use online mode to query the API
# Custom API endpoint
Default API: https://api.ballistics.7.62x51mm.sh/v1/calculate
Online mode benefits:
- Enhanced BC data - Access to doppler-derived ballistic coefficients
- ML predictions - Machine learning augmented trajectory calculations
- BC segments - Velocity-dependent BC modeling from measured data
- Form factor corrections - Bullet-specific drag adjustments
Data transmitted when using --online:
- All trajectory parameters (BC, mass, velocity, wind, atmospheric conditions, etc.)
- Your IP address and client version
- Request logs retained for 30 days, then deleted
To use only local calculations (no network, no data transmission):
Integration Methods
The engine supports two numerical integration methods:
- RK45 (Dormand-Prince Adaptive) - Default method, provides best accuracy with adaptive step sizing
- RK4 (Runge-Kutta 4th Order Fixed-Step) - Available with
--use-rk4-fixedflag for faster computation
Wind Shear Modeling
Model altitude-dependent wind variations:
Transonic Stability Analysis
Analyze projectile stability through the transonic regime:
Provides warnings about transonic instability and minimum pitch damping coefficients.
Trajectory Sampling
Collect trajectory data at regular intervals for detailed analysis:
Angular Motion Physics
Model precession and nutation of spinning projectiles:
Complete Advanced Physics Example
Physics Modeling
The ballistics engine implements comprehensive physics modeling for accurate trajectory prediction:
Aerodynamic Effects
- Drag Modeling - Full standard-projectile family (G1, G2, G5, G6, G7, G8, GI, GS, RA4) plus custom Cd(Mach) curves, with transonic flow corrections
- Form Factor - Projectile efficiency corrections based on shape and design
- Reynolds Number Effects - Reynolds diagnostics and an opt-in helper for genuinely low-Re flow; standard drag tables are not multiplied by an extra correction
Gyroscopic Effects
- Spin Drift - Lateral deviation due to gyroscopic and Magnus effects
- Precession - Gyroscopic precession of spinning projectile
- Nutation - Oscillatory motion superimposed on precession
- Spin Decay - Reduction in spin rate over time due to aerodynamic damping
- Pitch Damping - Aerodynamic moments opposing angular motion
Environmental Effects
- Coriolis Effect - Earth's rotation influence on long-range trajectories
- Magnus Effect - Force from spinning projectile in crossflow
- Wind Shear - Altitude-dependent wind variations
- Atmospheric Stratification - Density and sound speed variations with altitude
Stability Modeling
- Dynamic Stability - Gyroscopic and aerodynamic stability calculations
- Yaw of Repose - Gravity/gyroscopic equilibrium yaw; crosswind yaw is a transient handled by aerodynamic jump
- Limit Cycle Yaw - Bounded oscillatory motion analysis
Language Bindings
Official language bindings are maintained as separate projects:
- Python: ballistics-engine-py - PyO3 bindings via maturin
- Ruby: ballistics-engine-rb - Magnus bindings via rb_sys
These bindings depend on the ballistics-engine crate published on crates.io.
WASM / npm Package
The engine also compiles to WebAssembly (src/wasm.rs, wasm-bindgen) and already powers
ballistics.sh and ballistics.rs in the browser.
It is not yet published to npm for third-party use — scripts/build-npm.sh builds and prepares a
publish-ready package; publishing itself is a manual step (see below).
This builds two wasm-bindgen targets, both with --no-default-features (the default
pdf/online features pull in printpdf/ureq+ring, which do not compile for
wasm32-unknown-unknown — see "Updating the WASM Module" in CLAUDE.md):
pkg/—--target bundler, the package meant fornpm publish. Consumed via a native.wasmES import by bundlers that understand it (webpack withexperiments.asyncWebAssembly, Vite, Rollup +@rollup/plugin-wasm, Parcel).pkg-web/—--target web, a no-bundler build for direct<script type="module">browser use or manual Node usage without a bundler — the same--targetalready used to build ballistics.sh/ballistics.rs's WASM. Documented and built for completeness; not published under the primary package name in this initial pass.
wasm-pack has no built-in dual-target/"publish both" mode, and stitching bundler- and web-target
output into one package.json via manual exports conditions isn't something wasm-pack generates
or tests for you — see the comment header of scripts/build-npm.sh for the full reasoning. A
single bundler-target package as the published npm artifact, with the web build documented
separately, is the ecosystem-standard shape for wasm-bindgen crates on npm.
The script also post-processes each package.json (name, description, license, repository,
keywords, and the files list — including an LICENSE-APACHE entry wasm-pack itself omits even
though it copies the file) and installs README-npm.md as the package's README.md.
Before publishing, edit pkg/package.json's "name" — it ships as the placeholder
"@SCOPE/ballistics-engine". Replace SCOPE with the maintainer's real npm org/user scope (a
scope decision, plus an npm account with publish rights to it, are both needed and don't exist yet
as of this writing). Then:
(--access public is required the first time a scoped package is published, since scoped packages
default to private on free npm accounts; pkg/package.json also sets publishConfig.access to
public so a plain npm publish works too.)
FFI Layer
The library includes a Foreign Function Interface (FFI) layer for integration with iOS, Android, and other platforms. The FFI provides C-compatible bindings for all major functionality.
FFI Features
- C-Compatible Structures - All data structures use C-compatible layouts
- Safe Memory Management - Proper handling of memory across language boundaries
- iOS/Swift Integration - Ready for use with Swift through bridging headers
- Android/JNI Support - Compatible with Java Native Interface
- Monte Carlo Simulation - Statistical analysis with parameter variations
- Error Handling - Graceful error propagation across FFI boundary
Example FFI Usage (C/Swift)
// Create input parameters
FFIBallisticInputs inputs = ;
// Calculate trajectory. The final argument is the integration step in milliseconds
// (minimum 0.1 ms; smaller or non-finite values return NULL).
FFITrajectoryResult* result = ;
// NULL also reports invalid inputs or the 250,000-point resource ceiling.
// Increase the step, reduce the range, or use adaptive RK45 for an over-budget solve.
if
Monte Carlo Simulation via FFI
// Set up Monte Carlo parameters
FFIMonteCarloParams params = ;
// Run simulation with an independent 0.1-radian wind-direction sigma.
// Use ballistics_monte_carlo(...) when no direction variation is desired.
FFIMonteCarloResults* results =
;
// Use statistical results
;
;
// Access individual shots
for
// Clean up
;
Output Formats
All commands support three output formats via the -o flag:
- table (default) - Formatted ASCII table for terminal display
- json - Complete data in JSON format for programmatic use
- csv - Comma-separated values for spreadsheet analysis
Practical Examples
Hunting Zero
Zero a hunting rifle at 200 yards with environmental conditions:
# Calculate zero angle
# Verify trajectory with auto-zero
Long Range Shooting
Analyze trajectory for 1000-yard shot:
Load Development
Compare different loads using Monte Carlo:
# Load 1: Higher velocity, more variation
# Load 2: Lower velocity, more consistent
Advanced Features
BC Segmentation
Velocity-dependent BC modeling accounts for how ballistic coefficient changes as the bullet slows down. Enable with --use-bc-segments:
- Automatically estimates BC segments based on bullet characteristics
- No external data required - uses caliber, weight, and BC
- Identifies bullet type (Match, Hunting, VLD, etc.) from parameters
- Applies physics-based BC degradation curves
Example:
Manual velocity-keyed BC segments — supply your own VMIN:VMAX:BC pairs (repeatable,
velocities in --units) instead of the auto-estimated/table ones. Keyed to velocity, so it
composes with distance-keyed --wind-segment; implies --use-bc-segments and overrides
--bc-table and --bc-table-dir:
BC5D Correction Tables
BC5D tables provide ML-derived, 5-dimensional BC corrections indexed by weight, BC, muzzle velocity, current velocity, and drag model. Tables are caliber-specific and capture the complete velocity-dependent behavior.
Auto-Download Mode (requires online feature):
# Downloads tables automatically on first use
# Force refresh cached tables
Offline Mode with pre-downloaded tables:
Available calibers: .224, .243, .264, .277, .284, .308, .338
Cache locations:
- macOS:
~/Library/Caches/ballistics-engine/bc5d/ - Linux:
~/.cache/ballistics-engine/bc5d/ - Windows:
%LOCALAPPDATA%\ballistics-engine\cache\bc5d\
Tables are approximately 1-1.5 MB each and include CRC32 validation to ensure data integrity.
Advanced Physics Modeling
When enabled, the engine calculates:
- Magnus Effect - Side force from spinning projectiles
- Spin Drift - Lateral drift due to gyroscopic effects
- Coriolis Effect - Earth rotation effects (with latitude input)
- Transonic Drag - Enhanced drag modeling in transonic regime
- Low-Reynolds Helper - Opt-in viscous correction below the standard projectile-table regime
Building from Source
Requirements
- Rust 1.70 or later
- Cargo build system
Build Commands
# Debug build
# Release build (optimized)
# Run tests
# Build documentation
Library Usage
Use as a Rust library in your own projects:
use ;
Performance
Optimized Rust implementation provides:
- Single trajectory (1000m): ~5ms
- Monte Carlo (1000 runs): ~500ms
- BC estimation: ~50ms
- Zero calculation: ~10ms
Common Ballistic Coefficients
| Caliber | Weight | BC (G1) | BC (G7) | Description |
|---|---|---|---|---|
| .223 | 55gr | 0.250 | - | FMJ |
| .223 | 77gr | 0.362 | 0.182 | Match |
| .308 | 168gr | 0.475 | 0.224 | Match |
| .308 | 175gr | 0.505 | 0.253 | Match |
| .308 | 180gr | 0.480 | - | Hunting |
| .338 | 300gr | 0.768 | 0.383 | Match |
| 6.5mm | 140gr | 0.620 | 0.310 | Match |
| .50 | 750gr | 1.050 | 0.520 | Match |
Troubleshooting
Trajectory hits ground early
- Check if you're using
--auto-zeroor setting--anglemanually - Default angle is 0° (horizontal), which will hit ground quickly
- Use
--auto-zero <distance>to automatically calculate proper angle
Units confusion
- Default is Imperial (fps, grains, yards)
- Use
--units metricfor metric system - All inputs must match the selected unit system
Unexpected BC behavior
- G1 and G7 models have different BC values for same bullet
- G7 typically better for boat-tail bullets
- BC segmentation automatically applied based on bullet type
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Run
cargo testandcargo fmt - Submit a pull request
License
This project is licensed under the MIT License - see LICENSE file for details.
Note: The MIT license applies to the open source ballistics-engine library, CLI, and FFI bindings. The --online feature connects to a proprietary cloud service with separate terms. See ONLINE_SERVICE.md for details.
Acknowledgments
- Ballistics physics based on Robert McCoy's "Modern Exterior Ballistics"
- Drag tables from military ballistics research
- BC segmentation algorithms from Bryan Litz's research
- Community contributions and testing
Support
For issues, questions, or contributions:
- GitHub Issues: github.com/ajokela/ballistics-engine/issues