ballistics-engine 0.33.1 → 0.33.4

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
@@ -673,7 +673,9 @@ scripts/build-npm.sh
673
673
 
674
674
  This builds two `wasm-bindgen` targets, both with `--no-default-features` (the default
675
675
  `pdf`/`online` features pull in `printpdf`/`ureq`+`ring`, which do not compile for
676
- `wasm32-unknown-unknown` — see "Updating the WASM Module" in `CLAUDE.md`):
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)):
677
679
 
678
680
  - **`pkg/`** — `--target bundler`, the package meant for `npm publish`. Consumed via a native
679
681
  `.wasm` ES import by bundlers that understand it (webpack with `experiments.asyncWebAssembly`,
@@ -689,6 +691,103 @@ or tests for you — see the comment header of `scripts/build-npm.sh` for the fu
689
691
  single bundler-target package as the published npm artifact, with the web build documented
690
692
  separately, is the ecosystem-standard shape for `wasm-bindgen` crates on npm.
691
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
+
692
791
  The script also post-processes each `package.json` (name, description, license, repository,
693
792
  keywords, and the `files` list — including an `LICENSE-APACHE` entry `wasm-pack` itself omits even
694
793
  though it copies the file) and installs `README-npm.md` as the package's `README.md`.
@@ -32,6 +32,14 @@ export class Calculator {
32
32
  * Returns array of: [{ range_yards, drop_inches, windage_inches, velocity_fps, energy_ftlb, time_sec }, ...]
33
33
  */
34
34
  getFullTrajectory(): any;
35
+ /**
36
+ * Solve all the way to the requested range instead of stopping at ground impact.
37
+ *
38
+ * Without this, `calculateTrajectory(range)` returns the closest point it actually
39
+ * solved, which for a typical .308 is around 516 yd — short of a 1000 yd request, and
40
+ * reported only by the `range_yards` field of the returned object.
41
+ */
42
+ ignoreGroundImpact(ignore: boolean): Calculator;
35
43
  /**
36
44
  * Create a new calculator with default values
37
45
  * Defaults: .308 Winchester 168gr at 2700 fps, standard atmosphere
@@ -39,6 +47,13 @@ export class Calculator {
39
47
  constructor();
40
48
  setAltitude(altitude_ft: number): Calculator;
41
49
  setBC(bc: number): Calculator;
50
+ /**
51
+ * Height of the bore above the ground, in inches (default 60 = 5 ft). The solve stops
52
+ * when the projectile falls this far below the muzzle, so raising it pushes the
53
+ * ground-impact cutoff further downrange. Use [`Self::ignore_ground_impact`] to remove
54
+ * the cutoff entirely rather than setting an implausible height.
55
+ */
56
+ setBoreHeight(height_inches: number): Calculator;
42
57
  setDiameter(diameter_inches: number): Calculator;
43
58
  setDragModel(model: string): Calculator;
44
59
  setHumidity(humidity: number): Calculator;
@@ -155,9 +170,11 @@ export interface InitOutput {
155
170
  readonly calculator_enableCoriolis: (a: number, b: number, c: number, d: number) => number;
156
171
  readonly calculator_enableSpinDrift: (a: number, b: number, c: number, d: number) => number;
157
172
  readonly calculator_getFullTrajectory: (a: number) => [number, number, number];
173
+ readonly calculator_ignoreGroundImpact: (a: number, b: number) => number;
158
174
  readonly calculator_new: () => number;
159
175
  readonly calculator_setAltitude: (a: number, b: number) => number;
160
176
  readonly calculator_setBC: (a: number, b: number) => number;
177
+ readonly calculator_setBoreHeight: (a: number, b: number) => number;
161
178
  readonly calculator_setDiameter: (a: number, b: number) => number;
162
179
  readonly calculator_setDragModel: (a: number, b: number, c: number) => number;
163
180
  readonly calculator_setHumidity: (a: number, b: number) => number;
@@ -91,6 +91,20 @@ export class Calculator {
91
91
  }
92
92
  return takeFromExternrefTable0(ret[0]);
93
93
  }
94
+ /**
95
+ * Solve all the way to the requested range instead of stopping at ground impact.
96
+ *
97
+ * Without this, `calculateTrajectory(range)` returns the closest point it actually
98
+ * solved, which for a typical .308 is around 516 yd — short of a 1000 yd request, and
99
+ * reported only by the `range_yards` field of the returned object.
100
+ * @param {boolean} ignore
101
+ * @returns {Calculator}
102
+ */
103
+ ignoreGroundImpact(ignore) {
104
+ const ptr = this.__destroy_into_raw();
105
+ const ret = wasm.calculator_ignoreGroundImpact(ptr, ignore);
106
+ return Calculator.__wrap(ret);
107
+ }
94
108
  /**
95
109
  * Create a new calculator with default values
96
110
  * Defaults: .308 Winchester 168gr at 2700 fps, standard atmosphere
@@ -119,6 +133,19 @@ export class Calculator {
119
133
  const ret = wasm.calculator_setBC(ptr, bc);
120
134
  return Calculator.__wrap(ret);
121
135
  }
136
+ /**
137
+ * Height of the bore above the ground, in inches (default 60 = 5 ft). The solve stops
138
+ * when the projectile falls this far below the muzzle, so raising it pushes the
139
+ * ground-impact cutoff further downrange. Use [`Self::ignore_ground_impact`] to remove
140
+ * the cutoff entirely rather than setting an implausible height.
141
+ * @param {number} height_inches
142
+ * @returns {Calculator}
143
+ */
144
+ setBoreHeight(height_inches) {
145
+ const ptr = this.__destroy_into_raw();
146
+ const ret = wasm.calculator_setBoreHeight(ptr, height_inches);
147
+ return Calculator.__wrap(ret);
148
+ }
122
149
  /**
123
150
  * @param {number} diameter_inches
124
151
  * @returns {Calculator}
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.33.1",
8
+ "version": "0.33.4",
9
9
  "license": "MIT OR Apache-2.0",
10
10
  "repository": {
11
11
  "type": "git",