feat(battlenode): evolve resolves on engine state via view-untangle (M-HC-4b)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-06 23:08:59 -04:00
parent 7a02cb3626
commit 2e8f9ab64e
3 changed files with 162 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ using NetworkBattleDefine = engine::NetworkBattleDefine;
using BattleManagerBase = engine::BattleManagerBase;
using BattlePlayerBase = engine::BattlePlayerBase;
using BattleCardBase = engine::BattleCardBase;
using UnitBattleCard = engine::UnitBattleCard;
using ClassBattleCardBase = engine::ClassBattleCardBase;
using CardCreatorBase = engine::CardCreatorBase;
using CostAddModifier = engine::CostAddModifier;
@@ -92,6 +93,16 @@ internal sealed class SessionBattleEngine
player.IsSelfTurn = true;
enemy.IsSelfTurn = false;
// Seat the evolve points + evolve-wait-turn counters exactly as the real match-load's
// SetupInitialGameState -> SetupEvolCount does (BattleManagerBase.cs:1115/1132). The headless
// Setup builds the seats by hand and never runs SetupInitialGameState, so without this both seats'
// CurrentEpCount/EvolveWaitTurnCount stay at their field defaults (0/0) and CanEvolution always
// fails (CurrentEpCount - GetEp() < 0). doesPlayerGoFirst == false here: seat A (BattlePlayer) is
// the SECOND player (IsFirst defaults false; seat A's turn-1 draws 2), so it gets SECOND_PLAYER_EP
// (3) + EvolveWaitTurnCount 4, and seat B (BattleEnemy, first) gets FIRST_PLAYER_EP (2) +
// EvolveWaitTurnCount 5. TurnEvolveControl (run on each TurnStart receive) counts the wait down.
mgr.SetupEvolCount(doesPlayerGoFirst: false);
InitLeaderLife(mgr); // a 0-life leader reads as game-over and silently blocks plays
InitCardTemplates(mgr); // play/draw resolution touches the (no-op) card view layer
InitHeadlessViews(mgr); // turn/play cycle dereferences UI-container + emotion refs
@@ -201,6 +212,25 @@ internal sealed class SessionBattleEngine
public bool InPlayCardAttackable(bool playerSeat, int boardPos) =>
Seat(playerSeat).ClassAndInPlayCardList[boardPos + 1].Attackable;
/// <summary>True once the in-play follower at <paramref name="boardPos"/> (0-based, leader excluded)
/// has evolved (<see cref="UnitBattleCard.IsEvolution"/>, set true inside the engine's own
/// <c>UnitBattleCard.Evolution</c> mutation). Only <see cref="UnitBattleCard"/> followers carry the
/// flag; a non-follower (or the leader) reads false. The evolve test's decisive engine-state assertion
/// (M-HC-4b).</summary>
public bool IsEvolved(bool playerSeat, int boardPos) =>
(Seat(playerSeat).ClassAndInPlayCardList[boardPos + 1] as UnitBattleCard)?.IsEvolution ?? false;
/// <summary>The seat's current evolve-point count (<see cref="BattlePlayerBase.CurrentEpCount"/>). An
/// 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
/// <c>EvolveWaitTurnCount</c> has counted down (M-HC-4b).</summary>
public int Ep(bool playerSeat) => Seat(playerSeat).CurrentEpCount;
/// <summary>Turns remaining until <paramref name="playerSeat"/> may evolve
/// (<see cref="BattlePlayerBase.EvolveWaitTurnCount"/>); 0 means evolve is unlocked. Lets a test ramp to
/// the evolve-enabled turn deterministically (M-HC-4b).</summary>
public int EvolveWaitTurnCount(bool playerSeat) => Seat(playerSeat).EvolveWaitTurnCount;
/// <summary>The engine-RESOLVED play-time cost of the card whose engine <c>Index</c> == <paramref name="idx"/>
/// on <paramref name="playerSeat"/> (M-HC-3a). This is the discounted cost the play actually paid —
/// spellboost reduction, board-dependent modifiers and all — read straight off the engine, so the