ballistics-engine 0.36.2 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -1097
- package/ballistics_engine.d.ts +0 -70
- package/ballistics_engine.js +8 -753
- package/ballistics_engine_bg.js +655 -0
- package/ballistics_engine_bg.wasm +0 -0
- package/package.json +12 -5
package/README.md
CHANGED
|
@@ -1,1145 +1,168 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ballistics-engine (WASM)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
WebAssembly build of [`ballistics-engine`](https://github.com/ajokela/ballistics-engine), a
|
|
4
|
+
high-performance ballistics trajectory engine (RK4 integration, wind/Coriolis/spin-drift/Magnus
|
|
5
|
+
effects, custom drag tables, Monte Carlo, and more). This package exposes the same CLI-style
|
|
6
|
+
command surface as the native Rust binary, plus a small object-oriented `Calculator` class, to
|
|
7
|
+
JavaScript/TypeScript.
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
Built with `wasm-pack` and `--no-default-features` — the native crate's default `pdf`/`online`
|
|
10
|
+
features pull in dependencies that don't compile for `wasm32-unknown-unknown`, so the PDF dope-card
|
|
11
|
+
export and the online BC-estimation API are not available from WASM.
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
If you only use the `Calculator` API, you can rebuild this module without the terminal commands
|
|
14
|
+
you never call and cut it roughly in half — see **Size** under [Caveats](#caveats).
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **Canted-Rifle Modeling** - Model a rifle zeroed level but fired canted (`--cant <DEGREES>`, alias `--cant-angle`, on `trajectory`/`monte-carlo`); clockwise cant shifts point of impact right and low downrange for a rifle with an upward zero correction — see [CLI_USAGE.md](CLI_USAGE.md#canted-shooting)
|
|
13
|
-
- **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, on `trajectory` and every zero-solving subcommand; solve-json `shot.zero_poi_up_m`/`zero_poi_right_m`; saved-profile fields; `.a7p` zero click counts convertible via `profile import --zero-click`) — see [CLI_USAGE.md](CLI_USAGE.md#sight-geometry-and-zero-state)
|
|
14
|
-
- **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-json `rifle.sight_offset_lateral_m`; saved-profile field; also on `monte-carlo`/WEZ mirroring `--cant`) — see [CLI_USAGE.md](CLI_USAGE.md#sight-geometry-and-zero-state)
|
|
15
|
-
- **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 NAME` on `trajectory`/`come-ups`/`wind-card`/`range-table`/`dsf`/`plan-truing`; corrections are added to total-correction dial outputs before the tracking-CF division; profile-CSV `V_OFFSET_MIL`/`H_OFFSET_MIL` columns and `.a7p --zero-click` import feed sets automatically; unknown names fail loudly listing the available sets) — see [CLI_USAGE.md](CLI_USAGE.md#named-zero-sets-and-per-load-offsets-profile-zero-set---zero-set)
|
|
16
|
-
- **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 relabeled `Drop (target)`/`drop_target_in`; solve-json `shot.drops_reference`) — see [CLI_USAGE.md](CLI_USAGE.md#drops-reference-los-vs-target-plane---drops-reference--mba-1403)
|
|
17
|
-
- **Scope Tracking Correction Factors** - Compensate turrets that don't track their nominal click value: derive `CF = actual/dialed` from a tall-target test (`tall-target` subcommand, 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-velocity` multiplies dialed observations by the CF (scope-dial to true angular) so scope error is not baked into trued MV/BC — see [CLI_USAGE.md](CLI_USAGE.md#scope-tracking-correction-factors---elevation-cf---windage-cf-tall-target--mba-1358)
|
|
18
|
-
- **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 (`trajectory` summary line; solve-json `summary.equivalent_horizontal_range_m`; public `TrajectorySolver::equivalent_horizontal_range`) — see [CLI_USAGE.md](CLI_USAGE.md#equivalent-horizontal-range-bdc-shoot-to--mba-1395)
|
|
19
|
-
- **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|bdc` builds descriptions in one shared serde schema that saved profiles, `--reticle-json` and solve-json's optional `reticle` block all speak). Horus/TREMOR grid layouts, wind-dot calibration and any vendor catalog are deliberately excluded — see [CLI_USAGE.md](CLI_USAGE.md#reticle-hold-points-reticle--mba-1361)
|
|
20
|
-
- **Reticle/BDC Inverse Solvers** - Three read-only solvers over an existing load, sharing one drop-vs-range root find: `mark-to-range` maps 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-match` fits 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-zero` min-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](CLI_USAGE.md#reticlebdc-inverse-solvers--mba-1362)
|
|
21
|
-
- **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 versioned `RobustHoldReportV1` JSON 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](CLI_USAGE.md#robust-hold-corridors-hold-corridor--mba-1349)
|
|
22
|
-
- **Moving-Target Lead** - Wind-aware hold tables for targets moving at a constant speed/angle, with iterative intercept-range correction for non-perpendicular motion (`lead` subcommand; public `ballistics_engine::calculate_lead` API) — see [CLI_USAGE.md](CLI_USAGE.md#moving-target-lead)
|
|
23
|
-
- **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); `lead` also gained `trajectory`'s powder-temperature flags for muzzle-velocity parity between the two — see [CLI_USAGE.md](CLI_USAGE.md#mover-ring---target-speed)
|
|
24
|
-
- **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](CLI_USAGE.md#load-comparison-compare)
|
|
25
|
-
- **Powder Temperature Command** - Resolve the temperature-adjusted muzzle velocity standalone, without a trajectory solve (`powder` subcommand): linear fps-per-degree model or a measured temperature→velocity curve, optional `--sweep` velocity ladder and muzzle energy; shares the solvers' exact resolution code (public `resolve_powder_adjusted_velocity` API) — see [CLI_USAGE.md](CLI_USAGE.md#powder-temperature-velocity-powder)
|
|
26
|
-
- **Unit Conversion** - Seamless switching between Imperial (default) and Metric units
|
|
27
|
-
- **BC Segmentation** - Velocity-dependent ballistic coefficient modeling with automatic estimation
|
|
28
|
-
- **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-type` entirely, with an explicit `--temperature` still honored for correct powder-temperature sensitivity — see [CLI_USAGE.md](CLI_USAGE.md#density-altitude-as-a-direct-input---density-altitude)
|
|
29
|
-
- **Clock-Position Wind Entry** - Enter wind direction as the dominant field convention: marked clock positions (`--wind-direction 3oc`, `10h30`, or `10:30`; 12 o'clock = headwind, minutes count 0.5°) alongside plain degrees on every wind-direction flag and the WASM terminal; inside `--wind-segment` the colon-free forms apply (`10:3oc:400`) while `10:30:400` keeps its numeric SPEED:ANGLE:DIST meaning; bare numbers stay degrees everywhere — see [CLI_USAGE.md](CLI_USAGE.md#wind-direction-entry-degrees-clock-positions--mba-1367)
|
|
30
|
-
- **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-direction` on `trajectory`/`monte-carlo`; covers the single direction, location-CSV WIND_DIR, and every `--wind-segment` angle; Monte Carlo converts before dispersion sampling; solve-json `wind.wind_reference`; WASM builder `setWindReference`/`setShotDirection`; wind FROM north on a shot due north = pure headwind, pinned) — see [CLI_USAGE.md](CLI_USAGE.md#earth-fixed-compass-bearings---wind-ref-compass--mba-1368)
|
|
31
|
-
- **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>` on `trajectory`/`monte-carlo`, or the segment's optional 4th field; positive = updraft, raises point of impact) — see [CLI_USAGE.md](CLI_USAGE.md#vertical-wind)
|
|
32
|
-
- **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](CLI_USAGE.md#wind-card)
|
|
33
|
-
- **Monte Carlo Simulations** - Statistical analysis with parameter uncertainties
|
|
34
|
-
- **BC Estimation** - Estimate ballistic coefficients from trajectory data
|
|
35
|
-
- **Advanced Physics**:
|
|
36
|
-
- **Spin Effects**: Magnus effect and empirical Litz spin drift
|
|
37
|
-
- **Earth Effects**: Coriolis effect with latitude-dependent calculations
|
|
38
|
-
- **Angular Motion**: Gyroscopic precession and nutation physics
|
|
39
|
-
- **Transonic Analysis**: Pitch damping coefficients and stability warnings
|
|
40
|
-
- **Trajectory Sampling**: Regular interval data collection for analysis
|
|
41
|
-
- **Form Factor Corrections**: Bullet-specific drag adjustments
|
|
42
|
-
- **Multiple Output Formats** - JSON, CSV, formatted tables, and printable PDF dope cards
|
|
43
|
-
- **Terminal Chart** - Inline drop, drift, velocity, and energy vs. range charts right in the terminal (`trajectory --plot`, Unicode braille-dot canvas by default, `--plot ascii` fallback); pure Rust, zero new dependencies, no ANSI colors — see [CLI_USAGE.md](CLI_USAGE.md#terminal-chart---plot)
|
|
44
|
-
- **Profile import**: `ballistics profile import file.a7p` — imports ArcherBC2 `.a7p` profiles (rifle, bullet, atmosphere, zero) with a full mapping report; `--dry-run` previews without saving
|
|
45
|
-
- **Online Reverse Solvers** - Optional `login` + `recommend-powder`/`recommend-twist`/`recommend-col`/`calibrate-bc` subcommands query the hosted service for load, twist, cartridge-overall-length, and BC suggestions using a CLI access token saved from your ballisticsinsight.com account (`BALLISTICS_API_TOKEN` env var, or `~/.ballistics/credentials.toml`); all local subcommands work offline with no token — see [CLI_USAGE.md](CLI_USAGE.md#online-reverse-solvers)
|
|
46
|
-
- **Solution Diff Attribution** — explain why two resolved solutions differ, attributed by input group with an explicit interaction remainder (`explain`) — see [CLI_USAGE.md](CLI_USAGE.md#solution-diff-attribution-explain--mba-1345)
|
|
47
|
-
- **Per-Input Error Budget** — rank which input is worth measuring better by its share of impact uncertainty, with the hit-probability gain if it were perfected (`error-budget`) — see [CLI_USAGE.md](CLI_USAGE.md#per-input-error-budget-error-budget--mba-1347)
|
|
48
|
-
- **Tolerance Envelopes** — how wrong one input may be before the shot leaves the target (`tolerance`) — see [CLI_USAGE.md](CLI_USAGE.md#tolerance-envelopes-tolerance--mba-1350)
|
|
49
|
-
- **Constrained Dial & Hold Planning** — rank whole-click dial, reticle-hold, and hybrid execution plans for a TRUE angular correction against a real optic's turret mechanics, travel, and hold bounds, with infeasibility naming the limiting mechanism rather than a silent clamp (`dial-plan`) — see [CLI_USAGE.md](CLI_USAGE.md#constrained-dial--hold-planning-dial-plan--mba-1348)
|
|
50
|
-
- **Adaptive Range Cards** — a range card that provably reconstructs the trajectory within a stated elevation/windage error budget, with click rounding from a saved optic and a footer stating the measured worst-case error and the grid it was verified against — a MEASURED error bound and always-present anchors, not a claim of fewer rows than a well-chosen fixed step (`adaptive-card`) — see [CLI_USAGE.md](CLI_USAGE.md#adaptive-range-cards-adaptive-card--mba-1351)
|
|
51
|
-
- **Confidence-Controlled Monte Carlo** — every *sampled* hit-probability estimate (the fixed-count run and the opt-in `--adaptive` mode — not the separate `--wez` sweep, which reports a bare point estimate by design) states its sample count, method, confidence level, and interval: an additive Wilson companion line/JSON key on the existing fixed-count run, and `--adaptive` sampling in batches until an anytime-valid confidence sequence meets a requested half-width instead of guessing `--num-sims` (`monte-carlo --adaptive`, MBA-1352) — see [CLI_USAGE.md](CLI_USAGE.md#confidence-controlled-sampling---adaptive--mba-1352)
|
|
16
|
+
> This package is built from `pkg/` (the `wasm-pack --target bundler` output) via
|
|
17
|
+
> `scripts/build-npm.sh` in the source repo. The same script also produces a `pkg-web/` build
|
|
18
|
+
> (`--target web`) for use without a bundler — see "Browser without a bundler" below.
|
|
52
19
|
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
### From crates.io
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
cargo install ballistics-engine
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### From Source
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
git clone https://github.com/ajokela/ballistics-engine.git
|
|
65
|
-
cd ballistics-engine
|
|
66
|
-
cargo build --release
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
The binary will be at: `target/release/ballistics`
|
|
70
|
-
|
|
71
|
-
### Feature Flags
|
|
72
|
-
|
|
73
|
-
| Feature | Default | Description |
|
|
74
|
-
|---------|---------|-------------|
|
|
75
|
-
| `online` | ✅ Yes | HTTP client for API integration (`--online` flag) |
|
|
76
|
-
|
|
77
|
-
To build without network capabilities:
|
|
78
|
-
```bash
|
|
79
|
-
cargo build --release --no-default-features
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Quick Start
|
|
83
|
-
|
|
84
|
-
### Basic Trajectory (Imperial Units - Default)
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
# .308 Winchester, 168gr bullet at 2700 fps
|
|
88
|
-
./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 1000
|
|
89
|
-
|
|
90
|
-
# With automatic zeroing at 200 yards
|
|
91
|
-
./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --auto-zero 200 --max-range 500
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Metric Units
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# Same bullet in metric units
|
|
98
|
-
./ballistics trajectory --units metric -v 823 -b 0.475 -m 10.9 -d 7.82 --max-range 1000
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Unit Systems
|
|
102
|
-
|
|
103
|
-
The engine supports two unit systems, selectable with the `--units` flag:
|
|
104
|
-
|
|
105
|
-
### Imperial (Default)
|
|
106
|
-
- **Velocity**: feet per second (fps)
|
|
107
|
-
- **Mass**: grains
|
|
108
|
-
- **Distance**: yards
|
|
109
|
-
- **Diameter**: inches
|
|
110
|
-
- **Temperature**: Fahrenheit
|
|
111
|
-
- **Pressure**: inHg
|
|
112
|
-
- **Wind**: mph
|
|
113
|
-
|
|
114
|
-
### Metric
|
|
115
|
-
- **Velocity**: meters per second (m/s)
|
|
116
|
-
- **Mass**: grams
|
|
117
|
-
- **Distance**: meters
|
|
118
|
-
- **Diameter**: millimeters
|
|
119
|
-
- **Temperature**: Celsius
|
|
120
|
-
- **Pressure**: hPa (millibars)
|
|
121
|
-
- **Wind**: m/s
|
|
122
|
-
|
|
123
|
-
## Commands
|
|
124
|
-
|
|
125
|
-
### Trajectory Calculation
|
|
126
|
-
|
|
127
|
-
Calculate ballistic trajectory with environmental conditions:
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
# Imperial units (default)
|
|
131
|
-
./ballistics trajectory \
|
|
132
|
-
-v 2700 # Velocity (fps)
|
|
133
|
-
-b 0.475 # Ballistic coefficient
|
|
134
|
-
-m 168 # Mass (grains)
|
|
135
|
-
-d 0.308 # Diameter (inches)
|
|
136
|
-
--drag-model g7 # G7 drag model
|
|
137
|
-
--angle 0 # Launch angle (degrees)
|
|
138
|
-
--max-range 1000 # Maximum range (yards)
|
|
139
|
-
--wind-speed 10 # Wind speed (mph)
|
|
140
|
-
--wind-direction 90 # Wind from right (degrees)
|
|
141
|
-
--temperature 59 # Temperature (Fahrenheit)
|
|
142
|
-
--pressure 29.92 # Pressure (inHg)
|
|
143
|
-
--humidity 50 # Relative humidity (%)
|
|
144
|
-
--altitude 0 # Altitude (feet)
|
|
145
|
-
--full # Show all trajectory points
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
#### Auto-Zero Feature
|
|
149
|
-
|
|
150
|
-
Automatically calculate and apply the zero angle for a specific distance:
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
# Zero at 200 yards and show trajectory to 500 yards
|
|
154
|
-
./ballistics trajectory \
|
|
155
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
156
|
-
--auto-zero 200 \ # Automatically zero at 200 yards
|
|
157
|
-
--max-range 500 \
|
|
158
|
-
--full
|
|
159
|
-
|
|
160
|
-
# Custom sight height for auto-zero
|
|
161
|
-
./ballistics trajectory \
|
|
162
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
163
|
-
--auto-zero 100 \
|
|
164
|
-
--sight-height 0.055 # 2.2 inches in yards
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
#### Zero-Day Conditions (zero shift)
|
|
168
|
-
|
|
169
|
-
A rifle's zero is a fixed barrel angle set on the day you sighted in. If you later shoot
|
|
170
|
-
in different weather — or with a different muzzle velocity (e.g. a cold vs. warm powder
|
|
171
|
-
temperature) — the point of impact shifts. By default `--auto-zero` solves the zero angle
|
|
172
|
-
using the same conditions you pass for the shot, which assumes you zeroed in today's
|
|
173
|
-
conditions. The `--zero-*` flags let you decouple the two: the zero **angle** is solved
|
|
174
|
-
under the conditions the rifle was actually zeroed in, while the trajectory itself runs
|
|
175
|
-
under the current shot-day conditions.
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
# Zeroed on a cold morning (28 F) at 2600 fps; shooting this afternoon at 85 F / 2700 fps.
|
|
179
|
-
# The zero angle is solved for the cold/slow load, then the warm/fast trajectory is
|
|
180
|
-
# computed against it — so the dope correctly shows the point of impact drifting high.
|
|
181
|
-
./ballistics trajectory \
|
|
182
|
-
-v 2700 -b 0.19 -m 77 -d 0.224 --drag-model g7 \
|
|
183
|
-
--temperature 85 --pressure 29.92 \
|
|
184
|
-
--auto-zero 100 --max-range 1000 --full \
|
|
185
|
-
--zero-velocity 2600 \
|
|
186
|
-
--zero-temperature 28
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
Available overrides (each independently optional; any omitted flag falls back to the
|
|
190
|
-
shot-day value, so leaving them all off reproduces the previous behavior exactly):
|
|
191
|
-
|
|
192
|
-
| Flag | Meaning | Units (imperial / metric) |
|
|
193
|
-
|------|---------|---------------------------|
|
|
194
|
-
| `--zero-velocity` | Muzzle velocity on the zeroing day | fps / m·s⁻¹ |
|
|
195
|
-
| `--zero-temperature` | Air temperature on the zeroing day | °F / °C |
|
|
196
|
-
| `--zero-pressure` | Barometric pressure on the zeroing day | inHg / hPa |
|
|
197
|
-
| `--zero-humidity` | Relative humidity on the zeroing day | percent |
|
|
198
|
-
| `--zero-altitude` | Altitude on the zeroing day | feet / meters |
|
|
199
|
-
|
|
200
|
-
#### Powder Temperature
|
|
201
|
-
|
|
202
|
-
Propellant temperature changes muzzle velocity. Two models are available:
|
|
203
|
-
|
|
204
|
-
**Linear** — a constant sensitivity (fps or m/s per degree) applied relative to the
|
|
205
|
-
temperature the load was chronographed at:
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
./ballistics trajectory -v 2700 -b 0.19 -m 77 -d 0.224 --drag-model g7 \
|
|
209
|
-
--temperature 85 --use-powder-sensitivity \
|
|
210
|
-
--powder-temp-sensitivity 1.2 --powder-temp 70 # +1.2 fps per F above 70 F
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
**Measured curve (non-linear)** — real powders aren't perfectly linear (temperature-
|
|
214
|
-
stable powders flatten; others steepen when hot). If you've chronographed the load at
|
|
215
|
-
several temperatures, pass the points directly and the muzzle velocity is interpolated
|
|
216
|
-
at the powder temperature (clamped at the endpoints — no extrapolation). This
|
|
217
|
-
**overrides** `--powder-temp-sensitivity` when supplied:
|
|
218
|
-
|
|
219
|
-
```bash
|
|
220
|
-
./ballistics trajectory -v 2700 -b 0.19 -m 77 -d 0.224 --drag-model g7 \
|
|
221
|
-
--temperature 85 \
|
|
222
|
-
--powder-temp-curve "40:2620,70:2700,100:2760" # TEMP:VELOCITY points
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**Powder temperature vs air temperature.** The curve maps *powder* temperature to
|
|
226
|
-
velocity, while `--temperature` drives air *density*. These are decoupled: the curve is
|
|
227
|
-
looked up at `--powder-temp` when given, otherwise at `--temperature` (powder assumed at
|
|
228
|
-
air temperature). So a load left in a hot chamber or a cold pocket:
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
# 85 F air (density), but the powder is at 60 F (velocity from the curve at 60 F)
|
|
232
|
-
./ballistics trajectory ... --temperature 85 --powder-temp 60 \
|
|
233
|
-
--powder-temp-curve "40:2620,70:2700,100:2760"
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
Both powder models compose with `--auto-zero`, symmetrically. For the linear model,
|
|
237
|
-
`--zero-temperature` resolves zero-day velocity relative to the reference `--powder-temp`.
|
|
238
|
-
For a curve, `--zero-powder-temp` overrides the powder lookup; otherwise an explicit
|
|
239
|
-
`--zero-temperature` is used, or the shot-day `--powder-temp` is inherited when no zero-day
|
|
240
|
-
temperature was supplied. Zero-day atmosphere flags still drive air density independently.
|
|
241
|
-
An explicit `--zero-velocity` takes precedence over either powder model.
|
|
242
|
-
|
|
243
|
-
#### Bore Height and Ground Impact
|
|
244
|
-
|
|
245
|
-
Control bore height above ground and ground impact detection:
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
# Set bore height for prone shooting position (2 feet)
|
|
249
|
-
./ballistics trajectory \
|
|
250
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
251
|
-
--auto-zero 100 \
|
|
252
|
-
--bore-height 2 # 2 feet (imperial) or meters (metric)
|
|
253
|
-
|
|
254
|
-
# Disable ground impact detection for full trajectory to max range
|
|
255
|
-
./ballistics trajectory \
|
|
256
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
257
|
-
--auto-zero 100 \
|
|
258
|
-
--max-range 1000 \
|
|
259
|
-
--ignore-ground-impact
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Bore height defaults: 5 feet (imperial) / 1.5 meters (metric) - standing position.
|
|
263
|
-
|
|
264
|
-
#### Advanced BC Modeling
|
|
265
|
-
|
|
266
|
-
Enable velocity-dependent BC modeling for more accurate long-range predictions:
|
|
267
|
-
|
|
268
|
-
```bash
|
|
269
|
-
# Enable BC segmentation (velocity-based BC changes)
|
|
270
|
-
./ballistics trajectory \
|
|
271
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
272
|
-
--use-bc-segments \
|
|
273
|
-
--auto-zero 600 \
|
|
274
|
-
--max-range 1000
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
#### Advanced Physics - Magnus and Spin Drift
|
|
278
|
-
|
|
279
|
-
Enable advanced gyroscopic and aerodynamic effects:
|
|
280
|
-
|
|
281
|
-
```bash
|
|
282
|
-
# Magnus effect and spin drift calculation
|
|
283
|
-
./ballistics trajectory \
|
|
284
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
285
|
-
--twist-rate 10 # 1:10" barrel twist
|
|
286
|
-
--twist-right # Right-hand twist
|
|
287
|
-
--enable-magnus # Enable Magnus effect
|
|
288
|
-
--enable-spin-drift # Enable empirical Litz spin drift
|
|
289
|
-
--wind-speed 10 \
|
|
290
|
-
--wind-direction 90 \
|
|
291
|
-
--max-range 1000
|
|
292
|
-
|
|
293
|
-
# Coriolis effect for extreme long range
|
|
294
|
-
./ballistics trajectory \
|
|
295
|
-
-v 3000 -b 0.750 -m 250 -d 0.338 \
|
|
296
|
-
--enable-coriolis \
|
|
297
|
-
--latitude 45 # Shooting latitude
|
|
298
|
-
--shooting-angle 90 # Azimuth (0=N, 90=E)
|
|
299
|
-
--max-range 2000
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### Zero Calculation
|
|
303
|
-
|
|
304
|
-
Calculate the sight adjustment needed to zero at a specific distance:
|
|
305
|
-
|
|
306
|
-
```bash
|
|
307
|
-
# Calculate zero for 200 yards
|
|
308
|
-
./ballistics zero \
|
|
309
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
310
|
-
--target-distance 200
|
|
311
|
-
|
|
312
|
-
# With custom sight height (default is 0.05 yards / 1.8 inches)
|
|
313
|
-
./ballistics zero \
|
|
314
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
315
|
-
--target-distance 300 \
|
|
316
|
-
--sight-height 0.055 # 2.2 inches
|
|
317
|
-
|
|
318
|
-
# Metric example
|
|
319
|
-
./ballistics zero --units metric \
|
|
320
|
-
-v 823 -b 0.475 -m 10.9 -d 7.82 \
|
|
321
|
-
--target-distance 200 # 200 meters
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
Output includes:
|
|
325
|
-
- Zero angle in degrees
|
|
326
|
-
- Adjustment in MOA (Minutes of Angle)
|
|
327
|
-
- Adjustment in mrad (milliradians)
|
|
328
|
-
- Maximum ordinate (highest point of trajectory)
|
|
329
|
-
|
|
330
|
-
### Monte Carlo Simulation
|
|
331
|
-
|
|
332
|
-
Run statistical analysis with parameter variations:
|
|
333
|
-
|
|
334
|
-
```bash
|
|
335
|
-
./ballistics monte-carlo \
|
|
336
|
-
-v 2700 # Base velocity (fps)
|
|
337
|
-
-b 0.475 # Base BC
|
|
338
|
-
-m 168 # Mass (grains)
|
|
339
|
-
-d 0.308 # Diameter (inches)
|
|
340
|
-
-n 1000 # Number of simulations
|
|
341
|
-
--velocity-std 10 # Velocity std dev (fps)
|
|
342
|
-
--angle-std 0.5 # Angle std dev (degrees)
|
|
343
|
-
--bc-std 0.01 # BC std dev
|
|
344
|
-
--wind-std 2 # Wind speed std dev (mph)
|
|
345
|
-
--wind-direction-std 5 # Wind direction std dev (degrees)
|
|
346
|
-
--target-distance 300 # Target distance for hit probability
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### BC Estimation
|
|
350
|
-
|
|
351
|
-
Estimate ballistic coefficient from observed trajectory data:
|
|
352
|
-
|
|
353
|
-
```bash
|
|
354
|
-
./ballistics estimate-bc \
|
|
355
|
-
-v 2700 -m 168 -d 0.308 \
|
|
356
|
-
--distance1 100 --drop1 0.0 # First data point
|
|
357
|
-
--distance2 200 --drop2 0.023 # Second data point
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
### True Velocity (Velocity Truing)
|
|
361
|
-
|
|
362
|
-
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.
|
|
363
|
-
|
|
364
|
-
```bash
|
|
365
|
-
# Basic offline calculation
|
|
366
|
-
./ballistics true-velocity \
|
|
367
|
-
--measured-drop 5.1 --range 600 \
|
|
368
|
-
--bc 0.27 --drag-model g7 \
|
|
369
|
-
--mass 140 --diameter 0.264 \
|
|
370
|
-
--offline
|
|
371
|
-
|
|
372
|
-
# With chronograph comparison
|
|
373
|
-
./ballistics true-velocity \
|
|
374
|
-
--measured-drop 5.1 --range 600 \
|
|
375
|
-
--bc 0.27 --drag-model g7 \
|
|
376
|
-
--mass 140 --diameter 0.264 \
|
|
377
|
-
--chrono-velocity 2822 \
|
|
378
|
-
--offline
|
|
379
|
-
|
|
380
|
-
# Chronograph measured downrange, not at the muzzle (MBA-1377): most
|
|
381
|
-
# chronographs read 10-15 ft (or 25 m) downrange, so --chrono-distance
|
|
382
|
-
# back-solves the true muzzle velocity from the raw reading
|
|
383
|
-
./ballistics true-velocity \
|
|
384
|
-
--measured-drop 5.1 --range 600 \
|
|
385
|
-
--bc 0.27 --drag-model g7 \
|
|
386
|
-
--mass 140 --diameter 0.264 \
|
|
387
|
-
--chrono-velocity 2822 --chrono-distance 15 \
|
|
388
|
-
--offline
|
|
389
|
-
|
|
390
|
-
# With BC5D tables for improved accuracy
|
|
391
|
-
./ballistics true-velocity \
|
|
392
|
-
--measured-drop 5.1 --range 600 \
|
|
393
|
-
--bc 0.27 --drag-model g7 \
|
|
394
|
-
--mass 140 --diameter 0.264 \
|
|
395
|
-
--bc-table-auto --offline
|
|
396
|
-
|
|
397
|
-
# Joint MV + BC calibration from multiple observed impacts
|
|
398
|
-
./ballistics true-velocity \
|
|
399
|
-
--range 300 --measured-drop 1.30 \
|
|
400
|
-
--observed 600:4.40 --observed 900:9.00 \
|
|
401
|
-
--bc 0.45 --drag-model g1 \
|
|
402
|
-
--mass 168 --diameter 0.308
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
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.
|
|
406
|
-
|
|
407
|
-
**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.
|
|
408
|
-
|
|
409
|
-
**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](CLI_USAGE.md#joint-mv--bc-calibration-multiple-observed-impacts) for details.
|
|
410
|
-
|
|
411
|
-
**Plan the observations before shooting (MBA-1346).** `plan-truing` evaluates a
|
|
412
|
-
discrete set of ranges with the same forward model and finite-difference Jacobian
|
|
413
|
-
used by the fitter, then chooses an exact-size, minimum-separation-compliant design.
|
|
414
|
-
It reports information gain, singular values, conditioning, rejected/unreachable
|
|
415
|
-
candidates, and an explicit MV-only recommendation when the available facility
|
|
416
|
-
cannot identify BC:
|
|
417
|
-
|
|
418
|
-
```bash
|
|
419
|
-
./ballistics plan-truing \
|
|
420
|
-
-v 2700 -b 0.475 --drag-model g1 -m 168 -d 0.308 \
|
|
421
|
-
--candidate-ranges 200,300,400,500,600,700,800,900 \
|
|
422
|
-
--observation-count 3 --minimum-separation 100 \
|
|
423
|
-
--measurement-resolution 0.03 --drop-unit mil
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
`--measurement-resolution` is the assumed independent **one-standard-deviation**
|
|
427
|
-
impact-reading error, not a tolerance or extreme bound. A saved scalar G1/G7
|
|
428
|
-
profile may replace the explicit load flags (`--profile NAME`); velocity-banded BC
|
|
429
|
-
profiles and custom drag curves are rejected because they do not have one scalar BC
|
|
430
|
-
parameter to identify.
|
|
431
|
-
|
|
432
|
-
**Quantify what the observations actually learned (MBA-1353).** Add
|
|
433
|
-
`--observation-sigma` to opt into a weighted joint MV/BC MAP fit and local Gaussian
|
|
434
|
-
uncertainty report. A third `--observed RANGE:DROP:SIGMA` field overrides the default
|
|
435
|
-
for one reading. Optional priors are always visible and explicit; predictive output
|
|
436
|
-
separates uncertainty in the modeled drop from the wider interval for a future
|
|
437
|
-
reading:
|
|
438
|
-
|
|
439
|
-
```bash
|
|
440
|
-
./ballistics true-velocity \
|
|
441
|
-
--range 500 --measured-drop 3.18 \
|
|
442
|
-
--observed 600:4.35:0.03 --observed 900:8.89:0.02 \
|
|
443
|
-
--observation-sigma 0.03 \
|
|
444
|
-
--bc 0.45 --drag-model g1 --mass 168 --diameter 0.308 \
|
|
445
|
-
--predict-range 1000 --prediction-sigma 0.03 --output json
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
The report includes MV/BC 95% intervals, covariance and correlation, chi-square,
|
|
449
|
-
effective degrees of freedom, prior-domination/weak-identification warnings, and
|
|
450
|
-
propagated drop bands. Declared sigmas are treated as absolute known errors, so the
|
|
451
|
-
covariance is not rescaled by residual RMS. With no uncertainty flags, the existing
|
|
452
|
-
point estimate and output schema are unchanged.
|
|
453
|
-
|
|
454
|
-
### Wind-Call Truing (`true-wind`)
|
|
455
|
-
|
|
456
|
-
`true-velocity` trues the vertical axis; `true-wind` (MBA-1392) trues the other one.
|
|
457
|
-
Give it where your groups actually landed left/right of the aim point and it back-solves
|
|
458
|
-
the crosswind that reproduces that miss through the real forward model, plus a wind-call
|
|
459
|
-
correction factor against the wind you *called*:
|
|
460
|
-
|
|
461
|
-
```bash
|
|
462
|
-
./ballistics true-wind \
|
|
463
|
-
--miss 500:14.0 --miss 700:29.5 \
|
|
464
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 --drag-model g7 \
|
|
465
|
-
--twist-rate 11 --called-wind 9
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
A horizontal miss is not purely wind, so the command separates it: `--twist-rate` is
|
|
469
|
-
**required** and gyroscopic spin drift is always modelled and subtracted (a 1:11" .308
|
|
470
|
-
drifts ~3.5 in right at 700 yd — read as wind, that alone is several mph of error), and
|
|
471
|
-
`--latitude` with `--shot-direction` adds Coriolis. Anything the model had no data for
|
|
472
|
-
stays absorbed in the solved wind and is named in the report, so a contaminated number is
|
|
473
|
-
never presented as pure wind. Signs are documented in one block: `--miss` positive =
|
|
474
|
-
impact **right** of aim, solved wind positive = wind **from the shooter's left**
|
|
475
|
-
(9 o'clock) pushing impacts right. `--miss` values are linear inches off the target, not
|
|
476
|
-
dial readings, so scope tracking correction factors deliberately do not apply.
|
|
477
|
-
See [CLI_USAGE.md](CLI_USAGE.md#wind-call-truing-true-wind--mba-1392).
|
|
478
|
-
|
|
479
|
-
### DSF (Drop-Scale-Factor) Truing
|
|
480
|
-
|
|
481
|
-
Second stage of the Applied Ballistics-style two-stage truing workflow (MBA-1357).
|
|
482
|
-
Once `true-velocity` has fixed the supersonic (Mach > 1.2) muzzle velocity/BC, the
|
|
483
|
-
drop discrepancies that grow through the transonic region and into subsonic flight are
|
|
484
|
-
no longer fixable by a single MV correction — the residual is a slowly-varying function
|
|
485
|
-
of Mach. `dsf` records observed-drop/predicted-drop ratios at specific Mach <= 1.2
|
|
486
|
-
ranges and keys them, one saved profile at a time, to a Mach-indexed table:
|
|
487
|
-
|
|
488
|
-
```bash
|
|
489
|
-
# Stage 1: true the muzzle velocity from a supersonic-range drop reading (as above).
|
|
490
|
-
./ballistics true-velocity \
|
|
491
|
-
--measured-drop 3.2 --range 500 \
|
|
492
|
-
--bc 0.475 --mass 168 --diameter 0.308 \
|
|
493
|
-
--offline
|
|
494
|
-
|
|
495
|
-
# Stage 2: record a subsonic/transonic drop observation on the trued, saved profile.
|
|
496
|
-
./ballistics dsf --saved-profile my-rifle --range 900 --observed-drop 5.1mil
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
`dsf` takes no ballistic parameters of its own — it solves the named saved profile's
|
|
500
|
-
own trajectory (no CLI overrides) and derives everything else from `--range` and
|
|
501
|
-
`--observed-drop` (`mil`, `moa`, or `in`, no separator between number and unit). An
|
|
502
|
-
observation whose target-range Mach exceeds 1.2 is rejected outright, pointing back to
|
|
503
|
-
`true-velocity`. Up to 6 distinct Mach-keyed points accumulate per profile; a new point
|
|
504
|
-
within 0.05 Mach of an existing one supersedes it (announced on stdout); a 7th distinct
|
|
505
|
-
point is rejected, naming `--clear-dsf` to make room. `trajectory --saved-profile` and
|
|
506
|
-
`come-ups --profile` then auto-apply the table as a **drop-only** correction — velocity,
|
|
507
|
-
energy, and time of flight are byte-identical to the untrued solve — printing a
|
|
508
|
-
table-output-only note; JSON/CSV carry the corrected drop numbers with no equivalent
|
|
509
|
-
text. `profile save NAME ... --clear-dsf` removes an existing table. See
|
|
510
|
-
[CLI_USAGE.md](CLI_USAGE.md#dsf-drop-scale-factor-truing) for the full staging-gate
|
|
511
|
-
reference.
|
|
512
|
-
|
|
513
|
-
## Advanced Features
|
|
514
|
-
|
|
515
|
-
### Online Mode (API Integration)
|
|
516
|
-
|
|
517
|
-
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.
|
|
518
|
-
|
|
519
|
-
> **Important:** The `--online` feature 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](ONLINE_SERVICE.md) for full terms, privacy policy, and data handling practices.
|
|
520
|
-
|
|
521
|
-
```bash
|
|
522
|
-
# Use online mode to query the API
|
|
523
|
-
./ballistics trajectory \
|
|
524
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
525
|
-
--online \
|
|
526
|
-
--max-range 1000
|
|
527
|
-
|
|
528
|
-
# Custom API endpoint
|
|
529
|
-
./ballistics trajectory \
|
|
530
|
-
-v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
531
|
-
--online \
|
|
532
|
-
--api-url https://your-api.example.com/v1/calculate \
|
|
533
|
-
--max-range 1000
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
**Default API**: `https://api.ballistics.7.62x51mm.sh/v1/calculate`
|
|
537
|
-
|
|
538
|
-
Online mode benefits:
|
|
539
|
-
- **Enhanced BC data** - Access to doppler-derived ballistic coefficients
|
|
540
|
-
- **ML predictions** - Machine learning augmented trajectory calculations
|
|
541
|
-
- **BC segments** - Velocity-dependent BC modeling from measured data
|
|
542
|
-
- **Form factor corrections** - Bullet-specific drag adjustments
|
|
543
|
-
|
|
544
|
-
**Data transmitted when using --online:**
|
|
545
|
-
- All trajectory parameters (BC, mass, velocity, wind, atmospheric conditions, etc.)
|
|
546
|
-
- Your IP address and client version
|
|
547
|
-
- Request logs retained for 30 days, then deleted
|
|
548
|
-
|
|
549
|
-
To use only local calculations (no network, no data transmission):
|
|
550
|
-
```bash
|
|
551
|
-
cargo install ballistics-engine --no-default-features --features cli
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
### Integration Methods
|
|
555
|
-
|
|
556
|
-
The engine supports two numerical integration methods:
|
|
557
|
-
|
|
558
|
-
- **RK45 (Dormand-Prince Adaptive)** - Default method, provides best accuracy with adaptive step sizing
|
|
559
|
-
- **RK4 (Runge-Kutta 4th Order Fixed-Step)** - Available with `--use-rk4-fixed` flag for faster computation
|
|
560
|
-
|
|
561
|
-
### Wind Shear Modeling
|
|
562
|
-
|
|
563
|
-
Model altitude-dependent wind variations:
|
|
564
|
-
|
|
565
|
-
```bash
|
|
566
|
-
./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
567
|
-
--wind-speed 10 --wind-direction 90 \
|
|
568
|
-
--enable-wind-shear \
|
|
569
|
-
--max-range 1000
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
### Transonic Stability Analysis
|
|
573
|
-
|
|
574
|
-
Analyze projectile stability through the transonic regime:
|
|
575
|
-
|
|
576
|
-
```bash
|
|
577
|
-
./ballistics trajectory -v 3000 -b 0.475 -m 168 -d 0.308 \
|
|
578
|
-
--enable-pitch-damping \
|
|
579
|
-
--max-range 2000
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
Provides warnings about transonic instability and minimum pitch damping coefficients.
|
|
583
|
-
|
|
584
|
-
### Trajectory Sampling
|
|
585
|
-
|
|
586
|
-
Collect trajectory data at regular intervals for detailed analysis:
|
|
587
|
-
|
|
588
|
-
```bash
|
|
589
|
-
./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
590
|
-
--sample-trajectory \
|
|
591
|
-
--sample-interval 25 # Sample every 25 meters
|
|
592
|
-
--max-range 1000 -o json
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
### Angular Motion Physics
|
|
596
|
-
|
|
597
|
-
Model precession and nutation of spinning projectiles:
|
|
598
|
-
|
|
599
|
-
```bash
|
|
600
|
-
./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 \
|
|
601
|
-
--twist-rate 10 \
|
|
602
|
-
--enable-precession \
|
|
603
|
-
--max-range 1000
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
### Complete Advanced Physics Example
|
|
607
|
-
|
|
608
|
-
```bash
|
|
609
|
-
./ballistics trajectory \
|
|
610
|
-
-v 2850 -b 0.690 -m 230 -d 0.338 \
|
|
611
|
-
--drag-model g7 \
|
|
612
|
-
--twist-rate 8.5 --twist-right \
|
|
613
|
-
--enable-magnus \
|
|
614
|
-
--enable-coriolis \
|
|
615
|
-
--enable-spin-drift \
|
|
616
|
-
--enable-wind-shear \
|
|
617
|
-
--enable-pitch-damping \
|
|
618
|
-
--enable-precession \
|
|
619
|
-
--sample-trajectory \
|
|
620
|
-
--latitude 38.5 \
|
|
621
|
-
--shooting-angle 45 \
|
|
622
|
-
--wind-speed 15 --wind-direction 270 \
|
|
623
|
-
--altitude 6000 \
|
|
624
|
-
--max-range 2000
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
## Physics Modeling
|
|
628
|
-
|
|
629
|
-
The ballistics engine implements comprehensive physics modeling for accurate trajectory prediction:
|
|
630
|
-
|
|
631
|
-
### Aerodynamic Effects
|
|
632
|
-
- **Drag Modeling** - Full standard-projectile family (G1, G2, G5, G6, G7, G8, GI, GS, RA4) plus custom Cd(Mach) curves, with transonic flow corrections
|
|
633
|
-
- **Form Factor** - Projectile efficiency corrections based on shape and design
|
|
634
|
-
- **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
|
|
635
|
-
|
|
636
|
-
### Gyroscopic Effects
|
|
637
|
-
- **Spin Drift** - Lateral deviation due to gyroscopic and Magnus effects
|
|
638
|
-
- **Precession** - Gyroscopic precession of spinning projectile
|
|
639
|
-
- **Nutation** - Oscillatory motion superimposed on precession
|
|
640
|
-
- **Spin Decay** - Reduction in spin rate over time due to aerodynamic damping
|
|
641
|
-
- **Pitch Damping** - Aerodynamic moments opposing angular motion
|
|
642
|
-
|
|
643
|
-
### Environmental Effects
|
|
644
|
-
- **Coriolis Effect** - Earth's rotation influence on long-range trajectories
|
|
645
|
-
- **Magnus Effect** - Force from spinning projectile in crossflow
|
|
646
|
-
- **Wind Shear** - Altitude-dependent wind variations
|
|
647
|
-
- **Atmospheric Stratification** - Density and sound speed variations with altitude
|
|
648
|
-
|
|
649
|
-
### Stability Modeling
|
|
650
|
-
- **Dynamic Stability** - Gyroscopic and aerodynamic stability calculations
|
|
651
|
-
- **Yaw of Repose** - Gravity/gyroscopic equilibrium yaw; crosswind yaw is a transient handled by aerodynamic jump
|
|
652
|
-
- **Limit Cycle Yaw** - Bounded oscillatory motion analysis
|
|
653
|
-
|
|
654
|
-
## Language Bindings
|
|
655
|
-
|
|
656
|
-
Official language bindings are maintained as separate projects:
|
|
657
|
-
|
|
658
|
-
- **Python**: [ballistics-engine-py](https://github.com/ajokela/ballistics-engine-py) - PyO3 bindings via maturin
|
|
659
|
-
- **Ruby**: [ballistics-engine-rb](https://github.com/ajokela/ballistics-engine-rb) - Magnus bindings via rb_sys
|
|
660
|
-
|
|
661
|
-
These bindings depend on the `ballistics-engine` crate published on [crates.io](https://crates.io/crates/ballistics-engine).
|
|
662
|
-
|
|
663
|
-
### WASM / npm Package
|
|
664
|
-
|
|
665
|
-
The engine also compiles to WebAssembly (`src/wasm.rs`, `wasm-bindgen`) and already powers
|
|
666
|
-
[ballistics.sh](https://ballistics.sh) and [ballistics.rs](https://ballistics.rs) in the browser.
|
|
667
|
-
It is not yet published to npm for third-party use — `scripts/build-npm.sh` builds and prepares a
|
|
668
|
-
publish-ready package; publishing itself is a manual step (see below).
|
|
669
|
-
|
|
670
|
-
```bash
|
|
671
|
-
scripts/build-npm.sh
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
This builds two `wasm-bindgen` targets, both with `--no-default-features` (the default
|
|
675
|
-
`pdf`/`online` features pull in `printpdf`/`ureq`+`ring`, which do not compile for
|
|
676
|
-
`wasm32-unknown-unknown`) plus
|
|
677
|
-
`--features wasm-terminal` (the browser terminal's command set — see
|
|
678
|
-
[Trimming the WASM module](#trimming-the-wasm-module)):
|
|
679
|
-
|
|
680
|
-
- **`pkg/`** — `--target bundler`, the package meant for `npm publish`. Consumed via a native
|
|
681
|
-
`.wasm` ES import by bundlers that understand it (webpack with `experiments.asyncWebAssembly`,
|
|
682
|
-
Vite, Rollup + `@rollup/plugin-wasm`, Parcel).
|
|
683
|
-
- **`pkg-web/`** — `--target web`, a no-bundler build for direct `<script type="module">` browser
|
|
684
|
-
use or manual Node usage without a bundler — the same `--target` already used to build
|
|
685
|
-
ballistics.sh/ballistics.rs's WASM. Documented and built for completeness; not published under
|
|
686
|
-
the primary package name in this initial pass.
|
|
687
|
-
|
|
688
|
-
`wasm-pack` has no built-in dual-target/"publish both" mode, and stitching bundler- and web-target
|
|
689
|
-
output into one package.json via manual `exports` conditions isn't something `wasm-pack` generates
|
|
690
|
-
or tests for you — see the comment header of `scripts/build-npm.sh` for the full reasoning. A
|
|
691
|
-
single bundler-target package as the published npm artifact, with the web build documented
|
|
692
|
-
separately, is the ecosystem-standard shape for `wasm-bindgen` crates on npm.
|
|
693
|
-
|
|
694
|
-
### Trimming the WASM module
|
|
695
|
-
|
|
696
|
-
The published module carries two independent surfaces: the **`Calculator`** builder API
|
|
697
|
-
(`setBC`, `setDragModel`, `setWind`, `enableSpinDrift`, `enableCoriolis`, `calculateTrajectory`,
|
|
698
|
-
`getFullTrajectory`, …) and the **browser terminal** (`WasmBallistics.runCommand`) that powers
|
|
699
|
-
ballistics.sh. An app that only solves trajectories pays for the terminal's other twelve
|
|
700
|
-
commands, which is most of the binary.
|
|
701
|
-
|
|
702
|
-
Each non-trajectory command sits behind its own cargo feature, so you can select the subset you
|
|
703
|
-
actually call. `trajectory`, `version`, and the whole `Calculator` API are **never** gated —
|
|
704
|
-
`Calculator` composes a `trajectory` command line internally, so it keeps working with every
|
|
705
|
-
feature below turned off.
|
|
706
|
-
|
|
707
|
-
Build through `scripts/build-wasm.sh`, which is the one entry point every WASM build uses —
|
|
708
|
-
the ballistics.rs deploy and `build-npm.sh` included:
|
|
709
|
-
|
|
710
|
-
```bash
|
|
711
|
-
# Everything. Also what you get with no arguments at all — the default is deliberately the
|
|
712
|
-
# complete terminal, so a forgotten flag can never silently ship a stripped module.
|
|
713
|
-
scripts/build-wasm.sh
|
|
714
|
-
|
|
715
|
-
# Trajectory only — the Calculator API and nothing else
|
|
716
|
-
scripts/build-wasm.sh --preset slim
|
|
717
|
-
|
|
718
|
-
# À la carte
|
|
719
|
-
scripts/build-wasm.sh --features wasm-zero,wasm-lead
|
|
720
|
-
|
|
721
|
-
# --target and --out-dir pass through; so does the environment
|
|
722
|
-
CARGO_PROFILE_RELEASE_OPT_LEVEL=z scripts/build-wasm.sh --target nodejs --out-dir /tmp/pkg
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
After every build the script **verifies the artifact against the preset it was asked for** —
|
|
726
|
-
it reads the emitted `.wasm` and checks that exactly the promised commands are present, failing
|
|
727
|
-
the build otherwise. `--preset full` expects all twelve regardless of how the feature list was
|
|
728
|
-
computed, so a dropped flag is a hard error rather than a terminal that deploys cleanly and
|
|
729
|
-
then answers `Unknown command` to everything but `trajectory`.
|
|
730
|
-
|
|
731
|
-
If you invoke `wasm-pack` directly instead, note the bare `--`: it forwards only post-`--`
|
|
732
|
-
arguments to cargo, so `--features` placed before it is consumed as an (invalid) `wasm-pack`
|
|
733
|
-
flag.
|
|
734
|
-
|
|
735
|
-
Measured on 0.33.2, `--target web`, default release profile (`opt-level = 3`, LTO), against the
|
|
736
|
-
full build's 918 KB raw / 345 KB gzip (all sizes decimal KB):
|
|
737
|
-
|
|
738
|
-
| feature | command(s) removed | raw | gzip |
|
|
739
|
-
|---|---|---:|---:|
|
|
740
|
-
| `wasm-monte-carlo` | `monte-carlo`, including its `--wez` sweep | 115 KB | 46 KB |
|
|
741
|
-
| `wasm-truing` | `true-velocity`, `true-wind` | 92 KB | 31 KB |
|
|
742
|
-
| `wasm-bc-convert` | `bc-convert` | 65 KB | 22 KB |
|
|
743
|
-
| `wasm-reticle` | `reticle` | 55 KB | 21 KB |
|
|
744
|
-
| `wasm-lead` | `lead` | 21 KB | 7 KB |
|
|
745
|
-
| `wasm-powder` | `powder` | 17 KB | 6 KB |
|
|
746
|
-
| `wasm-estimate-bc` | `estimate-bc` | 17 KB | 7 KB |
|
|
747
|
-
| `wasm-zero` | `zero` | 15 KB | 4 KB |
|
|
748
|
-
| `wasm-recoil` | `recoil` | 12 KB | 3 KB |
|
|
749
|
-
| `wasm-power-factor` | `power-factor` | 11 KB | 4 KB |
|
|
750
|
-
| `wasm-drag-curve` | `drag-curve` | 7 KB | 3 KB |
|
|
751
|
-
| **all of the above** | **`Calculator` + `trajectory` only** | **434 KB** | **153 KB** |
|
|
752
|
-
|
|
753
|
-
Each row is that feature's marginal cost, measured by dropping it from the full set. The
|
|
754
|
-
commands share almost nothing, so the rows are close to additive: they sum to 427 KB raw /
|
|
755
|
-
153 KB gzip against a measured all-removed saving of 434 KB / 153 KB — pick any subset and the
|
|
756
|
-
rows add up. A trajectory-only module is **483,496 bytes raw, 191,421 gzip, 154,797 brotli**,
|
|
757
|
-
against 917,924 / 344,592 / 273,503 for the full build — 44% off the wire.
|
|
758
|
-
|
|
759
|
-
Splitting the help text into per-command chunks costs the full build about 3 KB raw / 1 KB
|
|
760
|
-
gzipped (35 `push_str` calls where there was one literal). That is the price of the table
|
|
761
|
-
above; every configuration that drops a command is far ahead.
|
|
762
|
-
|
|
763
|
-
**The `.wasm` and the JS glue are a matched pair — replace both together.** `wasm-bindgen`
|
|
764
|
-
generates the glue to match one specific module, and trimming genuinely changes the module's
|
|
765
|
-
import list: dropping `wasm-monte-carlo` removes the last user of `rand`, so the slim `.wasm`
|
|
766
|
-
no longer imports `crypto.getRandomValues` and the slim glue no longer supplies it. Ship a
|
|
767
|
-
stale full `.wasm` against new slim glue and instantiation fails outright:
|
|
768
|
-
|
|
769
|
-
```
|
|
770
|
-
LinkError: WebAssembly.Instance(): Import #4 "./ballistics_engine_bg.js"
|
|
771
|
-
"__wbg_getRandomValues_..." function import requires a callable
|
|
772
|
-
```
|
|
773
|
-
|
|
774
|
-
The reverse pairing — slim `.wasm` with full glue — happens to load, because the extra import
|
|
775
|
-
simply goes unused. Do not rely on that: it is a coincidence of which imports differ today, not
|
|
776
|
-
a compatibility guarantee, and it will not hold for a different feature subset. Copy every file
|
|
777
|
-
`build-wasm.sh` emits, from the same run, and clear any bundler cache that may hold the old one.
|
|
778
|
-
|
|
779
|
-
Removing a command does not change any number the remaining ones produce: the full-terminal
|
|
780
|
-
build is byte-identical to an ungated build across every command, and `Calculator` output is
|
|
781
|
-
byte-identical between the full and trajectory-only builds. A command compiled out reports
|
|
782
|
-
`Unknown command`, and the `help` text lists only what is actually present.
|
|
783
|
-
|
|
784
|
-
Two things are *not* separable, because they are not separate to begin with:
|
|
785
|
-
|
|
786
|
-
- **`--wez`** is a flag on `monte-carlo`, not a command, so it leaves with `wasm-monte-carlo`.
|
|
787
|
-
- **`explain`, `error-budget`, `tolerance`, `dial-plan`, `adaptive-card`** (0.33.x
|
|
788
|
-
decision-support) are native-CLI-only — they were never wired into the WASM dispatch, and
|
|
789
|
-
dead-code elimination already keeps them out of the module. There is nothing to remove.
|
|
790
|
-
|
|
791
|
-
The script also post-processes each `package.json` (name, description, license, repository,
|
|
792
|
-
keywords, and the `files` list — including an `LICENSE-APACHE` entry `wasm-pack` itself omits even
|
|
793
|
-
though it copies the file) and installs `README-npm.md` as the package's `README.md`.
|
|
794
|
-
|
|
795
|
-
**Before publishing**, edit `pkg/package.json`'s `"name"` — it ships as the placeholder
|
|
796
|
-
`"@SCOPE/ballistics-engine"`. Replace `SCOPE` with the maintainer's real npm org/user scope (a
|
|
797
|
-
scope decision, plus an npm account with publish rights to it, are both needed and don't exist yet
|
|
798
|
-
as of this writing). Then:
|
|
799
|
-
|
|
800
|
-
```bash
|
|
801
|
-
scripts/build-npm.sh
|
|
802
|
-
cd pkg
|
|
803
|
-
npm pack --dry-run # sanity-check the tarball contents first
|
|
804
|
-
npm publish --access public
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
(`--access public` is required the first time a scoped package is published, since scoped packages
|
|
808
|
-
default to private on free npm accounts; `pkg/package.json` also sets `publishConfig.access` to
|
|
809
|
-
`public` so a plain `npm publish` works too.)
|
|
810
|
-
|
|
811
|
-
## FFI Layer
|
|
812
|
-
|
|
813
|
-
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.
|
|
814
|
-
|
|
815
|
-
<img src="ios.png" alt="iOS Integration Example" width="35%">
|
|
816
|
-
|
|
817
|
-
### FFI Features
|
|
818
|
-
- **C-Compatible Structures** - All data structures use C-compatible layouts
|
|
819
|
-
- **Safe Memory Management** - Proper handling of memory across language boundaries
|
|
820
|
-
- **iOS/Swift Integration** - Ready for use with Swift through bridging headers
|
|
821
|
-
- **Android/JNI Support** - Compatible with Java Native Interface
|
|
822
|
-
- **Monte Carlo Simulation** - Statistical analysis with parameter variations
|
|
823
|
-
- **Error Handling** - Graceful error propagation across FFI boundary
|
|
824
|
-
|
|
825
|
-
### Example FFI Usage (C/Swift)
|
|
826
|
-
```c
|
|
827
|
-
// Create input parameters
|
|
828
|
-
FFIBallisticInputs inputs = {
|
|
829
|
-
.muzzle_velocity = 823.0, // m/s
|
|
830
|
-
.ballistic_coefficient = 0.475,
|
|
831
|
-
.mass = 0.0109, // kg
|
|
832
|
-
.diameter = 0.00782, // meters
|
|
833
|
-
.drag_model = 0, // G1
|
|
834
|
-
.sight_height = 0.05, // meters
|
|
835
|
-
.temperature = 15.0, // Celsius
|
|
836
|
-
.altitude = 0.0
|
|
837
|
-
};
|
|
838
|
-
|
|
839
|
-
// Calculate trajectory. The final argument is the integration step in milliseconds
|
|
840
|
-
// (minimum 0.1 ms; smaller or non-finite values return NULL).
|
|
841
|
-
FFITrajectoryResult* result = ballistics_calculate_trajectory(&inputs, NULL, NULL, 1000.0, 0.1);
|
|
842
|
-
|
|
843
|
-
// NULL also reports invalid inputs or the 250,000-point resource ceiling.
|
|
844
|
-
// Increase the step, reduce the range, or use adaptive RK45 for an over-budget solve.
|
|
845
|
-
if (result != NULL) {
|
|
846
|
-
printf("Max range: %.2f meters\n", result->max_range);
|
|
847
|
-
ballistics_free_trajectory_result(result);
|
|
848
|
-
}
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
### Monte Carlo Simulation via FFI
|
|
852
|
-
```c
|
|
853
|
-
// Set up Monte Carlo parameters
|
|
854
|
-
FFIMonteCarloParams params = {
|
|
855
|
-
.num_simulations = 1000,
|
|
856
|
-
.velocity_std_dev = 10.0, // m/s variation
|
|
857
|
-
.angle_std_dev = 0.001, // radian variation (elevation)
|
|
858
|
-
.bc_std_dev = 0.01, // BC variation
|
|
859
|
-
.wind_speed_std_dev = 2.0, // m/s wind variation
|
|
860
|
-
.target_distance = 600.0, // Target at 600m
|
|
861
|
-
.azimuth_std_dev = 0.001 // radian variation (horizontal)
|
|
862
|
-
};
|
|
863
|
-
|
|
864
|
-
// Run simulation with an independent 0.1-radian wind-direction sigma.
|
|
865
|
-
// Use ballistics_monte_carlo(...) when no direction variation is desired.
|
|
866
|
-
FFIMonteCarloResults* results =
|
|
867
|
-
ballistics_monte_carlo_with_direction_std_dev(&inputs, NULL, ¶ms, 0.1);
|
|
868
|
-
|
|
869
|
-
// Use statistical results
|
|
870
|
-
printf("Mean range: %.2f m (σ=%.2f)\n", results->mean_range, results->std_dev_range);
|
|
871
|
-
printf("Hit probability at 600m: %.1f%%\n", results->hit_probability * 100);
|
|
872
|
-
|
|
873
|
-
// Access individual shots
|
|
874
|
-
for (int i = 0; i < results->num_results; i++) {
|
|
875
|
-
printf("Shot %d: Range %.2f m, Impact velocity %.2f m/s\n",
|
|
876
|
-
i, results->ranges[i], results->impact_velocities[i]);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
// Clean up
|
|
880
|
-
ballistics_free_monte_carlo_results(results);
|
|
881
|
-
```
|
|
882
|
-
|
|
883
|
-
## Output Formats
|
|
884
|
-
|
|
885
|
-
All commands support three output formats via the `-o` flag:
|
|
886
|
-
|
|
887
|
-
- **table** (default) - Formatted ASCII table for terminal display
|
|
888
|
-
- **json** - Complete data in JSON format for programmatic use
|
|
889
|
-
- **csv** - Comma-separated values for spreadsheet analysis
|
|
890
|
-
|
|
891
|
-
## Practical Examples
|
|
892
|
-
|
|
893
|
-
### Hunting Zero
|
|
894
|
-
|
|
895
|
-
Zero a hunting rifle at 200 yards with environmental conditions:
|
|
20
|
+
## Install
|
|
896
21
|
|
|
897
22
|
```bash
|
|
898
|
-
|
|
899
|
-
./ballistics zero \
|
|
900
|
-
-v 2650 -b 0.460 -m 180 -d 0.308 \
|
|
901
|
-
--target-distance 200
|
|
902
|
-
|
|
903
|
-
# Verify trajectory with auto-zero
|
|
904
|
-
./ballistics trajectory \
|
|
905
|
-
-v 2650 -b 0.460 -m 180 -d 0.308 \
|
|
906
|
-
--auto-zero 200 \
|
|
907
|
-
--max-range 400 \
|
|
908
|
-
--wind-speed 15 \
|
|
909
|
-
--wind-direction 270 \
|
|
910
|
-
--temperature 32 \
|
|
911
|
-
--humidity 30 \
|
|
912
|
-
--altitude 5000 \
|
|
913
|
-
--full
|
|
23
|
+
npm install @SCOPE/ballistics-engine
|
|
914
24
|
```
|
|
915
25
|
|
|
916
|
-
|
|
26
|
+
`@SCOPE` is a placeholder — see the source repo's `README.md` ("WASM / npm Package" section) for
|
|
27
|
+
the real published name once one exists.
|
|
917
28
|
|
|
918
|
-
|
|
29
|
+
## Quick start (bundler: webpack, Vite, Rollup, Parcel)
|
|
919
30
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
--auto-zero 100 \
|
|
925
|
-
--max-range 1100 \
|
|
926
|
-
--wind-speed 10 \
|
|
927
|
-
--wind-direction 45 \
|
|
928
|
-
--full \
|
|
929
|
-
-o json > trajectory.json
|
|
930
|
-
```
|
|
31
|
+
This package's `main` entry imports its `.wasm` file as a native ES module, which is how
|
|
32
|
+
`wasm-pack --target bundler` output is meant to be consumed. It works out of the box with Vite and
|
|
33
|
+
Rollup (`@rollup/plugin-wasm`), and with webpack once `experiments.asyncWebAssembly` (or
|
|
34
|
+
`experiments.syncWebAssembly`) is enabled — check your bundler's WASM docs if the import fails.
|
|
931
35
|
|
|
932
|
-
|
|
36
|
+
```js
|
|
37
|
+
import { WasmBallistics } from '@SCOPE/ballistics-engine';
|
|
933
38
|
|
|
934
|
-
|
|
39
|
+
const calc = new WasmBallistics();
|
|
935
40
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
-v
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
--target-distance 600
|
|
943
|
-
|
|
944
|
-
# Load 2: Lower velocity, more consistent
|
|
945
|
-
./ballistics monte-carlo \
|
|
946
|
-
-v 2680 -b 0.475 -m 168 -d 0.308 \
|
|
947
|
-
-n 1000 \
|
|
948
|
-
--velocity-std 8 \
|
|
949
|
-
--target-distance 600
|
|
41
|
+
// The command surface mirrors the native CLI (see CLI_USAGE.md in the source repo for the full
|
|
42
|
+
// flag reference): .308 Winchester, 168gr @ 2700 fps, zeroed at 200 yd, table out to 500 yd.
|
|
43
|
+
const table = calc.runCommand(
|
|
44
|
+
'trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 500 --auto-zero 200',
|
|
45
|
+
);
|
|
46
|
+
console.log(table);
|
|
950
47
|
```
|
|
951
48
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
### BC Segmentation
|
|
49
|
+
### Custom drag tables (`loadDragTable`)
|
|
955
50
|
|
|
956
|
-
|
|
51
|
+
Supply a measured or manufacturer-published Mach:Cd drag curve (Hornady CDM data, a Lapua/Doppler
|
|
52
|
+
deck, or your own) instead of a G1/G7 model + BC. Once loaded it's applied automatically to every
|
|
53
|
+
`trajectory`, `zero`, `lead`, and `monte-carlo` run — no extra flag needed.
|
|
957
54
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
Example:
|
|
964
|
-
```bash
|
|
965
|
-
./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --use-bc-segments --max-range 1000
|
|
966
|
-
```
|
|
55
|
+
```js
|
|
56
|
+
const csv = 'mach,cd\n0.5,0.220\n0.8,0.230\n1.0,0.520\n1.2,0.480\n1.5,0.400\n2.0,0.330\n2.5,0.300\n';
|
|
57
|
+
calc.loadDragTable(new TextEncoder().encode(csv));
|
|
58
|
+
calc.hasDragTable(); // true
|
|
967
59
|
|
|
968
|
-
|
|
969
|
-
velocities in `--units`) instead of the auto-estimated/table ones. Keyed to velocity, so it
|
|
970
|
-
composes with distance-keyed `--wind-segment`; implies `--use-bc-segments` and overrides
|
|
971
|
-
`--bc-table` and `--bc-table-dir`:
|
|
972
|
-
```bash
|
|
973
|
-
./ballistics trajectory -v 2600 -b 0.243 -m 175 -d 0.308 --drag-model g7 --max-range 1000 \
|
|
974
|
-
--bc-segment 1800:4000:0.243 --bc-segment 1500:1800:0.228 --bc-segment 1200:1500:0.205
|
|
60
|
+
calc.runCommand('trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 500');
|
|
975
61
|
```
|
|
976
62
|
|
|
977
|
-
|
|
63
|
+
`loadDragTable` takes raw bytes because WASM has no filesystem access — fetch the CSV yourself
|
|
64
|
+
(`fetch()` in the browser, `fs.readFileSync` in Node) and pass the bytes in. The CSV format is
|
|
65
|
+
documented in the source repo's `CLI_USAGE.md` ("Custom Drag Tables"); a matching
|
|
66
|
+
`loadBc5dTable(bytes)` / `hasBc5dTable()` pair exists for BC5D correction tables.
|
|
978
67
|
|
|
979
|
-
|
|
68
|
+
## Browser without a bundler
|
|
980
69
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
70
|
+
For a plain `<script type="module">` page (no build step), use the `pkg-web/` build instead —
|
|
71
|
+
produced by the same `scripts/build-npm.sh`, and the same build already deployed at
|
|
72
|
+
[ballistics.sh](https://ballistics.sh). It ships an explicit async `init()` you call once before
|
|
73
|
+
constructing `WasmBallistics`:
|
|
985
74
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
75
|
+
```html
|
|
76
|
+
<script type="module">
|
|
77
|
+
import init, { WasmBallistics } from './ballistics_engine.js';
|
|
989
78
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
79
|
+
await init(); // fetches and instantiates ballistics_engine_bg.wasm relative to this file
|
|
80
|
+
const calc = new WasmBallistics();
|
|
81
|
+
console.log(calc.runCommand('trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 500'));
|
|
82
|
+
</script>
|
|
993
83
|
```
|
|
994
84
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
- macOS: `~/Library/Caches/ballistics-engine/bc5d/`
|
|
999
|
-
- Linux: `~/.cache/ballistics-engine/bc5d/`
|
|
1000
|
-
- Windows: `%LOCALAPPDATA%\ballistics-engine\cache\bc5d\`
|
|
1001
|
-
|
|
1002
|
-
Tables are approximately 1-1.5 MB each and include CRC32 validation to ensure data integrity.
|
|
85
|
+
Serve `ballistics_engine.js` and `ballistics_engine_bg.wasm` from the same directory, and make sure
|
|
86
|
+
your host serves `.wasm` with `Content-Type: application/wasm` (all major static hosts and CDNs do
|
|
87
|
+
this by default).
|
|
1003
88
|
|
|
1004
|
-
|
|
89
|
+
This `pkg-web/` build is not published to npm as part of this package in the current release —
|
|
90
|
+
it's built locally alongside `pkg/` for direct use or self-hosting. If you need it from npm, either
|
|
91
|
+
vendor the files from `pkg-web/` yourself or publish it as a second package.
|
|
1005
92
|
|
|
1006
|
-
|
|
1007
|
-
- **Magnus Effect** - Side force from spinning projectiles
|
|
1008
|
-
- **Spin Drift** - Lateral drift due to gyroscopic effects
|
|
1009
|
-
- **Coriolis Effect** - Earth rotation effects (with latitude input)
|
|
1010
|
-
- **Transonic Drag** - Enhanced drag modeling in transonic regime
|
|
1011
|
-
- **Low-Reynolds Helper** - Opt-in viscous correction below the standard projectile-table regime
|
|
93
|
+
### Node.js without a bundler
|
|
1012
94
|
|
|
1013
|
-
|
|
95
|
+
Plain Node `import`/`require` cannot load this package's bundler-target `.wasm` import directly.
|
|
96
|
+
Use the `pkg-web/` build instead, passing the file bytes explicitly (Node's `fetch()` does not
|
|
97
|
+
support `file://` URLs):
|
|
1014
98
|
|
|
1015
|
-
|
|
99
|
+
```js
|
|
100
|
+
import { readFileSync } from 'node:fs';
|
|
101
|
+
import init, { WasmBallistics } from './pkg-web/ballistics_engine.js';
|
|
1016
102
|
|
|
1017
|
-
|
|
1018
|
-
|
|
103
|
+
const wasmBytes = readFileSync(new URL('./pkg-web/ballistics_engine_bg.wasm', import.meta.url));
|
|
104
|
+
await init({ module_or_path: wasmBytes });
|
|
1019
105
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
```bash
|
|
1023
|
-
# Debug build
|
|
1024
|
-
cargo build
|
|
1025
|
-
|
|
1026
|
-
# Release build (optimized)
|
|
1027
|
-
cargo build --release
|
|
1028
|
-
|
|
1029
|
-
# Run tests
|
|
1030
|
-
cargo test
|
|
1031
|
-
|
|
1032
|
-
# Build documentation
|
|
1033
|
-
cargo doc --open
|
|
106
|
+
const calc = new WasmBallistics();
|
|
107
|
+
console.log(calc.runCommand('trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 500'));
|
|
1034
108
|
```
|
|
1035
109
|
|
|
1036
|
-
|
|
110
|
+
If you need a CommonJS (`require()`) Node build, generate one yourself:
|
|
111
|
+
`scripts/build-wasm.sh --target nodejs` (add `--preset slim` for a trajectory-only module —
|
|
112
|
+
see **Size** under Caveats).
|
|
1037
113
|
|
|
1038
|
-
|
|
114
|
+
## Caveats
|
|
1039
115
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
WindConditions, AtmosphericConditions
|
|
1044
|
-
};
|
|
116
|
+
- **Size**: this package ships the full command surface — 918 KB raw, 345 KB gzipped, 274 KB
|
|
117
|
+
brotli (measured on 0.33.2; decimal KB). It is not code-split or lazily loaded, so the whole engine loads
|
|
118
|
+
up front.
|
|
1045
119
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
ballistic_coefficient: 0.475,
|
|
1051
|
-
mass: 0.0109, // kg
|
|
1052
|
-
diameter: 0.00782, // meters
|
|
1053
|
-
sight_height: 0.05, // meters
|
|
1054
|
-
..Default::default()
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
|
-
let wind = WindConditions {
|
|
1058
|
-
speed: 5.0, // m/s
|
|
1059
|
-
direction: 1.5708, // 90 degrees in radians
|
|
1060
|
-
..Default::default()
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
|
-
let atmosphere = AtmosphericConditions {
|
|
1064
|
-
temperature: 15.0, // Celsius
|
|
1065
|
-
pressure: 1013.25, // hPa
|
|
1066
|
-
humidity: 50.0, // %
|
|
1067
|
-
altitude: 0.0, // meters
|
|
1068
|
-
..Default::default()
|
|
1069
|
-
};
|
|
1070
|
-
|
|
1071
|
-
let solver = TrajectorySolver::new(inputs, wind, atmosphere);
|
|
1072
|
-
let result = solver.solve()?;
|
|
1073
|
-
|
|
1074
|
-
println!("Max range: {:.2} m", result.max_range);
|
|
1075
|
-
println!("Max height: {:.2} m", result.max_height);
|
|
1076
|
-
println!("Time of flight: {:.3} s", result.time_of_flight);
|
|
1077
|
-
|
|
1078
|
-
Ok(())
|
|
1079
|
-
}
|
|
1080
|
-
```
|
|
120
|
+
**Most of that is the terminal, and you can drop it.** Every command except `trajectory` is
|
|
121
|
+
behind its own cargo feature, while the `Calculator` API is never gated. Rebuilding with only
|
|
122
|
+
what you call gets a trajectory-only module down to **483 KB raw / 191 KB gzipped** — 44% off
|
|
123
|
+
the wire — with byte-identical trajectory output:
|
|
1081
124
|
|
|
1082
|
-
|
|
125
|
+
```bash
|
|
126
|
+
# Calculator + trajectory only
|
|
127
|
+
scripts/build-wasm.sh --preset slim --target bundler
|
|
1083
128
|
|
|
1084
|
-
|
|
1085
|
-
-
|
|
1086
|
-
|
|
1087
|
-
- BC estimation: ~50ms
|
|
1088
|
-
- Zero calculation: ~10ms
|
|
129
|
+
# ...or keep a subset
|
|
130
|
+
scripts/build-wasm.sh --features wasm-zero,wasm-lead --target bundler
|
|
131
|
+
```
|
|
1089
132
|
|
|
1090
|
-
|
|
133
|
+
The script verifies the module it produced actually matches the set you asked for.
|
|
1091
134
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
135
|
+
The per-command features and their measured savings are tabulated under
|
|
136
|
+
"Trimming the WASM module" in the [source repo's
|
|
137
|
+
README](https://github.com/ajokela/ballistics-engine#trimming-the-wasm-module). The biggest
|
|
138
|
+
single win is `wasm-monte-carlo` (46 KB gzipped, and it takes the `--wez` sweep with it).
|
|
139
|
+
- **Single-threaded**: no SIMD/threads assumptions; no `SharedArrayBuffer` or
|
|
140
|
+
cross-origin-isolation (COOP/COEP) headers required.
|
|
141
|
+
- **No filesystem/network**: table loaders (`loadDragTable`, `loadBc5dTable`) and any file-based
|
|
142
|
+
CLI flags (e.g. native `--drag-table <FILE>`) need the host to fetch bytes and hand them in; see
|
|
143
|
+
`loadDragTable` above.
|
|
144
|
+
- **`pdf`/`online` features are unavailable**: this build excludes them (see above), so
|
|
145
|
+
PDF dope-card export and the online BC-estimation API are not part of the WASM surface.
|
|
146
|
+
- **Solves stop at ground impact.** The trajectory ends where the projectile reaches the ground
|
|
147
|
+
plane, which sits `boreHeight` below the muzzle and defaults to 60 in (5 ft) — for a typical
|
|
148
|
+
.308 that is around 516 yd. `calculateTrajectory(1000)` on such a setup returns the impact
|
|
149
|
+
point, not a 1000 yd point; the returned `range_yards` tells you which you got. Use
|
|
150
|
+
`.ignoreGroundImpact(true)` to solve to the requested range regardless, or
|
|
151
|
+
`.setBoreHeight(inches)` to model a genuinely higher firing position:
|
|
1102
152
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
- Default is Imperial (fps, grains, yards)
|
|
1112
|
-
- Use `--units metric` for metric system
|
|
1113
|
-
- All inputs must match the selected unit system
|
|
1114
|
-
|
|
1115
|
-
### Unexpected BC behavior
|
|
1116
|
-
- G1 and G7 models have different BC values for same bullet
|
|
1117
|
-
- G7 typically better for boat-tail bullets
|
|
1118
|
-
- BC segmentation automatically applied based on bullet type
|
|
1119
|
-
|
|
1120
|
-
## Contributing
|
|
1121
|
-
|
|
1122
|
-
Contributions are welcome! Please:
|
|
1123
|
-
1. Fork the repository
|
|
1124
|
-
2. Create a feature branch
|
|
1125
|
-
3. Add tests for new features
|
|
1126
|
-
4. Run `cargo test` and `cargo fmt`
|
|
1127
|
-
5. Submit a pull request
|
|
153
|
+
```js
|
|
154
|
+
new Calculator().setBC(0.243).setVelocity(2700).setDragModel('G7')
|
|
155
|
+
.setZeroRange(100).ignoreGroundImpact(true)
|
|
156
|
+
.calculateTrajectory(1000); // reaches 1000 yd
|
|
157
|
+
```
|
|
158
|
+
- Full API surface (including the `Calculator` builder class) is documented in the bundled
|
|
159
|
+
`.d.ts`; the full CLI flag reference `runCommand` accepts is documented in the source repo's
|
|
160
|
+
`CLI_USAGE.md`.
|
|
1128
161
|
|
|
1129
162
|
## License
|
|
1130
163
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
**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](ONLINE_SERVICE.md) for details.
|
|
1134
|
-
|
|
1135
|
-
## Acknowledgments
|
|
1136
|
-
|
|
1137
|
-
- Ballistics physics based on Robert McCoy's "Modern Exterior Ballistics"
|
|
1138
|
-
- Drag tables from military ballistics research
|
|
1139
|
-
- BC segmentation algorithms from Bryan Litz's research
|
|
1140
|
-
- Community contributions and testing
|
|
164
|
+
MIT OR Apache-2.0 — see `LICENSE` and `LICENSE-APACHE` in this package.
|
|
1141
165
|
|
|
1142
|
-
##
|
|
166
|
+
## Source
|
|
1143
167
|
|
|
1144
|
-
|
|
1145
|
-
- GitHub Issues: [github.com/ajokela/ballistics-engine/issues](https://github.com/ajokela/ballistics-engine/issues)
|
|
168
|
+
https://github.com/ajokela/ballistics-engine
|