ballistics-ng 0.1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +12 -0
- data/Rakefile +51 -0
- data/examples/table.rb +10 -0
- data/ext/ballistics/ext.c +136 -0
- data/ext/ballistics/extconf.rb +3 -0
- data/ext/ballistics/gnu_ballistics.h +577 -0
- data/lib/ballistics.rb +77 -0
- data/lib/ballistics/atmosphere.rb +110 -0
- data/lib/ballistics/cartridge.rb +177 -0
- data/lib/ballistics/cartridges/300_blk.yaml +159 -0
- data/lib/ballistics/gun.rb +123 -0
- data/lib/ballistics/guns/pistols.yaml +6 -0
- data/lib/ballistics/guns/rifles.yaml +34 -0
- data/lib/ballistics/guns/shotguns.yaml +6 -0
- data/lib/ballistics/problem.rb +102 -0
- data/lib/ballistics/projectile.rb +165 -0
- data/lib/ballistics/projectiles/300_blk.yaml +321 -0
- data/lib/ballistics/util.rb +67 -0
- data/lib/ballistics/yaml.rb +57 -0
- data/test/atmosphere.rb +26 -0
- data/test/ballistics.rb +31 -0
- data/test/cartridge.rb +121 -0
- data/test/gun.rb +85 -0
- data/test/problem.rb +137 -0
- data/test/projectile.rb +163 -0
- data/test/util.rb +28 -0
- metadata +102 -0
data/lib/ballistics.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'ballistics/ext'
|
2
|
+
|
3
|
+
module Ballistics
|
4
|
+
TABLE_FIELDS = {
|
5
|
+
range: {
|
6
|
+
label: "Range",
|
7
|
+
format: "%i",
|
8
|
+
},
|
9
|
+
time: {
|
10
|
+
label: "Time",
|
11
|
+
format: "%0.3f",
|
12
|
+
},
|
13
|
+
velocity: {
|
14
|
+
label: "FPS",
|
15
|
+
format: "%0.1f",
|
16
|
+
},
|
17
|
+
moa_correction: {
|
18
|
+
label: "MOA",
|
19
|
+
format: "%0.1f",
|
20
|
+
},
|
21
|
+
path: {
|
22
|
+
label: "Path",
|
23
|
+
format: "%0.1f",
|
24
|
+
},
|
25
|
+
}
|
26
|
+
|
27
|
+
def self.table(trajectory: nil, fields: nil, opts: {})
|
28
|
+
trajectory ||= self.trajectory(opts)
|
29
|
+
fields ||= [:range, :time, :velocity, :path]
|
30
|
+
|
31
|
+
# Create an array of field labels and format strings once
|
32
|
+
labels = []
|
33
|
+
formats = []
|
34
|
+
fields.each { |sym|
|
35
|
+
cfg = TABLE_FIELDS.fetch(sym)
|
36
|
+
labels << cfg.fetch(:label)
|
37
|
+
formats << cfg.fetch(:format)
|
38
|
+
}
|
39
|
+
|
40
|
+
# Iterate over trajectory structure and return a multiline string
|
41
|
+
labels.join("\t") + "\n" + trajectory.map { |hsh|
|
42
|
+
formats.join("\t") % fields.map { |sym| hsh.fetch(sym.to_s) }
|
43
|
+
}.join("\n")
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.zero_angle(opts = {})
|
47
|
+
opts[:y_intercept] ||= 0
|
48
|
+
args = [
|
49
|
+
:drag_number,
|
50
|
+
:ballistic_coefficient,
|
51
|
+
:velocity,
|
52
|
+
:sight_height,
|
53
|
+
:zero_range,
|
54
|
+
:y_intercept,
|
55
|
+
].map { |arg| opts.fetch(arg) }
|
56
|
+
|
57
|
+
Ballistics::Ext.zero_angle(*args)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.trajectory(opts = {})
|
61
|
+
opts[:zero_angle] ||= self.zero_angle(opts)
|
62
|
+
args = [
|
63
|
+
:drag_number,
|
64
|
+
:ballistic_coefficient,
|
65
|
+
:velocity,
|
66
|
+
:sight_height,
|
67
|
+
:shooting_angle,
|
68
|
+
:zero_angle,
|
69
|
+
:wind_speed,
|
70
|
+
:wind_angle,
|
71
|
+
:max_range,
|
72
|
+
:interval,
|
73
|
+
].map { |arg| opts.fetch(arg) }
|
74
|
+
|
75
|
+
Ballistics::Ext.trajectory(*args)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'bigdecimal/util'
|
3
|
+
require 'ballistics/yaml'
|
4
|
+
|
5
|
+
# http://www.exteriorballistics.com/ebexplained/4th/51.cfm
|
6
|
+
|
7
|
+
class Ballistics::Atmosphere
|
8
|
+
MANDATORY = {
|
9
|
+
"altitude" => :float, # feet
|
10
|
+
"humidity" => :percent, # float between 0 and 1
|
11
|
+
"pressure" => :float, # inches of mercury
|
12
|
+
"temp" => :float, # degrees fahrenheit
|
13
|
+
}
|
14
|
+
|
15
|
+
# US Army standard, used by most commercial ammunition specs
|
16
|
+
ARMY = {
|
17
|
+
"altitude" => 0,
|
18
|
+
"humidity" => 0.78,
|
19
|
+
"pressure" => 29.5275,
|
20
|
+
"temp" => 59,
|
21
|
+
}
|
22
|
+
|
23
|
+
# International Civil Aviation Organization
|
24
|
+
ICAO = {
|
25
|
+
"altitude" => 0,
|
26
|
+
"humidity" => 0,
|
27
|
+
"pressure" => 29.9213,
|
28
|
+
"temp" => 59,
|
29
|
+
}
|
30
|
+
|
31
|
+
# altitude coefficients
|
32
|
+
ALTITUDE_A = -4e-15.to_d
|
33
|
+
ALTITUDE_B = 4e-10.to_d
|
34
|
+
ALTITUDE_C = -3e-5.to_d
|
35
|
+
ALTITUDE_D = 1.to_d
|
36
|
+
|
37
|
+
# humidity coefficients
|
38
|
+
VAPOR_A = 4e-6.to_d
|
39
|
+
VAPOR_B = -0.0004.to_d
|
40
|
+
VAPOR_C = 0.0234.to_d
|
41
|
+
VAPOR_D = -0.2517.to_d
|
42
|
+
|
43
|
+
DRY_AIR_INCREASE = 0.3783.to_d
|
44
|
+
DRY_AIR_REDUCTION = 0.9950.to_d
|
45
|
+
RANKLINE_CORRECTION = 459.4.to_d
|
46
|
+
TEMP_ALTITUDE_CORRECTION = -0.0036.to_d # degrees per foot
|
47
|
+
|
48
|
+
def self.altitude_factor(altitude)
|
49
|
+
altitude = altitude.to_d
|
50
|
+
1 / (ALTITUDE_A * altitude ** 3 +
|
51
|
+
ALTITUDE_B * altitude ** 2 +
|
52
|
+
ALTITUDE_C * altitude +
|
53
|
+
ALTITUDE_D)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.humidity_factor(temp, pressure, humidity)
|
57
|
+
vpw = VAPOR_A * temp ** 3 +
|
58
|
+
VAPOR_B * temp ** 2 +
|
59
|
+
VAPOR_C * temp +
|
60
|
+
VAPOR_D
|
61
|
+
DRY_AIR_REDUCTION * pressure /
|
62
|
+
(pressure - DRY_AIR_INCREASE * humidity * vpw)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.pressure_factor(pressure)
|
66
|
+
pressure = pressure.to_d
|
67
|
+
(pressure - ARMY["pressure"]) / ARMY["pressure"]
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.temp_factor(temp, altitude)
|
71
|
+
std_temp = ARMY["temp"] + altitude * TEMP_ALTITUDE_CORRECTION
|
72
|
+
(temp - std_temp) / (RANKLINE_CORRECTION + std_temp)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.translate(bc, altitude:, humidity:, pressure:, temp:)
|
76
|
+
bc.to_d *
|
77
|
+
self.altitude_factor(altitude) *
|
78
|
+
self.humidity_factor(temp, pressure, humidity) *
|
79
|
+
(self.temp_factor(temp, altitude) -
|
80
|
+
self.pressure_factor(pressure) + 1)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.icao
|
84
|
+
self.new ICAO
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.army
|
88
|
+
self.new ARMY
|
89
|
+
end
|
90
|
+
|
91
|
+
attr_reader(*MANDATORY.keys)
|
92
|
+
attr_reader(:yaml_data, :extra)
|
93
|
+
|
94
|
+
def initialize(hsh = ARMY.dup)
|
95
|
+
@yaml_data = hsh
|
96
|
+
MANDATORY.each { |field, type|
|
97
|
+
val = hsh[field] || hsh[field.to_sym] or raise("#{field} not provided")
|
98
|
+
Ballistics::YAML.check_type!(val, type)
|
99
|
+
self.instance_variable_set("@#{field}", val.to_d)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def translate(ballistic_coefficient)
|
104
|
+
self.class.translate(ballistic_coefficient,
|
105
|
+
altitude: @altitude,
|
106
|
+
humidity: @humidity,
|
107
|
+
pressure: @pressure,
|
108
|
+
temp: @temp)
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'ballistics/yaml'
|
2
|
+
|
3
|
+
class Ballistics::Cartridge
|
4
|
+
MANDATORY = {
|
5
|
+
"name" => :string,
|
6
|
+
"case" => :string,
|
7
|
+
"projectile" => :reference,
|
8
|
+
}
|
9
|
+
OPTIONAL = {
|
10
|
+
"desc" => :string,
|
11
|
+
"powder_grains" => :float,
|
12
|
+
"powder_type" => :string,
|
13
|
+
}
|
14
|
+
|
15
|
+
# used to guesstimate muzzle velocities for unknown barrel lengths
|
16
|
+
BURN_LENGTH = {
|
17
|
+
'300 BLK' => 9,
|
18
|
+
'5.56' => 20,
|
19
|
+
'.223' => 20,
|
20
|
+
'.308' => 24,
|
21
|
+
}
|
22
|
+
|
23
|
+
# Load a built-in YAML file and instantiate cartridge objects
|
24
|
+
# Return a hash of cartridge objects keyed by the cartridge id as in the YAML
|
25
|
+
#
|
26
|
+
def self.find(short_name)
|
27
|
+
objects = {}
|
28
|
+
Ballistics::YAML.load_built_in('cartridges', short_name).each { |id, hsh|
|
29
|
+
obj = self.new(hsh)
|
30
|
+
if block_given?
|
31
|
+
objects[id] = obj if yield obj
|
32
|
+
else
|
33
|
+
objects[id] = obj
|
34
|
+
end
|
35
|
+
}
|
36
|
+
objects
|
37
|
+
end
|
38
|
+
|
39
|
+
# This is a helper method to perform loading of cartridges and projectiles
|
40
|
+
# and to perform the cross ref. This works only with built in cartridge and
|
41
|
+
# projectile data. To do this for user-supplied data files, the user should
|
42
|
+
# perform the cross_ref explicitly
|
43
|
+
#
|
44
|
+
def self.find_with_projectiles(chamber)
|
45
|
+
require 'ballistics/projectile'
|
46
|
+
|
47
|
+
cartridges = self.find(chamber)
|
48
|
+
projectiles = Ballistics::Projectile.find(chamber)
|
49
|
+
self.cross_ref(cartridges, projectiles)
|
50
|
+
cartridges
|
51
|
+
end
|
52
|
+
|
53
|
+
# A cartridge object starts with a string identifier for its projectile
|
54
|
+
# Given a hash of cartridge objects (keyed by cartridge id)
|
55
|
+
# and a hash of projectile objects (keyed by projectile id)
|
56
|
+
# Set the cartridge's projectile to the projectile object identified
|
57
|
+
# by the string value
|
58
|
+
# The cartridge objects in the cartridges hash are updated in place
|
59
|
+
# The return value reports what was updated
|
60
|
+
#
|
61
|
+
def self.cross_ref(cartridges, projectiles)
|
62
|
+
retval = {}
|
63
|
+
cartridges.values.each { |c|
|
64
|
+
if c.projectile.is_a?(String)
|
65
|
+
proj_id = c.projectile
|
66
|
+
proj = projectiles[proj_id]
|
67
|
+
if proj
|
68
|
+
c.projectile = proj
|
69
|
+
retval[:updated] ||= []
|
70
|
+
retval[:updated] << proj_id
|
71
|
+
else
|
72
|
+
warn "#{proj_id} not found in projectiles"
|
73
|
+
end
|
74
|
+
else
|
75
|
+
warn "c.projectile is not a string"
|
76
|
+
end
|
77
|
+
}
|
78
|
+
retval
|
79
|
+
end
|
80
|
+
|
81
|
+
# Given a single data point (barrel_length, muzzle_velocity)
|
82
|
+
# and a known burn length
|
83
|
+
# Guess a muzzle velocity for an unknown length
|
84
|
+
#
|
85
|
+
def self.guess_mv(known_length, known_mv, burn_length, unknown_length)
|
86
|
+
inch_diff = known_length - unknown_length
|
87
|
+
known_bf = burn_length.to_f / known_length
|
88
|
+
unknown_bf = burn_length.to_f / unknown_length
|
89
|
+
# assume 1% FPS per inch; adjust for burn_length and take the average
|
90
|
+
fps_per_inch = known_mv * (known_bf + unknown_bf) / 2 / 100
|
91
|
+
known_mv - inch_diff * fps_per_inch
|
92
|
+
end
|
93
|
+
|
94
|
+
# Match and extract e.g. "16" from "16_inch_fps"
|
95
|
+
BARREL_LENGTH_REGEX = /([0-9]+)_inch_fps/i
|
96
|
+
|
97
|
+
attr_reader(*MANDATORY.keys)
|
98
|
+
attr_reader(*OPTIONAL.keys)
|
99
|
+
attr_reader :muzzle_velocity, :yaml_data, :extra
|
100
|
+
attr_writer :projectile
|
101
|
+
|
102
|
+
def initialize(hsh)
|
103
|
+
@yaml_data = hsh
|
104
|
+
MANDATORY.each { |field, type|
|
105
|
+
val = hsh.fetch(field)
|
106
|
+
Ballistics::YAML.check_type!(val, type)
|
107
|
+
self.instance_variable_set("@#{field}", val)
|
108
|
+
}
|
109
|
+
|
110
|
+
OPTIONAL.each { |field, type|
|
111
|
+
if hsh.key?(field)
|
112
|
+
val = hsh[field]
|
113
|
+
Ballistics::YAML.check_type!(val, type)
|
114
|
+
self.instance_variable_set("@#{field}", val)
|
115
|
+
end
|
116
|
+
}
|
117
|
+
|
118
|
+
# Keep track of fields that we don't expect
|
119
|
+
@extra = {}
|
120
|
+
(hsh.keys - MANDATORY.keys - OPTIONAL.keys).each { |k| @extra[k] = hsh[k] }
|
121
|
+
|
122
|
+
# Extract muzzle velocities per barrel length and remove from @extra
|
123
|
+
# We need at least one
|
124
|
+
#
|
125
|
+
@muzzle_velocity = {}
|
126
|
+
extracted = []
|
127
|
+
@extra.each { |key, val|
|
128
|
+
matches = key.match(BARREL_LENGTH_REGEX)
|
129
|
+
if matches
|
130
|
+
bl = matches[1].to_f.round
|
131
|
+
@muzzle_velocity[bl] = val
|
132
|
+
extracted << key
|
133
|
+
end
|
134
|
+
}
|
135
|
+
extracted.each { |k| @extra.delete(k) }
|
136
|
+
raise "no valid muzzle velocity" if @muzzle_velocity.empty?
|
137
|
+
end
|
138
|
+
|
139
|
+
# estimate muzzle velocity for a given barrel length
|
140
|
+
def mv(barrel_length, burn_length = nil)
|
141
|
+
[barrel_length, barrel_length.floor, barrel_length.ceil].each { |candidate|
|
142
|
+
mv = @muzzle_velocity[candidate]
|
143
|
+
return mv if mv
|
144
|
+
}
|
145
|
+
burn_length ||= BURN_LENGTH.fetch(@case)
|
146
|
+
known_lengths = @muzzle_velocity.keys
|
147
|
+
|
148
|
+
case known_lengths.length
|
149
|
+
when 0
|
150
|
+
raise "no muzzle velocities available"
|
151
|
+
when 1
|
152
|
+
known_length = known_lengths.first
|
153
|
+
self.class.guess_mv(known_length,
|
154
|
+
@muzzle_velocity[known_length],
|
155
|
+
burn_length,
|
156
|
+
barrel_length)
|
157
|
+
else
|
158
|
+
# ok, now we need to interpolate if we can
|
159
|
+
raise "not implemented yet"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def multiline
|
164
|
+
lines = ["CARTRIDGE: #{@name}", "========="]
|
165
|
+
fields = {
|
166
|
+
"Case" => @case,
|
167
|
+
}
|
168
|
+
@muzzle_velocity.keys.sort.each { |bar_len|
|
169
|
+
fields["MV @ #{bar_len}"] = @muzzle_velocity[bar_len]
|
170
|
+
}
|
171
|
+
fields["Desc"] = @desc if @desc
|
172
|
+
fields.each { |name, val|
|
173
|
+
lines << [name.rjust(9, ' '), val].join(': ')
|
174
|
+
}
|
175
|
+
lines.join("\n")
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# MANDATORY
|
2
|
+
# $id:
|
3
|
+
# name:
|
4
|
+
# case:
|
5
|
+
# projectile: # ref to projectiles.yaml
|
6
|
+
# AT LEAST ONE OF
|
7
|
+
# 16_inch_fps:
|
8
|
+
# 20_inch_fps:
|
9
|
+
# 24_inch_fps:
|
10
|
+
# OPTIONAL
|
11
|
+
# desc:
|
12
|
+
# powder_type:
|
13
|
+
# powder_grains:
|
14
|
+
# $n_inch_fps:
|
15
|
+
|
16
|
+
# FLAT BASE PROJECTILES
|
17
|
+
|
18
|
+
barnes_90_range_ar:
|
19
|
+
name: Barnes 300 BLK 90gr Range AR (90gr OTFB)
|
20
|
+
case: 300 BLK
|
21
|
+
projectile: barnes_90_otfb
|
22
|
+
16_inch_fps: 2550
|
23
|
+
desc: Target, training, competition
|
24
|
+
|
25
|
+
barnes_110_vor_tx:
|
26
|
+
name: Barnes 300 BLK 110gr VOR-TX
|
27
|
+
case: 300 BLK
|
28
|
+
projectile: barnes_110_tac_tx_300_blk
|
29
|
+
16_inch_fps: 2350
|
30
|
+
10_inch_fps: 2180
|
31
|
+
desc: Hunting round for penetration and expansion
|
32
|
+
|
33
|
+
hornady_110_v_max_black:
|
34
|
+
name: Hornady 300 BLK 110gr V-MAX BLACK
|
35
|
+
case: 300 BLK
|
36
|
+
projectile: hornady_110_v_max_black
|
37
|
+
16_inch_fps: 2375
|
38
|
+
10_inch_fps: 2250
|
39
|
+
desc: Varmint round for a black rifle
|
40
|
+
|
41
|
+
hpr_110_tac_tx:
|
42
|
+
name: HPR 300 BLK 110gr TAC-TX
|
43
|
+
case: 300 BLK
|
44
|
+
projectile: barnes_110_tac_tx_300_blk
|
45
|
+
10_inch_fps: 2170
|
46
|
+
16_inch_fps: 2300
|
47
|
+
desc: Accurate and consistent load
|
48
|
+
|
49
|
+
wilson_110_tac_tx:
|
50
|
+
name: Wilson Combat 300 BLK 110gr TAC-TX
|
51
|
+
case: 300 BLK
|
52
|
+
projectile: barnes_110_tac_tx_300_blk
|
53
|
+
10_inch_fps: 2240
|
54
|
+
16_inch_fps: 2400
|
55
|
+
desc: Fast, accurate, and consistent load
|
56
|
+
|
57
|
+
remington_120_umc_otfb:
|
58
|
+
name: Remington 300 BLK 120gr UMC OTFB
|
59
|
+
case: 300 BLK
|
60
|
+
projectile: remington_120_umc_otfb
|
61
|
+
16_inch_fps: 2200
|
62
|
+
11_inch_fps: 2080
|
63
|
+
9_inch_fps: 2000
|
64
|
+
desc: Value ammo
|
65
|
+
|
66
|
+
hornady_125_hp:
|
67
|
+
name: Hornady 300 BLK 125gr HP American Gunner
|
68
|
+
case: 300 BLK
|
69
|
+
projectile: hornady_125_hp_match
|
70
|
+
24_inch_fps: 2175
|
71
|
+
desc: Target, hunting, self defense
|
72
|
+
|
73
|
+
remington_125_premier_mkfb:
|
74
|
+
name: Remington Premier Match 300 BLK 125gr MatchKing Flat Base
|
75
|
+
case: 300 BLK
|
76
|
+
projectile: sierra_125_mk
|
77
|
+
16_inch_fps: 2215
|
78
|
+
desc: Match ammo
|
79
|
+
|
80
|
+
hornady_135_ftx:
|
81
|
+
name: Hornady 300 BLK 135gr FTX
|
82
|
+
case: 300 BLK
|
83
|
+
projectile: hornady_135_ftx
|
84
|
+
24_inch_fps: 2085
|
85
|
+
desc: Expanding round for hunting, personal defense
|
86
|
+
|
87
|
+
hornady_208_a_max_black:
|
88
|
+
name: Hornady 300 BLK 208gr A-MAX BLACK
|
89
|
+
case: 300 BLK
|
90
|
+
projectile: hornady_208_a_max_black
|
91
|
+
16_inch_fps: 1020
|
92
|
+
10_inch_fps: 915
|
93
|
+
desc: Optimized for accuracy in a black rifle
|
94
|
+
|
95
|
+
remington_220_umc_otfb:
|
96
|
+
name: Remington 300 BLK 220gr UMC OTFB
|
97
|
+
case: 300 BLK
|
98
|
+
projectile: remington_220_umc_otfb
|
99
|
+
16_inch_fps: 1050
|
100
|
+
10_inch_fps: 1005
|
101
|
+
desc: Value / training round
|
102
|
+
|
103
|
+
remington_220_high_perf_otfb:
|
104
|
+
name: Remington 300 BLK 220gr High Performance OTFB
|
105
|
+
case: 300 BLK
|
106
|
+
projectile: remington_220_otfb
|
107
|
+
16_inch_fps: 1015
|
108
|
+
desc: Match style round
|
109
|
+
|
110
|
+
# BOAT TAIL PROJECTILES
|
111
|
+
|
112
|
+
hornady_110_gmx:
|
113
|
+
name: Hornady 300 BLK 110gr GMX Full Boar
|
114
|
+
case: 300 BLK
|
115
|
+
projectile: hornady_110_gmx
|
116
|
+
24_inch_fps: 2350
|
117
|
+
desc: Penetrating hunting round
|
118
|
+
|
119
|
+
barnes_120_vor_tx:
|
120
|
+
name: Barnes 300 BLK 120gr VOR-TX
|
121
|
+
case: 300 BLK
|
122
|
+
projectile: barnes_120_tac_tx
|
123
|
+
16_inch_fps: 2150
|
124
|
+
desc: Hunting round for penetration and expansion
|
125
|
+
|
126
|
+
remington_130_hog_hammer:
|
127
|
+
name: Remington 300 BLK 130gr Hog Hammer
|
128
|
+
case: 300 BLK
|
129
|
+
projectile: barnes_130_tsx
|
130
|
+
24_inch_fps: 2075
|
131
|
+
desc: Hog hunting round for penetration and expansion
|
132
|
+
|
133
|
+
remington_130_htp:
|
134
|
+
name: Remington 300 BLK 130gr HTP
|
135
|
+
case: 300 BLK
|
136
|
+
projectile: barnes_130_tsx
|
137
|
+
16_inch_fps: 2075
|
138
|
+
desc: Hunting round for penetration and expansion
|
139
|
+
|
140
|
+
remington_220_premier_mkbthp:
|
141
|
+
name: Remington 300 BLK 220gr Premier Match SMK
|
142
|
+
case: 300 BLK
|
143
|
+
projectile: sierra_220_hpbt_mk
|
144
|
+
16_inch_fps: 1015
|
145
|
+
desc: Match round
|
146
|
+
|
147
|
+
silencerco_220_smk:
|
148
|
+
name: SilencerCo 300 BLK 220gr Sierra MatchKing
|
149
|
+
case: 300 BLK
|
150
|
+
projectile: sierra_220_hpbt_mk
|
151
|
+
16_inch_fps: 1030
|
152
|
+
desc: Match round
|
153
|
+
|
154
|
+
wilson_220_smk:
|
155
|
+
name: Wilson 300 BLK 220gr Sierra MatchKing
|
156
|
+
case: 300 BLK
|
157
|
+
projectile: sierra_220_hpbt_mk
|
158
|
+
16_inch_fps: 1025
|
159
|
+
desc: Match round with factory new Remington 300 BLK case
|