Files
SVSimServer/SVSim.BattleEngine/Engine/Wizard.DeckCardEdit/CardSelectListUI_State_Edit.cs
gamer147 2d9a6eea4b engine cleanup passes 4-7 + multi-instancing ambient rip
Squashes 146 commits from battle-engine-extraction. Net: 2,045 files changed,
+11,896 / -158,687 lines. Ships engine passes 4-7 (dead-code cull, view-layer
stub, receive-path shrink) plus the Phase-5 AsyncLocal ambient deletion that
turns concurrent battles into a type-system property rather than a scope
contract.

## What landed

**Passes 4-7 (chunks 1-34):** Extended the Phase-4 const-false collapse into a
cascading cull across the skill graph, view layer, and receive-path periphery.
Six mode flags (IsWatchBattle/IsReplayBattle/IsAdmin/IsAdminWatch/IsPuzzleQuest/
IsAINetwork) became `const false`, every guarded block deleted. Field*.cs
subclass ctors + BackGroundBase + ObjectChecker culled to no-ops. Mulligan
family reworked to take a mgr param through IMulliganMgr.InitMulligan.
Emotion/Recovery/Resource clusters null-stubbed. Prediction/OperationSimulator/
skill filters converted from static ambient reads to per-mgr reads via
SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr / ins.BattleMgr / this.BattleMgr.

**Phase-5 ambient rip (chunks 35-47):** Deleted BattleAmbient / BattleAmbient-
Context / TestBattleScope in full. Every per-battle mutable slot now lives on
the mgr instance itself:
  mgr.InstanceIsForecast / InstanceIsRandomDraw / InstanceRecoveryInfo /
  InstanceViewerId / InstanceNetworkAgent / GameMgr
BattleManagerBase.GetIns() returns null unconditionally; the residual static
flags + 3 façades (Certification.ViewerId, Data.BattleRecoveryInfo,
ToolboxGame.RealTimeNetworkAgent) are null-tolerant defaults kept for the
handful of engine-internal readers that still reference their types. Zero
BattleAmbient references anywhere in engine + node + tests.

Added pre-seeded GameMgr ctor overload threaded through the mgr chain
(BattleManagerBase → SingleBattleMgr / NetworkBattleManagerBase → NetworkStandard-
BattleMgr → HeadlessBattleMgr / HeadlessNetworkBattleMgr). Fixtures build a
GameMgr, seed it via HeadlessEngineEnv.SeedCharaIds/SeedNetUser, and pass it
to the mgr's ctor — no ambient reach.

Node side (SVSim.BattleNode/SessionBattleEngine): _ctx replaced with a plain
GameMgr field; 34 `using var _ambient = BattleAmbient.Enter(_ctx)` scope wraps
ripped from every accessor and mutator; EngineGlobalInit.WirePerSessionGameMgr
takes GameMgr as a param and runs from SessionBattleEngine.SetupInternal
BEFORE mgr construction.

Test side: TestBattleScope deleted; 18 fixture [SetUp]s migrated to
`HeadlessEngineEnv.EnsureProcessGlobals()`; MultiInstanceEngineTests rewritten
around per-mgr construction (GetIns() → null is the pinned invariant).

## Regression fixes

- **chunk-48** (MulliganCtrl): chunk-35's `= null` stubs on card lookups broke
  the live receive-driven Deal path (BattlePlayerBase.DrawCard NRE'd downstream
  of NetworkPlayerMulliganCtrl.StartMulliganVfx). Restored the three lookups
  via `_battlePlayer.BattleMgr.GetBattleCardIdx`. Engine tests were satisfied
  by the WireMulliganPhase seam; unit tests exposed the live-path gap.

## Ship state

- SVSim.BattleEngine.Tests: 56/56 pass, 2 skip
- SVSim.UnitTests: 1554/1554 pass (was 1523/31-fail before chunk 48)
- Solution build: 0 source warnings (40 pre-existing NU1902 MessagePack CVEs
  in SVSim.EmulatedEntrypoint, unrelated)
- Sequential PVP smoke: verified live (two back-to-back battles, no regression
  on cleanup/spinup)
- Concurrent PVP smoke: verified live

Adds tools/engine-port/ClosureAnalyzer/ — the Roslyn transitive-type-closure
analyzer needed to make future cascade cleanup safe (per feedback memory
"Engine cleanup needs closure tool" from the 2026-06-28 pass-3 failure).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-03 19:18:54 -04:00

312 lines
8.1 KiB
C#

using System;
using System.Collections;
using Cute;
using UnityEngine;
namespace Wizard.DeckCardEdit;
[RequireComponent(typeof(CardSelectListUI_Positioning))]
public class CardSelectListUI_State_Edit : MecanimStateBase
{
[SerializeField]
private CardSelectListUIBase m_scene;
[SerializeField]
private CardSelectListUI_State_CardDrag m_stateDragMode;
[SerializeField]
private UIScrollView m_scrollView;
private UIPanel _scrollViewPanel;
private SpringPanel _springPanel;
[SerializeField]
private UICenterOnChild m_scrollViewCenterOnChild;
[SerializeField]
private CardSelectListUI_Positioning _selectionAreaCardPositioning;
[SerializeField]
private CardSelectListUI_Positioning _pagingAreaCardPositioning;
[SerializeField]
private UIPanel _maskPanel;
private GameObject _pressObj;
private float _pressTime;
private Vector2 _pressPoint = Vector2.zero;
private Coroutine _pagingCoroutine;
private bool _requestImmediateMove;
public bool IsClick { get; private set; }
public override void onOpen()
{
base.onOpen();
RefreshSelectionArea(isImmediate: false);
RefreshPage(isImmediate: false);
m_scrollView.enabled = true;
_maskPanel.clipping = UIDrawCall.Clipping.None;
}
public override void onClose()
{
base.onClose();
RefreshSelectionArea(isImmediate: false);
RefreshPage(isImmediate: false);
}
public void RefreshSelectionArea(bool isImmediate)
{
RefreshBase(m_scene.SelectionAreaList, onPressSelectionAreaCard, delegate(GameObject o)
{
onDoubleClick(o, CardSelectListUI_State_CardDrag.Mode.InToOut);
}, _selectionAreaCardPositioning, isMerge: false, isImmediate);
}
public void RefreshPage(bool isImmediate)
{
RefreshBase(m_scene.PagingList, onPressPagingCard, delegate(GameObject o)
{
onDoubleClick(o, CardSelectListUI_State_CardDrag.Mode.OutToIn);
}, _pagingAreaCardPositioning, isMerge: false, isImmediate);
}
private void RefreshBase(CardBundle cardList, UIEventListener.BoolDelegate callback, UIEventListener.VoidDelegate doubleClickCallback, CardSelectListUI_Positioning positioning, bool isMerge, bool isImmediate)
{
int countKind = cardList.CountKind;
GameObject[] array = new GameObject[countKind];
for (int i = 0; i < countKind; i++)
{
array[i] = cardList.FindWithIndex(i).CardObj;
if (!(array[i] != null))
{
continue;
}
BoxCollider component = array[i].GetComponent<BoxCollider>();
if (!component)
{
component = array[i].AddComponent<BoxCollider>();
UIWidget component2 = array[i].GetComponent<UIWidget>();
if ((bool)component2)
{
component.size = component2.localSize;
}
}
UIEventListener uIEventListener = UIEventListener.Get(array[i]);
uIEventListener.onPress = (UIEventListener.BoolDelegate)Delegate.Combine(uIEventListener.onPress, callback);
UIEventListener uIEventListener2 = UIEventListener.Get(array[i]);
uIEventListener2.onPressRight = (UIEventListener.BoolDelegate)Delegate.Combine(uIEventListener2.onPressRight, callback);
UIEventListener uIEventListener3 = UIEventListener.Get(array[i]);
uIEventListener3.onDoubleClick = (UIEventListener.VoidDelegate)Delegate.Combine(uIEventListener3.onDoubleClick, doubleClickCallback);
}
if (isMerge)
{
positioning.Add(array);
}
else
{
positioning.Change(array);
}
if (isImmediate)
{
positioning.Immediate();
}
}
public void NextPage(Action onMoved)
{
if (_pagingCoroutine != null)
{
StopCoroutine(_pagingCoroutine);
}
_pagingCoroutine = StartCoroutine(PagingBase(-1f, onMoved));
}
public void PrevPage(Action onMoved)
{
if (_pagingCoroutine != null)
{
StopCoroutine(_pagingCoroutine);
}
_pagingCoroutine = StartCoroutine(PagingBase(1f, onMoved));
}
private IEnumerator PagingBase(float direction, Action onMoved)
{
Vector3 offset = _pagingAreaCardPositioning.Offset;
offset.x = 1400f * direction;
_pagingAreaCardPositioning.Offset = offset;
_pagingAreaCardPositioning.Speed = 0.4f;
while (_pagingAreaCardPositioning.IsMoving)
{
yield return null;
}
offset.x = 1400f * (0f - direction);
_pagingAreaCardPositioning.Offset = offset;
_pagingAreaCardPositioning.Speed = 1f;
onMoved.Call();
yield return null;
offset.x = 0f;
_pagingAreaCardPositioning.Offset = offset;
_pagingAreaCardPositioning.Speed = 0.4f;
}
public void Fit()
{
SetupScrollComponent();
float num = Mathf.Max(0f, _scrollViewPanel.clipOffset.x);
int a = Mathf.Max(0, m_scene.SelectionAreaList.CountKind - 10);
float breakWidth = _selectionAreaCardPositioning.BreakWidth;
int b = Mathf.RoundToInt(num / breakWidth);
Fit(Mathf.Min(a, b));
m_scrollView.customMovement = ((m_scene.SelectionAreaList.CountKind > 10) ? Vector2.right : Vector2.zero);
}
public void CenterOn(int idx)
{
SetupScrollComponent();
idx -= 5;
int a = Mathf.Max(0, m_scene.SelectionAreaList.CountKind - 10);
int a2 = 0;
Fit(Mathf.Max(a2, Mathf.Min(a, idx)));
}
private void Fit(int idx)
{
float breakWidth = _selectionAreaCardPositioning.BreakWidth;
_springPanel.target.x = (float)(-idx) * breakWidth;
_springPanel.enabled = true;
}
public void ResetScroll()
{
SetupScrollComponent();
m_scrollView.ResetPosition();
_springPanel.target.x = 0f;
_springPanel.enabled = true;
}
private void SetupScrollComponent()
{
if (_scrollViewPanel == null)
{
_scrollViewPanel = m_scrollView.GetComponent<UIPanel>();
}
if (_springPanel == null)
{
_springPanel = m_scrollView.GetComponent<SpringPanel>();
}
m_scrollViewCenterOnChild.gameObject.SetActive(value: true);
}
public void RemoveSelectionArea()
{
_selectionAreaCardPositioning.Clear();
}
private void StartDragState(bool immediate)
{
CardObject cardObject = m_stateDragMode.TryGetCard(_pressObj);
if (cardObject != null)
{
m_scene.HideDetail();
m_scene.ChangeState(m_stateDragMode, skipCloseAnim: true);
m_stateDragMode.ImmediateMove = immediate;
m_stateDragMode.CreateDragCard(cardObject);
m_scrollView.enabled = false;
m_scrollViewCenterOnChild.gameObject.SetActive(value: true);
IsClick = false;
}
_pressObj = null;
_pressTime = 0f;
}
public override void onMove()
{
base.onMove();
if (!_pressObj)
{
return;
}
if (_requestImmediateMove)
{
StartDragState(immediate: true);
_requestImmediateMove = false;
}
else if (Input.GetMouseButton(0))
{
IsClick = true;
_pressTime += Time.deltaTime;
Vector2 vector = _pressPoint - (Vector2)Input.mousePosition;
vector.Normalize();
float num = Mathf.Abs(vector.y);
float num2 = Vector2.Distance(_pressPoint, Input.mousePosition);
if (num2 >= 35f && num < 0.8f)
{
_pressObj = null;
_pressTime = 0f;
}
else if (_pressTime >= 0.5f || (num >= 0.8f && num2 >= 35f))
{
StartDragState(immediate: false);
}
}
else
{
_pressObj = null;
_pressTime = 0f;
}
}
private void onPressSelectionAreaCard(GameObject obj, bool state)
{
if (!m_scene.IsLoading)
{
bool mouseButton = Input.GetMouseButton(0);
bool flag = !mouseButton && Input.GetMouseButtonDown(1);
if (mouseButton || flag)
{
initPress(obj, CardSelectListUI_State_CardDrag.Mode.InToOut);
_requestImmediateMove = flag;
}
}
}
private void onPressPagingCard(GameObject obj, bool state)
{
if (!m_scene.IsLoading)
{
bool mouseButton = Input.GetMouseButton(0);
bool flag = !mouseButton && Input.GetMouseButtonDown(1);
if (mouseButton || flag)
{
initPress(obj, CardSelectListUI_State_CardDrag.Mode.OutToIn);
_requestImmediateMove = flag;
}
}
}
private void initPress(GameObject obj, CardSelectListUI_State_CardDrag.Mode mode)
{
m_stateDragMode.EditMode = mode;
_pressObj = obj;
_pressPoint = Input.mousePosition;
}
private void onDoubleClick(GameObject obj, CardSelectListUI_State_CardDrag.Mode mode)
{
if (!m_scene.IsLoading)
{
_requestImmediateMove = true;
initPress(obj, mode);
}
}
}