Skip to main content

ballistics_engine/
spin_decay.rs

1//! Spin Decay Physics for Ballistics Calculations
2//!
3//! This module implements realistic spin decay modeling based on:
4//! - Aerodynamic torque opposing spin
5//! - Calibrated roll-damping coefficients
6//! - Velocity-dependent decay rates
7//! - Projectile shape and surface-finish presets
8
9use std::f64::consts::PI;
10
11const MATCH_REFERENCE_DECAY_RATE_PER_SECOND: f64 = 0.025;
12const GENERAL_REFERENCE_DECAY_RATE_PER_SECOND: f64 = 0.04;
13
14/// Parameters affecting spin decay rate
15#[derive(Debug, Clone, Copy)]
16pub struct SpinDecayParameters {
17    /// Surface roughness in meters (typical: 0.1mm)
18    pub surface_roughness: f64,
19    /// Effective dimensionless roll-damping coefficient magnitude (`|C_lp|`) before applying
20    /// `form_factor`. The field name is retained for API compatibility.
21    pub skin_friction_coefficient: f64,
22    /// Shape factor for spin damping
23    pub form_factor: f64,
24}
25
26impl SpinDecayParameters {
27    /// Create default parameters
28    pub fn new() -> Self {
29        // The effective coefficient (coefficient * form factor) is calibrated against the
30        // empirical 4%/s reference decay used by update_spin_rate for a 175gr .308 ogive.
31        Self {
32            surface_roughness: 0.0001,
33            skin_friction_coefficient: 0.00363,
34            form_factor: 1.0,
35        }
36    }
37
38    /// Get typical parameters for different bullet types
39    pub fn from_bullet_type(bullet_type: &str) -> Self {
40        // Match bullets calibrate to the empirical 2.5%/s reference; the other presets calibrate
41        // to 4%/s after their shape form factor is applied.
42        match bullet_type.to_lowercase().as_str() {
43            "match" => Self {
44                surface_roughness: 0.00005,
45                skin_friction_coefficient: 0.00252,
46                form_factor: 0.9,
47            },
48            "hunting" => Self {
49                surface_roughness: 0.0001,
50                skin_friction_coefficient: 0.00363,
51                form_factor: 1.0,
52            },
53            "fmj" => Self {
54                surface_roughness: 0.00015,
55                skin_friction_coefficient: 0.00330,
56                form_factor: 1.1,
57            },
58            "cast" => Self {
59                surface_roughness: 0.0002,
60                skin_friction_coefficient: 0.00303,
61                form_factor: 1.2,
62            },
63            _ => Self::new(),
64        }
65    }
66}
67
68impl Default for SpinDecayParameters {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74/// Calculate the magnitude of the aerodynamic moment opposing spin.
75///
76/// Uses the conventional roll-damping relation
77/// `M = 1/4 * rho * V * S * d^2 * |C_lp| * |p|`, where `S` is projectile reference area.
78/// Here `C_lp` is the derivative with respect to reduced spin `p*d/(2*V)`; conventions using
79/// `p*d/V` report a coefficient smaller by a factor of two.
80/// The returned value is nonnegative; [`calculate_spin_decay_rate`] applies the direction that
81/// opposes the signed spin rate.
82pub fn calculate_spin_damping_moment(
83    spin_rate_rad_s: f64,
84    velocity_mps: f64,
85    air_density_kg_m3: f64,
86    caliber_m: f64,
87    length_m: f64,
88    decay_params: &SpinDecayParameters,
89) -> f64 {
90    if !spin_rate_rad_s.is_finite()
91        || spin_rate_rad_s == 0.0
92        || !velocity_mps.is_finite()
93        || velocity_mps <= 0.0
94        || !air_density_kg_m3.is_finite()
95        || air_density_kg_m3 <= 0.0
96        || !caliber_m.is_finite()
97        || caliber_m <= 0.0
98        || !length_m.is_finite()
99        || length_m <= 0.0
100        || !decay_params.skin_friction_coefficient.is_finite()
101        || decay_params.skin_friction_coefficient <= 0.0
102        || !decay_params.form_factor.is_finite()
103        || decay_params.form_factor <= 0.0
104    {
105        return 0.0;
106    }
107
108    let reference_area = PI * (caliber_m / 2.0).powi(2);
109    let roll_damping_coefficient =
110        decay_params.skin_friction_coefficient * decay_params.form_factor;
111
112    0.25 * air_density_kg_m3
113        * velocity_mps
114        * reference_area
115        * caliber_m.powi(2)
116        * roll_damping_coefficient
117        * spin_rate_rad_s.abs()
118}
119
120/// Calculate moment of inertia about the longitudinal axis
121pub fn calculate_moment_of_inertia(
122    mass_kg: f64,
123    caliber_m: f64,
124    _length_m: f64,
125    shape: &str,
126) -> f64 {
127    let radius = caliber_m / 2.0;
128
129    match shape {
130        "cylinder" => {
131            // Simple cylinder: I = (1/2) * m * r²
132            0.5 * mass_kg * radius.powi(2)
133        }
134        "ogive" => {
135            // Ogive shape has less mass at the edges
136            0.4 * mass_kg * radius.powi(2)
137        }
138        "boat_tail" => {
139            // Boat tail has even less mass at the rear
140            0.35 * mass_kg * radius.powi(2)
141        }
142        _ => {
143            // Default to cylinder
144            0.5 * mass_kg * radius.powi(2)
145        }
146    }
147}
148
149/// Calculate the rate of spin decay in rad/s²
150#[allow(clippy::too_many_arguments)] // Public compatibility API; grouping would be breaking.
151pub fn calculate_spin_decay_rate(
152    spin_rate_rad_s: f64,
153    velocity_mps: f64,
154    air_density_kg_m3: f64,
155    mass_grains: f64,
156    caliber_inches: f64,
157    length_inches: f64,
158    decay_params: &SpinDecayParameters,
159    bullet_shape: &str,
160) -> f64 {
161    // Convert units
162    let mass_kg = mass_grains * 0.00006479891; // grains to kg
163    let caliber_m = caliber_inches * 0.0254;
164    let length_m = length_inches * 0.0254;
165
166    // Calculate damping moment
167    let damping_moment = calculate_spin_damping_moment(
168        spin_rate_rad_s,
169        velocity_mps,
170        air_density_kg_m3,
171        caliber_m,
172        length_m,
173        decay_params,
174    );
175
176    // Calculate moment of inertia
177    let moment_of_inertia = calculate_moment_of_inertia(mass_kg, caliber_m, length_m, bullet_shape);
178
179    // Apply the damping-moment magnitude opposite to either signed spin direction.
180    if moment_of_inertia > 0.0 && spin_rate_rad_s.is_finite() {
181        -spin_rate_rad_s.signum() * damping_moment / moment_of_inertia
182    } else {
183        0.0
184    }
185}
186
187/// Calculate the spin rate after accounting for decay
188///
189/// Uses an empirical model based on published ballistics data.
190/// Real bullets typically lose 5-15% of spin over a 3-second flight.
191#[allow(clippy::too_many_arguments)] // Public compatibility API; grouping would be breaking.
192pub fn update_spin_rate(
193    initial_spin_rad_s: f64,
194    time_elapsed_s: f64,
195    velocity_mps: f64,
196    air_density_kg_m3: f64,
197    mass_grains: f64,
198    caliber_inches: f64,
199    length_inches: f64,
200    decay_params: Option<&SpinDecayParameters>,
201) -> f64 {
202    if time_elapsed_s <= 0.0 {
203        return initial_spin_rad_s;
204    }
205
206    // Mass factor (heavier bullets retain spin better)
207    let mass_factor = (175.0 / mass_grains).sqrt(); // Normalized to 175gr
208
209    // Velocity factor (higher velocity means more decay)
210    let velocity_factor = velocity_mps / 850.0; // Normalized to 850 m/s
211
212    // Air density scaling: higher density = more aerodynamic damping
213    // Normalized to standard sea-level density (1.225 kg/m^3)
214    let density_factor = if air_density_kg_m3 > 0.0 {
215        air_density_kg_m3 / 1.225
216    } else {
217        1.0 // Standard conditions fallback
218    };
219
220    // Surface area factor from caliber and length
221    // Larger surface area relative to a reference .308 bullet = more skin-friction damping.
222    // Use sqrt of the ratio: skin-friction torque scales with surface area but rotational
223    // inertia also grows with size, so the net effect on decay rate is sub-linear.
224    // Reference: .308 caliber (0.308") x 1.3" length
225    let ref_surface = PI * 0.308 * 1.3; // reference lateral surface area (inches^2)
226    let surface_factor = if caliber_inches > 0.0 && length_inches > 0.0 {
227        let bullet_surface = PI * caliber_inches * length_inches;
228        (bullet_surface / ref_surface).sqrt()
229    } else {
230        1.0 // Standard conditions fallback
231    };
232
233    // Base decay rate per second (empirical)
234    let base_decay_rate = if let Some(params) = decay_params {
235        if params.form_factor < 1.0 {
236            // Match bullet
237            MATCH_REFERENCE_DECAY_RATE_PER_SECOND
238        } else {
239            // Hunting/FMJ bullet
240            GENERAL_REFERENCE_DECAY_RATE_PER_SECOND
241        }
242    } else {
243        GENERAL_REFERENCE_DECAY_RATE_PER_SECOND
244    };
245
246    // Adjusted decay rate blending empirical model with physical parameters.
247    // At standard conditions (sea-level density, .308 reference bullet) the
248    // density_factor and surface_factor are both 1.0, preserving legacy behavior.
249    let decay_rate_per_second =
250        base_decay_rate * mass_factor * velocity_factor * density_factor * surface_factor;
251
252    // Apply exponential decay
253    let decay_factor = (-decay_rate_per_second * time_elapsed_s).exp();
254
255    // Ensure reasonable bounds (minimum 50% retention over any flight)
256    initial_spin_rad_s * decay_factor.clamp(0.5, 1.0)
257}
258
259/// Calculate a simple correction factor for spin-dependent effects
260///
261/// This returns a value between 0 and 1 that represents the fraction
262/// of initial spin remaining.
263pub fn calculate_spin_decay_correction_factor(
264    time_elapsed_s: f64,
265    velocity_mps: f64,
266    air_density_kg_m3: f64,
267    mass_grains: f64,
268    caliber_inches: f64,
269    length_inches: f64,
270    decay_params: Option<&SpinDecayParameters>,
271) -> f64 {
272    if time_elapsed_s <= 0.0 {
273        return 1.0;
274    }
275
276    // Initial spin doesn't matter for the ratio calculation
277    let initial_spin = 1000.0; // rad/s (arbitrary reference)
278
279    let current_spin = update_spin_rate(
280        initial_spin,
281        time_elapsed_s,
282        velocity_mps,
283        air_density_kg_m3,
284        mass_grains,
285        caliber_inches,
286        length_inches,
287        decay_params,
288    );
289
290    current_spin / initial_spin
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    #[test]
298    fn test_spin_decay_parameters() {
299        let match_params = SpinDecayParameters::from_bullet_type("match");
300        assert_eq!(match_params.form_factor, 0.9);
301        assert_eq!(match_params.surface_roughness, 0.00005);
302
303        let hunting_params = SpinDecayParameters::from_bullet_type("hunting");
304        assert_eq!(hunting_params.form_factor, 1.0);
305    }
306
307    #[test]
308    fn test_moment_of_inertia() {
309        let mass_kg = 0.01134; // 175 grains
310        let caliber_m = 0.00782; // .308 inches
311
312        let i_cylinder = calculate_moment_of_inertia(mass_kg, caliber_m, 0.033, "cylinder");
313        let i_ogive = calculate_moment_of_inertia(mass_kg, caliber_m, 0.033, "ogive");
314
315        assert!(i_cylinder > i_ogive); // Cylinder has more inertia than ogive
316    }
317
318    #[test]
319    fn test_spin_decay_realistic() {
320        // Test realistic spin decay for a .308 match bullet
321        let initial_spin = 2800.0 * 2.0 * PI; // 2800 rev/s to rad/s
322        let params = SpinDecayParameters::from_bullet_type("match");
323
324        // After 3 seconds of flight
325        let spin_after_3s = update_spin_rate(
326            initial_spin,
327            3.0,   // 3 seconds
328            750.0, // 750 m/s average velocity
329            1.2,   // air density
330            175.0, // 175 grains
331            0.308, // caliber
332            1.3,   // length
333            Some(&params),
334        );
335
336        let decay_percent = (1.0 - spin_after_3s / initial_spin) * 100.0;
337
338        // Should lose between 2% and 15% of spin
339        assert!(decay_percent > 2.0 && decay_percent < 15.0);
340    }
341
342    #[test]
343    fn test_spin_decay_bounds() {
344        let initial_spin = 1000.0;
345        let params = SpinDecayParameters::new();
346
347        // Test extreme time - should never lose more than 50%
348        let spin_long_time = update_spin_rate(
349            initial_spin,
350            100.0, // Very long flight time
351            500.0,
352            1.225,
353            150.0,
354            0.308,
355            1.2,
356            Some(&params),
357        );
358
359        assert!(spin_long_time >= initial_spin * 0.5);
360    }
361
362    #[test]
363    fn test_spin_damping_moment() {
364        let params = SpinDecayParameters::from_bullet_type("match");
365
366        // Test with typical values
367        let moment = calculate_spin_damping_moment(
368            1000.0,  // spin rate rad/s
369            800.0,   // velocity m/s
370            1.225,   // air density
371            0.00782, // caliber in meters (.308")
372            0.033,   // length in meters
373            &params,
374        );
375
376        // Moment should be positive (opposes spin)
377        assert!(moment > 0.0);
378        assert!(moment < 1.0); // Should be small for a bullet
379
380        // Test zero spin
381        let zero_moment = calculate_spin_damping_moment(0.0, 800.0, 1.225, 0.00782, 0.033, &params);
382        assert_eq!(zero_moment, 0.0);
383
384        // Test zero velocity
385        let zero_vel_moment =
386            calculate_spin_damping_moment(1000.0, 0.0, 1.225, 0.00782, 0.033, &params);
387        assert_eq!(zero_vel_moment, 0.0);
388    }
389
390    #[test]
391    fn test_spin_decay_rate() {
392        let params = SpinDecayParameters::from_bullet_type("fmj");
393
394        let decay_rate = calculate_spin_decay_rate(
395            1000.0, // spin rate rad/s
396            800.0,  // velocity m/s
397            1.225,  // air density
398            168.0,  // mass grains
399            0.308,  // caliber inches
400            1.2,    // length inches
401            &params,
402            "boat_tail",
403        );
404
405        // Decay rate should be negative (spin decreases)
406        assert!(decay_rate < 0.0);
407        assert!(decay_rate > -1000.0); // Should be reasonable magnitude
408    }
409
410    #[test]
411    fn physical_spin_decay_matches_empirical_reference_rate() {
412        let spin_rate = 17_000.0;
413        let velocity = 800.0;
414        let cases = [
415            SpinDecayParameters::from_bullet_type("match"),
416            SpinDecayParameters::from_bullet_type("hunting"),
417        ];
418
419        for params in cases {
420            let actual_rate = calculate_spin_decay_rate(
421                spin_rate, velocity, 1.225, 175.0, 0.308, 1.3, &params, "ogive",
422            );
423            let empirical_spin_after_one_second = update_spin_rate(
424                spin_rate,
425                1.0,
426                velocity,
427                1.225,
428                175.0,
429                0.308,
430                1.3,
431                Some(&params),
432            );
433            let expected_rate = (empirical_spin_after_one_second / spin_rate).ln() * spin_rate;
434
435            assert!(
436                (actual_rate - expected_rate).abs() <= expected_rate.abs() * 0.05,
437                "physical rate {actual_rate} did not match empirical reference {expected_rate}"
438            );
439        }
440    }
441
442    #[test]
443    fn roll_damping_uses_canonical_reduced_spin_moment() {
444        let params = SpinDecayParameters {
445            surface_roughness: 0.0,
446            skin_friction_coefficient: 0.01,
447            form_factor: 1.0,
448        };
449        let moment = calculate_spin_damping_moment(
450            17_000.0,
451            800.0,
452            1.225,
453            0.308 * 0.0254,
454            1.3 * 0.0254,
455            &params,
456        );
457        let expected = 1.225_300_524_995_314e-4;
458
459        assert!((moment - expected).abs() <= expected * 1e-12);
460    }
461
462    #[test]
463    fn roll_damping_moment_has_physical_scaling() {
464        let params = SpinDecayParameters::from_bullet_type("match");
465        let caliber = 0.308 * 0.0254;
466        let length = 1.3 * 0.0254;
467        let moment = |spin, velocity, density, diameter, projectile_length| {
468            calculate_spin_damping_moment(
469                spin,
470                velocity,
471                density,
472                diameter,
473                projectile_length,
474                &params,
475            )
476        };
477        let base = moment(17_000.0, 800.0, 1.225, caliber, length);
478        let doubled_coefficient_params = SpinDecayParameters {
479            skin_friction_coefficient: 2.0 * params.skin_friction_coefficient,
480            ..params
481        };
482        let doubled_form_factor_params = SpinDecayParameters {
483            form_factor: 2.0 * params.form_factor,
484            ..params
485        };
486        let cases = [
487            ("spin", moment(34_000.0, 800.0, 1.225, caliber, length), 2.0),
488            (
489                "velocity",
490                moment(17_000.0, 1_600.0, 1.225, caliber, length),
491                2.0,
492            ),
493            (
494                "density",
495                moment(17_000.0, 800.0, 2.45, caliber, length),
496                2.0,
497            ),
498            (
499                "caliber",
500                moment(17_000.0, 800.0, 1.225, 2.0 * caliber, length),
501                16.0,
502            ),
503            (
504                "length",
505                moment(17_000.0, 800.0, 1.225, caliber, 2.0 * length),
506                1.0,
507            ),
508            (
509                "coefficient",
510                calculate_spin_damping_moment(
511                    17_000.0,
512                    800.0,
513                    1.225,
514                    caliber,
515                    length,
516                    &doubled_coefficient_params,
517                ),
518                2.0,
519            ),
520            (
521                "form factor",
522                calculate_spin_damping_moment(
523                    17_000.0,
524                    800.0,
525                    1.225,
526                    caliber,
527                    length,
528                    &doubled_form_factor_params,
529                ),
530                2.0,
531            ),
532        ];
533
534        for (name, moment, expected_ratio) in cases {
535            let actual_ratio = moment / base;
536            assert!(
537                (actual_ratio - expected_ratio).abs() <= expected_ratio * 1e-12,
538                "{name} scaling was {actual_ratio}, expected {expected_ratio}"
539            );
540        }
541    }
542
543    #[test]
544    fn spin_decay_always_opposes_spin_direction() {
545        let params = SpinDecayParameters::from_bullet_type("match");
546        let positive =
547            calculate_spin_decay_rate(17_000.0, 800.0, 1.225, 175.0, 0.308, 1.3, &params, "ogive");
548        let negative =
549            calculate_spin_decay_rate(-17_000.0, 800.0, 1.225, 175.0, 0.308, 1.3, &params, "ogive");
550
551        assert!(positive < 0.0);
552        assert!(negative > 0.0);
553        assert!((positive + negative).abs() <= positive.abs() * 1e-12);
554    }
555
556    #[test]
557    fn roll_damping_rejects_nonphysical_inputs() {
558        let params = SpinDecayParameters::from_bullet_type("match");
559        let invalid_states = [
560            (0.0, 800.0, 1.225, 0.00782, 0.033),
561            (17_000.0, 0.0, 1.225, 0.00782, 0.033),
562            (17_000.0, -800.0, 1.225, 0.00782, 0.033),
563            (17_000.0, 800.0, 0.0, 0.00782, 0.033),
564            (17_000.0, 800.0, -1.225, 0.00782, 0.033),
565            (17_000.0, 800.0, 1.225, 0.0, 0.033),
566            (17_000.0, 800.0, 1.225, -0.00782, 0.033),
567            (17_000.0, 800.0, 1.225, 0.00782, 0.0),
568            (17_000.0, 800.0, 1.225, 0.00782, -0.033),
569        ];
570
571        for (spin, velocity, density, caliber, length) in invalid_states {
572            let moment =
573                calculate_spin_damping_moment(spin, velocity, density, caliber, length, &params);
574            assert_eq!(
575                moment, 0.0,
576                "nonphysical state produced damping moment {moment}"
577            );
578        }
579    }
580
581    #[test]
582    fn test_different_bullet_types() {
583        // Test all bullet type parameters
584        let types = ["match", "hunting", "fmj", "cast", "unknown"];
585
586        for bullet_type in &types {
587            let params = SpinDecayParameters::from_bullet_type(bullet_type);
588            assert!(params.surface_roughness > 0.0);
589            assert!(params.skin_friction_coefficient > 0.0);
590            assert!(params.form_factor > 0.0);
591        }
592    }
593
594    #[test]
595    fn test_moment_of_inertia_shapes() {
596        let mass_kg = 0.01;
597        let caliber_m = 0.008;
598        let length_m = 0.03;
599
600        let i_cylinder = calculate_moment_of_inertia(mass_kg, caliber_m, length_m, "cylinder");
601        let i_ogive = calculate_moment_of_inertia(mass_kg, caliber_m, length_m, "ogive");
602        let i_boat_tail = calculate_moment_of_inertia(mass_kg, caliber_m, length_m, "boat_tail");
603        let i_default = calculate_moment_of_inertia(mass_kg, caliber_m, length_m, "unknown");
604
605        // Check relative magnitudes
606        assert!(i_cylinder > i_ogive);
607        assert!(i_ogive > i_boat_tail);
608        assert_eq!(i_cylinder, i_default); // Unknown defaults to cylinder
609
610        // Check absolute values are reasonable
611        assert!(i_cylinder > 0.0);
612        assert!(i_boat_tail > 0.0);
613    }
614
615    #[test]
616    fn test_spin_decay_correction_factor() {
617        let params = SpinDecayParameters::from_bullet_type("match");
618
619        // At time zero, factor should be 1.0
620        let factor_t0 = calculate_spin_decay_correction_factor(
621            0.0,
622            800.0,
623            1.225,
624            175.0,
625            0.308,
626            1.3,
627            Some(&params),
628        );
629        assert_eq!(factor_t0, 1.0);
630
631        // After some time, factor should be less than 1.0 but greater than 0.5
632        let factor_t3 = calculate_spin_decay_correction_factor(
633            3.0,
634            800.0,
635            1.225,
636            175.0,
637            0.308,
638            1.3,
639            Some(&params),
640        );
641        assert!(factor_t3 < 1.0);
642        assert!(factor_t3 > 0.5);
643
644        // Factor should decrease with time
645        let factor_t1 = calculate_spin_decay_correction_factor(
646            1.0,
647            800.0,
648            1.225,
649            175.0,
650            0.308,
651            1.3,
652            Some(&params),
653        );
654        let factor_t2 = calculate_spin_decay_correction_factor(
655            2.0,
656            800.0,
657            1.225,
658            175.0,
659            0.308,
660            1.3,
661            Some(&params),
662        );
663        assert!(factor_t1 > factor_t2);
664        assert!(factor_t2 > factor_t3);
665    }
666
667    #[test]
668    fn test_default_impl() {
669        let params1 = SpinDecayParameters::new();
670        let params2 = SpinDecayParameters::default();
671
672        assert_eq!(params1.surface_roughness, params2.surface_roughness);
673        assert_eq!(
674            params1.skin_friction_coefficient,
675            params2.skin_friction_coefficient
676        );
677        assert_eq!(params1.form_factor, params2.form_factor);
678    }
679
680    #[test]
681    fn test_mass_factor_effects() {
682        let params = SpinDecayParameters::from_bullet_type("match");
683
684        // Light bullet (55gr)
685        let spin_light =
686            update_spin_rate(1000.0, 2.0, 800.0, 1.225, 55.0, 0.224, 0.9, Some(&params));
687
688        // Heavy bullet (300gr)
689        let spin_heavy =
690            update_spin_rate(1000.0, 2.0, 800.0, 1.225, 300.0, 0.338, 1.8, Some(&params));
691
692        // Heavy bullet should retain more spin (decay less)
693        assert!(spin_heavy > spin_light);
694    }
695
696    #[test]
697    fn test_velocity_factor_effects() {
698        let params = SpinDecayParameters::from_bullet_type("hunting");
699
700        // Low velocity
701        let spin_low_vel =
702            update_spin_rate(1000.0, 2.0, 400.0, 1.225, 175.0, 0.308, 1.3, Some(&params));
703
704        // High velocity
705        let spin_high_vel =
706            update_spin_rate(1000.0, 2.0, 1200.0, 1.225, 175.0, 0.308, 1.3, Some(&params));
707
708        // Higher velocity should cause more decay (less spin remaining)
709        assert!(spin_low_vel > spin_high_vel);
710    }
711}