From f1c96ed37d012f6c864c11a6f6f83ff4922df4e9 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sun, 7 Jun 2026 00:47:33 -0400 Subject: [PATCH] =?UTF-8?q?refactor(battlenode):=20M-HC-4=20cleanup=20?= =?UTF-8?q?=E2=80=94=20EpCount=20rename,=20dedupe=20evolve-ramp,=20drop=20?= =?UTF-8?q?tautological=20guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../Sessions/Engine/SessionBattleEngine.cs | 2 +- .../Integration/HeadlessConductorTests.cs | 63 +++++++------------ .../Integration/NodeNativeBattleHarness.cs | 2 +- 3 files changed, 26 insertions(+), 41 deletions(-) diff --git a/SVSim.BattleNode/Sessions/Engine/SessionBattleEngine.cs b/SVSim.BattleNode/Sessions/Engine/SessionBattleEngine.cs index a198da8..ef5d2d3 100644 --- a/SVSim.BattleNode/Sessions/Engine/SessionBattleEngine.cs +++ b/SVSim.BattleNode/Sessions/Engine/SessionBattleEngine.cs @@ -224,7 +224,7 @@ internal sealed class SessionBattleEngine /// evolve spends one EP, so the evolve test asserts this decrements by 1. EP is granted at setup by /// the engine's SetupEvolCount (2 for the game-first seat, 3 for the second) and unlocks once /// EvolveWaitTurnCount has counted down (M-HC-4b). - public int Ep(bool playerSeat) => Seat(playerSeat).CurrentEpCount; + public int EpCount(bool playerSeat) => Seat(playerSeat).CurrentEpCount; /// Turns remaining until may evolve /// (); 0 means evolve is unlocked. Lets a test ramp to diff --git a/SVSim.UnitTests/BattleNode/Integration/HeadlessConductorTests.cs b/SVSim.UnitTests/BattleNode/Integration/HeadlessConductorTests.cs index 7b37fa6..3459bf8 100644 --- a/SVSim.UnitTests/BattleNode/Integration/HeadlessConductorTests.cs +++ b/SVSim.UnitTests/BattleNode/Integration/HeadlessConductorTests.cs @@ -461,24 +461,13 @@ public class HeadlessConductorTests int attackerIdx = harness.InPlayCardIndex(playerSeat: true, boardPos: 0); // --- ramp seat A to the turn its evolve unlocks (EvolveWaitTurnCount counts down per seat-A turn) --- - // End turn 1 first (TurnEnd sets NowTurnEvol = true, the other CanEvolution precondition), then - // alternate A/B TurnStart/TurnEnd until seat A's EvolveWaitTurnCount reaches 0, leaving seat A's - // turn OPEN. A guard bounds the loop so a never-unlocking bug fails loud instead of hanging. + // End turn 1 first (TurnEnd sets NowTurnEvol = true, the other CanEvolution precondition), then ramp. Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: true).Accepted, Is.True, "turn1 TurnEnd"); - bool seatA = false; // next TurnStart is seat B's - int guard = 0; - while (harness.EvolveWaitTurnCount(playerSeat: true) > 0) - { - Assert.That(++guard, Is.LessThan(20), "evolve never unlocked — EvolveWaitTurnCount stuck > 0"); - Assert.That(harness.Push(NetworkBattleUri.TurnStart, TurnStartBody(), isPlayerSeat: seatA).Accepted, Is.True, "ramp TurnStart"); - if (seatA && harness.EvolveWaitTurnCount(playerSeat: true) == 0) break; // leave seat A's turn open - Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: seatA).Accepted, Is.True, "ramp TurnEnd"); - seatA = !seatA; - } + RampSeatAToEvolveTurn(harness); // EP precondition: seat A holds at least 1 evolve point and evolve is now unlocked. Assert.That(harness.EvolveWaitTurnCount(playerSeat: true), Is.EqualTo(0), "evolve unlocked on seat A's turn"); - int epBefore = harness.Ep(playerSeat: true); + int epBefore = harness.EpCount(playerSeat: true); Assert.That(epBefore, Is.GreaterThanOrEqualTo(1), "seat A must hold >= 1 EP before evolving"); // Pre-evolve stats: the un-evolved vanilla is 1/2 and not yet flagged evolved. @@ -499,7 +488,7 @@ public class HeadlessConductorTests "evolved atk must equal the card's evo_atk (3) — base 1 + evolve delta +2"); Assert.That(harness.InPlayCardLife(playerSeat: true, boardPos: 0), Is.EqualTo(evolvedLife), "evolved life must equal the card's evo_life (4) — base 2 + evolve delta +2"); - Assert.That(harness.Ep(playerSeat: true), Is.EqualTo(epBefore - 1), + Assert.That(harness.EpCount(playerSeat: true), Is.EqualTo(epBefore - 1), "an evolve must spend exactly one evolve point"); } @@ -923,6 +912,24 @@ public class HeadlessConductorTests } } + // Ramp seat A to the turn its evolve unlocks (seat A's EvolveWaitTurnCount counts down per seat-A turn), + // leaving seat A's turn OPEN. Caller must have already ended seat A's first turn (TurnEnd sets + // NowTurnEvol = true, the other CanEvolution precondition) so the next TurnStart is seat B's. A guard + // bounds the loop so a never-unlocking bug fails loud instead of hanging. + private static void RampSeatAToEvolveTurn(NodeNativeBattleHarness harness) + { + bool seatA = false; // next TurnStart is seat B's + int guard = 0; + while (harness.EvolveWaitTurnCount(playerSeat: true) > 0) + { + Assert.That(++guard, Is.LessThan(20), "evolve never unlocked — EvolveWaitTurnCount stuck > 0"); + Assert.That(harness.Push(NetworkBattleUri.TurnStart, TurnStartBody(), isPlayerSeat: seatA).Accepted, Is.True, "ramp TurnStart"); + if (seatA && harness.EvolveWaitTurnCount(playerSeat: true) == 0) break; // leave seat A's turn open + Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: seatA).Accepted, Is.True, "ramp TurnEnd"); + seatA = !seatA; + } + } + [Test] public void Real_spell_charge_drops_engine_cost_and_count_no_seam() { @@ -1094,25 +1101,13 @@ public class HeadlessConductorTests // --- ramp seat A to the turn its evolve unlocks (mirrors Evolve_resolves_on_engine_state_headless) --- Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: true).Accepted, Is.True, "turn1 TurnEnd"); - bool seatA = false; // next TurnStart is seat B's - int guard = 0; - while (harness.EvolveWaitTurnCount(playerSeat: true) > 0) - { - Assert.That(++guard, Is.LessThan(20), "evolve never unlocked — EvolveWaitTurnCount stuck > 0"); - Assert.That(harness.Push(NetworkBattleUri.TurnStart, TurnStartBody(), isPlayerSeat: seatA).Accepted, Is.True, "ramp TurnStart"); - if (seatA && harness.EvolveWaitTurnCount(playerSeat: true) == 0) break; // leave seat A's turn open - Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: seatA).Accepted, Is.True, "ramp TurnEnd"); - seatA = !seatA; - } + RampSeatAToEvolveTurn(harness); Assert.That(harness.EvolveWaitTurnCount(playerSeat: true), Is.EqualTo(0), "evolve unlocked on seat A's turn"); - Assert.That(harness.Ep(playerSeat: true), Is.GreaterThanOrEqualTo(1), "seat A must hold >= 1 EP before evolving"); + Assert.That(harness.EpCount(playerSeat: true), Is.GreaterThanOrEqualTo(1), "seat A must hold >= 1 EP before evolving"); // The reducer must be IN HAND across the evolve (its when_evolve_other skill is scanned off the hand). int reducerHandIdx = FindHandIdxByCardId(harness, BoardDependentCostCardId); Assert.That(reducerHandIdx, Is.GreaterThan(0), "the board-dependent cost-reducer must be in seat A's hand at the evolve"); - Assert.That(harness.HandCardId(playerSeat: true, - FindHandPosByEngineIdx(harness, reducerHandIdx)), Is.EqualTo((int)BoardDependentCostCardId), - "located the reducer by identity"); // PRE-EVOLVE pin (non-vacuity + causation baseline): the reducer resolves to its BASE cost (6) while // no follower has evolved yet. Read it WHILE in hand by its engine Index. @@ -1395,14 +1390,4 @@ public class HeadlessConductorTests Assert.That(tokenIdx, Is.EqualTo(0), "FINDING: the autonomous token_draw seats the chosen token at engine Index 0 headless — not addressable by a wire idx"); } - - // The hand POSITION (0-based) of the seat-A hand card with the given engine Index, or -1. Lets a test - // re-derive a HandCardId(seat, pos) lookup from an engine Index it already located by identity. - private static int FindHandPosByEngineIdx(NodeNativeBattleHarness harness, int engineIdx) - { - for (int i = 0; i < harness.HandCount(playerSeat: true); i++) - if (harness.HandCardIndex(playerSeat: true, i) == engineIdx) - return i; - return -1; - } } diff --git a/SVSim.UnitTests/BattleNode/Integration/NodeNativeBattleHarness.cs b/SVSim.UnitTests/BattleNode/Integration/NodeNativeBattleHarness.cs index 4634df3..cbac8a5 100644 --- a/SVSim.UnitTests/BattleNode/Integration/NodeNativeBattleHarness.cs +++ b/SVSim.UnitTests/BattleNode/Integration/NodeNativeBattleHarness.cs @@ -281,7 +281,7 @@ internal sealed class NodeNativeBattleHarness : IDisposable public bool IsEvolved(bool playerSeat, int boardPos) => Engine.IsEvolved(playerSeat, boardPos); /// The seat's current evolve-point count (M-HC-4b). An evolve spends one EP. - public int Ep(bool playerSeat) => Engine.Ep(playerSeat); + public int EpCount(bool playerSeat) => Engine.EpCount(playerSeat); /// Turns remaining until the seat may evolve (0 == unlocked) (M-HC-4b). public int EvolveWaitTurnCount(bool playerSeat) => Engine.EvolveWaitTurnCount(playerSeat);