ballistics-engine 0.25.1 → 0.30.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 CHANGED
@@ -1,131 +1,1003 @@
1
- # ballistics-engine (WASM)
1
+ # Ballistics Engine
2
+
3
+ A high-performance ballistics trajectory calculation engine with comprehensive physics modeling, automatic zeroing, and statistical analysis capabilities.
4
+
5
+ **Project Website:** [https://ballistics.rs/](https://ballistics.rs/)
6
+
7
+ ## Features
8
+
9
+ - **Full 3D Trajectory Integration** - Six-state ballistic modeling with adaptive RK45 and fixed-step RK4 integration methods
10
+ - **Advanced Drag Models** - Full standard-projectile family (G1, G2, G5, G6, G7, G8, GI, GS, and the British RA4 reference function), each backed by its own real Mach-indexed table with automatic transonic corrections, plus user-supplied custom Cd(Mach) drag tables (`--drag-table`, used as-is with endpoint hold outside their measured domain, no transonic correction applied — see [CLI_USAGE.md](CLI_USAGE.md#custom-drag-tables); `bc_value` is ignored while a custom table is active), with an optional `--cd-scale <FACTOR>` whole-curve truing multiplier (Hornady AFF / AB CDF style; `1.0` = neutral, typical range 0.90-1.10; requires `--drag-table` — see [CLI_USAGE.md](CLI_USAGE.md#whole-curve-drag-scale---cd-scale)). `true-velocity`/`plan-truing`'s forward model is deliberately G1/G7 only. GL is out of scope — its only public source is velocity-domain data, which doesn't fit this engine's Mach-indexed table format.
11
+ - **Automatic Zeroing** - Calculate sight adjustments and apply zero angles automatically; `trajectory` echoes the solved bore angle (degrees, additive across table/JSON/CSV) whenever auto-zero ran, and `zero --from-angle <DEGREES>` solves the zero RANGE(S) a previously solved/stored bore angle produces — a bore angle generally implies two zeros (the classic 25/300-yard relationship), so both are reported rather than one being silently picked — so the angle can be captured once and reused later, independent of the day it was solved — see [CLI_USAGE.md](CLI_USAGE.md#solving-range-from-a-stored-angle---from-angle)
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
+ - **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)
14
+ - **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)
15
+ - **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)
16
+ - **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)
17
+ - **Unit Conversion** - Seamless switching between Imperial (default) and Metric units
18
+ - **BC Segmentation** - Velocity-dependent ballistic coefficient modeling with automatic estimation
19
+ - **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)
20
+ - **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)
21
+ - **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)
22
+ - **Monte Carlo Simulations** - Statistical analysis with parameter uncertainties
23
+ - **BC Estimation** - Estimate ballistic coefficients from trajectory data
24
+ - **Advanced Physics**:
25
+ - **Spin Effects**: Magnus effect and empirical Litz spin drift
26
+ - **Earth Effects**: Coriolis effect with latitude-dependent calculations
27
+ - **Angular Motion**: Gyroscopic precession and nutation physics
28
+ - **Transonic Analysis**: Pitch damping coefficients and stability warnings
29
+ - **Trajectory Sampling**: Regular interval data collection for analysis
30
+ - **Form Factor Corrections**: Bullet-specific drag adjustments
31
+ - **Multiple Output Formats** - JSON, CSV, formatted tables, and printable PDF dope cards
32
+ - **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)
33
+ - **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
34
+
35
+ ## Installation
36
+
37
+ ### From crates.io
2
38
 
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.
39
+ ```bash
40
+ cargo install ballistics-engine
41
+ ```
42
+
43
+ ### From Source
44
+
45
+ ```bash
46
+ git clone https://github.com/ajokela/ballistics-engine.git
47
+ cd ballistics-engine
48
+ cargo build --release
49
+ ```
50
+
51
+ The binary will be at: `target/release/ballistics`
52
+
53
+ ### Feature Flags
54
+
55
+ | Feature | Default | Description |
56
+ |---------|---------|-------------|
57
+ | `online` | ✅ Yes | HTTP client for API integration (`--online` flag) |
58
+
59
+ To build without network capabilities:
60
+ ```bash
61
+ cargo build --release --no-default-features
62
+ ```
63
+
64
+ ## Quick Start
65
+
66
+ ### Basic Trajectory (Imperial Units - Default)
67
+
68
+ ```bash
69
+ # .308 Winchester, 168gr bullet at 2700 fps
70
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 1000
71
+
72
+ # With automatic zeroing at 200 yards
73
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --auto-zero 200 --max-range 500
74
+ ```
75
+
76
+ ### Metric Units
77
+
78
+ ```bash
79
+ # Same bullet in metric units
80
+ ./ballistics trajectory --units metric -v 823 -b 0.475 -m 10.9 -d 7.82 --max-range 1000
81
+ ```
82
+
83
+ ## Unit Systems
84
+
85
+ The engine supports two unit systems, selectable with the `--units` flag:
86
+
87
+ ### Imperial (Default)
88
+ - **Velocity**: feet per second (fps)
89
+ - **Mass**: grains
90
+ - **Distance**: yards
91
+ - **Diameter**: inches
92
+ - **Temperature**: Fahrenheit
93
+ - **Pressure**: inHg
94
+ - **Wind**: mph
95
+
96
+ ### Metric
97
+ - **Velocity**: meters per second (m/s)
98
+ - **Mass**: grams
99
+ - **Distance**: meters
100
+ - **Diameter**: millimeters
101
+ - **Temperature**: Celsius
102
+ - **Pressure**: hPa (millibars)
103
+ - **Wind**: m/s
104
+
105
+ ## Commands
106
+
107
+ ### Trajectory Calculation
108
+
109
+ Calculate ballistic trajectory with environmental conditions:
110
+
111
+ ```bash
112
+ # Imperial units (default)
113
+ ./ballistics trajectory \
114
+ -v 2700 # Velocity (fps)
115
+ -b 0.475 # Ballistic coefficient
116
+ -m 168 # Mass (grains)
117
+ -d 0.308 # Diameter (inches)
118
+ --drag-model g7 # G7 drag model
119
+ --angle 0 # Launch angle (degrees)
120
+ --max-range 1000 # Maximum range (yards)
121
+ --wind-speed 10 # Wind speed (mph)
122
+ --wind-direction 90 # Wind from right (degrees)
123
+ --temperature 59 # Temperature (Fahrenheit)
124
+ --pressure 29.92 # Pressure (inHg)
125
+ --humidity 50 # Relative humidity (%)
126
+ --altitude 0 # Altitude (feet)
127
+ --full # Show all trajectory points
128
+ ```
129
+
130
+ #### Auto-Zero Feature
131
+
132
+ Automatically calculate and apply the zero angle for a specific distance:
133
+
134
+ ```bash
135
+ # Zero at 200 yards and show trajectory to 500 yards
136
+ ./ballistics trajectory \
137
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
138
+ --auto-zero 200 \ # Automatically zero at 200 yards
139
+ --max-range 500 \
140
+ --full
141
+
142
+ # Custom sight height for auto-zero
143
+ ./ballistics trajectory \
144
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
145
+ --auto-zero 100 \
146
+ --sight-height 0.055 # 2.2 inches in yards
147
+ ```
148
+
149
+ #### Zero-Day Conditions (zero shift)
150
+
151
+ A rifle's zero is a fixed barrel angle set on the day you sighted in. If you later shoot
152
+ in different weather — or with a different muzzle velocity (e.g. a cold vs. warm powder
153
+ temperature) — the point of impact shifts. By default `--auto-zero` solves the zero angle
154
+ using the same conditions you pass for the shot, which assumes you zeroed in today's
155
+ conditions. The `--zero-*` flags let you decouple the two: the zero **angle** is solved
156
+ under the conditions the rifle was actually zeroed in, while the trajectory itself runs
157
+ under the current shot-day conditions.
158
+
159
+ ```bash
160
+ # Zeroed on a cold morning (28 F) at 2600 fps; shooting this afternoon at 85 F / 2700 fps.
161
+ # The zero angle is solved for the cold/slow load, then the warm/fast trajectory is
162
+ # computed against it — so the dope correctly shows the point of impact drifting high.
163
+ ./ballistics trajectory \
164
+ -v 2700 -b 0.19 -m 77 -d 0.224 --drag-model g7 \
165
+ --temperature 85 --pressure 29.92 \
166
+ --auto-zero 100 --max-range 1000 --full \
167
+ --zero-velocity 2600 \
168
+ --zero-temperature 28
169
+ ```
170
+
171
+ Available overrides (each independently optional; any omitted flag falls back to the
172
+ shot-day value, so leaving them all off reproduces the previous behavior exactly):
173
+
174
+ | Flag | Meaning | Units (imperial / metric) |
175
+ |------|---------|---------------------------|
176
+ | `--zero-velocity` | Muzzle velocity on the zeroing day | fps / m·s⁻¹ |
177
+ | `--zero-temperature` | Air temperature on the zeroing day | °F / °C |
178
+ | `--zero-pressure` | Barometric pressure on the zeroing day | inHg / hPa |
179
+ | `--zero-humidity` | Relative humidity on the zeroing day | percent |
180
+ | `--zero-altitude` | Altitude on the zeroing day | feet / meters |
181
+
182
+ #### Powder Temperature
183
+
184
+ Propellant temperature changes muzzle velocity. Two models are available:
185
+
186
+ **Linear** — a constant sensitivity (fps or m/s per degree) applied relative to the
187
+ temperature the load was chronographed at:
188
+
189
+ ```bash
190
+ ./ballistics trajectory -v 2700 -b 0.19 -m 77 -d 0.224 --drag-model g7 \
191
+ --temperature 85 --use-powder-sensitivity \
192
+ --powder-temp-sensitivity 1.2 --powder-temp 70 # +1.2 fps per F above 70 F
193
+ ```
194
+
195
+ **Measured curve (non-linear)** — real powders aren't perfectly linear (temperature-
196
+ stable powders flatten; others steepen when hot). If you've chronographed the load at
197
+ several temperatures, pass the points directly and the muzzle velocity is interpolated
198
+ at the powder temperature (clamped at the endpoints — no extrapolation). This
199
+ **overrides** `--powder-temp-sensitivity` when supplied:
8
200
 
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.
201
+ ```bash
202
+ ./ballistics trajectory -v 2700 -b 0.19 -m 77 -d 0.224 --drag-model g7 \
203
+ --temperature 85 \
204
+ --powder-temp-curve "40:2620,70:2700,100:2760" # TEMP:VELOCITY points
205
+ ```
206
+
207
+ **Powder temperature vs air temperature.** The curve maps *powder* temperature to
208
+ velocity, while `--temperature` drives air *density*. These are decoupled: the curve is
209
+ looked up at `--powder-temp` when given, otherwise at `--temperature` (powder assumed at
210
+ air temperature). So a load left in a hot chamber or a cold pocket:
211
+
212
+ ```bash
213
+ # 85 F air (density), but the powder is at 60 F (velocity from the curve at 60 F)
214
+ ./ballistics trajectory ... --temperature 85 --powder-temp 60 \
215
+ --powder-temp-curve "40:2620,70:2700,100:2760"
216
+ ```
217
+
218
+ Both powder models compose with `--auto-zero`, symmetrically. For the linear model,
219
+ `--zero-temperature` resolves zero-day velocity relative to the reference `--powder-temp`.
220
+ For a curve, `--zero-powder-temp` overrides the powder lookup; otherwise an explicit
221
+ `--zero-temperature` is used, or the shot-day `--powder-temp` is inherited when no zero-day
222
+ temperature was supplied. Zero-day atmosphere flags still drive air density independently.
223
+ An explicit `--zero-velocity` takes precedence over either powder model.
224
+
225
+ #### Bore Height and Ground Impact
226
+
227
+ Control bore height above ground and ground impact detection:
228
+
229
+ ```bash
230
+ # Set bore height for prone shooting position (2 feet)
231
+ ./ballistics trajectory \
232
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
233
+ --auto-zero 100 \
234
+ --bore-height 2 # 2 feet (imperial) or meters (metric)
235
+
236
+ # Disable ground impact detection for full trajectory to max range
237
+ ./ballistics trajectory \
238
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
239
+ --auto-zero 100 \
240
+ --max-range 1000 \
241
+ --ignore-ground-impact
242
+ ```
243
+
244
+ Bore height defaults: 5 feet (imperial) / 1.5 meters (metric) - standing position.
12
245
 
13
- > This package is built from `pkg/` (the `wasm-pack --target bundler` output) via
14
- > `scripts/build-npm.sh` in the source repo. The same script also produces a `pkg-web/` build
15
- > (`--target web`) for use without a bundler — see "Browser without a bundler" below.
246
+ #### Advanced BC Modeling
16
247
 
17
- ## Install
248
+ Enable velocity-dependent BC modeling for more accurate long-range predictions:
18
249
 
19
250
  ```bash
20
- npm install @SCOPE/ballistics-engine
251
+ # Enable BC segmentation (velocity-based BC changes)
252
+ ./ballistics trajectory \
253
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
254
+ --use-bc-segments \
255
+ --auto-zero 600 \
256
+ --max-range 1000
21
257
  ```
22
258
 
23
- `@SCOPE` is a placeholder see the source repo's `README.md` ("WASM / npm Package" section) for
24
- the real published name once one exists.
259
+ #### Advanced Physics - Magnus and Spin Drift
25
260
 
26
- ## Quick start (bundler: webpack, Vite, Rollup, Parcel)
261
+ Enable advanced gyroscopic and aerodynamic effects:
27
262
 
28
- This package's `main` entry imports its `.wasm` file as a native ES module, which is how
29
- `wasm-pack --target bundler` output is meant to be consumed. It works out of the box with Vite and
30
- Rollup (`@rollup/plugin-wasm`), and with webpack once `experiments.asyncWebAssembly` (or
31
- `experiments.syncWebAssembly`) is enabled check your bundler's WASM docs if the import fails.
263
+ ```bash
264
+ # Magnus effect and spin drift calculation
265
+ ./ballistics trajectory \
266
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
267
+ --twist-rate 10 # 1:10" barrel twist
268
+ --twist-right # Right-hand twist
269
+ --enable-magnus # Enable Magnus effect
270
+ --enable-spin-drift # Enable empirical Litz spin drift
271
+ --wind-speed 10 \
272
+ --wind-direction 90 \
273
+ --max-range 1000
274
+
275
+ # Coriolis effect for extreme long range
276
+ ./ballistics trajectory \
277
+ -v 3000 -b 0.750 -m 250 -d 0.338 \
278
+ --enable-coriolis \
279
+ --latitude 45 # Shooting latitude
280
+ --shooting-angle 90 # Azimuth (0=N, 90=E)
281
+ --max-range 2000
282
+ ```
32
283
 
33
- ```js
34
- import { WasmBallistics } from '@SCOPE/ballistics-engine';
284
+ ### Zero Calculation
35
285
 
36
- const calc = new WasmBallistics();
286
+ Calculate the sight adjustment needed to zero at a specific distance:
37
287
 
38
- // The command surface mirrors the native CLI (see CLI_USAGE.md in the source repo for the full
39
- // flag reference): .308 Winchester, 168gr @ 2700 fps, zeroed at 200 yd, table out to 500 yd.
40
- const table = calc.runCommand(
41
- 'trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 500 --auto-zero 200',
42
- );
43
- console.log(table);
288
+ ```bash
289
+ # Calculate zero for 200 yards
290
+ ./ballistics zero \
291
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
292
+ --target-distance 200
293
+
294
+ # With custom sight height (default is 0.05 yards / 1.8 inches)
295
+ ./ballistics zero \
296
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
297
+ --target-distance 300 \
298
+ --sight-height 0.055 # 2.2 inches
299
+
300
+ # Metric example
301
+ ./ballistics zero --units metric \
302
+ -v 823 -b 0.475 -m 10.9 -d 7.82 \
303
+ --target-distance 200 # 200 meters
44
304
  ```
45
305
 
46
- ### Custom drag tables (`loadDragTable`)
306
+ Output includes:
307
+ - Zero angle in degrees
308
+ - Adjustment in MOA (Minutes of Angle)
309
+ - Adjustment in mrad (milliradians)
310
+ - Maximum ordinate (highest point of trajectory)
47
311
 
48
- Supply a measured or manufacturer-published Mach:Cd drag curve (Hornady CDM data, a Lapua/Doppler
49
- deck, or your own) instead of a G1/G7 model + BC. Once loaded it's applied automatically to every
50
- `trajectory`, `zero`, `lead`, and `monte-carlo` run — no extra flag needed.
312
+ ### Monte Carlo Simulation
51
313
 
52
- ```js
53
- 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';
54
- calc.loadDragTable(new TextEncoder().encode(csv));
55
- calc.hasDragTable(); // true
314
+ Run statistical analysis with parameter variations:
56
315
 
57
- calc.runCommand('trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 500');
316
+ ```bash
317
+ ./ballistics monte-carlo \
318
+ -v 2700 # Base velocity (fps)
319
+ -b 0.475 # Base BC
320
+ -m 168 # Mass (grains)
321
+ -d 0.308 # Diameter (inches)
322
+ -n 1000 # Number of simulations
323
+ --velocity-std 10 # Velocity std dev (fps)
324
+ --angle-std 0.5 # Angle std dev (degrees)
325
+ --bc-std 0.01 # BC std dev
326
+ --wind-std 2 # Wind speed std dev (mph)
327
+ --wind-direction-std 5 # Wind direction std dev (degrees)
328
+ --target-distance 300 # Target distance for hit probability
58
329
  ```
59
330
 
60
- `loadDragTable` takes raw bytes because WASM has no filesystem access — fetch the CSV yourself
61
- (`fetch()` in the browser, `fs.readFileSync` in Node) and pass the bytes in. The CSV format is
62
- documented in the source repo's `CLI_USAGE.md` ("Custom Drag Tables"); a matching
63
- `loadBc5dTable(bytes)` / `hasBc5dTable()` pair exists for BC5D correction tables.
331
+ ### BC Estimation
64
332
 
65
- ## Browser without a bundler
333
+ Estimate ballistic coefficient from observed trajectory data:
334
+
335
+ ```bash
336
+ ./ballistics estimate-bc \
337
+ -v 2700 -m 168 -d 0.308 \
338
+ --distance1 100 --drop1 0.0 # First data point
339
+ --distance2 200 --drop2 0.023 # Second data point
340
+ ```
66
341
 
67
- For a plain `<script type="module">` page (no build step), use the `pkg-web/` build instead —
68
- produced by the same `scripts/build-npm.sh`, and the same build already deployed at
69
- [ballistics.sh](https://ballistics.sh). It ships an explicit async `init()` you call once before
70
- constructing `WasmBallistics`:
342
+ ### True Velocity (Velocity Truing)
71
343
 
72
- ```html
73
- <script type="module">
74
- import init, { WasmBallistics } from './ballistics_engine.js';
344
+ 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.
75
345
 
76
- await init(); // fetches and instantiates ballistics_engine_bg.wasm relative to this file
77
- const calc = new WasmBallistics();
78
- console.log(calc.runCommand('trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 500'));
79
- </script>
346
+ ```bash
347
+ # Basic offline calculation
348
+ ./ballistics true-velocity \
349
+ --measured-drop 5.1 --range 600 \
350
+ --bc 0.27 --drag-model g7 \
351
+ --mass 140 --diameter 0.264 \
352
+ --offline
353
+
354
+ # With chronograph comparison
355
+ ./ballistics true-velocity \
356
+ --measured-drop 5.1 --range 600 \
357
+ --bc 0.27 --drag-model g7 \
358
+ --mass 140 --diameter 0.264 \
359
+ --chrono-velocity 2822 \
360
+ --offline
361
+
362
+ # Chronograph measured downrange, not at the muzzle (MBA-1377): most
363
+ # chronographs read 10-15 ft (or 25 m) downrange, so --chrono-distance
364
+ # back-solves the true muzzle velocity from the raw reading
365
+ ./ballistics true-velocity \
366
+ --measured-drop 5.1 --range 600 \
367
+ --bc 0.27 --drag-model g7 \
368
+ --mass 140 --diameter 0.264 \
369
+ --chrono-velocity 2822 --chrono-distance 15 \
370
+ --offline
371
+
372
+ # With BC5D tables for improved accuracy
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
+ --bc-table-auto --offline
378
+
379
+ # Joint MV + BC calibration from multiple observed impacts
380
+ ./ballistics true-velocity \
381
+ --range 300 --measured-drop 1.30 \
382
+ --observed 600:4.40 --observed 900:9.00 \
383
+ --bc 0.45 --drag-model g1 \
384
+ --mass 168 --diameter 0.308
80
385
  ```
81
386
 
82
- Serve `ballistics_engine.js` and `ballistics_engine_bg.wasm` from the same directory, and make sure
83
- your host serves `.wasm` with `Content-Type: application/wasm` (all major static hosts and CDNs do
84
- this by default).
387
+ 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.
388
+
389
+ **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.
85
390
 
86
- This `pkg-web/` build is not published to npm as part of this package in the current release
87
- it's built locally alongside `pkg/` for direct use or self-hosting. If you need it from npm, either
88
- vendor the files from `pkg-web/` yourself or publish it as a second package.
391
+ **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.
89
392
 
90
- ### Node.js without a bundler
393
+ **Plan the observations before shooting (MBA-1346).** `plan-truing` evaluates a
394
+ discrete set of ranges with the same forward model and finite-difference Jacobian
395
+ used by the fitter, then chooses an exact-size, minimum-separation-compliant design.
396
+ It reports information gain, singular values, conditioning, rejected/unreachable
397
+ candidates, and an explicit MV-only recommendation when the available facility
398
+ cannot identify BC:
91
399
 
92
- Plain Node `import`/`require` cannot load this package's bundler-target `.wasm` import directly.
93
- Use the `pkg-web/` build instead, passing the file bytes explicitly (Node's `fetch()` does not
94
- support `file://` URLs):
400
+ ```bash
401
+ ./ballistics plan-truing \
402
+ -v 2700 -b 0.475 --drag-model g1 -m 168 -d 0.308 \
403
+ --candidate-ranges 200,300,400,500,600,700,800,900 \
404
+ --observation-count 3 --minimum-separation 100 \
405
+ --measurement-resolution 0.03 --drop-unit mil
406
+ ```
95
407
 
96
- ```js
97
- import { readFileSync } from 'node:fs';
98
- import init, { WasmBallistics } from './pkg-web/ballistics_engine.js';
408
+ `--measurement-resolution` is the assumed independent **one-standard-deviation**
409
+ impact-reading error, not a tolerance or extreme bound. A saved scalar G1/G7
410
+ profile may replace the explicit load flags (`--profile NAME`); velocity-banded BC
411
+ profiles and custom drag curves are rejected because they do not have one scalar BC
412
+ parameter to identify.
99
413
 
100
- const wasmBytes = readFileSync(new URL('./pkg-web/ballistics_engine_bg.wasm', import.meta.url));
101
- await init({ module_or_path: wasmBytes });
414
+ **Quantify what the observations actually learned (MBA-1353).** Add
415
+ `--observation-sigma` to opt into a weighted joint MV/BC MAP fit and local Gaussian
416
+ uncertainty report. A third `--observed RANGE:DROP:SIGMA` field overrides the default
417
+ for one reading. Optional priors are always visible and explicit; predictive output
418
+ separates uncertainty in the modeled drop from the wider interval for a future
419
+ reading:
102
420
 
103
- const calc = new WasmBallistics();
104
- console.log(calc.runCommand('trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --max-range 500'));
421
+ ```bash
422
+ ./ballistics true-velocity \
423
+ --range 500 --measured-drop 3.18 \
424
+ --observed 600:4.35:0.03 --observed 900:8.89:0.02 \
425
+ --observation-sigma 0.03 \
426
+ --bc 0.45 --drag-model g1 --mass 168 --diameter 0.308 \
427
+ --predict-range 1000 --prediction-sigma 0.03 --output json
105
428
  ```
106
429
 
107
- If you need a CommonJS (`require()`) Node build, generate one yourself:
108
- `wasm-pack build --target nodejs --no-default-features`.
430
+ The report includes MV/BC 95% intervals, covariance and correlation, chi-square,
431
+ effective degrees of freedom, prior-domination/weak-identification warnings, and
432
+ propagated drop bands. Declared sigmas are treated as absolute known errors, so the
433
+ covariance is not rescaled by residual RMS. With no uncertainty flags, the existing
434
+ point estimate and output schema are unchanged.
435
+
436
+ ### DSF (Drop-Scale-Factor) Truing
109
437
 
110
- ## Caveats
438
+ Second stage of the Applied Ballistics-style two-stage truing workflow (MBA-1357).
439
+ Once `true-velocity` has fixed the supersonic (Mach > 1.2) muzzle velocity/BC, the
440
+ drop discrepancies that grow through the transonic region and into subsonic flight are
441
+ no longer fixable by a single MV correction — the residual is a slowly-varying function
442
+ of Mach. `dsf` records observed-drop/predicted-drop ratios at specific Mach <= 1.2
443
+ ranges and keys them, one saved profile at a time, to a Mach-indexed table:
444
+
445
+ ```bash
446
+ # Stage 1: true the muzzle velocity from a supersonic-range drop reading (as above).
447
+ ./ballistics true-velocity \
448
+ --measured-drop 3.2 --range 500 \
449
+ --bc 0.475 --mass 168 --diameter 0.308 \
450
+ --offline
451
+
452
+ # Stage 2: record a subsonic/transonic drop observation on the trued, saved profile.
453
+ ./ballistics dsf --saved-profile my-rifle --range 900 --observed-drop 5.1mil
454
+ ```
455
+
456
+ `dsf` takes no ballistic parameters of its own — it solves the named saved profile's
457
+ own trajectory (no CLI overrides) and derives everything else from `--range` and
458
+ `--observed-drop` (`mil`, `moa`, or `in`, no separator between number and unit). An
459
+ observation whose target-range Mach exceeds 1.2 is rejected outright, pointing back to
460
+ `true-velocity`. Up to 6 distinct Mach-keyed points accumulate per profile; a new point
461
+ within 0.05 Mach of an existing one supersedes it (announced on stdout); a 7th distinct
462
+ point is rejected, naming `--clear-dsf` to make room. `trajectory --saved-profile` and
463
+ `come-ups --profile` then auto-apply the table as a **drop-only** correction — velocity,
464
+ energy, and time of flight are byte-identical to the untrued solve — printing a
465
+ table-output-only note; JSON/CSV carry the corrected drop numbers with no equivalent
466
+ text. `profile save NAME ... --clear-dsf` removes an existing table. See
467
+ [CLI_USAGE.md](CLI_USAGE.md#dsf-drop-scale-factor-truing) for the full staging-gate
468
+ reference.
469
+
470
+ ## Advanced Features
471
+
472
+ ### Online Mode (API Integration)
473
+
474
+ 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.
475
+
476
+ > **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.
477
+
478
+ ```bash
479
+ # Use online mode to query the API
480
+ ./ballistics trajectory \
481
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
482
+ --online \
483
+ --max-range 1000
484
+
485
+ # Custom API endpoint
486
+ ./ballistics trajectory \
487
+ -v 2700 -b 0.475 -m 168 -d 0.308 \
488
+ --online \
489
+ --api-url https://your-api.example.com/v1/calculate \
490
+ --max-range 1000
491
+ ```
492
+
493
+ **Default API**: `https://api.ballistics.7.62x51mm.sh/v1/calculate`
494
+
495
+ Online mode benefits:
496
+ - **Enhanced BC data** - Access to doppler-derived ballistic coefficients
497
+ - **ML predictions** - Machine learning augmented trajectory calculations
498
+ - **BC segments** - Velocity-dependent BC modeling from measured data
499
+ - **Form factor corrections** - Bullet-specific drag adjustments
500
+
501
+ **Data transmitted when using --online:**
502
+ - All trajectory parameters (BC, mass, velocity, wind, atmospheric conditions, etc.)
503
+ - Your IP address and client version
504
+ - Request logs retained for 30 days, then deleted
505
+
506
+ To use only local calculations (no network, no data transmission):
507
+ ```bash
508
+ cargo install ballistics-engine --no-default-features
509
+ ```
510
+
511
+ ### Integration Methods
512
+
513
+ The engine supports two numerical integration methods:
514
+
515
+ - **RK45 (Dormand-Prince Adaptive)** - Default method, provides best accuracy with adaptive step sizing
516
+ - **RK4 (Runge-Kutta 4th Order Fixed-Step)** - Available with `--use-rk4-fixed` flag for faster computation
517
+
518
+ ### Wind Shear Modeling
519
+
520
+ Model altitude-dependent wind variations:
521
+
522
+ ```bash
523
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 \
524
+ --wind-speed 10 --wind-direction 90 \
525
+ --enable-wind-shear \
526
+ --max-range 1000
527
+ ```
111
528
 
112
- - **Size**: the `.wasm` binary is roughly 430 KB (about 175 KB gzipped). It is not code-split or
113
- lazily loaded — the whole engine loads up front.
114
- - **Single-threaded**: no SIMD/threads assumptions; no `SharedArrayBuffer` or
115
- cross-origin-isolation (COOP/COEP) headers required.
116
- - **No filesystem/network**: table loaders (`loadDragTable`, `loadBc5dTable`) and any file-based
117
- CLI flags (e.g. native `--drag-table <FILE>`) need the host to fetch bytes and hand them in; see
118
- `loadDragTable` above.
119
- - **`pdf`/`online` features are unavailable**: this build excludes them (see above), so
120
- PDF dope-card export and the online BC-estimation API are not part of the WASM surface.
121
- - Full API surface (including the `Calculator` builder class) is documented in the bundled
122
- `.d.ts`; the full CLI flag reference `runCommand` accepts is documented in the source repo's
123
- `CLI_USAGE.md`.
529
+ ### Transonic Stability Analysis
530
+
531
+ Analyze projectile stability through the transonic regime:
532
+
533
+ ```bash
534
+ ./ballistics trajectory -v 3000 -b 0.475 -m 168 -d 0.308 \
535
+ --enable-pitch-damping \
536
+ --max-range 2000
537
+ ```
538
+
539
+ Provides warnings about transonic instability and minimum pitch damping coefficients.
540
+
541
+ ### Trajectory Sampling
542
+
543
+ Collect trajectory data at regular intervals for detailed analysis:
544
+
545
+ ```bash
546
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 \
547
+ --sample-trajectory \
548
+ --sample-interval 25 # Sample every 25 meters
549
+ --max-range 1000 -o json
550
+ ```
551
+
552
+ ### Angular Motion Physics
553
+
554
+ Model precession and nutation of spinning projectiles:
555
+
556
+ ```bash
557
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 \
558
+ --twist-rate 10 \
559
+ --enable-precession \
560
+ --max-range 1000
561
+ ```
562
+
563
+ ### Complete Advanced Physics Example
564
+
565
+ ```bash
566
+ ./ballistics trajectory \
567
+ -v 2850 -b 0.690 -m 230 -d 0.338 \
568
+ --drag-model g7 \
569
+ --twist-rate 8.5 --twist-right \
570
+ --enable-magnus \
571
+ --enable-coriolis \
572
+ --enable-spin-drift \
573
+ --enable-wind-shear \
574
+ --enable-pitch-damping \
575
+ --enable-precession \
576
+ --sample-trajectory \
577
+ --latitude 38.5 \
578
+ --shooting-angle 45 \
579
+ --wind-speed 15 --wind-direction 270 \
580
+ --altitude 6000 \
581
+ --max-range 2000
582
+ ```
583
+
584
+ ## Physics Modeling
585
+
586
+ The ballistics engine implements comprehensive physics modeling for accurate trajectory prediction:
587
+
588
+ ### Aerodynamic Effects
589
+ - **Drag Modeling** - Full standard-projectile family (G1, G2, G5, G6, G7, G8, GI, GS, RA4) plus custom Cd(Mach) curves, with transonic flow corrections
590
+ - **Form Factor** - Projectile efficiency corrections based on shape and design
591
+ - **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
592
+
593
+ ### Gyroscopic Effects
594
+ - **Spin Drift** - Lateral deviation due to gyroscopic and Magnus effects
595
+ - **Precession** - Gyroscopic precession of spinning projectile
596
+ - **Nutation** - Oscillatory motion superimposed on precession
597
+ - **Spin Decay** - Reduction in spin rate over time due to aerodynamic damping
598
+ - **Pitch Damping** - Aerodynamic moments opposing angular motion
599
+
600
+ ### Environmental Effects
601
+ - **Coriolis Effect** - Earth's rotation influence on long-range trajectories
602
+ - **Magnus Effect** - Force from spinning projectile in crossflow
603
+ - **Wind Shear** - Altitude-dependent wind variations
604
+ - **Atmospheric Stratification** - Density and sound speed variations with altitude
605
+
606
+ ### Stability Modeling
607
+ - **Dynamic Stability** - Gyroscopic and aerodynamic stability calculations
608
+ - **Yaw of Repose** - Gravity/gyroscopic equilibrium yaw; crosswind yaw is a transient handled by aerodynamic jump
609
+ - **Limit Cycle Yaw** - Bounded oscillatory motion analysis
610
+
611
+ ## Language Bindings
612
+
613
+ Official language bindings are maintained as separate projects:
614
+
615
+ - **Python**: [ballistics-engine-py](https://github.com/ajokela/ballistics-engine-py) - PyO3 bindings via maturin
616
+ - **Ruby**: [ballistics-engine-rb](https://github.com/ajokela/ballistics-engine-rb) - Magnus bindings via rb_sys
617
+
618
+ These bindings depend on the `ballistics-engine` crate published on [crates.io](https://crates.io/crates/ballistics-engine).
619
+
620
+ ### WASM / npm Package
621
+
622
+ The engine also compiles to WebAssembly (`src/wasm.rs`, `wasm-bindgen`) and already powers
623
+ [ballistics.sh](https://ballistics.sh) and [ballistics.rs](https://ballistics.rs) in the browser.
624
+ It is not yet published to npm for third-party use — `scripts/build-npm.sh` builds and prepares a
625
+ publish-ready package; publishing itself is a manual step (see below).
626
+
627
+ ```bash
628
+ scripts/build-npm.sh
629
+ ```
630
+
631
+ This builds two `wasm-bindgen` targets, both with `--no-default-features` (the default
632
+ `pdf`/`online` features pull in `printpdf`/`ureq`+`ring`, which do not compile for
633
+ `wasm32-unknown-unknown` — see "Updating the WASM Module" in `CLAUDE.md`):
634
+
635
+ - **`pkg/`** — `--target bundler`, the package meant for `npm publish`. Consumed via a native
636
+ `.wasm` ES import by bundlers that understand it (webpack with `experiments.asyncWebAssembly`,
637
+ Vite, Rollup + `@rollup/plugin-wasm`, Parcel).
638
+ - **`pkg-web/`** — `--target web`, a no-bundler build for direct `<script type="module">` browser
639
+ use or manual Node usage without a bundler — the same `--target` already used to build
640
+ ballistics.sh/ballistics.rs's WASM. Documented and built for completeness; not published under
641
+ the primary package name in this initial pass.
642
+
643
+ `wasm-pack` has no built-in dual-target/"publish both" mode, and stitching bundler- and web-target
644
+ output into one package.json via manual `exports` conditions isn't something `wasm-pack` generates
645
+ or tests for you — see the comment header of `scripts/build-npm.sh` for the full reasoning. A
646
+ single bundler-target package as the published npm artifact, with the web build documented
647
+ separately, is the ecosystem-standard shape for `wasm-bindgen` crates on npm.
648
+
649
+ The script also post-processes each `package.json` (name, description, license, repository,
650
+ keywords, and the `files` list — including an `LICENSE-APACHE` entry `wasm-pack` itself omits even
651
+ though it copies the file) and installs `README-npm.md` as the package's `README.md`.
652
+
653
+ **Before publishing**, edit `pkg/package.json`'s `"name"` — it ships as the placeholder
654
+ `"@SCOPE/ballistics-engine"`. Replace `SCOPE` with the maintainer's real npm org/user scope (a
655
+ scope decision, plus an npm account with publish rights to it, are both needed and don't exist yet
656
+ as of this writing). Then:
657
+
658
+ ```bash
659
+ scripts/build-npm.sh
660
+ cd pkg
661
+ npm pack --dry-run # sanity-check the tarball contents first
662
+ npm publish --access public
663
+ ```
664
+
665
+ (`--access public` is required the first time a scoped package is published, since scoped packages
666
+ default to private on free npm accounts; `pkg/package.json` also sets `publishConfig.access` to
667
+ `public` so a plain `npm publish` works too.)
668
+
669
+ ## FFI Layer
670
+
671
+ 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.
672
+
673
+ <img src="ios.png" alt="iOS Integration Example" width="35%">
674
+
675
+ ### FFI Features
676
+ - **C-Compatible Structures** - All data structures use C-compatible layouts
677
+ - **Safe Memory Management** - Proper handling of memory across language boundaries
678
+ - **iOS/Swift Integration** - Ready for use with Swift through bridging headers
679
+ - **Android/JNI Support** - Compatible with Java Native Interface
680
+ - **Monte Carlo Simulation** - Statistical analysis with parameter variations
681
+ - **Error Handling** - Graceful error propagation across FFI boundary
682
+
683
+ ### Example FFI Usage (C/Swift)
684
+ ```c
685
+ // Create input parameters
686
+ FFIBallisticInputs inputs = {
687
+ .muzzle_velocity = 823.0, // m/s
688
+ .ballistic_coefficient = 0.475,
689
+ .mass = 0.0109, // kg
690
+ .diameter = 0.00782, // meters
691
+ .drag_model = 0, // G1
692
+ .sight_height = 0.05, // meters
693
+ .temperature = 15.0, // Celsius
694
+ .altitude = 0.0
695
+ };
696
+
697
+ // Calculate trajectory. The final argument is the integration step in milliseconds
698
+ // (minimum 0.1 ms; smaller or non-finite values return NULL).
699
+ FFITrajectoryResult* result = ballistics_calculate_trajectory(&inputs, NULL, NULL, 1000.0, 0.1);
700
+
701
+ // NULL also reports invalid inputs or the 250,000-point resource ceiling.
702
+ // Increase the step, reduce the range, or use adaptive RK45 for an over-budget solve.
703
+ if (result != NULL) {
704
+ printf("Max range: %.2f meters\n", result->max_range);
705
+ ballistics_free_trajectory_result(result);
706
+ }
707
+ ```
708
+
709
+ ### Monte Carlo Simulation via FFI
710
+ ```c
711
+ // Set up Monte Carlo parameters
712
+ FFIMonteCarloParams params = {
713
+ .num_simulations = 1000,
714
+ .velocity_std_dev = 10.0, // m/s variation
715
+ .angle_std_dev = 0.001, // radian variation (elevation)
716
+ .bc_std_dev = 0.01, // BC variation
717
+ .wind_speed_std_dev = 2.0, // m/s wind variation
718
+ .target_distance = 600.0, // Target at 600m
719
+ .azimuth_std_dev = 0.001 // radian variation (horizontal)
720
+ };
721
+
722
+ // Run simulation with an independent 0.1-radian wind-direction sigma.
723
+ // Use ballistics_monte_carlo(...) when no direction variation is desired.
724
+ FFIMonteCarloResults* results =
725
+ ballistics_monte_carlo_with_direction_std_dev(&inputs, NULL, &params, 0.1);
726
+
727
+ // Use statistical results
728
+ printf("Mean range: %.2f m (σ=%.2f)\n", results->mean_range, results->std_dev_range);
729
+ printf("Hit probability at 600m: %.1f%%\n", results->hit_probability * 100);
730
+
731
+ // Access individual shots
732
+ for (int i = 0; i < results->num_results; i++) {
733
+ printf("Shot %d: Range %.2f m, Impact velocity %.2f m/s\n",
734
+ i, results->ranges[i], results->impact_velocities[i]);
735
+ }
736
+
737
+ // Clean up
738
+ ballistics_free_monte_carlo_results(results);
739
+ ```
740
+
741
+ ## Output Formats
742
+
743
+ All commands support three output formats via the `-o` flag:
744
+
745
+ - **table** (default) - Formatted ASCII table for terminal display
746
+ - **json** - Complete data in JSON format for programmatic use
747
+ - **csv** - Comma-separated values for spreadsheet analysis
748
+
749
+ ## Practical Examples
750
+
751
+ ### Hunting Zero
752
+
753
+ Zero a hunting rifle at 200 yards with environmental conditions:
754
+
755
+ ```bash
756
+ # Calculate zero angle
757
+ ./ballistics zero \
758
+ -v 2650 -b 0.460 -m 180 -d 0.308 \
759
+ --target-distance 200
760
+
761
+ # Verify trajectory with auto-zero
762
+ ./ballistics trajectory \
763
+ -v 2650 -b 0.460 -m 180 -d 0.308 \
764
+ --auto-zero 200 \
765
+ --max-range 400 \
766
+ --wind-speed 15 \
767
+ --wind-direction 270 \
768
+ --temperature 32 \
769
+ --humidity 30 \
770
+ --altitude 5000 \
771
+ --full
772
+ ```
773
+
774
+ ### Long Range Shooting
775
+
776
+ Analyze trajectory for 1000-yard shot:
777
+
778
+ ```bash
779
+ ./ballistics trajectory \
780
+ -v 2850 -b 0.690 -m 230 -d 0.338 \
781
+ --drag-model g7 \
782
+ --auto-zero 100 \
783
+ --max-range 1100 \
784
+ --wind-speed 10 \
785
+ --wind-direction 45 \
786
+ --full \
787
+ -o json > trajectory.json
788
+ ```
789
+
790
+ ### Load Development
791
+
792
+ Compare different loads using Monte Carlo:
793
+
794
+ ```bash
795
+ # Load 1: Higher velocity, more variation
796
+ ./ballistics monte-carlo \
797
+ -v 2750 -b 0.475 -m 168 -d 0.308 \
798
+ -n 1000 \
799
+ --velocity-std 15 \
800
+ --target-distance 600
801
+
802
+ # Load 2: Lower velocity, more consistent
803
+ ./ballistics monte-carlo \
804
+ -v 2680 -b 0.475 -m 168 -d 0.308 \
805
+ -n 1000 \
806
+ --velocity-std 8 \
807
+ --target-distance 600
808
+ ```
809
+
810
+ ## Advanced Features
811
+
812
+ ### BC Segmentation
813
+
814
+ Velocity-dependent BC modeling accounts for how ballistic coefficient changes as the bullet slows down. Enable with `--use-bc-segments`:
815
+
816
+ - Automatically estimates BC segments based on bullet characteristics
817
+ - No external data required - uses caliber, weight, and BC
818
+ - Identifies bullet type (Match, Hunting, VLD, etc.) from parameters
819
+ - Applies physics-based BC degradation curves
820
+
821
+ Example:
822
+ ```bash
823
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --use-bc-segments --max-range 1000
824
+ ```
825
+
826
+ **Manual velocity-keyed BC segments** — supply your own `VMIN:VMAX:BC` pairs (repeatable,
827
+ velocities in `--units`) instead of the auto-estimated/table ones. Keyed to velocity, so it
828
+ composes with distance-keyed `--wind-segment`; implies `--use-bc-segments` and overrides
829
+ `--bc-table` and `--bc-table-dir`:
830
+ ```bash
831
+ ./ballistics trajectory -v 2600 -b 0.243 -m 175 -d 0.308 --drag-model g7 --max-range 1000 \
832
+ --bc-segment 1800:4000:0.243 --bc-segment 1500:1800:0.228 --bc-segment 1200:1500:0.205
833
+ ```
834
+
835
+ ### BC5D Correction Tables
836
+
837
+ BC5D tables provide ML-derived, 5-dimensional BC corrections indexed by weight, BC, muzzle velocity, current velocity, and drag model. Tables are caliber-specific and capture the complete velocity-dependent behavior.
838
+
839
+ **Auto-Download Mode** (requires `online` feature):
840
+ ```bash
841
+ # Downloads tables automatically on first use
842
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --bc-table-auto
843
+
844
+ # Force refresh cached tables
845
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --bc-table-auto --bc-table-refresh
846
+ ```
847
+
848
+ **Offline Mode** with pre-downloaded tables:
849
+ ```bash
850
+ ./ballistics trajectory -v 2700 -b 0.475 -m 168 -d 0.308 --bc-table-dir ./bc_tables/
851
+ ```
852
+
853
+ **Available calibers:** .224, .243, .264, .277, .284, .308, .338
854
+
855
+ **Cache locations:**
856
+ - macOS: `~/Library/Caches/ballistics-engine/bc5d/`
857
+ - Linux: `~/.cache/ballistics-engine/bc5d/`
858
+ - Windows: `%LOCALAPPDATA%\ballistics-engine\cache\bc5d\`
859
+
860
+ Tables are approximately 1-1.5 MB each and include CRC32 validation to ensure data integrity.
861
+
862
+ ### Advanced Physics Modeling
863
+
864
+ When enabled, the engine calculates:
865
+ - **Magnus Effect** - Side force from spinning projectiles
866
+ - **Spin Drift** - Lateral drift due to gyroscopic effects
867
+ - **Coriolis Effect** - Earth rotation effects (with latitude input)
868
+ - **Transonic Drag** - Enhanced drag modeling in transonic regime
869
+ - **Low-Reynolds Helper** - Opt-in viscous correction below the standard projectile-table regime
870
+
871
+ ## Building from Source
872
+
873
+ ### Requirements
874
+
875
+ - Rust 1.70 or later
876
+ - Cargo build system
877
+
878
+ ### Build Commands
879
+
880
+ ```bash
881
+ # Debug build
882
+ cargo build
883
+
884
+ # Release build (optimized)
885
+ cargo build --release
886
+
887
+ # Run tests
888
+ cargo test
889
+
890
+ # Build documentation
891
+ cargo doc --open
892
+ ```
893
+
894
+ ## Library Usage
895
+
896
+ Use as a Rust library in your own projects:
897
+
898
+ ```rust
899
+ use ballistics_engine::{
900
+ BallisticInputs, TrajectorySolver,
901
+ WindConditions, AtmosphericConditions
902
+ };
903
+
904
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
905
+ let inputs = BallisticInputs {
906
+ muzzle_velocity: 823.0, // m/s
907
+ launch_angle: 0.0, // radians
908
+ ballistic_coefficient: 0.475,
909
+ mass: 0.0109, // kg
910
+ diameter: 0.00782, // meters
911
+ sight_height: 0.05, // meters
912
+ ..Default::default()
913
+ };
914
+
915
+ let wind = WindConditions {
916
+ speed: 5.0, // m/s
917
+ direction: 1.5708, // 90 degrees in radians
918
+ ..Default::default()
919
+ };
920
+
921
+ let atmosphere = AtmosphericConditions {
922
+ temperature: 15.0, // Celsius
923
+ pressure: 1013.25, // hPa
924
+ humidity: 50.0, // %
925
+ altitude: 0.0, // meters
926
+ ..Default::default()
927
+ };
928
+
929
+ let solver = TrajectorySolver::new(inputs, wind, atmosphere);
930
+ let result = solver.solve()?;
931
+
932
+ println!("Max range: {:.2} m", result.max_range);
933
+ println!("Max height: {:.2} m", result.max_height);
934
+ println!("Time of flight: {:.3} s", result.time_of_flight);
935
+
936
+ Ok(())
937
+ }
938
+ ```
939
+
940
+ ## Performance
941
+
942
+ Optimized Rust implementation provides:
943
+ - Single trajectory (1000m): ~5ms
944
+ - Monte Carlo (1000 runs): ~500ms
945
+ - BC estimation: ~50ms
946
+ - Zero calculation: ~10ms
947
+
948
+ ## Common Ballistic Coefficients
949
+
950
+ | Caliber | Weight | BC (G1) | BC (G7) | Description |
951
+ |---------|--------|---------|---------|-------------|
952
+ | .223 | 55gr | 0.250 | - | FMJ |
953
+ | .223 | 77gr | 0.362 | 0.182 | Match |
954
+ | .308 | 168gr | 0.475 | 0.224 | Match |
955
+ | .308 | 175gr | 0.505 | 0.253 | Match |
956
+ | .308 | 180gr | 0.480 | - | Hunting |
957
+ | .338 | 300gr | 0.768 | 0.383 | Match |
958
+ | 6.5mm | 140gr | 0.620 | 0.310 | Match |
959
+ | .50 | 750gr | 1.050 | 0.520 | Match |
960
+
961
+ ## Troubleshooting
962
+
963
+ ### Trajectory hits ground early
964
+ - Check if you're using `--auto-zero` or setting `--angle` manually
965
+ - Default angle is 0° (horizontal), which will hit ground quickly
966
+ - Use `--auto-zero <distance>` to automatically calculate proper angle
967
+
968
+ ### Units confusion
969
+ - Default is Imperial (fps, grains, yards)
970
+ - Use `--units metric` for metric system
971
+ - All inputs must match the selected unit system
972
+
973
+ ### Unexpected BC behavior
974
+ - G1 and G7 models have different BC values for same bullet
975
+ - G7 typically better for boat-tail bullets
976
+ - BC segmentation automatically applied based on bullet type
977
+
978
+ ## Contributing
979
+
980
+ Contributions are welcome! Please:
981
+ 1. Fork the repository
982
+ 2. Create a feature branch
983
+ 3. Add tests for new features
984
+ 4. Run `cargo test` and `cargo fmt`
985
+ 5. Submit a pull request
124
986
 
125
987
  ## License
126
988
 
127
- MIT OR Apache-2.0 see `LICENSE` and `LICENSE-APACHE` in this package.
989
+ This project is licensed under the MIT License - see LICENSE file for details.
990
+
991
+ **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.
992
+
993
+ ## Acknowledgments
994
+
995
+ - Ballistics physics based on Robert McCoy's "Modern Exterior Ballistics"
996
+ - Drag tables from military ballistics research
997
+ - BC segmentation algorithms from Bryan Litz's research
998
+ - Community contributions and testing
128
999
 
129
- ## Source
1000
+ ## Support
130
1001
 
131
- https://github.com/ajokela/ballistics-engine
1002
+ For issues, questions, or contributions:
1003
+ - GitHub Issues: [github.com/ajokela/ballistics-engine/issues](https://github.com/ajokela/ballistics-engine/issues)