ballistics-engine 0.48.0 → 0.49.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.
- checksums.yaml +4 -4
- data/Cargo.lock +3 -3
- data/Cargo.toml +2 -2
- data/README.md +29 -6
- data/lib/ballistics_engine.rb +1 -1
- data/src/lib.rs +68 -24
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d78d69e2c98bc2f78bc86c1a77e233fda3ceb01818bdd0d8b1d13be73f6e8dc9
|
|
4
|
+
data.tar.gz: 988f7119c92fd354bbfdef2293bc895c9f516f49ae36914836f7ce124241f730
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 012d3184f17540d49a74155c2f828e989bb718dd9acbcec7e2f997c4886d0b31e5c4c2bb1a0c7ee653146867c5131aa682e9296023096233592f97e96e42f059
|
|
7
|
+
data.tar.gz: 3580581e23bc0895f463337cac92e031dee891c21d0a75bfdff8314edf96721b6c23e5a786291ca62fa09284e069cc02a8ad9ca8eff7f3a63f8b602201d39440
|
data/Cargo.lock
CHANGED
|
@@ -43,9 +43,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
|
43
43
|
|
|
44
44
|
[[package]]
|
|
45
45
|
name = "ballistics-engine"
|
|
46
|
-
version = "0.
|
|
46
|
+
version = "0.49.0"
|
|
47
47
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
48
|
-
checksum = "
|
|
48
|
+
checksum = "963ccb10ced8a5caea141e511d78cf25f7576b978ccb558b45f2a84ef00aeaca"
|
|
49
49
|
dependencies = [
|
|
50
50
|
"getrandom",
|
|
51
51
|
"js-sys",
|
|
@@ -65,7 +65,7 @@ dependencies = [
|
|
|
65
65
|
|
|
66
66
|
[[package]]
|
|
67
67
|
name = "ballistics_engine_rb"
|
|
68
|
-
version = "0.
|
|
68
|
+
version = "0.49.0"
|
|
69
69
|
dependencies = [
|
|
70
70
|
"ballistics-engine",
|
|
71
71
|
"magnus",
|
data/Cargo.toml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# rake-compiler/rb-sys cargo-metadata lookup resolves (newer rb-sys does not
|
|
4
4
|
# normalize hyphens). The published gem name is set in the gemspec, independent.
|
|
5
5
|
name = "ballistics_engine_rb"
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.49.0"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
authors = ["Alex Jokela <email@tinycomputers.io>"]
|
|
9
9
|
description = "Ruby bindings for ballistics-engine"
|
|
@@ -18,7 +18,7 @@ crate-type = ["cdylib"]
|
|
|
18
18
|
# `bridge` exposes ballistics_engine::bridge::bridge_call — the versioned JSON command
|
|
19
19
|
# bridge that reaches every engine command from one string-in/string-out entry point.
|
|
20
20
|
# It is pure serde/serde_json (already core engine deps), so it adds no new dependencies.
|
|
21
|
-
ballistics-engine = { version = "0.
|
|
21
|
+
ballistics-engine = { version = "0.49.0", default-features = false, features = ["bridge"] }
|
|
22
22
|
magnus = "0.8"
|
|
23
23
|
|
|
24
24
|
[profile.release]
|
data/README.md
CHANGED
|
@@ -51,8 +51,9 @@ result = BallisticsEngine.solve(
|
|
|
51
51
|
"zero_distance_yards" => 100.0,
|
|
52
52
|
"drag_model" => "G7", # G1 | G7 | G8
|
|
53
53
|
"wind" => { "speed_mph" => 10.0, "direction_degrees" => 90.0 },
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
# Readings: pass the ones you have and leave the rest out (see "Atmosphere" below).
|
|
55
|
+
"atmosphere" => { "temperature_f" => 72.0, "pressure_inhg" => 29.31,
|
|
56
|
+
"humidity_percent" => 50.0, "altitude_feet" => 600.0 }
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
puts "Max range: #{result['max_range_yards'].round(1)} yd"
|
|
@@ -121,11 +122,16 @@ keys** in imperial units. Unknown keys are ignored; only the seven marked requir
|
|
|
121
122
|
`twist_rate_inches`(10), `is_right_twist`(true), `muzzle_height_inches`, `target_height_inches`.
|
|
122
123
|
- Wind: `wind` = `{speed_mph, direction_degrees}`; `wind_segments` = `[[speed_mph, angle_deg, until_yards], ...]`.
|
|
123
124
|
- Atmosphere: `atmosphere` = `{temperature_f, pressure_inhg, humidity_percent, altitude_feet}`.
|
|
125
|
+
`pressure_inhg` is absolute station pressure, not a sea-level-corrected altimeter setting.
|
|
126
|
+
See "Atmosphere readings" below.
|
|
124
127
|
- BC: `use_bc_segments`, `bc_segments_data` (see above), `bc_segments` = `[[mach, bc], ...]`.
|
|
125
128
|
- Coriolis: `enable_coriolis`, `latitude_degrees`, `shot_direction_degrees`.
|
|
126
129
|
- Physics flags: `enable_advanced_effects`, `enable_magnus`, `enable_aerodynamic_jump`,
|
|
127
130
|
`use_enhanced_spin_drift`, `use_form_factor`, `use_cluster_bc`, `enable_pitch_damping`,
|
|
128
131
|
`enable_precession_nutation`, `enable_wind_shear` (+ `wind_shear_model`), `use_rk4`, `use_adaptive_rk45`.
|
|
132
|
+
Leaving `wind_shear_model` out picks `"power_law"` (the CLI's default model) when
|
|
133
|
+
`enable_wind_shear` is true and `"none"` when it is false; a model you pass, `"none"`
|
|
134
|
+
included, is used exactly as given.
|
|
129
135
|
- Powder: `use_powder_sensitivity`, `powder_temp_sensitivity`, `powder_temp_f`,
|
|
130
136
|
`powder_temp_curve` = `[[temp_f, velocity_fps], ...]`, `powder_curve_temp_f`.
|
|
131
137
|
- Sampling/solver: `enable_trajectory_sampling`, `sample_interval_yards`, `max_range_yards`, `time_step_seconds`.
|
|
@@ -135,6 +141,20 @@ keys** in imperial units. Unknown keys are ignored; only the seven marked requir
|
|
|
135
141
|
y=drop, z=windage, in yards). Present when enabled: `sampled_points`, `aerodynamic_jump`,
|
|
136
142
|
`angular_state`, `max_yaw_angle_rad`, `max_precession_angle_rad`, `min_pitch_damping`, `transonic_mach`.
|
|
137
143
|
|
|
144
|
+
#### Atmosphere readings
|
|
145
|
+
|
|
146
|
+
A `temperature_f` or `pressure_inhg` key in the `atmosphere` hash is a reading and flies
|
|
147
|
+
exactly as given, whatever its value and whatever the altitude. A real 59 °F or 29.92 inHg
|
|
148
|
+
at 5000 ft flies as 59 °F or 29.92 inHg. Leave a key out (or set it to `nil`) when you have
|
|
149
|
+
no reading. It then defaults to 59 °F / 29.92 inHg, and above 1 m of altitude it flies the
|
|
150
|
+
ICAO standard air at `altitude_feet` instead (about 41 °F and 24.9 inHg at 5000 ft). With no
|
|
151
|
+
`atmosphere` hash at all the shot is at sea level in standard air.
|
|
152
|
+
|
|
153
|
+
Before engine 0.49.0 the binding could not tell the two apart. At any altitude above 1 m, a
|
|
154
|
+
temperature within 0.1 °C of 15 °C (58.82 to 59.18 °F) or a pressure within 0.5 hPa of
|
|
155
|
+
1013.25 hPa (about 29.906 to 29.936 inHg) was treated as omitted and replaced with standard
|
|
156
|
+
air, even when it was a real reading.
|
|
157
|
+
|
|
138
158
|
### `BallisticsEngine.bridge_call(request_json) => String`
|
|
139
159
|
|
|
140
160
|
Raw pass-through to the engine's versioned JSON command bridge: one full request
|
|
@@ -152,8 +172,8 @@ def bridge(command, request)
|
|
|
152
172
|
end
|
|
153
173
|
|
|
154
174
|
bridge("meta.version", {})
|
|
155
|
-
# => {"ok"=>true, "api_version"=>1, "engine_version"=>"0.
|
|
156
|
-
# "command"=>"meta.version", "result"=>{"engine_version"=>"0.
|
|
175
|
+
# => {"ok"=>true, "api_version"=>1, "engine_version"=>"0.49.0",
|
|
176
|
+
# "command"=>"meta.version", "result"=>{"engine_version"=>"0.49.0"}}
|
|
157
177
|
|
|
158
178
|
# Every command reachable in this build:
|
|
159
179
|
bridge("meta.capabilities", {})["result"]["commands"]
|
|
@@ -194,8 +214,9 @@ what this build can actually run.
|
|
|
194
214
|
### `BallisticsEngine.calculate_zero_angle(hash) => hash`
|
|
195
215
|
|
|
196
216
|
Same bullet keys as `solve`, plus **required** `target_distance_yards` (and optional
|
|
197
|
-
`target_height_inches`, `wind`, `atmosphere`).
|
|
198
|
-
`zero_angle_degrees`, `zero_angle_moa`. Raises on
|
|
217
|
+
`target_height_inches`, `wind`, `atmosphere`; atmosphere readings work as for `solve`).
|
|
218
|
+
Returns `zero_angle_radians`, `zero_angle_degrees`, `zero_angle_moa`. Raises on
|
|
219
|
+
non-convergence.
|
|
199
220
|
|
|
200
221
|
### `BallisticsEngine.monte_carlo(hash) => hash`
|
|
201
222
|
|
|
@@ -205,6 +226,8 @@ Bullet keys (optionally nested under `base_inputs`) plus std-dev params:
|
|
|
205
226
|
`base_wind_speed_mph`, `base_wind_direction_degrees`, `hit_radius_inches`(≈11.8).
|
|
206
227
|
Returns `ranges_yards`, `impact_velocities_fps`, `impact_positions`
|
|
207
228
|
(`[{vertical_inches, lateral_inches}, ...]`), `hit_probability`, `num_simulations`.
|
|
229
|
+
It has no atmosphere input: every simulation flies sea-level standard air, and an
|
|
230
|
+
`atmosphere` hash is ignored.
|
|
208
231
|
## Development
|
|
209
232
|
|
|
210
233
|
After checking out the repo, run `bundle install` to install dependencies.
|
data/lib/ballistics_engine.rb
CHANGED
data/src/lib.rs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
use magnus::{Error, RArray, RHash, function};
|
|
2
2
|
use ballistics_engine::{
|
|
3
3
|
AtmosphericConditions, BCSegmentData, BallisticInputs, DragModel, MonteCarloParams,
|
|
4
|
-
TrajectorySolver, WindConditions,
|
|
4
|
+
StationAtmospherePresence, TrajectorySolver, WindConditions,
|
|
5
|
+
calculate_zero_angle_with_station_presence, run_monte_carlo,
|
|
5
6
|
};
|
|
6
7
|
use ballistics_engine::wind::WindSegment;
|
|
7
8
|
|
|
@@ -59,25 +60,43 @@ fn build_wind(h: &RHash) -> Result<WindConditions, Error> {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
/// Build AtmosphericConditions from an optional `atmosphere` sub-hash
|
|
63
|
-
///
|
|
64
|
-
|
|
63
|
+
/// Build AtmosphericConditions from an optional `atmosphere` sub-hash, with which of its
|
|
64
|
+
/// temperature and pressure are readings. Note: AtmosphericConditions.humidity is PERCENT
|
|
65
|
+
/// (0-100), pressure is hPa, temp is Celsius.
|
|
66
|
+
///
|
|
67
|
+
/// A `temperature_f` or `pressure_inhg` key that is present (and not nil) is a reading and
|
|
68
|
+
/// flies exactly as given, whatever its value and whatever the altitude. A key left out takes
|
|
69
|
+
/// the 59 °F / 29.92 inHg default and keeps the engine's legacy rule: above 1 m of altitude
|
|
70
|
+
/// the default becomes the ICAO standard air there, as an omitted value always has. With no
|
|
71
|
+
/// `atmosphere` hash nothing is a reading, at sea level.
|
|
72
|
+
fn build_atmosphere(
|
|
73
|
+
h: &RHash,
|
|
74
|
+
) -> Result<(AtmosphericConditions, StationAtmospherePresence), Error> {
|
|
65
75
|
if let Some(a) = h.lookup::<_, Option<RHash>>("atmosphere")? {
|
|
66
|
-
let temp_f =
|
|
67
|
-
let pressure_inhg =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
let temp_f = a.lookup::<_, Option<f64>>("temperature_f")?;
|
|
77
|
+
let pressure_inhg = a.lookup::<_, Option<f64>>("pressure_inhg")?;
|
|
78
|
+
let presence = StationAtmospherePresence::new(temp_f.is_some(), pressure_inhg.is_some());
|
|
79
|
+
let temp_f = temp_f.unwrap_or(59.0);
|
|
80
|
+
let pressure_inhg = pressure_inhg.unwrap_or(29.92);
|
|
81
|
+
Ok((
|
|
82
|
+
AtmosphericConditions {
|
|
83
|
+
temperature: (temp_f - 32.0) * 5.0 / 9.0,
|
|
84
|
+
pressure: pressure_inhg * INHG_TO_HPA,
|
|
85
|
+
humidity: opt_f64(&a, "humidity_percent", 50.0)?,
|
|
86
|
+
altitude: opt_f64(&a, "altitude_feet", 0.0)? * FPS_TO_MPS,
|
|
87
|
+
},
|
|
88
|
+
presence,
|
|
89
|
+
))
|
|
74
90
|
} else {
|
|
75
|
-
Ok(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
Ok((
|
|
92
|
+
AtmosphericConditions {
|
|
93
|
+
temperature: 15.0,
|
|
94
|
+
pressure: 1013.25,
|
|
95
|
+
humidity: 50.0,
|
|
96
|
+
altitude: 0.0,
|
|
97
|
+
},
|
|
98
|
+
StationAtmospherePresence::NEITHER,
|
|
99
|
+
))
|
|
81
100
|
}
|
|
82
101
|
}
|
|
83
102
|
|
|
@@ -149,6 +168,27 @@ fn extract_powder_curve(h: &RHash) -> Result<Option<Vec<(f64, f64)>>, Error> {
|
|
|
149
168
|
}
|
|
150
169
|
}
|
|
151
170
|
|
|
171
|
+
/// The model `enable_wind_shear` turns on when the hash names none: the CLI's default
|
|
172
|
+
/// `--wind-shear-model` and the profile the C ABI names, since neither lets a caller ask for
|
|
173
|
+
/// shear without getting one.
|
|
174
|
+
const DEFAULT_WIND_SHEAR_MODEL: &str = "power_law";
|
|
175
|
+
|
|
176
|
+
/// Resolve `wind_shear_model`. A model the hash names, `"none"` included, is kept exactly as
|
|
177
|
+
/// given. When the key is absent (or nil) the default follows the flag: `"power_law"` when
|
|
178
|
+
/// `enable_wind_shear` is set, `"none"` when it is not.
|
|
179
|
+
///
|
|
180
|
+
/// The omitted model used to default to `"none"` whatever the flag said. `TrajectorySolver`,
|
|
181
|
+
/// which every hash entry point here reaches, read that as the power law until engine
|
|
182
|
+
/// 0.49.0; from 0.49.0 it reads `"none"` as no shear, so without this default a hash that
|
|
183
|
+
/// only set the flag would silently lose the shear it has always had.
|
|
184
|
+
fn resolve_wind_shear_model(h: &RHash, enable_wind_shear: bool) -> Result<String, Error> {
|
|
185
|
+
Ok(match h.lookup::<_, Option<String>>("wind_shear_model")? {
|
|
186
|
+
Some(model) => model,
|
|
187
|
+
None if enable_wind_shear => DEFAULT_WIND_SHEAR_MODEL.to_string(),
|
|
188
|
+
None => "none".to_string(),
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
152
192
|
/// Build a fully-populated BallisticInputs from an imperial Ruby hash (imperial -> SI).
|
|
153
193
|
/// Shared by solve, calculate_zero_angle, and monte_carlo.
|
|
154
194
|
fn build_inputs(ruby: &magnus::Ruby, h: &RHash) -> Result<BallisticInputs, Error> {
|
|
@@ -157,6 +197,8 @@ fn build_inputs(ruby: &magnus::Ruby, h: &RHash) -> Result<BallisticInputs, Error
|
|
|
157
197
|
let bullet_weight_grains: f64 = h.fetch("bullet_weight_grains")?;
|
|
158
198
|
|
|
159
199
|
let bc_seg_data = extract_bc_segments_data(h)?;
|
|
200
|
+
let enable_wind_shear = opt_bool(h, "enable_wind_shear", false)?;
|
|
201
|
+
let wind_shear_model = resolve_wind_shear_model(h, enable_wind_shear)?;
|
|
160
202
|
|
|
161
203
|
let mut inputs = BallisticInputs {
|
|
162
204
|
bc_type: drag_model,
|
|
@@ -202,8 +244,8 @@ fn build_inputs(ruby: &magnus::Ruby, h: &RHash) -> Result<BallisticInputs, Error
|
|
|
202
244
|
.lookup::<_, Option<f64>>("powder_curve_temp_f")?
|
|
203
245
|
.map(|tf| (tf - 32.0) * 5.0 / 9.0),
|
|
204
246
|
// wind shear
|
|
205
|
-
enable_wind_shear
|
|
206
|
-
wind_shear_model
|
|
247
|
+
enable_wind_shear,
|
|
248
|
+
wind_shear_model,
|
|
207
249
|
// sampling
|
|
208
250
|
enable_trajectory_sampling: opt_bool(h, "enable_trajectory_sampling", false)?,
|
|
209
251
|
// BC segments
|
|
@@ -238,9 +280,10 @@ fn build_inputs(ruby: &magnus::Ruby, h: &RHash) -> Result<BallisticInputs, Error
|
|
|
238
280
|
fn solve_trajectory(ruby: &magnus::Ruby, h: RHash) -> Result<RHash, Error> {
|
|
239
281
|
let inputs = build_inputs(ruby, &h)?;
|
|
240
282
|
let wind = build_wind(&h)?;
|
|
241
|
-
let atmosphere = build_atmosphere(&h)?;
|
|
283
|
+
let (atmosphere, presence) = build_atmosphere(&h)?;
|
|
242
284
|
|
|
243
|
-
let mut solver =
|
|
285
|
+
let mut solver =
|
|
286
|
+
TrajectorySolver::new_with_station_presence(inputs, wind, atmosphere, presence);
|
|
244
287
|
let segs = extract_wind_segments(&h)?;
|
|
245
288
|
if !segs.is_empty() {
|
|
246
289
|
solver.set_wind_segments(segs);
|
|
@@ -338,14 +381,15 @@ fn calculate_zero_angle(ruby: &magnus::Ruby, h: RHash) -> Result<RHash, Error> {
|
|
|
338
381
|
let target_yd: f64 = h.fetch("target_distance_yards")?;
|
|
339
382
|
let target_h_in: f64 = h.lookup2("target_height_inches", 0.0)?;
|
|
340
383
|
let wind = build_wind(&h)?;
|
|
341
|
-
let atmosphere = build_atmosphere(&h)?;
|
|
384
|
+
let (atmosphere, presence) = build_atmosphere(&h)?;
|
|
342
385
|
|
|
343
|
-
let rad =
|
|
386
|
+
let rad = calculate_zero_angle_with_station_presence(
|
|
344
387
|
inputs,
|
|
345
388
|
target_yd * YARDS_TO_METERS,
|
|
346
389
|
target_h_in * INCHES_TO_METERS,
|
|
347
390
|
wind,
|
|
348
391
|
atmosphere,
|
|
392
|
+
presence,
|
|
349
393
|
)
|
|
350
394
|
.map_err(|e| {
|
|
351
395
|
Error::new(
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ballistics-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.49.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Jokela
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|