/ 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  }