ballistics-engine 0.26.0 → 0.30.1

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
@@ -7,14 +7,16 @@ A high-performance ballistics trajectory calculation engine with comprehensive p
7
7
  ## Features
8
8
 
9
9
  - **Full 3D Trajectory Integration** - Six-state ballistic modeling with adaptive RK45 and fixed-step RK4 integration methods
10
- - **Advanced Drag Models** - Support for G1 and G7 reference curves (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)
11
- - **Automatic Zeroing** - Calculate sight adjustments and apply zero angles automatically
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
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
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
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)
15
17
  - **Unit Conversion** - Seamless switching between Imperial (default) and Metric units
16
18
  - **BC Segmentation** - Velocity-dependent ballistic coefficient modeling with automatic estimation
17
- - **Atmospheric Modeling** - Temperature, pressure, humidity, and altitude effects with ICAO standard atmosphere
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)
18
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)
19
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)
20
22
  - **Monte Carlo Simulations** - Statistical analysis with parameter uncertainties
@@ -27,7 +29,7 @@ A high-performance ballistics trajectory calculation engine with comprehensive p
27
29
  - **Trajectory Sampling**: Regular interval data collection for analysis
28
30
  - **Form Factor Corrections**: Bullet-specific drag adjustments
29
31
  - **Multiple Output Formats** - JSON, CSV, formatted tables, and printable PDF dope cards
30
- - **Terminal Chart** - Inline drop-vs-range and drift-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)
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)
31
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
32
34
 
33
35
  ## Installation
@@ -357,6 +359,16 @@ Calculate the effective muzzle velocity that produces a measured drop at a known
357
359
  --chrono-velocity 2822 \
358
360
  --offline
359
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
+
360
372
  # With BC5D tables for improved accuracy
361
373
  ./ballistics true-velocity \
362
374
  --measured-drop 5.1 --range 600 \
@@ -374,8 +386,87 @@ Calculate the effective muzzle velocity that produces a measured drop at a known
374
386
 
375
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.
376
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.
390
+
377
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.
378
392
 
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:
399
+
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
+ ```
407
+
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.
413
+
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:
420
+
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
428
+ ```
429
+
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
437
+
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
+
379
470
  ## Advanced Features
380
471
 
381
472
  ### Online Mode (API Integration)
@@ -495,7 +586,7 @@ Model precession and nutation of spinning projectiles:
495
586
  The ballistics engine implements comprehensive physics modeling for accurate trajectory prediction:
496
587
 
497
588
  ### Aerodynamic Effects
498
- - **Drag Modeling** - Multiple drag functions (G1-G8, JBM, custom curves) with transonic flow corrections
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
499
590
  - **Form Factor** - Projectile efficiency corrections based on shape and design
500
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
501
592
 
@@ -113,6 +113,13 @@ export class WasmBallistics {
113
113
  *
114
114
  * Returns a short human-readable summary of the loaded table (point count + Mach
115
115
  * range). Replaces any previously loaded table.
116
+ *
117
+ * MBA-1409: also accepts `.drg` vendor drag-curve text (the same format the native
118
+ * `--drag-table` CLI accepts by `.drg` file extension) as a fallback. WASM has no
119
+ * filesystem and thus no extension to dispatch on, so the bytes are tried as CSV first
120
+ * (exactly as before this change); only on CSV failure, if the text
121
+ * [`crate::drag_file::looks_like_drg`], it is retried through
122
+ * [`crate::drag_file::parse_drg`]. If both fail, the returned error names both formats.
116
123
  */
117
124
  loadDragTable(bytes: Uint8Array): string;
118
125
  constructor();
@@ -6,7 +6,6 @@
6
6
  */
7
7
  export class Calculator {
8
8
  static __wrap(ptr) {
9
- ptr = ptr >>> 0;
10
9
  const obj = Object.create(Calculator.prototype);
11
10
  obj.__wbg_ptr = ptr;
12
11
  CalculatorFinalization.register(obj, obj.__wbg_ptr, obj);
@@ -98,7 +97,7 @@ export class Calculator {
98
97
  */
99
98
  constructor() {
100
99
  const ret = wasm.calculator_new();
101
- this.__wbg_ptr = ret >>> 0;
100
+ this.__wbg_ptr = ret;
102
101
  CalculatorFinalization.register(this, this.__wbg_ptr, this);
103
102
  return this;
104
103
  }
@@ -327,6 +326,13 @@ export class WasmBallistics {
327
326
  *
328
327
  * Returns a short human-readable summary of the loaded table (point count + Mach
329
328
  * range). Replaces any previously loaded table.
329
+ *
330
+ * MBA-1409: also accepts `.drg` vendor drag-curve text (the same format the native
331
+ * `--drag-table` CLI accepts by `.drg` file extension) as a fallback. WASM has no
332
+ * filesystem and thus no extension to dispatch on, so the bytes are tried as CSV first
333
+ * (exactly as before this change); only on CSV failure, if the text
334
+ * [`crate::drag_file::looks_like_drg`], it is retried through
335
+ * [`crate::drag_file::parse_drg`]. If both fail, the returned error names both formats.
330
336
  * @param {Uint8Array} bytes
331
337
  * @returns {string}
332
338
  */
@@ -352,7 +358,7 @@ export class WasmBallistics {
352
358
  }
353
359
  constructor() {
354
360
  const ret = wasm.wasmballistics_new();
355
- this.__wbg_ptr = ret >>> 0;
361
+ this.__wbg_ptr = ret;
356
362
  WasmBallisticsFinalization.register(this, this.__wbg_ptr, this);
357
363
  return this;
358
364
  }
@@ -383,29 +389,28 @@ export class WasmBallistics {
383
389
  }
384
390
  }
385
391
  if (Symbol.dispose) WasmBallistics.prototype[Symbol.dispose] = WasmBallistics.prototype.free;
386
-
387
392
  function __wbg_get_imports() {
388
393
  const import0 = {
389
394
  __proto__: null,
390
- __wbg___wbindgen_throw_39bc967c0e5a9b58: function(arg0, arg1) {
395
+ __wbg___wbindgen_throw_344f42d3211c4765: function(arg0, arg1) {
391
396
  throw new Error(getStringFromWasm0(arg0, arg1));
392
397
  },
393
398
  __wbg_getRandomValues_cc7f052a444bb2ce: function() { return handleError(function (arg0, arg1) {
394
399
  globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1));
395
400
  }, arguments); },
396
- __wbg_new_cbee8c0d5c479eac: function() {
401
+ __wbg_new_32b398fb48b6d94a: function() {
397
402
  const ret = new Array();
398
403
  return ret;
399
404
  },
400
- __wbg_new_ed69e637b553a997: function() {
405
+ __wbg_new_da52cf8fe3429cb2: function() {
401
406
  const ret = new Object();
402
407
  return ret;
403
408
  },
404
- __wbg_push_a6f9488ffd3fae3b: function(arg0, arg1) {
409
+ __wbg_push_d2ae3af0c1217ae6: function(arg0, arg1) {
405
410
  const ret = arg0.push(arg1);
406
411
  return ret;
407
412
  },
408
- __wbg_set_bad5c505cc70b5f8: function() { return handleError(function (arg0, arg1, arg2) {
413
+ __wbg_set_8535240470bf2500: function() { return handleError(function (arg0, arg1, arg2) {
409
414
  const ret = Reflect.set(arg0, arg1, arg2);
410
415
  return ret;
411
416
  }, arguments); },
@@ -437,10 +442,10 @@ function __wbg_get_imports() {
437
442
 
438
443
  const CalculatorFinalization = (typeof FinalizationRegistry === 'undefined')
439
444
  ? { register: () => {}, unregister: () => {} }
440
- : new FinalizationRegistry(ptr => wasm.__wbg_calculator_free(ptr >>> 0, 1));
445
+ : new FinalizationRegistry(ptr => wasm.__wbg_calculator_free(ptr, 1));
441
446
  const WasmBallisticsFinalization = (typeof FinalizationRegistry === 'undefined')
442
447
  ? { register: () => {}, unregister: () => {} }
443
- : new FinalizationRegistry(ptr => wasm.__wbg_wasmballistics_free(ptr >>> 0, 1));
448
+ : new FinalizationRegistry(ptr => wasm.__wbg_wasmballistics_free(ptr, 1));
444
449
 
445
450
  function addToExternrefTable0(obj) {
446
451
  const idx = wasm.__externref_table_alloc();
@@ -454,8 +459,7 @@ function getArrayU8FromWasm0(ptr, len) {
454
459
  }
455
460
 
456
461
  function getStringFromWasm0(ptr, len) {
457
- ptr = ptr >>> 0;
458
- return decodeText(ptr, len);
462
+ return decodeText(ptr >>> 0, len);
459
463
  }
460
464
 
461
465
  let cachedUint8ArrayMemory0 = null;
@@ -558,8 +562,9 @@ if (!('encodeInto' in cachedTextEncoder)) {
558
562
 
559
563
  let WASM_VECTOR_LEN = 0;
560
564
 
561
- let wasmModule, wasm;
565
+ let wasmModule, wasmInstance, wasm;
562
566
  function __wbg_finalize_init(instance, module) {
567
+ wasmInstance = instance;
563
568
  wasm = instance.exports;
564
569
  wasmModule = module;
565
570
  cachedUint8ArrayMemory0 = null;
Binary file
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "Alex Jokela <email@tinycomputers.io>"
6
6
  ],
7
7
  "description": "High-performance ballistics trajectory engine with professional physics",
8
- "version": "0.26.0",
8
+ "version": "0.30.1",
9
9
  "license": "MIT OR Apache-2.0",
10
10
  "repository": {
11
11
  "type": "git",