ballistics-ng 0.1.0.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.
- 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
|