This is the only official repository for ballistic-solver.
Official repository: https://github.com/ujinf74/ballistic-solver
Do not download ZIP files, binaries, or installers from third-party copies, mirrors, or reuploads. Use only the Releases page of this repository.
ballistic-solver is a native C/C++ numerical solver that computes launch angles to intercept moving targets under gravity and quadratic air drag, with optional wind.
Unlike vacuum / closed-form solvers, this project simulates the projectile and solves the intercept numerically, aiming for robust real-time use even when trajectories are strongly curved.
pip install ballistic-solverRequires Python >= 3.10.
import ballistic_solver as bs
result = bs.solve(
relPos0=(120, 30, 5),
relVel=(2, -1, 0),
v0=90,
kDrag=0.002,
)
print(result["theta"], result["phi"], result["miss"])
print(result["success"], result["status"], result["message"])For tighter convergence without manually tuning every knob:
params = bs.params_preset("precise")
result = bs.solve((120, 30, 5), (2, -1, 0), 90, 0.002, params=params)For repeated solves, Python also provides a thin convenience wrapper:
solver = bs.Solver.preset("precise")
result = solver.solve((120, 30, 5), (2, -1, 0), 90, 0.002)Highly curved trajectories under strong air drag, still converging to a hit against moving targets.
ballistic_demo_low.mp4
Many launch-angle solvers depend on vacuum assumptions or partially linearized models. This project instead simulates the projectile and solves the intercept numerically, targeting robustness in real-time simulations and integration scenarios.
- Moving targets supported
- Constant-acceleration targets supported via the extended API
- Strong air resistance (quadratic drag) supported
- Low / High arc selection (since v0.2)
- Wind vector supported (since v0.3)
- Extended C ABI utilities (since v0.4)
- Fast / balanced / precise solver presets
- Physical drag helper:
kDrag = 0.5 * rho * Cd * area / mass - Robust in strongly nonlinear regimes (no analytic assumptions)
- Best-effort result returned even without perfect convergence
- Explicit success / failure reporting (+ diagnostic message)
- Stable C ABI for multi-language use
- Header-only C++ core
- Easy install via PyPI:
pip install ballistic-solver
solve(relPos0, relVel, v0, kDrag, arcMode=0, params=None) -> dictrelPos0: target relative position at t=0 (x,y,z)relVel: target relative velocity (x,y,z)v0: muzzle speed (scalar)kDrag: quadratic drag coefficientarcMode:0/1or"low"/"high"(case-insensitive)params: optionalBallisticParamsfor advanced tuning (gravity, wind, integrator and solver knobs)
Returned dict keys include:
success(bool)theta,phi(radians)miss(closest-approach distance)tStar(time of closest approach)relMissAtStar(3-vector miss attStar)status(SolveStatus integer)message(short diagnostic string)- plus convergence diagnostics (
iterations,acceptedSteps,lastLambda,lastAlpha)
params = bs.params_preset("fast") # or "balanced", "precise"
kDrag = bs.k_drag_from_physical(
airDensity=1.225,
dragCoefficient=0.30,
area=0.00426,
mass=0.145,
)For constant-acceleration targets:
result = bs.solve_accel(
relPos0=(120, 30, 5),
relVel=(2, -1, 0),
relAcc=(0, 0.2, 0),
v0=90,
kDrag=0.002,
params=bs.params_preset("precise"),
)The solver internally integrates projectile motion using:
- 4th-order Runge–Kutta (RK4)
- Fixed timestep
dt - Quadratic drag:
a = (0, 0, -g) - kDrag * |v - wind| * (v - wind) - Wind as air velocity
To match in-game ballistics, your runtime simulation must use the same physical model and integrator configuration.
If your game uses a different integrator (e.g., Euler) or a different timestep, the computed launch angles may not hit even if the solver reports success.
Primary intercept API:
void ballistic_inputs_init(BallisticInputs* in);
int32_t ballistic_solve(const BallisticInputs* in, BallisticOutputs* out);Return value policy:
0: API call completed andoutwas filled.<0: API-level failure, such as null pointers or an internal exception.- Numerical solve success is reported separately by
out->successandout->status. - ABI v3 adds convergence diagnostics to
BallisticOutputs:iterations,acceptedSteps,lastLambda, andlastAlpha.
Callers should check both:
int32_t rc = ballistic_solve(&in, &out);
if (rc != 0) {
/* API call failed */
}
if (!out.success) {
/* Solver ran but did not satisfy the requested tolerance. */
}Since v0.4.0, additional utility functions are available:
void ballistic_rk4_step(...);
int32_t ballistic_simulate_trajectory(...);
int32_t ballistic_simulate_trajectory_from_angles(...);
int32_t ballistic_find_closest_approach(...);
int32_t ballistic_vacuum_arc_angles_to_point(...);
void ballistic_initial_guess_vacuum_lead(...);Additional extended APIs include:
void ballistic_accel_inputs_init(BallisticAccelInputs* in);
int32_t ballistic_solve_accel(const BallisticAccelInputs* in, BallisticOutputs* out);
int32_t ballistic_inputs_apply_preset(BallisticInputs* in, int32_t preset);
int32_t ballistic_k_drag_from_physical(...);
int32_t ballistic_make_relative_motion(...);See ballistic_solver_c_api.h for full signatures and parameter definitions.
This enables usage from:
- C / C++
- Python (ctypes via the C ABI)
- C# / .NET / Unity (P/Invoke)
- Unity (UnityEngine.Vector3 wrapper example)
- Godot 4 (GDExtension addon-style example)
- Others via FFI
Prebuilt native binaries are provided via GitHub Releases.
C ABI convention:
arcMode = 0→ LowarcMode = 1→ High
High arc example:
ballistic_demo_high.mp4
C ABI convention:
wind[3]= air velocity vector (same frame asrelPos0/relVel)- Drag uses relative airspeed:
v_rel = v_projectile - wind
Wind demo:
ballistic_demo_wind.mp4
Download the archive for your platform from Releases.
Each release contains:
-
Shared library
- Windows:
ballistic_solver.dll - Linux:
libballistic_solver.so - macOS:
libballistic_solver.dylib
- Windows:
-
C ABI header:
ballistic_solver_c_api.h
A C# P/Invoke example is available in:
examples/dotnet/
On Windows, place ballistic_solver.dll next to the executable
(or ensure it is discoverable via PATH),
then call ballistic_solve via DllImport.
This works directly inside Unity.
- Simulate projectile motion using RK4 integration with drag (+ wind)
- Track the closest approach between projectile and target
- Express the miss at closest approach as an angular residual
- Solve the nonlinear system using damped least squares (Levenberg–Marquardt)
- Accelerate Jacobian updates with Broyden-style refinement
- Return the best solution found
Failure cases are explicitly detected and reported.
The solver returns a best-effort result even when it cannot satisfy tolMiss.
Common causes include:
- invalid inputs (
v0 <= 0, non-positiveg,dt,tMax, ormaxIter) - geometrically unreachable vacuum targets
- targets that require an intercept beyond
tMax - strong drag or high-arc cases where the selected arc branch cannot be maintained
- iteration limits that are too tight for the requested tolerance
Use success, status, message, and miss together when deciding whether to
accept a solution.
BallisticOutputs.status / Python result["status"] corresponds to:
0= Ok1= InvalidInput2= InitialResidualFailed3= JacobianFailed4= LMStepSingular5= ResidualFailedDuringSearch6= LineSearchRejected7= LambdaTriesExhausted8= MaxIterReached
message contains a short diagnostic string.
cmake -S . -B build
cmake --build build -j
ctest --test-dir buildThe shared library target is ballistic_solver.
Distributed regression and benchmark scripts are available:
python tests/random_regression.py
python benchmarks/linear_cases.pyThe CI workflow runs CTest smoke coverage plus the Python regression script. The regression script includes analytic vacuum checks, constructed moving-target vacuum cases, unreachable-target checks, randomized linear cases, and constant-acceleration API smoke coverage.
Experimental solver-variant benchmarks live under tools/bench_variants/.
They are local development tools and are intentionally excluded from source distributions.
Benchmark numbers depend on CPU, OS, compiler, build type, Python version, and whether native or Python entrypoints are measured. Record those fields when publishing comparisons.
Reference result from a local Windows Release build, 500 generated linear-target cases:
fast: median 0.107 ms, p95 0.233 ms, p95 miss 3.399e-02 m
balanced: median 0.219 ms, p95 0.492 ms, p95 miss 7.287e-03 m
precise: median 0.265 ms, p95 0.583 ms, p95 miss 7.655e-06 m
- Plain C layout across the ABI boundary
- Fixed-size arrays only
- No dynamic allocation across the boundary
MIT License