diff --git a/SVSim.BattleEngine.Tests/SessionEngine/SessionEngineSpellboostTests.cs b/SVSim.BattleEngine.Tests/SessionEngine/SessionEngineSpellboostTests.cs
new file mode 100644
index 0000000..4233c67
--- /dev/null
+++ b/SVSim.BattleEngine.Tests/SessionEngine/SessionEngineSpellboostTests.cs
@@ -0,0 +1,22 @@
+using NUnit.Framework;
+using SVSim.BattleNode.Sessions.Engine;
+using System.Linq;
+
+namespace SVSim.BattleEngine.Tests.SessionEngine;
+
+[TestFixture]
+public class SessionEngineSpellboostTests
+{
+ [Test]
+ public void EngineGlobalInit_makes_a_fresh_engine_ready()
+ {
+ EngineGlobalInit.EnsureInitialized();
+ var cl1 = CaptureReplay.Load("battle_test_cl1.ndjson");
+ var cl2 = CaptureReplay.Load("battle_test_cl2.ndjson");
+ var deckA = CaptureReplay.SelfDeckFrom(cl1);
+ var deckB = CaptureReplay.SelfDeckFrom(cl2);
+ var engine = new SessionBattleEngine();
+ Assert.DoesNotThrow(() => engine.Setup(masterSeed: 12345, seatADeck: deckA, seatBDeck: deckB));
+ Assert.That(engine.IsReady, Is.True, "engine must be ready after EngineGlobalInit (carried-risk fix)");
+ }
+}
diff --git a/SVSim.BattleNode/SVSim.BattleNode.csproj b/SVSim.BattleNode/SVSim.BattleNode.csproj
index fe0fa9a..4816d9b 100644
--- a/SVSim.BattleNode/SVSim.BattleNode.csproj
+++ b/SVSim.BattleNode/SVSim.BattleNode.csproj
@@ -22,4 +22,9 @@
+
+
+
+
diff --git a/SVSim.BattleNode/Sessions/Engine/EngineGlobalInit.cs b/SVSim.BattleNode/Sessions/Engine/EngineGlobalInit.cs
new file mode 100644
index 0000000..6f386c8
--- /dev/null
+++ b/SVSim.BattleNode/Sessions/Engine/EngineGlobalInit.cs
@@ -0,0 +1,293 @@
+extern alias engine;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Text.Json;
+using BattleManagerBase = engine::BattleManagerBase;
+using CardCSVData = engine::Wizard.CardCSVData;
+using CardMaster = engine::Wizard.CardMaster;
+using Certification = engine::Cute.Certification;
+using ClassCharacterMasterData = engine::Wizard.ClassCharacterMasterData;
+using Crossover = engine::Wizard.Crossover;
+using Data = engine::Wizard.Data;
+using GameMgr = engine::GameMgr;
+using Load = engine::Load;
+using LoadDetail = engine::LoadDetail;
+using Master = engine::Wizard.Master;
+using NetworkUserInfoData = engine::NetworkUserInfoData;
+
+namespace SVSim.BattleNode.Sessions.Engine;
+
+/// Host-owned, process-once initializer for the engine's global statics (Phase 2 N2,
+/// carried-risk A). The decompiled engine assumes a set of process-globals exist that the client
+/// populates from /load/index at login: the static CardMaster, Wizard.Data
+/// (Load/Master/Crossover), the GameMgr DataMgr chara ids, a NetworkUserInfoData, and
+/// Cute.Certification.udid. Without them throws inside
+/// its try/catch and the shadow silently no-ops (the N1 carried risk). Calling
+/// once at host startup primes them so Setup succeeds.
+///
+/// This is the production analogue of the test fixtures HeadlessEngineEnv.EnsureInitialized +
+/// HeadlessCardMaster + HeadlessMasterData; the reflection seams are transcribed verbatim.
+/// It differs in exactly three ways: (1) it loads the FULL cards.json (every row, no id filter) since
+/// the live host serves arbitrary decks; (2) it installs ALL 8 classes in ClassCharacterList; and
+/// (3) every set is guarded so the call is idempotent AND does not fight the test
+/// HeadlessEngineEnv if both run in one NUnit process.
+internal static class EngineGlobalInit
+{
+ private static readonly object _gate = new();
+ private static bool _done;
+
+ private static readonly string CardsJsonPath =
+ Path.Combine(AppContext.BaseDirectory, "Data", "cards.json");
+
+ // chara ids -> a ClassCharacterMasterData in Master; mirrors HeadlessMasterData.
+ private const int PlayerCharaId = 1;
+ private const int EnemyCharaId = 2;
+
+ public static void EnsureInitialized()
+ {
+ if (_done) return;
+ lock (_gate)
+ {
+ if (_done) return;
+
+ // --- Wizard.Data globals (the static /load/index snapshot) -----------------------------
+ // The mgr ctor's CreateBackgroundId reads Data.Load.data._userTutorial (LoadDetail
+ // self-inits _userTutorial). ??= so we don't clobber a snapshot HeadlessEngineEnv set.
+ Data.Load ??= new Load { data = new LoadDetail() };
+
+ // CardParameter(CardCSVData) reads Data.Crossover.RestrictedCard for the deck-limit calc;
+ // an empty Crossover returns the default count (no restriction). Private setter -> reflect.
+ // Only set when null so we coexist with HeadlessEngineEnv.
+ if (Data.Crossover == null)
+ {
+ typeof(Data).GetProperty("Crossover",
+ BindingFlags.Static | BindingFlags.Public)!
+ .SetValue(null, new Crossover());
+ }
+
+ // Suppress VFX / take the virtual-battle resolution path (no live view layer).
+ BattleManagerBase.IsForecast = true;
+
+ // --- static CardMaster (full cards.json) ----------------------------------------------
+ // Skip the (expensive) full load if a CardMaster is already registered as Default — a
+ // prior EngineGlobalInit call OR a HeadlessEngineEnv/HeadlessCardMaster load in the same
+ // NUnit process already populated it.
+ if (!IsCardMasterPopulated())
+ LoadFullCardMaster();
+
+ // --- Master reference data (all 8 classes' chara list) ---------------------------------
+ // Skip if Data.Master is already non-null with a non-empty ClassCharacterList.
+ if (!IsMasterPopulated())
+ InstallMaster();
+
+ // --- GameMgr DataMgr leader chara ids --------------------------------------------------
+ // Set the backing fields directly: the public SetPlayerCharaId() also pulls MyRotation/
+ // AvatarBattle info (more null statics) the resolution path doesn't need. Idempotent
+ // (plain assignment); only meaningful when still 0.
+ var dm = GameMgr.GetIns().GetDataMgr();
+ SetFieldIfZeroOrNull(dm, "_playerCharaId", PlayerCharaId);
+ SetFieldIfZeroOrNull(dm, "_enemyCharaId", EnemyCharaId);
+
+ // --- NetworkUserInfoData (background lookup on the network mgr's CreateBackgroundId) ----
+ // NetworkBattleManagerBase.CreateBackgroundId reads
+ // GameMgr.GetIns().GetNetworkUserInfoData().GetFieldId() when the RecoveryManager yields no
+ // bg id. GameMgr leaves _netUser null with no lazy init; seed a no-op instance whose
+ // _selfInfo carries just fieldId=1 (== ForestField, a valid background). Only seed when
+ // absent so a HeadlessEngineEnv-set instance is preserved.
+ if (GameMgr.GetIns().GetNetworkUserInfoData() == null)
+ {
+ var netUser = new NetworkUserInfoData();
+ netUser.SetSelfInfo(
+ new Dictionary { ["fieldId"] = 1 },
+ isWatchReplayRecovery: false);
+ GameMgr.GetIns().SetNetworkUserInfoData(netUser);
+ }
+
+ // --- Cute.Certification.udid -----------------------------------------------------------
+ // The emit-path payload builder reads Certification.Udid, whose getter lazily decodes from
+ // Toolbox.SavedataManager (null headless). Seed the private static backing field with a
+ // non-empty placeholder so the getter short-circuits. Only set when empty (coexistence).
+ var udidField = typeof(Certification).GetField("udid",
+ BindingFlags.Static | BindingFlags.NonPublic)!;
+ if (string.IsNullOrEmpty(udidField.GetValue(null) as string))
+ udidField.SetValue(null, "host-udid");
+
+ _done = true;
+ }
+ }
+
+ // --- CardMaster (full load) ----------------------------------------------------------------------
+
+ private static bool IsCardMasterPopulated()
+ {
+ var idType = typeof(CardMaster).GetNestedType("CardMasterId")!;
+ var defaultId = Enum.Parse(idType, "Default");
+ var fld = typeof(CardMaster).GetField("_dictCardMaster",
+ BindingFlags.Static | BindingFlags.NonPublic)!;
+ if (fld.GetValue(null) is not IDictionary dict) return false;
+ return dict.Contains(defaultId) && dict[defaultId] != null;
+ }
+
+ // Production difference (1): enumerate EVERY card row — no want.Contains(id) filter.
+ private static void LoadFullCardMaster()
+ {
+ var rows = new List();
+ using (var doc = JsonDocument.Parse(File.ReadAllText(CardsJsonPath)))
+ {
+ int sort = 0;
+ foreach (var el in doc.RootElement.EnumerateArray())
+ {
+ if (!el.TryGetProperty("card_id", out var idEl)) continue;
+ if (!int.TryParse(idEl.GetString(), out _)) continue; // skip malformed ids
+ rows.Add(BuildCardCsvData(el, sort++));
+ }
+ }
+
+ var cm = NewCardMaster(rows);
+ InjectAsDefault(cm);
+ }
+
+ // Transcribed from HeadlessCardMaster.BuildCardCsvData.
+ private static CardCSVData BuildCardCsvData(JsonElement el, int sortIndex)
+ {
+ var c = (CardCSVData)FormatterServices.GetUninitializedObject(typeof(CardCSVData));
+ const BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
+ foreach (var prop in el.EnumerateObject())
+ {
+ string val = prop.Value.ValueKind == JsonValueKind.Null ? null : prop.Value.ToString();
+ var f = typeof(CardCSVData).GetField(prop.Name, bf);
+ if (f != null) { SetMember(f.FieldType, val, v => f.SetValue(c, v)); continue; }
+ var p = typeof(CardCSVData).GetProperty(prop.Name, bf);
+ if (p != null && p.CanWrite) SetMember(p.PropertyType, val, v => p.SetValue(c, v));
+ }
+ var si = typeof(CardCSVData).GetProperty("SortIndex", bf);
+ if (si != null && si.CanWrite) si.SetValue(c, sortIndex);
+ return c;
+ }
+
+ private static void SetMember(Type t, string val, Action