/ GUNRPG.Tests / SuppressiveFireModelTests.cs
SuppressiveFireModelTests.cs
1 using GUNRPG.Core; 2 using GUNRPG.Core.Combat; 3 using GUNRPG.Core.Operators; 4 using GUNRPG.Core.Weapons; 5 using Xunit; 6 7 namespace GUNRPG.Tests; 8 9 /// <summary> 10 /// Unit tests for the SuppressiveFireModel - suppressive fire behavior against concealed targets. 11 /// </summary> 12 public class SuppressiveFireModelTests 13 { 14 #region Burst Size Tests 15 16 [Fact] 17 public void CalculateSuppressiveBurstSize_StandardWeapon_ReturnsDefaultBurst() 18 { 19 var weapon = WeaponFactory.CreateSturmwolf45(); // Standard SMG 20 int burstSize = SuppressiveFireModel.CalculateSuppressiveBurstSize(weapon, availableAmmo: 30); 21 22 Assert.InRange(burstSize, 23 SuppressiveFireModel.MinSuppressiveBurstSize, 24 SuppressiveFireModel.MaxSuppressiveBurstSize); 25 } 26 27 [Fact] 28 public void CalculateSuppressiveBurstSize_LimitedAmmo_RespectsAmmoLimit() 29 { 30 var weapon = WeaponFactory.CreateSturmwolf45(); 31 int burstSize = SuppressiveFireModel.CalculateSuppressiveBurstSize(weapon, availableAmmo: 2); 32 33 Assert.Equal(2, burstSize); 34 } 35 36 [Fact] 37 public void CalculateSuppressiveBurstSize_AmmoEqualsMinBurst_ReturnsMinBurst() 38 { 39 var weapon = WeaponFactory.CreateSturmwolf45(); 40 int burstSize = SuppressiveFireModel.CalculateSuppressiveBurstSize(weapon, availableAmmo: 2); 41 42 Assert.Equal(2, burstSize); 43 } 44 45 [Fact] 46 public void CalculateSuppressiveBurstSize_AmmoLessThanMinBurst_ReturnsAvailableAmmo() 47 { 48 var weapon = WeaponFactory.CreateSturmwolf45(); 49 int burstSize = SuppressiveFireModel.CalculateSuppressiveBurstSize(weapon, availableAmmo: 1); 50 51 // Should return available ammo, not exceed it 52 Assert.Equal(1, burstSize); 53 } 54 55 #endregion 56 57 #region Suppression Severity Tests 58 59 [Fact] 60 public void CalculateSuppressiveBurstSeverity_CloseRange_ReturnsHigherSeverity() 61 { 62 var weapon = WeaponFactory.CreateSturmwolf45(); 63 64 float closeSeverity = SuppressiveFireModel.CalculateSuppressiveBurstSeverity( 65 weapon, burstSize: 3, distanceMeters: 5f); 66 67 float farSeverity = SuppressiveFireModel.CalculateSuppressiveBurstSeverity( 68 weapon, burstSize: 3, distanceMeters: 30f); 69 70 Assert.True(closeSeverity > farSeverity, 71 $"Close range ({closeSeverity:F3}) should be more suppressive than far range ({farSeverity:F3})"); 72 } 73 74 [Fact] 75 public void CalculateSuppressiveBurstSeverity_LargerBurst_ReturnsHigherSeverity() 76 { 77 var weapon = WeaponFactory.CreateSturmwolf45(); 78 79 float smallBurstSeverity = SuppressiveFireModel.CalculateSuppressiveBurstSeverity( 80 weapon, burstSize: 2, distanceMeters: 15f); 81 82 float largeBurstSeverity = SuppressiveFireModel.CalculateSuppressiveBurstSeverity( 83 weapon, burstSize: 5, distanceMeters: 15f); 84 85 Assert.True(largeBurstSeverity > smallBurstSeverity, 86 $"Larger burst ({largeBurstSeverity:F3}) should be more suppressive than smaller ({smallBurstSeverity:F3})"); 87 } 88 89 [Fact] 90 public void CalculateSuppressiveBurstSeverity_DoesNotExceedMax() 91 { 92 var weapon = WeaponFactory.CreateSturmwolf45(); 93 94 float severity = SuppressiveFireModel.CalculateSuppressiveBurstSeverity( 95 weapon, burstSize: 10, distanceMeters: 1f); 96 97 Assert.True(severity <= SuppressionModel.MaxSuppressionLevel, 98 $"Severity ({severity}) should not exceed max ({SuppressionModel.MaxSuppressionLevel})"); 99 } 100 101 [Fact] 102 public void CalculateSuppressiveBurstSeverity_AppliesFullCoverMultiplier() 103 { 104 var weapon = WeaponFactory.CreateSturmwolf45(); 105 106 float severity = SuppressiveFireModel.CalculateSuppressiveBurstSeverity( 107 weapon, burstSize: 3, distanceMeters: 10f); 108 109 // The severity should be reduced by full cover multiplier 110 // This is implicitly tested since the method applies the multiplier 111 Assert.True(severity > 0f && severity < 1.0f, 112 $"Severity ({severity}) should be reduced by full cover multiplier"); 113 } 114 115 #endregion 116 117 #region Burst Duration Tests 118 119 [Fact] 120 public void CalculateBurstDurationMs_ScalesWithBurstSize() 121 { 122 var weapon = WeaponFactory.CreateSturmwolf45(); 123 124 long smallBurstDuration = SuppressiveFireModel.CalculateBurstDurationMs(weapon, burstSize: 2); 125 long largeBurstDuration = SuppressiveFireModel.CalculateBurstDurationMs(weapon, burstSize: 5); 126 127 Assert.True(largeBurstDuration > smallBurstDuration, 128 $"Larger burst ({largeBurstDuration}ms) should take longer than smaller ({smallBurstDuration}ms)"); 129 } 130 131 [Fact] 132 public void CalculateBurstDurationMs_SingleShot_ReturnsZero() 133 { 134 var weapon = WeaponFactory.CreateSturmwolf45(); 135 136 long duration = SuppressiveFireModel.CalculateBurstDurationMs(weapon, burstSize: 1); 137 138 Assert.Equal(0, duration); // No time between shots for single shot 139 } 140 141 #endregion 142 143 #region ShouldUseSuppressiveFire Tests 144 145 [Fact] 146 public void ShouldUseSuppressiveFire_FullCoverRecentlyVisible_ReturnsTrue() 147 { 148 bool result = SuppressiveFireModel.ShouldUseSuppressiveFire( 149 attackerAmmo: 30, 150 targetCoverState: CoverState.Full, 151 targetLastVisibleMs: 1000, 152 currentTimeMs: 2000); 153 154 Assert.True(result); 155 } 156 157 [Fact] 158 public void ShouldUseSuppressiveFire_FullCoverLongTimeAgo_ReturnsFalse() 159 { 160 bool result = SuppressiveFireModel.ShouldUseSuppressiveFire( 161 attackerAmmo: 30, 162 targetCoverState: CoverState.Full, 163 targetLastVisibleMs: 1000, 164 currentTimeMs: 10000); // More than TargetLastSeenWindowMs ago 165 166 Assert.False(result); 167 } 168 169 [Fact] 170 public void ShouldUseSuppressiveFire_PartialCover_ReturnsFalse() 171 { 172 bool result = SuppressiveFireModel.ShouldUseSuppressiveFire( 173 attackerAmmo: 30, 174 targetCoverState: CoverState.Partial, 175 targetLastVisibleMs: 1000, 176 currentTimeMs: 1500); 177 178 Assert.False(result); 179 } 180 181 [Fact] 182 public void ShouldUseSuppressiveFire_NoCover_ReturnsFalse() 183 { 184 bool result = SuppressiveFireModel.ShouldUseSuppressiveFire( 185 attackerAmmo: 30, 186 targetCoverState: CoverState.None, 187 targetLastVisibleMs: 1000, 188 currentTimeMs: 1500); 189 190 Assert.False(result); 191 } 192 193 [Fact] 194 public void ShouldUseSuppressiveFire_InsufficientAmmo_ReturnsFalse() 195 { 196 bool result = SuppressiveFireModel.ShouldUseSuppressiveFire( 197 attackerAmmo: 1, // Less than minimum burst 198 targetCoverState: CoverState.Full, 199 targetLastVisibleMs: 1000, 200 currentTimeMs: 1500); 201 202 Assert.False(result); 203 } 204 205 [Fact] 206 public void ShouldUseSuppressiveFire_NeverSeen_ReturnsFalse() 207 { 208 bool result = SuppressiveFireModel.ShouldUseSuppressiveFire( 209 attackerAmmo: 30, 210 targetCoverState: CoverState.Full, 211 targetLastVisibleMs: null, 212 currentTimeMs: 1500); 213 214 Assert.False(result); 215 } 216 217 #endregion 218 219 #region Constant Validation Tests 220 221 [Fact] 222 public void BurstSizeConstants_AreValid() 223 { 224 Assert.True(SuppressiveFireModel.MinSuppressiveBurstSize >= 1, "Min burst must be at least 1"); 225 Assert.True(SuppressiveFireModel.MaxSuppressiveBurstSize > SuppressiveFireModel.MinSuppressiveBurstSize, 226 "Max burst must be greater than min"); 227 Assert.True(SuppressiveFireModel.DefaultSuppressiveBurstSize >= SuppressiveFireModel.MinSuppressiveBurstSize, 228 "Default burst must be at least min"); 229 Assert.True(SuppressiveFireModel.DefaultSuppressiveBurstSize <= SuppressiveFireModel.MaxSuppressiveBurstSize, 230 "Default burst must not exceed max"); 231 } 232 233 [Fact] 234 public void FullCoverSuppressionMultiplier_ReducesSuppression() 235 { 236 Assert.True(SuppressiveFireModel.FullCoverSuppressionMultiplier < 1.0f, 237 "Full cover should reduce suppression effectiveness"); 238 Assert.True(SuppressiveFireModel.FullCoverSuppressionMultiplier > 0f, 239 "Full cover should not eliminate suppression entirely"); 240 } 241 242 #endregion 243 }