ballistics-ng 0.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,123 @@
1
+ require 'ballistics/yaml'
2
+
3
+ class Ballistics::Gun
4
+ class ChamberNotFound < KeyError; end
5
+
6
+ MANDATORY = {
7
+ "name" => :string,
8
+ "chamber" => :string,
9
+ "barrel_length" => :float,
10
+ "sight_height" => :float,
11
+ }
12
+ OPTIONAL = {
13
+ "zero_range" => :float,
14
+ }
15
+
16
+ # map a chamber name to a built-in cartridge YAML filename
17
+ CHAMBER_CARTRIDGE = {
18
+ '5.56' => '5_56',
19
+ '300 BLK' => '300_blk',
20
+ # TODO: create these cartridge yamls
21
+ # '.45 ACP' => '45_acp',
22
+ # '12ga' => '12ga',
23
+ }
24
+
25
+ # Load a built-in YAML file and instantiate gun objects
26
+ # Return a hash of gun objects keyed by gun id (per the YAML)
27
+ #
28
+ def self.find(short_name)
29
+ objects = {}
30
+ Ballistics::YAML.load_built_in('guns', short_name).each { |id, hsh|
31
+ obj = self.new(hsh)
32
+ if block_given?
33
+ objects[id] = obj if yield obj
34
+ else
35
+ objects[id] = obj
36
+ end
37
+ }
38
+ objects
39
+ end
40
+
41
+ def self.find_id(id)
42
+ Ballistics::YAML::BUILT_IN.fetch('guns').each { |short_name|
43
+ object = self.find(short_name)[id]
44
+ return object if object
45
+ }
46
+ nil
47
+ end
48
+
49
+ def self.fetch_id(id)
50
+ self.find_id(id) or raise("id #{id} not found")
51
+ end
52
+
53
+ attr_reader(*MANDATORY.keys)
54
+ attr_reader(*OPTIONAL.keys)
55
+ attr_reader(:yaml_data, :extra)
56
+
57
+ def initialize(hsh)
58
+ @yaml_data = hsh
59
+ MANDATORY.each { |field, type|
60
+ val = hsh.fetch(field)
61
+ val = val.to_s if field == "chamber"
62
+ Ballistics::YAML.check_type!(val, type)
63
+ self.instance_variable_set("@#{field}", val)
64
+ }
65
+
66
+ OPTIONAL.each { |field, type|
67
+ if hsh.key?(field)
68
+ val = hsh[field]
69
+ Ballistics::YAML.check_type!(val, type)
70
+ self.instance_variable_set("@#{field}", val)
71
+ end
72
+ }
73
+
74
+ # Keep track of fields that we don't expect
75
+ @extra = {}
76
+ (hsh.keys - MANDATORY.keys - OPTIONAL.keys).each { |k|
77
+ @extra[k] = hsh[k]
78
+ }
79
+ @cartridges = []
80
+ end
81
+
82
+ def chamber=(new_chamber)
83
+ @cartridges = []
84
+ @chamber = new_chamber
85
+ end
86
+
87
+ def cartridge_file
88
+ CHAMBER_CARTRIDGE[@chamber] or raise(ChamberNotFound, @chamber)
89
+ end
90
+
91
+ # this will pull in cartridges and projectiles based on the gun chamber
92
+ def cartridges
93
+ if @cartridges.empty?
94
+ require 'ballistics/cartridge'
95
+
96
+ cartridge_file = CHAMBER_CARTRIDGE[@chamber] or
97
+ raise(ChamberNotFound, @chamber)
98
+ @cartridges =
99
+ Ballistics::Cartridge.find_with_projectiles(cartridge_file)
100
+ end
101
+ @cartridges
102
+ end
103
+
104
+ def params
105
+ params = { sight_height: @sight_height }
106
+ params[:zero_range] = @zero_range if @zero_range
107
+ params
108
+ end
109
+
110
+ def multiline
111
+ lines = ["GUN: #{name}", "==="]
112
+ fields = {
113
+ "Chamber" => @chamber,
114
+ "Barrel length" => @barrel_length,
115
+ "Sight height" => @sight_height,
116
+ }
117
+ fields["Zero Range"] = @zero_range if @zero_range
118
+ fields.each { |name, val|
119
+ lines << [name.rjust(13, ' ' ), val].join(': ')
120
+ }
121
+ lines.join("\n")
122
+ end
123
+ end
@@ -0,0 +1,6 @@
1
+ "1911":
2
+ name: M1911A1
3
+ sight_height: 0.8
4
+ barrel_length: 5
5
+ zero_range: 25
6
+ chamber: .45 ACP
@@ -0,0 +1,34 @@
1
+ ar15_carbine:
2
+ name: Standard AR-15 carbine with 16 inch barrel (5.56)
3
+ sight_height: 2.6
4
+ barrel_length: 16
5
+ zero_range: 50
6
+ chamber: 5.56
7
+
8
+ m4:
9
+ name: Standard M4 carbine with 14.5 inch barrel (5.56)
10
+ sight_height: 2.6
11
+ barrel_length: 14.5
12
+ zero_range: 50
13
+ chamber: 5.56
14
+
15
+ m16:
16
+ name: Standard M16 rifle with 20 inch barrel (5.56)
17
+ sight_height: 2.6
18
+ barrel_length: 20
19
+ zero_range: 100
20
+ chamber: 5.56
21
+
22
+ ar15_sbr:
23
+ name: AR-15 SBR with 10.5 inch barrel (5.56)
24
+ sight_height: 2.6
25
+ barrel_length: 10.5
26
+ zero_range: 50
27
+ chamber: 5.56
28
+
29
+ ar15_300_blk:
30
+ name: AR-15 with 10.5 inch barrel (300 BLK)
31
+ sight_height: 2.6
32
+ barrel_length: 10.5
33
+ zero_range: 50
34
+ chamber: 300 BLK
@@ -0,0 +1,6 @@
1
+ mossberg_500:
2
+ name: "Mossberg 500 12ga 18.5in"
3
+ sight_height: 1.2
4
+ barrel_length: 18.5
5
+ zero_range: 25
6
+ chamber: 12ga
@@ -0,0 +1,102 @@
1
+ require 'ballistics'
2
+ require 'ballistics/projectile'
3
+ require 'ballistics/cartridge'
4
+ require 'ballistics/gun'
5
+ require 'ballistics/atmosphere'
6
+
7
+ class Ballistics::Problem
8
+ DEFAULTS = {
9
+ shooting_angle: 0, # degrees; downhill 0 to -90, uphill 0 to +90
10
+ wind_speed: 0, # mph
11
+ wind_angle: 90, # degrees; 0-360 clockwise; 90 right, 270 left
12
+ interval: 50, # yards
13
+ max_range: 500, # yards
14
+ y_intercept: 0, # inches; -2 means POI 2 inches below POA @ zero range
15
+ }
16
+
17
+ def self.simple(gun_id:, cart_id:, gun_family: nil)
18
+ if gun_family
19
+ gun = Ballistics::Gun.find(gun_family).fetch(gun_id)
20
+ else
21
+ gun = Ballistics::Gun.fetch_id(gun_id)
22
+ end
23
+ cart = gun.cartridges.fetch(cart_id)
24
+ self.new(projectile: cart.projectile,
25
+ cartridge: cart,
26
+ gun: gun)
27
+ end
28
+
29
+ attr_accessor :projectile, :cartridge, :gun, :atmosphere
30
+
31
+ def initialize(projectile: nil,
32
+ cartridge: nil,
33
+ gun: nil,
34
+ atmosphere: nil)
35
+ @projectile = projectile
36
+ @cartridge = cartridge
37
+ @gun = gun
38
+ @atmosphere = atmosphere
39
+ end
40
+
41
+ # Given a hash of specified options / params
42
+ # Return a hash of params enriched by DEFAULTS as well as any inferred
43
+ # parameters from @projectile, @cartridge, @gun, and @atmosphere
44
+ #
45
+ def enrich(opts = {})
46
+ mine = {}
47
+ mine.merge!(@projectile.params) if @projectile
48
+ mine.merge!(@gun.params) if @gun
49
+ mine[:velocity] = @cartridge.mv(@gun.barrel_length) if @cartridge and @gun
50
+
51
+ # Set up the return hash
52
+ # opts overrides mine overrides DEFAULT
53
+ ret = DEFAULTS.merge(mine.merge(opts))
54
+
55
+ # validate drag function and replace with the numeral
56
+ if ret[:drag_function] and !ret[:drag_number]
57
+ ret[:drag_number] =
58
+ Ballistics::Projectile.drag_number(ret[:drag_function])
59
+ end
60
+
61
+ # apply atmospheric correction to ballistic coefficient
62
+ if ret[:ballistic_coefficient] and @atmosphere
63
+ ret[:ballistic_coefficient] =
64
+ @atmosphere.translate(ret[:ballistic_coefficient])
65
+ end
66
+
67
+ ret
68
+ end
69
+
70
+ # Return a multiline string showing each component of the problem
71
+ #
72
+ def report
73
+ lines = []
74
+ lines << @gun.multiline if @gun
75
+ lines << @cartridge.multiline if @cartridge
76
+ lines << @projectile.multiline if @projectile
77
+ lines << @atmosphere.multiline if @atmosphere
78
+ lines.join("\n\n")
79
+ end
80
+
81
+ # Given a zero range and basic ballistics parameters
82
+ # Return the angle between sight axis and bore axis necessary to achieve zero
83
+ #
84
+ def zero_angle(opts = {})
85
+ Ballistics.zero_angle self.enrich opts
86
+ end
87
+
88
+ # Return a data structure with trajectory data at every interval for the
89
+ # specified range
90
+ #
91
+ def trajectory(opts = {})
92
+ Ballistics.trajectory self.enrich opts
93
+ end
94
+
95
+ # Return a multiline string based on trajectory data
96
+ #
97
+ def table(trajectory: nil, fields: nil, opts: {})
98
+ Ballistics.table(trajectory: trajectory,
99
+ fields: fields,
100
+ opts: self.enrich(opts))
101
+ end
102
+ end
@@ -0,0 +1,165 @@
1
+ require 'ballistics/yaml'
2
+
3
+ class Ballistics::Projectile
4
+ MANDATORY = {
5
+ "name" => :string,
6
+ "cal" => :float,
7
+ "grains" => :count,
8
+ }
9
+ # one of these fields is mandatory
10
+ BALLISTIC_COEFFICIENT = {
11
+ "g1" => :float,
12
+ "g7" => :float,
13
+ }
14
+ OPTIONAL = {
15
+ "sd" => :float,
16
+ "intended" => :string,
17
+ "base" => :string,
18
+ "desc" => :string,
19
+ }
20
+ DRAG_FUNCTION = {
21
+ "flat" => "g1",
22
+ "boat" => "g7",
23
+ }
24
+ DRAG_NUMBER = {
25
+ "g1" => 1,
26
+ "g7" => 7,
27
+ }
28
+
29
+ # Load a built-in YAML file and instantiate projectile objects
30
+ # Return a hash of projectile objects keyed by projectile id (per the YAML)
31
+ #
32
+ def self.find(short_name)
33
+ objects = {}
34
+ Ballistics::YAML.load_built_in('projectiles', short_name).each { |id, hsh|
35
+ obj = self.new(hsh)
36
+ if block_given?
37
+ objects[id] = obj if yield obj
38
+ else
39
+ objects[id] = obj
40
+ end
41
+ }
42
+ objects
43
+ end
44
+
45
+ # Normalize common flat-base and boat-tail terms to flat or boat
46
+ #
47
+ def self.base(candidate)
48
+ c = candidate.to_s.downcase.gsub(/[\-\_ ]/, '')
49
+ case c
50
+ when "boat", "boattail", "bt"
51
+ "boat"
52
+ when "flat", "flatbase", "fb"
53
+ "flat"
54
+ else
55
+ raise "unknown base: #{candidate}"
56
+ end
57
+ end
58
+
59
+ # Convert e.g. G1 to 1
60
+ #
61
+ def self.drag_number(drag_function)
62
+ DRAG_NUMBER.fetch(drag_function.to_s.downcase)
63
+ end
64
+
65
+ attr_reader(*MANDATORY.keys)
66
+ attr_reader(*BALLISTIC_COEFFICIENT.keys)
67
+ attr_reader(*OPTIONAL.keys)
68
+ attr_reader :ballistic_coefficient, :yaml_data, :extra
69
+
70
+ def initialize(hsh)
71
+ @yaml_data = hsh
72
+ MANDATORY.each { |field, type|
73
+ val = hsh.fetch(field)
74
+ Ballistics::YAML.check_type!(val, type)
75
+ self.instance_variable_set("@#{field}", val)
76
+ }
77
+
78
+ # Extract ballistic coefficients per drag model (e.g. G1)
79
+ # We need at least one
80
+ #
81
+ @ballistic_coefficient = {}
82
+ BALLISTIC_COEFFICIENT.each { |field, type|
83
+ if hsh.key?(field)
84
+ val = hsh[field]
85
+ Ballistics::YAML.check_type!(val, type)
86
+ self.instance_variable_set("@#{field}", val)
87
+ @ballistic_coefficient[field] = val
88
+ end
89
+ }
90
+ raise "no valid BC" if @ballistic_coefficient.empty?
91
+
92
+ OPTIONAL.each { |field, type|
93
+ if hsh.key?(field)
94
+ val = hsh[field]
95
+ Ballistics::YAML.check_type!(val, type)
96
+ if field == "base"
97
+ @base = self.class.base(val)
98
+ else
99
+ self.instance_variable_set("@#{field}", val)
100
+ end
101
+ end
102
+ }
103
+
104
+ # Keep track of fields that we don't expect
105
+ @extra = {}
106
+ (hsh.keys -
107
+ MANDATORY.keys -
108
+ BALLISTIC_COEFFICIENT.keys -
109
+ OPTIONAL.keys).each { |k|
110
+ @extra[k] = hsh[k]
111
+ }
112
+
113
+ # Make sure @base and @drag_function are initialized so that
114
+ # self.drag_function works without warnings
115
+ #
116
+ @base ||= nil
117
+ @drag_function = nil
118
+ end
119
+
120
+ # Return the preferred drag function if there is a BC available
121
+ #
122
+ def drag_function
123
+ return @drag_function if @drag_function
124
+ @drag_function = @ballistic_coefficient.keys.first
125
+ if @base
126
+ preferred = DRAG_FUNCTION.fetch(@base)
127
+ if @ballistic_coefficient.key?(preferred)
128
+ @drag_function = preferred
129
+ end
130
+ end
131
+ @drag_function
132
+ end
133
+
134
+ # Return the BC for the preferred drag function
135
+ #
136
+ def bc
137
+ @ballistic_coefficient.fetch(self.drag_function)
138
+ end
139
+
140
+ # Return params that can be used by Ballistics::Problem
141
+ #
142
+ def params
143
+ { drag_function: self.drag_function,
144
+ drag_number: self.class.drag_number(self.drag_function),
145
+ ballistic_coefficient: self.bc }
146
+ end
147
+
148
+ # Return lines of text separated by newlines
149
+ #
150
+ def multiline
151
+ lines = ["PROJECTILE: #{@name}", "=========="]
152
+ fields = {
153
+ "Caliber" => @cal,
154
+ "Grains" => @grains,
155
+ }
156
+ @ballistic_coefficient.each { |df, bc|
157
+ fields["BC (#{df.upcase})"] = bc
158
+ }
159
+ fields["Desc"] = @desc if @desc
160
+ fields.each { |name, val|
161
+ lines << [name.rjust(7, ' '), val].join(': ')
162
+ }
163
+ lines.join("\n")
164
+ end
165
+ end
@@ -0,0 +1,321 @@
1
+ # MANDATORY
2
+ # $id:
3
+ # name:
4
+ # cal:
5
+ # grains:
6
+ # AT LEAST ONE OF
7
+ # g1:
8
+ # g7:
9
+ # OPTIONAL
10
+ # desc:
11
+ # intended: # e.g. 300 BLK
12
+ # base: flat|boat
13
+ # sd:
14
+
15
+ # FLAT BASE INTENDED FOR 300 BLK
16
+
17
+ barnes_90_otfb:
18
+ name: Barnes 90gr Zinc OTFB
19
+ cal: .308
20
+ grains: 90
21
+ g1: .246
22
+ intended: 300 BLK
23
+ base: flat
24
+ desc: Open tip, copper jacket, zinc core
25
+
26
+ barnes_110_tac_tx_300_blk:
27
+ name: Barnes 110gr TAC-TX 300 BLK
28
+ cal: .308
29
+ grains: 110
30
+ g1: .289
31
+ sd: .166
32
+ intended: 300 BLK
33
+ base: flat
34
+ desc: Black polymer tip, copper bullet
35
+
36
+ hornady_110_v_max_black:
37
+ name: Hornady 110gr V-MAX BLACK
38
+ cal: .308
39
+ grains: 110
40
+ g1: .290
41
+ sd: .166
42
+ intended: 300 BLK
43
+ base: flat
44
+ desc: Red polymer tip, copper jacket, lead core
45
+
46
+ remington_120_umc_otfb:
47
+ name: Remington 120gr UMC OTFB
48
+ cal: .308
49
+ grains: 120
50
+ g1: .297
51
+ intended: 300 BLK
52
+ base: flat
53
+ desc: Copper jacket, lead core
54
+
55
+ hornady_125_hp_match:
56
+ name: Hornady 125gr HP Match
57
+ cal: .308
58
+ grains: 125
59
+ g1: .320
60
+ sd: .128
61
+ intended: 300 BLK
62
+ base: flat
63
+ desc: Open tip, copper jacket, lead core
64
+
65
+ sierra_125_mk:
66
+ name: Sierra 125gr MatchKing OTFB
67
+ cal: .308
68
+ grains: 125
69
+ g1: .330
70
+ sd: .188
71
+ intended: 300 BLK
72
+ base: flat
73
+ desc: Open tip, copper jacket, lead core
74
+
75
+ sierra_125_tmk:
76
+ name: Sierra 125gr Tipped MatchKing
77
+ cal: .308
78
+ grains: 125
79
+ g1: .332
80
+ sd: .188
81
+ intended: 300 BLK
82
+ base: flat
83
+ desc: Green polymer tip, copper jacket, lead core
84
+
85
+ hornady_135_ftx:
86
+ name: Hornady 135gr FTX
87
+ cal: .308
88
+ grains: 135
89
+ g1: .274
90
+ sd: .203
91
+ intended: 300 BLK
92
+ base: flat
93
+ desc: Red polymer tip, copper jacket, lead core with InterLock
94
+
95
+ hornady_208_a_max_black:
96
+ name: Hornady 208gr A-MAX BLACK
97
+ cal: .308
98
+ grains: 208
99
+ g1: .648
100
+ sd: .313
101
+ intended: 300 BLK
102
+ base: flat
103
+ desc: Red polymer tip, copper jacket, lead core
104
+
105
+ remington_220_umc_otfb:
106
+ name: Remington UMC 220gr OTFB
107
+ cal: .308
108
+ grains: 220
109
+ g1: 0.680 # best guess, based on Rem 220gr OTFB
110
+ sd: 0.331 # best guess, based on 220gr SMK
111
+ intended: 300 BLK
112
+ base: flat
113
+ desc: Open tip, copper jacket, lead core
114
+
115
+ remington_220_otfb:
116
+ name: Remington 220gr Open Tip Flat Base
117
+ cal: .308
118
+ grains: 220
119
+ g1: .680
120
+ sd: 0.331 # best guess, based on 220gr SMK
121
+ intended: 300 BLK
122
+ base: flat
123
+ desc: Open tip, copper jacket, lead core
124
+
125
+ # OTHER FLAT BASE
126
+
127
+ # Note, Barnes TAC-TX is essentially identical to TTSX -- just a rebranding
128
+ # with restricted choices appropriate to Mil/LE rifles
129
+ barnes_110_tac_tx:
130
+ name: Barnes 110gr TAC-TX 30 CAL
131
+ cal: .308
132
+ grains: 110
133
+ g1: .295
134
+ sd: .166
135
+ base: flat
136
+ desc: Blue polymer tip, copper bullet, general 30 cal
137
+
138
+ # Note, Barnes TAC-X is essentially identical to TSX -- just a rebranding
139
+ # with restricted choices appropriate to Mil/LE rifles
140
+ barnes_110_tac_x:
141
+ name: Barnes 110gr TAC X
142
+ cal: .308
143
+ grains: 110
144
+ g1: .264
145
+ sd: .166
146
+ base: flat
147
+ desc: All copper
148
+
149
+ barnes_110_tsx:
150
+ name: Barnes 110gr TSX
151
+ cal: .308
152
+ grains: 110
153
+ g1: .264
154
+ sd: .166
155
+ base: flat
156
+ desc: All copper
157
+
158
+ barnes_110_ttsx:
159
+ name: Barnes 110gr TTSX
160
+ cal: .308
161
+ grains: 110
162
+ g1: .295
163
+ sd: .166
164
+ base: flat
165
+ desc: Blue polymer tip, copper bullet
166
+
167
+ sierra_110_fmj:
168
+ name: Sierra 110gr FMJ
169
+ cal: .308
170
+ grains: 110
171
+ g1: .170
172
+ sd: .166
173
+ intended: M1 Carbine
174
+ base: flat
175
+ desc: Copper jacket, lead core
176
+
177
+ sierra_110_hp:
178
+ name: Sierra 110gr HP
179
+ cal: .308
180
+ grains: 110
181
+ g1: .204
182
+ sd: .166
183
+ base: flat
184
+ desc: Hollow point, copper jacket, lead core, high accuracy
185
+
186
+ sierra_110_rn:
187
+ name: Sierra 110gr RN
188
+ cal: .308
189
+ grains: 110
190
+ g1: .170
191
+ sd: .166
192
+ intended: M1 Carbine
193
+ base: flat
194
+ desc: Round lead nose, copper jacket, lead core, expands at low velocity
195
+
196
+ berger_115_fb:
197
+ name: Berger 115gr FB Target
198
+ cal: .308
199
+ grains: 115
200
+ g1: .289
201
+ base: flat
202
+ desc: Copper jacket, lead core, 1:19 twist
203
+
204
+ sierra_125_spt:
205
+ name: Sierra 125gr SPT
206
+ cal: .308
207
+ grains: 125
208
+ g1: .264
209
+ sd: .188
210
+ base: flat
211
+ desc: Copper jacket, lead core, spitzer, intended for hunting
212
+
213
+ sierra_135_hp:
214
+ name: Sierra 135gr HP Varminter
215
+ cal: .308
216
+ grains: 135
217
+ g1: .275
218
+ sd: .203
219
+ base: flat
220
+ desc: Hollow point, copper jacket, lead core, 300 BLK compatible
221
+
222
+ barnes_150_tac_rrlp:
223
+ name: Barnes 150gr TAC RRLP
224
+ cal: .308
225
+ grains: 150
226
+ g1: .357
227
+ sd: .226
228
+ base: flat
229
+ desc: Frangible; Reduced Ricochet Limited Penetration
230
+
231
+ sierra_150_spt:
232
+ name: Sierra 150gr SPT
233
+ cal: .308
234
+ grains: 150
235
+ g1: .360
236
+ sd: .226
237
+ base: flat
238
+ desc: Copper jacket, lead core, spitzer, intended for hunting
239
+
240
+ sierra_150_rn:
241
+ name: Sierra 150gr RN
242
+ cal: .308
243
+ grains: 150
244
+ g1: .270
245
+ sd: .226
246
+ base: flat
247
+ desc: Round lead nose, copper jacket, lead core, accurate short range hunting
248
+
249
+ #
250
+ # BOAT TAILS
251
+ #
252
+
253
+ hornady_110_gmx:
254
+ name: Hornady 110gr GMX
255
+ intended: 300 BLK
256
+ cal: .308
257
+ grains: 110
258
+ g1: .305
259
+ sd: .166
260
+ base: boat
261
+ desc: Red polymer tip, copper alloy bullet, very short boat tail
262
+ # g7: TODO
263
+
264
+ barnes_120_tac_tx:
265
+ name: Barnes 120gr TAC-TX
266
+ cal: .308
267
+ grains: 120
268
+ g1: .358
269
+ sd: .181
270
+ base: boat
271
+ desc: Black polymer tip, copper bullet
272
+ # g7: TODO
273
+
274
+ barnes_130_tsx:
275
+ name: Barnes 130gr TSX
276
+ cal: .308
277
+ grains: 130
278
+ g1: .340
279
+ sd: .196
280
+ base: boat
281
+ desc: All copper
282
+ # g7: TODO
283
+
284
+ barnes_130_ttsx:
285
+ name: Barnes 130gr TTSX
286
+ cal: .308
287
+ grains: 130
288
+ g1: .350
289
+ sd: .196
290
+ base: boat
291
+ desc: Blue polymer tip, copper bullet
292
+ # g7: TODO
293
+
294
+ barnes_150_tsx:
295
+ name: Barnes 150gr TSX
296
+ cal: .308
297
+ grains: 150
298
+ g1: .369
299
+ sd: .226
300
+ base: boat
301
+ desc: All copper
302
+
303
+ barnes_150_ttsx:
304
+ name: Barnes 150gr TTSX
305
+ cal: .308
306
+ grains: 150
307
+ g1: .420
308
+ sd: .226
309
+ base: boat
310
+ desc: Blue polymer tip, copper bullet
311
+ # g7: TODO
312
+
313
+ sierra_220_hpbt_mk:
314
+ name: Sierra 220gr
315
+ cal: .308
316
+ grains: 220
317
+ g1: .608
318
+ sd: .331
319
+ base: boat
320
+ desc: Open tip, copper jacket, lead core
321
+ # g7: TODO