test(rng-seam): M12 constants + NewAuthoritativeBattle harness factory
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using SVSim.BattleEngine.Rng;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Wizard;
|
using Wizard;
|
||||||
using Wizard.Battle;
|
using Wizard.Battle;
|
||||||
@@ -150,6 +151,26 @@ namespace SVSim.BattleEngine.Tests
|
|||||||
// (M4/M6/M8 discipline) varies this and watches the damage track it.
|
// (M4/M6/M8 discipline) varies this and watches the damage track it.
|
||||||
public const int DynamicSeededPlayCount = 4;
|
public const int DynamicSeededPlayCount = 4;
|
||||||
|
|
||||||
|
// M12 (the design §5 RNG oracle): reuse the M9 draw spell (800114010, when_play `draw` 1 from the
|
||||||
|
// caster's deck via a random_count=1 filter) but over a MULTI-card deck with IsRandomDraw=true.
|
||||||
|
// M9 passed only because IsRandomDraw=false takes BattlePlayerBase.LotteryRandomDrawCard's
|
||||||
|
// top-of-deck `else` branch (BattlePlayerBase.cs:3174-3185) — a 1-card pool made index 0 the only
|
||||||
|
// card. With IsRandomDraw=true the selection runs through SkillRandomSelectFilter.Filtering, which
|
||||||
|
// calls BattleManagerBase.GetIns().StableRandom(poolCount) per pick (SkillRandomSelectFilter.cs:42,
|
||||||
|
// gated on IsRandomDraw) — the chokepoint HeadlessBattleMgr overrides. So the scripted source picks
|
||||||
|
// exactly which deck card is drawn, proving a GENUINE multi-outcome roll (the dimension M9's
|
||||||
|
// one-card pool deliberately avoided).
|
||||||
|
//
|
||||||
|
// Three distinguishable deck cards seeded at consecutive indices; SkillRandomSelectFilter orders
|
||||||
|
// the pool by Index (line 34), so the pick index maps to position in this order:
|
||||||
|
// index 0 -> RngDeckCardA (100011010), index 1 -> RngDeckCardB (103111050), index 2 -> RngDeckCardC (100011020)
|
||||||
|
// All three are already loaded by HeadlessCardMaster.Load via EnsureInitialized (FollowerId,
|
||||||
|
// BuffFollowerId, SummonedTokenId), so no Load change is needed.
|
||||||
|
public const int RngDrawSpellId = DrawSpellId; // 800114010, when_play draw 1 (random_count=1)
|
||||||
|
public const int RngDeckCardA = FollowerId; // neutral 1/2 -> Index-order position 0
|
||||||
|
public const int RngDeckCardB = BuffFollowerId; // ELF 1/1 -> Index-order position 1
|
||||||
|
public const int RngDeckCardC = SummonedTokenId; // neutral 2/2 -> Index-order position 2
|
||||||
|
|
||||||
private static bool _done;
|
private static bool _done;
|
||||||
|
|
||||||
public static void EnsureInitialized()
|
public static void EnsureInitialized()
|
||||||
@@ -284,6 +305,30 @@ namespace SVSim.BattleEngine.Tests
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a headless battle wired for AUTHORITATIVE RNG: real rolls under IsForecast (via the
|
||||||
|
// injected source on HeadlessBattleMgr) AND IsRandomDraw=true (the second gate — without it the
|
||||||
|
// random-select filters bypass the roll and pick index 0; BattleManagerBase.cs:415,
|
||||||
|
// SkillRandomSelectFilter.cs:42). Mirrors the opponent/turn/leader-life wiring every oracle does.
|
||||||
|
// Returns the constructed HeadlessBattleMgr; the caller seeds hands/decks/boards and plays.
|
||||||
|
public static HeadlessBattleMgr NewAuthoritativeBattle(IRandomSource rng)
|
||||||
|
{
|
||||||
|
EnsureInitialized(); // sets IsForecast = true among other globals
|
||||||
|
BattleManagerBase.IsRandomDraw = true; // the second RNG gate (F-RNG-2)
|
||||||
|
var mgr = new HeadlessBattleMgr(new HeadlessContentsCreator(), rng);
|
||||||
|
mgr.IsRecovery = true; // collapse wait delays to 0 (F1)
|
||||||
|
|
||||||
|
var player = mgr.BattlePlayer;
|
||||||
|
var enemy = mgr.BattleEnemy;
|
||||||
|
SetField(player, "_opponentBattlePlayer", enemy);
|
||||||
|
SetField(enemy, "_opponentBattlePlayer", player);
|
||||||
|
player.IsSelfTurn = true;
|
||||||
|
enemy.IsSelfTurn = false;
|
||||||
|
|
||||||
|
InitLeaderLife(mgr); // a 0-life leader reads as game-over and blocks plays
|
||||||
|
InitCardTemplates(mgr); // the draw VFX touches the drawn card's view layer
|
||||||
|
return mgr;
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetField(object obj, string name, object value)
|
private static void SetField(object obj, string name, object value)
|
||||||
{
|
{
|
||||||
var f = obj.GetType().GetField(name,
|
var f = obj.GetType().GetField(name,
|
||||||
|
|||||||
Reference in New Issue
Block a user