refactor(battlenode): M-HC-4 cleanup — EpCount rename, dedupe evolve-ramp, drop tautological guard

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-07 00:47:33 -04:00
parent a30a496265
commit f1c96ed37d
3 changed files with 26 additions and 41 deletions

View File

@@ -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 /// evolve spends one EP, so the evolve test asserts this decrements by 1. EP is granted at setup by
/// the engine's <c>SetupEvolCount</c> (2 for the game-first seat, 3 for the second) and unlocks once /// the engine's <c>SetupEvolCount</c> (2 for the game-first seat, 3 for the second) and unlocks once
/// <c>EvolveWaitTurnCount</c> has counted down (M-HC-4b).</summary> /// <c>EvolveWaitTurnCount</c> has counted down (M-HC-4b).</summary>
public int Ep(bool playerSeat) => Seat(playerSeat).CurrentEpCount; public int EpCount(bool playerSeat) => Seat(playerSeat).CurrentEpCount;
/// <summary>Turns remaining until <paramref name="playerSeat"/> may evolve /// <summary>Turns remaining until <paramref name="playerSeat"/> may evolve
/// (<see cref="BattlePlayerBase.EvolveWaitTurnCount"/>); 0 means evolve is unlocked. Lets a test ramp to /// (<see cref="BattlePlayerBase.EvolveWaitTurnCount"/>); 0 means evolve is unlocked. Lets a test ramp to

View File

@@ -461,24 +461,13 @@ public class HeadlessConductorTests
int attackerIdx = harness.InPlayCardIndex(playerSeat: true, boardPos: 0); int attackerIdx = harness.InPlayCardIndex(playerSeat: true, boardPos: 0);
// --- ramp seat A to the turn its evolve unlocks (EvolveWaitTurnCount counts down per seat-A turn) --- // --- 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 // End turn 1 first (TurnEnd sets NowTurnEvol = true, the other CanEvolution precondition), then ramp.
// 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.
Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: true).Accepted, Is.True, "turn1 TurnEnd"); Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: true).Accepted, Is.True, "turn1 TurnEnd");
bool seatA = false; // next TurnStart is seat B's RampSeatAToEvolveTurn(harness);
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;
}
// EP precondition: seat A holds at least 1 evolve point and evolve is now unlocked. // 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"); 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"); 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. // 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"); "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), 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"); "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"); "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] [Test]
public void Real_spell_charge_drops_engine_cost_and_count_no_seam() 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) --- // --- 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"); Assert.That(harness.Push(NetworkBattleUri.TurnEnd, TurnEndBody(), isPlayerSeat: true).Accepted, Is.True, "turn1 TurnEnd");
bool seatA = false; // next TurnStart is seat B's RampSeatAToEvolveTurn(harness);
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;
}
Assert.That(harness.EvolveWaitTurnCount(playerSeat: true), Is.EqualTo(0), "evolve unlocked on seat A's turn"); 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). // The reducer must be IN HAND across the evolve (its when_evolve_other skill is scanned off the hand).
int reducerHandIdx = FindHandIdxByCardId(harness, BoardDependentCostCardId); 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(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 // 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. // 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), 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"); "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;
}
} }

View File

@@ -281,7 +281,7 @@ internal sealed class NodeNativeBattleHarness : IDisposable
public bool IsEvolved(bool playerSeat, int boardPos) => Engine.IsEvolved(playerSeat, boardPos); public bool IsEvolved(bool playerSeat, int boardPos) => Engine.IsEvolved(playerSeat, boardPos);
/// <summary>The seat's current evolve-point count (M-HC-4b). An evolve spends one EP.</summary> /// <summary>The seat's current evolve-point count (M-HC-4b). An evolve spends one EP.</summary>
public int Ep(bool playerSeat) => Engine.Ep(playerSeat); public int EpCount(bool playerSeat) => Engine.EpCount(playerSeat);
/// <summary>Turns remaining until the seat may evolve (0 == unlocked) (M-HC-4b).</summary> /// <summary>Turns remaining until the seat may evolve (0 == unlocked) (M-HC-4b).</summary>
public int EvolveWaitTurnCount(bool playerSeat) => Engine.EvolveWaitTurnCount(playerSeat); public int EvolveWaitTurnCount(bool playerSeat) => Engine.EvolveWaitTurnCount(playerSeat);