|
| 1 | +# Combat Outcome Flavor Implementation Plan |
| 2 | + |
| 3 | +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. |
| 4 | +
|
| 5 | +**Goal:** Add distinct miss, evasion, armor-absorb, and penetrating-hit combat log text for both player and enemy attacks in the authoritative raid combat flow. |
| 6 | + |
| 7 | +**Architecture:** Keep the change in the live Supabase SQL combat path. Introduce explicit armor-bonus data and small SQL helpers so attack branches classify outcomes consistently, while preserving the current damage roll and armor DR pipeline on penetrating hits only. |
| 8 | + |
| 9 | +**Tech Stack:** xUnit, .NET test runner, Supabase SQL migrations, PostgreSQL PL/pgSQL |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +### Task 1: Pin the new combat outcome model in migration-content tests |
| 14 | + |
| 15 | +**Files:** |
| 16 | +- Modify: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 17 | +- Test: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 18 | + |
| 19 | +**Step 1: Write the failing test** |
| 20 | + |
| 21 | +Extend `D20GunDamageMigrationAddsFireModeHelpersAndFullAutoRaidAction` or add a new test that asserts the latest combat migration contains: |
| 22 | + |
| 23 | +```csharp |
| 24 | +Assert.Contains("create or replace function game.armor_hit_bonus", migration); |
| 25 | +Assert.Contains("attack_total < 10", migration); |
| 26 | +Assert.Contains("attack_total < 10 + dodge_bonus", migration); |
| 27 | +Assert.Contains("attack_total < 10 + dodge_bonus + armor_bonus", migration); |
| 28 | +Assert.Contains("evades your attack", migration); |
| 29 | +Assert.Contains("is stopped by armor", migration); |
| 30 | +Assert.Contains("armor absorbs", migration); |
| 31 | +``` |
| 32 | + |
| 33 | +**Step 2: Run test to verify it fails** |
| 34 | + |
| 35 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 36 | + |
| 37 | +Expected: FAIL because the current migration has no armor-bonus helper and no new combat flavor text. |
| 38 | + |
| 39 | +**Step 3: Write minimal implementation** |
| 40 | + |
| 41 | +Do not change production code yet. Only update the test file with the new assertions and point it at the new latest combat migration path once that filename is chosen. |
| 42 | + |
| 43 | +**Step 4: Run test to verify it still fails for the expected reason** |
| 44 | + |
| 45 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 46 | + |
| 47 | +Expected: FAIL with missing SQL fragment assertions tied to the new combat flavor model. |
| 48 | + |
| 49 | +**Step 5: Commit** |
| 50 | + |
| 51 | +```bash |
| 52 | +git add tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs |
| 53 | +git commit -m "test: pin combat outcome flavor migration" |
| 54 | +``` |
| 55 | + |
| 56 | +### Task 2: Add armor-bonus data and lookup helpers |
| 57 | + |
| 58 | +**Files:** |
| 59 | +- Create: `supabase/migrations/2026032402_add_combat_outcome_flavor.sql` |
| 60 | +- Modify: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 61 | +- Test: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 62 | + |
| 63 | +**Step 1: Write the failing test** |
| 64 | + |
| 65 | +In the migration-content test, add assertions for authored armor bonus support: |
| 66 | + |
| 67 | +```csharp |
| 68 | +Assert.Contains("armor_hit_bonus int not null default 0", migration); |
| 69 | +Assert.Contains("create or replace function game.armor_hit_bonus(armor_name text)", migration); |
| 70 | +Assert.Contains("6B43 Zabralo-Sh body armor", migration); |
| 71 | +``` |
| 72 | + |
| 73 | +**Step 2: Run test to verify it fails** |
| 74 | + |
| 75 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 76 | + |
| 77 | +Expected: FAIL because the new migration file or armor-bonus SQL does not exist yet. |
| 78 | + |
| 79 | +**Step 3: Write minimal implementation** |
| 80 | + |
| 81 | +Create `supabase/migrations/2026032402_add_combat_outcome_flavor.sql` that: |
| 82 | + |
| 83 | +- adds `armor_hit_bonus` to `public.item_defs` |
| 84 | +- backfills authored armor rows with explicit bonus values |
| 85 | +- updates the authored-item seed/upsert path if needed |
| 86 | +- defines: |
| 87 | + |
| 88 | +```sql |
| 89 | +create or replace function game.armor_hit_bonus(armor_name text) |
| 90 | +returns int |
| 91 | +language plpgsql |
| 92 | +stable |
| 93 | +as $$ |
| 94 | +begin |
| 95 | + return coalesce(..., 0); |
| 96 | +end; |
| 97 | +$$; |
| 98 | +``` |
| 99 | + |
| 100 | +Choose conservative first-pass armor bonus values that scale with existing armor quality and DR. |
| 101 | + |
| 102 | +**Step 4: Run test to verify it passes** |
| 103 | + |
| 104 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 105 | + |
| 106 | +Expected: PASS for armor-bonus helper assertions, with remaining failures limited to combat outcome classification/log text. |
| 107 | + |
| 108 | +**Step 5: Commit** |
| 109 | + |
| 110 | +```bash |
| 111 | +git add supabase/migrations/2026032402_add_combat_outcome_flavor.sql tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs |
| 112 | +git commit -m "feat: add armor bonus lookup for combat outcome flavor" |
| 113 | +``` |
| 114 | + |
| 115 | +### Task 3: Add reusable SQL helpers for combat outcome classification and log text |
| 116 | + |
| 117 | +**Files:** |
| 118 | +- Modify: `supabase/migrations/2026032402_add_combat_outcome_flavor.sql` |
| 119 | +- Modify: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 120 | +- Test: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 121 | + |
| 122 | +**Step 1: Write the failing test** |
| 123 | + |
| 124 | +Add assertions that the migration defines helpers for classifying attack outcomes and building flavor text: |
| 125 | + |
| 126 | +```csharp |
| 127 | +Assert.Contains("create or replace function game.classify_attack_outcome", migration); |
| 128 | +Assert.Contains("create or replace function game.describe_player_attack_outcome", migration); |
| 129 | +Assert.Contains("create or replace function game.describe_enemy_attack_outcome", migration); |
| 130 | +Assert.Contains("when 'miss'", migration); |
| 131 | +Assert.Contains("when 'evaded'", migration); |
| 132 | +Assert.Contains("when 'armor-absorbed'", migration); |
| 133 | +Assert.Contains("when 'hit'", migration); |
| 134 | +``` |
| 135 | + |
| 136 | +**Step 2: Run test to verify it fails** |
| 137 | + |
| 138 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 139 | + |
| 140 | +Expected: FAIL because the helper functions are not present yet. |
| 141 | + |
| 142 | +**Step 3: Write minimal implementation** |
| 143 | + |
| 144 | +In the new migration, add helpers shaped like: |
| 145 | + |
| 146 | +```sql |
| 147 | +create or replace function game.classify_attack_outcome( |
| 148 | + attack_total int, |
| 149 | + dodge_bonus int, |
| 150 | + armor_bonus int) |
| 151 | +returns text |
| 152 | +``` |
| 153 | + |
| 154 | +Rules: |
| 155 | + |
| 156 | +- `attack_total < 10` => `miss` |
| 157 | +- `attack_total < 10 + dodge_bonus` => `evaded` |
| 158 | +- `attack_total < 10 + dodge_bonus + armor_bonus` => `armor-absorbed` |
| 159 | +- otherwise => `hit` |
| 160 | + |
| 161 | +Add small description helpers that take actor/target names plus absorbed DR and final damage so the player and enemy branches can reuse aligned wording. |
| 162 | + |
| 163 | +**Step 4: Run test to verify it passes** |
| 164 | + |
| 165 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 166 | + |
| 167 | +Expected: PASS for helper assertions, with remaining failures limited to the still-unwired live combat branches. |
| 168 | + |
| 169 | +**Step 5: Commit** |
| 170 | + |
| 171 | +```bash |
| 172 | +git add supabase/migrations/2026032402_add_combat_outcome_flavor.sql tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs |
| 173 | +git commit -m "feat: add combat outcome flavor helpers" |
| 174 | +``` |
| 175 | + |
| 176 | +### Task 4: Wire player attack branches through the new outcome model |
| 177 | + |
| 178 | +**Files:** |
| 179 | +- Modify: `supabase/migrations/2026032402_add_combat_outcome_flavor.sql` |
| 180 | +- Modify: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 181 | +- Test: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 182 | + |
| 183 | +**Step 1: Write the failing test** |
| 184 | + |
| 185 | +Add assertions that the migration text now uses armor bonus and flavor helpers in player attack, burst fire, and full auto sections: |
| 186 | + |
| 187 | +```csharp |
| 188 | +Assert.Contains("enemy_armor_bonus", migration); |
| 189 | +Assert.Contains("player_attack_total", migration); |
| 190 | +Assert.Contains("game.classify_attack_outcome(player_attack_total", migration); |
| 191 | +Assert.Contains("game.describe_player_attack_outcome", migration); |
| 192 | +``` |
| 193 | + |
| 194 | +**Step 2: Run test to verify it fails** |
| 195 | + |
| 196 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 197 | + |
| 198 | +Expected: FAIL because the player combat branches still log only plain hit-or-miss text. |
| 199 | + |
| 200 | +**Step 3: Write minimal implementation** |
| 201 | + |
| 202 | +Update player attack, burst-fire, and full-auto handling so each branch: |
| 203 | + |
| 204 | +- computes the rolled total once |
| 205 | +- computes enemy dodge bonus from DEX mod |
| 206 | +- resolves enemy armor bonus from equipped armor |
| 207 | +- classifies the outcome with `game.classify_attack_outcome(...)` |
| 208 | +- logs: |
| 209 | + - miss text for totals under `10` |
| 210 | + - evade text for totals in the dodge band |
| 211 | + - armor-stop text for totals in the plate band |
| 212 | + - hit text with absorbed DR amount when penetrating |
| 213 | + |
| 214 | +Use existing weapon damage and DR helpers only in the `hit` path. |
| 215 | + |
| 216 | +**Step 4: Run test to verify it passes** |
| 217 | + |
| 218 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 219 | + |
| 220 | +Expected: PASS for player-attack flavor assertions, with remaining failures limited to enemy retaliation paths. |
| 221 | + |
| 222 | +**Step 5: Commit** |
| 223 | + |
| 224 | +```bash |
| 225 | +git add supabase/migrations/2026032402_add_combat_outcome_flavor.sql tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs |
| 226 | +git commit -m "feat: add player combat outcome flavor text" |
| 227 | +``` |
| 228 | + |
| 229 | +### Task 5: Wire enemy retaliation and other incoming-attack branches through the new outcome model |
| 230 | + |
| 231 | +**Files:** |
| 232 | +- Modify: `supabase/migrations/2026032402_add_combat_outcome_flavor.sql` |
| 233 | +- Modify: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 234 | +- Test: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 235 | + |
| 236 | +**Step 1: Write the failing test** |
| 237 | + |
| 238 | +Add assertions that all incoming attack branches use the same helper path: |
| 239 | + |
| 240 | +```csharp |
| 241 | +Assert.Contains("player_armor_bonus", migration); |
| 242 | +Assert.Contains("enemy_attack_total", migration); |
| 243 | +Assert.Contains("game.classify_attack_outcome(enemy_attack_total", migration); |
| 244 | +Assert.Contains("game.describe_enemy_attack_outcome", migration); |
| 245 | +``` |
| 246 | + |
| 247 | +**Step 2: Run test to verify it fails** |
| 248 | + |
| 249 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 250 | + |
| 251 | +Expected: FAIL because enemy retaliation after attacks, medkit, reload, and failed flee still use plain hit-or-miss logs. |
| 252 | + |
| 253 | +**Step 3: Write minimal implementation** |
| 254 | + |
| 255 | +Update each incoming-attack site so enemy attacks: |
| 256 | + |
| 257 | +- use the same `miss` / `evaded` / `armor-absorbed` / `hit` bands |
| 258 | +- apply no health loss outside the `hit` band |
| 259 | +- preserve current damage roll and DR reduction only on `hit` |
| 260 | +- log absorbed DR when armor reduced penetrating damage |
| 261 | + |
| 262 | +Touch the retaliation after: |
| 263 | + |
| 264 | +- player attack loop |
| 265 | +- `use-medkit` |
| 266 | +- `reload` |
| 267 | +- failed `flee` |
| 268 | + |
| 269 | +If extraction ambush combat shares the same combat loop already, no extra branch is needed beyond the existing retaliation flow. |
| 270 | + |
| 271 | +**Step 4: Run test to verify it passes** |
| 272 | + |
| 273 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 274 | + |
| 275 | +Expected: PASS for the migration-content flavor assertions across both player and enemy combat branches. |
| 276 | + |
| 277 | +**Step 5: Commit** |
| 278 | + |
| 279 | +```bash |
| 280 | +git add supabase/migrations/2026032402_add_combat_outcome_flavor.sql tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs |
| 281 | +git commit -m "feat: add incoming combat outcome flavor text" |
| 282 | +``` |
| 283 | + |
| 284 | +### Task 6: Verify the full test suite and review migration diffs |
| 285 | + |
| 286 | +**Files:** |
| 287 | +- Modify: `supabase/migrations/2026032402_add_combat_outcome_flavor.sql` |
| 288 | +- Modify: `tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 289 | +- Modify: `docs/plans/2026-03-24-combat-outcome-flavor-implementation.md` |
| 290 | + |
| 291 | +**Step 1: Run focused tests** |
| 292 | + |
| 293 | +Run: `dotnet test tests/RaidLoop.Core.Tests/RaidLoop.Core.Tests.csproj --filter HomeMarkupBindingTests` |
| 294 | + |
| 295 | +Expected: PASS. |
| 296 | + |
| 297 | +**Step 2: Run the full suite** |
| 298 | + |
| 299 | +Run: `dotnet test RaidLoop.sln` |
| 300 | + |
| 301 | +Expected: PASS with no new failures. |
| 302 | + |
| 303 | +**Step 3: Review the final diff** |
| 304 | + |
| 305 | +Run: `git diff -- supabase/migrations/2026032402_add_combat_outcome_flavor.sql tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs` |
| 306 | + |
| 307 | +Confirm: |
| 308 | + |
| 309 | +- combat outcomes are split into miss, evaded, armor-absorbed, and hit |
| 310 | +- player and enemy attacks use the same threshold model |
| 311 | +- armor DR still applies only after a penetrating hit |
| 312 | +- hit logs mention absorbed DR when relevant |
| 313 | + |
| 314 | +**Step 4: Commit** |
| 315 | + |
| 316 | +```bash |
| 317 | +git add supabase/migrations/2026032402_add_combat_outcome_flavor.sql tests/RaidLoop.Core.Tests/HomeMarkupBindingTests.cs docs/plans/2026-03-24-combat-outcome-flavor-implementation.md |
| 318 | +git commit -m "feat: add richer combat outcome flavor text" |
| 319 | +``` |
0 commit comments