npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

js-ballistics

v3.1.0

Published

ISC library for small arms ballistic calculations (JavaScript ES6+)

Readme

js-ballistics

JavaScript/TypeScript library for small arms ballistic trajectory calculations, powered by a C++ engine compiled to a standalone WebAssembly module.

NPM version License powered by bclibc Jest codecov Demo

SWUbanner

Related projects

js-ballistics is part of the Ballistics Lab ecosystem — one C++/C99 physics core, bindings for every platform (Python, TypeScript, Dart, MicroPython) and the eBalistyka app built on top of it.


Table of Contents


Development

Prerequisites

  • Node.js 22+ (any WebAssembly host works: the module uses no exception handling)
  • Yarn (npm install -g yarn)
  • uv and zig (the ziglang package) for the bare-WASM build, or wasi-sdk 34+ as an alternative

Clone

git clone --recursive https://github.com/o-murphy/js-ballistics.git
cd js-ballistics
yarn install

If you already cloned without --recursive:

git submodule update --init --recursive

WASM toolchain

The engine is bclibc's bare WebAssembly module (imports nothing; the core never throws, so it is built without C++ exceptions). make build-wasm compiles it with zig (~84 KB):

uv run --with ziglang make build-wasm     # zig comes from the ziglang package; or put zig on PATH / pass ZIG=/path/to/zig

wasi-sdk builds the same module (~1 MB) if you prefer it:

export WASI_SDK_PATH=/opt/wasi-sdk-34.0
make build WASM_TOOLCHAIN=wasi-sdk

Build

make build          # bare WASM + TypeScript (full build)
make build-wasm     # WASM only  → generates build/bclibc.js (base64-embedded module)
make build-ts       # TypeScript only  → generates dist/

Run tests

yarn test           # requires WASM to be built first (make build-wasm)

Lint & type-check

npm run lint        # ESLint
npx tsc --noEmit    # TypeScript type check

Deploy to GitHub Pages

make build
npm run deploy      # pushes dist/ to the gh-pages branch

Then enable GitHub Pages in Settings → Pages → Branch: gh-pages.


Installation

npm install js-ballistics
# or
yarn add js-ballistics

The WASM engine is bundled inside the package — no separate build step required.


Quick Start

import {
    Calculator,
    Shot,
    Weapon,
    Ammo,
    DragModel,
    DragTables,
    Atmo,
    Wind,
    UNew,
    Distance,
    Velocity,
    Angular,
} from "js-ballistics";

const dm = new DragModel({
    bc: 0.223,
    dragTable: DragTables.G7,
    weight: 168,
    diameter: 0.308,
});
const ammo = new Ammo({ dm, mv: UNew.FPS(2750) });
const weapon = new Weapon({ sightHeight: UNew.Inch(2) });
const shot = new Shot({ weapon, ammo });

const calc = new Calculator();
await calc.setWeaponZero(shot, UNew.Yard(100));

const result = await calc.fire({
    shot,
    trajectoryRange: UNew.Yard(1000),
    trajectoryStep: UNew.Yard(100),
});

result.samples.forEach((p) => {
    console.log(
        `${p.distance.In(Distance.Yard).toFixed(0)} yd` +
            `  drop: ${p.height.In(Distance.Inch).toFixed(1)} in` +
            `  vel: ${p.velocity.In(Velocity.FPS).toFixed(0)} fps`
    );
});

Core Concepts

| Class | Purpose | | ---------------- | ------------------------------------------------------------------------------- | | DragModel | Ballistic coefficient + drag table (G1, G7, …) | | Ammo | Bullet + muzzle velocity + powder temperature | | Weapon | Sight height, twist rate, zero elevation | | Atmo | Atmospheric conditions (altitude, temp, pressure, humidity) | | Wind | Wind layer (velocity, direction, until-distance) | | Shot | Combines all inputs; holds look/relative/cant angles and Coriolis | | Calculator | Engine wrapper; async fire(), setWeaponZero(), barrelElevationForTarget() | | HitResult | Trajectory array + error + flag helpers | | TrajectoryData | Per-point data: distance, height, velocity, windage, energy, OGW, … |


Usage Examples

Basic Trajectory

import {
    Calculator,
    Shot,
    Weapon,
    Ammo,
    DragModel,
    DragTables,
    UNew,
} from "js-ballistics";

const shot = new Shot({
    weapon: new Weapon({ sightHeight: UNew.Inch(1.5) }),
    ammo: new Ammo({
        dm: new DragModel({ bc: 0.295, dragTable: DragTables.G7 }),
        mv: UNew.MPS(900),
    }),
});

const calc = new Calculator();
await calc.setWeaponZero(shot, UNew.Meter(100));

const hit = await calc.fire({
    shot,
    trajectoryRange: UNew.Meter(1000),
    trajectoryStep: UNew.Meter(100),
});

hit.samples.forEach((p) => console.log(p.formatted().join("  ")));

Zero-Finding

// setWeaponZero modifies shot.weapon.zeroElevation in place
await calc.setWeaponZero(shot, UNew.Meter(200));

// or get the elevation angle without modifying the shot
const elevation = await calc.barrelElevationForTarget(shot, UNew.Meter(200));
console.log(`Elevation: ${elevation.In(Angular.MOA).toFixed(2)} MOA`);

Wind Layers

import { Wind, UNew } from "js-ballistics";

// 5 m/s from the right up to 300 m, then 3 m/s head-on beyond
const winds = [
    new Wind({
        velocity: UNew.MPS(5),
        directionFrom: UNew.Degree(90),
        untilDistance: UNew.Meter(300),
    }),
    new Wind({ velocity: UNew.MPS(3), directionFrom: UNew.Degree(0) }),
];

const shot = new Shot({ weapon, ammo, winds });

Custom Atmosphere

import { Atmo, Vacuum, UNew } from "js-ballistics";

// ICAO standard at sea level
const standard = Atmo.standard();

// Custom conditions
const hot_dry = new Atmo({
    altitude: UNew.Meter(500),
    temperature: UNew.Celsius(35),
    pressure: UNew.hPa(1000),
    humidity: 0.2,
});

// Vacuum (no drag — useful for testing)
const vac = new Vacuum({ altitude: UNew.Foot(0) });

const shot = new Shot({ weapon, ammo, atmo: hot_dry });

Multi-BC Drag Model

Use measured BCs at several velocity/Mach nodes for better accuracy:

import { DragModelMultiBC, BCPoint, DragTables, UNew } from "js-ballistics";

const dm = DragModelMultiBC({
    bcPoints: [
        new BCPoint({ BC: 0.275, V: UNew.MPS(900) }),
        new BCPoint({ BC: 0.26, V: UNew.MPS(700) }),
        new BCPoint({ BC: 0.245, V: UNew.MPS(500) }),
    ],
    dragTable: DragTables.G7,
    weight: 175,
    diameter: 0.308,
});

Coriolis Effect

const shot = new Shot({
    weapon,
    ammo,
    coriolis: {
        latitudeDeg: 48.5, // Kyiv latitude
        azimuthDeg: 90.0, // shooting East
    },
});

// Or set after construction:
shot.latitudeDeg = 48.5;
shot.azimuthDeg = 90.0;

Omit azimuthDeg for the flat-fire approximation (horizontal drift only). Omit latitudeDeg entirely to disable Coriolis.


Trajectory Flags

Request specific event points and look them up by flag:

import { TrajFlag } from "js-ballistics";

const hit = await calc.fire({
    shot,
    trajectoryRange: UNew.Yard(1000),
    trajectoryStep: UNew.Yard(10),
    filterFlags: TrajFlag.ALL,
});

const zeroDown = hit.flag(TrajFlag.ZERO_DOWN);
const apex = hit.flag(TrajFlag.APEX);
const mach1 = hit.flag(TrajFlag.MACH);

console.log(
    `Zero crossing: ${zeroDown?.distance.In(Distance.Yard).toFixed(1)} yd`
);
console.log(`Apex at:       ${apex?.distance.In(Distance.Yard).toFixed(1)} yd`);
console.log(
    `Mach 1 at:     ${mach1?.distance.In(Distance.Yard).toFixed(1)} yd`
);

Available flags: NONE, ZERO_UP, ZERO_DOWN, ZERO, MACH, RANGE, APEX, ALL, MRT.


Danger Space

// How much ranging error is tolerable for a 1.5 m tall target at 500 m?
const ds = await hit.dangerSpace(UNew.Meter(500), UNew.Meter(1.5));

console.log(
    `Danger space begin: ${ds.begin.distance.In(Distance.Meter).toFixed(1)} m`
);
console.log(
    `Danger space end:   ${ds.end.distance.In(Distance.Meter).toFixed(1)} m`
);
console.log(
    `Depth:              ${(
        ds.end.slantDistance.In(Distance.Meter) -
        ds.begin.slantDistance.In(Distance.Meter)
    ).toFixed(1)} m`
);

Powder Temperature Sensitivity

const ammo = new Ammo({ dm, mv: UNew.FPS(2750), powderTemp: UNew.Celsius(15) });

// Compute sensitivity from two measurements
ammo.calcPowderSens(UNew.FPS(2723), UNew.Celsius(0));
ammo.usePowderSensitivity = true;

// Velocity is now automatically adjusted for the atmosphere's powder temperature
const shot = new Shot({
    weapon,
    ammo,
    atmo: new Atmo({ temperature: UNew.Celsius(-10) }),
});

Integration Method

Choose between RK4 (more accurate, default), Euler (faster), and Velocity Verlet (energy-conserving):

import { Calculator, IntegrationMethod } from "js-ballistics";

const calcRK4 = new Calculator({ method: IntegrationMethod.RK4 });
const calcEuler = new Calculator({ method: IntegrationMethod.EULER });
const calcVerlet = new Calculator({ method: IntegrationMethod.VELOCITY_VERLET });

Custom Calculator Config

const calc = new Calculator({
    method: IntegrationMethod.RK4,
    config: {
        minimumVelocity: 100, // fps — stop when velocity drops below this
        minimumAltitude: -1000, // ft  — stop when altitude drops below this
        maximumDrop: -5000, // ft  — stop when drop exceeds this
        stepMultiplier: 0.5, // halve integration step for higher precision
    },
});

Browser / CDN Usage

Via jsDelivr

<script type="module">
    import {
        Calculator,
        Shot,
        Weapon,
        Ammo,
        DragModel,
        DragTables,
        UNew,
    } from "https://cdn.jsdelivr.net/npm/js-ballistics/dist/index.js";

    const calc = new Calculator();
    // ... use as in Node.js examples above
</script>

Via unpkg

<script type="module">
    import * as Ballistics from "https://unpkg.com/js-ballistics/dist/index.js";
    const { Calculator, UNew, DragTables } = Ballistics;
</script>

Interactive HTML Example

Live demo →

A full browser demo with trajectory chart is included in the package at dist/index.html. Open it directly or serve with any static file server after running yarn build.

yarn build
npx serve dist/

API Reference

Calculator

new Calculator(options?: { method?: IntegrationMethod; config?: Partial<Config> })

calc.fire({
    shot:             Shot,
    trajectoryRange:  number | Distance,
    trajectoryStep?:  number | Distance,   // default: same as range (one output point)
    timeStep?:        number,              // integration sub-step in seconds
    filterFlags?:     TrajFlag,            // default: TrajFlag.RANGE
    raiseRangeError?: boolean,             // default: true
}): Promise<HitResult>

calc.setWeaponZero(shot: Shot, distance: number | Distance): Promise<Angular>
calc.barrelElevationForTarget(shot: Shot, distance: number | Distance): Promise<Angular>

HitResult

hit.records: TrajectoryData[]     // exact, chronological output: every scheduled sample and every event, each its own row
hit.samples: TrajectoryData[]     // deterministic RANGE/TIME schedule table; a sample is annotated with an event's flag
                                   // only when the two are the same instant, never merely the nearest one
hit.events:  TrajectoryData[]     // exact ZERO/MACH/APEX/MRT event rows
hit.error?:  Error                // set when raiseRangeError=false and shot is incomplete
hit.flag(flag: TrajFlag): TrajectoryData | undefined
hit.zeros():    TrajectoryData[]
hit.getAtDistance(d: Distance): TrajectoryData
hit.getAt(key, value, epsilon?, startFromTime?): Promise<TrajectoryData>
hit.dangerSpace(atRange, targetHeight): Promise<DangerSpace>

TrajectoryData fields

time, distance, velocity, mach, height, slantHeight, dropAngle, windage, windageAngle, slantDistance, angle, densityRatio, drag, energy, ogw, flag

Unit system

// Construct a unit value
UNew.Meter(100)      UNew.Foot(328)    UNew.Yard(109)
UNew.MPS(900)        UNew.FPS(2953)    UNew.MPH(35)
UNew.Celsius(15)     UNew.Fahrenheit(59)
UNew.hPa(1013.25)    UNew.InHg(29.92)
UNew.Degree(5)       UNew.MOA(17.2)    UNew.Radian(0.0873)
UNew.Grain(168)      UNew.Gram(10.9)
UNew.Inch(0.308)

// Convert
value.In(Distance.Meter)   // → number
value.foot                 // shorthand for .In(Distance.Foot)
value.meter                // shorthand for .In(Distance.Meter)

Risk Notice

The library performs a limited simulation of a complex physical process and uses many approximations. Calculation results must not be considered as completely or reliably reflecting actual projectile behavior.

Results may be used for educational purposes only. They must not be considered reliable for applications where incorrect results could cause financial harm or endanger human life.

THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.