525 lines
28 KiB
C#
525 lines
28 KiB
C#
|
|
// WL-813o 프로브 — 존 클리어 픽업 카드 실측 (에디트 모드 · Play 0 · 로그인 0).
|
||
|
|
// unity command run_script --file AgentScripts/WL813o_Probe.cs --entry WL813o_Probe.RunAll
|
||
|
|
// 산출물: <worktree>/AgentScripts/WL813o_PROBE.txt
|
||
|
|
// 🔴 batchmode 는 Time.unscaledTime 이 전혀 전진하지 않는다(실측: run_script 두 번 사이 12 s 를 두어도 11.35 고정).
|
||
|
|
// 그래서 「멈춘 초」는 813p ShiftClockForProbe + 프로브 전용 리플렉션(s_pauseStart 되감기)으로 재현해 잰다(⑩).
|
||
|
|
// 🔴 NewGameUI.prefab 은 LoadPrefabContents 로 열고 **저장하지 않는다**(UnloadPrefabContents(false)).
|
||
|
|
|
||
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.IO;
|
||
|
|
using System.Reflection;
|
||
|
|
using System.Text;
|
||
|
|
using UnityEngine;
|
||
|
|
using UnityEditor;
|
||
|
|
using TMPro;
|
||
|
|
using WL.UI;
|
||
|
|
using WL.Combat.Run;
|
||
|
|
|
||
|
|
public static class WL813o_Probe
|
||
|
|
{
|
||
|
|
const string kPrefab = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
|
||
|
|
const string kTableTxt = "Assets/ResWork/Table/table_selectskill.txt";
|
||
|
|
|
||
|
|
static StringBuilder _o;
|
||
|
|
static int _pass, _fail;
|
||
|
|
static GameObject _root;
|
||
|
|
static GameObject _fakeInfos; // table_selectskill · InGameInfo · ActorInfo 대역
|
||
|
|
|
||
|
|
static void H(string s) { _o.AppendLine(); _o.AppendLine("── " + s); }
|
||
|
|
static void N(string s) { _o.AppendLine(" " + s); }
|
||
|
|
static bool Chk(bool ok, string what)
|
||
|
|
{
|
||
|
|
if (ok) { _pass++; _o.AppendLine(" [PASS] " + what); }
|
||
|
|
else { _fail++; _o.AppendLine(" [FAIL] " + what); }
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
static string Root { get { return Directory.GetParent(Application.dataPath).FullName; } }
|
||
|
|
static string OutPath(string name) { return Path.Combine(Path.Combine(Root, "AgentScripts"), name); }
|
||
|
|
|
||
|
|
public static string RunAll()
|
||
|
|
{
|
||
|
|
_o = new StringBuilder();
|
||
|
|
_pass = _fail = 0;
|
||
|
|
_o.AppendLine("# WL813o Probe " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " (edit mode · Play 0 · 로그인 0)");
|
||
|
|
_o.AppendLine("playMode=" + Application.isPlaying);
|
||
|
|
|
||
|
|
bool savedDisabled = WLZonePickupSettings.RuntimeDisabled;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
Step0_Asset();
|
||
|
|
Step1_OriginalMissing();
|
||
|
|
Step2_Fakes();
|
||
|
|
Step3_Bind();
|
||
|
|
Step4_OpenOnZoneCleared();
|
||
|
|
Step5_TimeoutAutoPick();
|
||
|
|
Step6_ClickPick();
|
||
|
|
Step7_FinalZoneExcluded();
|
||
|
|
Step8_RevivePriority();
|
||
|
|
Step9_GuideWait();
|
||
|
|
Step10_TimerPauseWiring();
|
||
|
|
Step11_Gc();
|
||
|
|
Step12_C8();
|
||
|
|
}
|
||
|
|
catch (Exception e)
|
||
|
|
{
|
||
|
|
_fail++;
|
||
|
|
_o.AppendLine(" [FAIL] 예외 " + e.GetType().Name + " : " + e.Message);
|
||
|
|
_o.AppendLine(e.StackTrace);
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
WLZonePickupSettings.RuntimeDisabled = savedDisabled;
|
||
|
|
Step13_Cleanup();
|
||
|
|
}
|
||
|
|
|
||
|
|
_o.AppendLine();
|
||
|
|
_o.AppendLine("RESULT " + (_fail == 0 ? "PASS" : "FAIL") + " (pass=" + _pass + " fail=" + _fail + ")");
|
||
|
|
string path = OutPath("WL813o_PROBE.txt");
|
||
|
|
File.WriteAllText(path, _o.ToString(), new UTF8Encoding(false));
|
||
|
|
return _o.ToString() + "\n(파일: " + path + ")";
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────────────────────────────────────── ⓪ 에셋 · 계약
|
||
|
|
static void Step0_Asset()
|
||
|
|
{
|
||
|
|
H("⓪ 에셋 (WLZonePickupSettings · NEW) · 813p 계약");
|
||
|
|
WLZonePickupSettings.ClearCache();
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
Chk(s != null, "Resources.Load(\"" + WLZonePickupSettings.ResourcesPath + "\")");
|
||
|
|
if (s == null) return;
|
||
|
|
Chk(WLZonePickupSettings.Enabled, "Enabled = " + WLZonePickupSettings.Enabled);
|
||
|
|
N("값: cardCount=" + s.cardCount + " timeout=" + s.pickTimeoutSeconds + "s(unscaled=" + s.timeoutUnscaled +
|
||
|
|
") first=" + s.autoPickFirstCard + " includeFinalZone=" + s.includeFinalZone +
|
||
|
|
" guideWait=" + s.waitForGuideArrival + "(max " + s.maxGuideDelaySeconds + "s)" +
|
||
|
|
" deferRevive=" + s.deferWhileRevive + " pauseTimer=" + s.pauseRunTimer + " holdWorld=" + s.holdWorld +
|
||
|
|
" stopActors=" + s.stopActorsWhileOpen + " openDelay=" + s.openDelaySeconds + "s");
|
||
|
|
Chk(!s.includeFinalZone, "존4(마지막 존) 기본 제외 — 보스 흐름 방해 금지(발주서 §1-1)");
|
||
|
|
Chk(!s.holdWorld, "세계 정지 기본 off = 타이머만 정지(발주서 §1-3 권장)");
|
||
|
|
Chk(Math.Abs(s.pickTimeoutSeconds - 10f) < 0.001f, "타임아웃 10 s(발주서 §1-2) — 실측 " + s.pickTimeoutSeconds);
|
||
|
|
Chk(s.cardCount == 3, "3장(기획안 F-3) — 실측 " + s.cardCount);
|
||
|
|
Chk(s.edge.enabled && s.edge.outlineWidth > 0f,
|
||
|
|
"남은 초·카드 글자 아웃라인(813y ApplyEdge) outlineWidth=" + s.edge.outlineWidth);
|
||
|
|
|
||
|
|
var rs = WLRunSettings.Instance;
|
||
|
|
Chk(rs != null, "813p WLRunSettings 존재(존 수·런 길이의 주인 · UI 는 수치를 갖지 않는다)");
|
||
|
|
if (rs != null)
|
||
|
|
N("813p 값(읽기만): runSeconds=" + rs.runSeconds + " 존 " + rs.zoneSpawnerIds.Length + "개");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ① 🔴 원본 SelectSkillUI 노드 부재 실측(발주서 §1-1 과 다른 곳)
|
||
|
|
static void Step1_OriginalMissing()
|
||
|
|
{
|
||
|
|
H("① 🔴 원본 SelectSkillUI 노드 실측 — 발주서 §1-1 전제와 다르다");
|
||
|
|
var root = PrefabUtility.LoadPrefabContents(kPrefab);
|
||
|
|
_root = root;
|
||
|
|
Chk(root != null, "LoadPrefabContents(" + kPrefab + ")");
|
||
|
|
if (root == null) return;
|
||
|
|
|
||
|
|
var ui = root.GetComponentsInChildren<SelectSkillUI>(true);
|
||
|
|
var cards = root.GetComponentsInChildren<SelectSkillCard>(true);
|
||
|
|
var gameUi = root.GetComponentsInChildren<GameUI>(true);
|
||
|
|
var nguiLabels = root.GetComponentsInChildren<UILabel>(true);
|
||
|
|
var nguiSprites = root.GetComponentsInChildren<UISprite>(true);
|
||
|
|
var tmp = root.GetComponentsInChildren<TextMeshProUGUI>(true);
|
||
|
|
N("NewGameUI.prefab 안: SelectSkillUI=" + ui.Length + " SelectSkillCard=" + cards.Length +
|
||
|
|
" GameUI=" + gameUi.Length + " UILabel=" + nguiLabels.Length + " UISprite=" + nguiSprites.Length +
|
||
|
|
" TextMeshProUGUI=" + tmp.Length);
|
||
|
|
Chk(ui.Length == 0, "원본 SelectSkillUI 노드 0개 = 열 대상이 없다");
|
||
|
|
Chk(gameUi.Length == 0 && GameUI.Ins == null,
|
||
|
|
"GameUI 컴포넌트 0개 · GameUI.Ins=null → MyActor.cs:162 의 자동전투 정지 배선은 작동하지 않는다");
|
||
|
|
Chk(nguiLabels.Length == 0 && nguiSprites.Length == 0,
|
||
|
|
"실제 인게임 UI 에 NGUI 위젯 0개(원본 카드 UI 는 NGUI) · TMP 는 " + tmp.Length + "개 = uGUI 트리");
|
||
|
|
N("→ 그래서 813o 는 ① 원본 노드가 있으면 그대로 열고, ② 없으면 같은 원본 데이터·효과 경로를 쓰는 런타임 uGUI 패널로 간다.");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ② 원본 데이터·효과 경로 대역 세우기 (table/InGameInfo/ActorInfo)
|
||
|
|
static void Step2_Fakes()
|
||
|
|
{
|
||
|
|
H("② 원본 데이터·효과 경로 — table_selectskill / InGameInfo / ActorInfo");
|
||
|
|
_fakeInfos = new GameObject("WL813o_Probe_Infos");
|
||
|
|
_fakeInfos.hideFlags = HideFlags.HideAndDontSave;
|
||
|
|
|
||
|
|
var tbl = _fakeInfos.AddComponent<table_selectskill>();
|
||
|
|
tbl.m_json = AssetDatabase.LoadAssetAtPath<TextAsset>(kTableTxt);
|
||
|
|
Chk(tbl.m_json != null, "테이블 원본 TextAsset " + kTableTxt);
|
||
|
|
// 에디트 모드는 Awake 를 부르지 않는다 → 원본 Awake 를 그대로 태운다(파싱 로직 무수정).
|
||
|
|
// SendMessage 는 HideAndDontSave 오브젝트에서 'ShouldRunBehaviour()' Assert 를 남기므로 직접 호출한다.
|
||
|
|
var awake = typeof(table_selectskill).GetMethod("Awake", BindingFlags.NonPublic | BindingFlags.Instance);
|
||
|
|
Chk(awake != null, "원본 table_selectskill.Awake 확보");
|
||
|
|
if (awake != null) awake.Invoke(tbl, null);
|
||
|
|
Chk(table_selectskill.Ins != null, "table_selectskill.Ins 설정(원본 Awake 경로)");
|
||
|
|
|
||
|
|
var info = _fakeInfos.AddComponent<InGameInfo>();
|
||
|
|
InGameInfo.Ins = info; // Awake 는 무거워서 부르지 않는다(Ins 만 세운다)
|
||
|
|
Chk(InGameInfo.Ins != null, "InGameInfo.Ins 설정");
|
||
|
|
|
||
|
|
var actors = _fakeInfos.AddComponent<ActorInfo>();
|
||
|
|
ActorInfo.Ins = actors;
|
||
|
|
Chk(ActorInfo.Ins != null, "ActorInfo.Ins 설정(All_Stop 대상 0 = 무해)");
|
||
|
|
|
||
|
|
int all = table_selectskill.Ins != null ? table_selectskill.Ins.Get_DataList().Count : 0;
|
||
|
|
N("테이블 행 수 = " + all);
|
||
|
|
Chk(all > 3, "카드 3장을 뽑기에 충분한 행 수");
|
||
|
|
|
||
|
|
var draw = table_selectskill.Ins.Get_DataList(3);
|
||
|
|
Chk(draw != null && draw.Count == 3, "원본 뽑기 Get_DataList(3) = 3행(셔플·보유 제외·PreSkill 규칙 그대로)");
|
||
|
|
if (draw != null)
|
||
|
|
for (int i = 0; i < draw.Count; i++)
|
||
|
|
{
|
||
|
|
var r = draw[i] as SelectSkillTableData;
|
||
|
|
if (r != null) N(" 뽑기 예시 " + i + ": id=" + r.ID + " \"" + r.Name + "\" pet=" + r.PetSkill);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ③ 노드 조립 · 구독
|
||
|
|
static void Step3_Bind()
|
||
|
|
{
|
||
|
|
H("③ 노드 조립 — WL_ZonePickup(런타임 생성 · HideFlags.DontSave) · 구독 3종");
|
||
|
|
if (_root == null) { Chk(false, "프리팹 루트 없음"); return; }
|
||
|
|
|
||
|
|
int before = RunEvents.ZoneCleared.Count;
|
||
|
|
N("Subscribe : " + ZonePickup.Subscribe(true));
|
||
|
|
Chk(RunEvents.ZoneCleared.Count == before + 1, "ZoneCleared 구독 +1 — 실측 " + RunEvents.ZoneCleared.Count);
|
||
|
|
N("Subscribe(중복): " + ZonePickup.Subscribe(true));
|
||
|
|
Chk(RunEvents.ZoneCleared.Count == before + 1, "2회 호출해도 +1(중복 구독 0)");
|
||
|
|
|
||
|
|
var hud = WLVignetteUtil.FindUiPath(_root.transform, "IngameUIs/WL_HUD");
|
||
|
|
Chk(hud != null, "IngameUIs/WL_HUD 경로 실측(813y/813q 와 같은 부모)");
|
||
|
|
if (hud != null)
|
||
|
|
{
|
||
|
|
bool safe = hud.GetComponentInParent<SafeAreaFitter>(true) != null || hud.GetComponent<SafeAreaFitter>() != null;
|
||
|
|
Chk(safe, "WL_HUD 가 SafeAreaFitter 아래 = Safe Area 안");
|
||
|
|
}
|
||
|
|
N("Bind : " + ZonePickup.Bind(_root.transform));
|
||
|
|
Chk(ZonePickup.Bound, "WL_ZonePickup 노드 생성");
|
||
|
|
N("배치 : " + ZonePickup.ApplyLayout());
|
||
|
|
Chk(!ZonePickup.IsOpen, "조립 직후에는 닫혀 있다");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ④ 존 클리어 → 열림 · 3장
|
||
|
|
static void Step4_OpenOnZoneCleared()
|
||
|
|
{
|
||
|
|
H("④ 가짜 ZoneCleared(존 0) → 카드 3장 열림");
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
RunDirector.EnsureSubscribed();
|
||
|
|
RunDirector.StartRun("probe-813o");
|
||
|
|
N("런 시작: phase=" + RunDirector.Phase + " zoneCount=" + RunDirector.ZoneCount);
|
||
|
|
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(0, 813001, 31.5f, 12, 12, false);
|
||
|
|
Chk(ZonePickup.ZoneClearedSeen == 1, "ZoneCleared 수신 1 — 실측 " + ZonePickup.ZoneClearedSeen);
|
||
|
|
Chk(ZonePickup.Phase == ZonePickupPhase.Pending, "예약 상태 = " + ZonePickup.Phase);
|
||
|
|
Chk(ZonePickup.PendingZone == 0, "예약 존 = " + ZonePickup.PendingZone);
|
||
|
|
|
||
|
|
float t0 = ZonePickup.NowClock();
|
||
|
|
N("Tick(지연 전): " + ZonePickup.Tick(t0));
|
||
|
|
Chk(!ZonePickup.IsOpen, "openDelaySeconds 전에는 열리지 않는다");
|
||
|
|
|
||
|
|
N("Tick(지연 후): " + ZonePickup.Tick(t0 + s.openDelaySeconds + 0.01f));
|
||
|
|
Chk(ZonePickup.IsOpen, "카드 열림 — " + ZonePickup.Dump());
|
||
|
|
Chk(!ZonePickup.UsedOriginalUi, "원본 노드가 없어 런타임 패널 경로(폴백)로 열렸다");
|
||
|
|
Chk(ZonePickup.CardCount == 3, "카드 3장 — 실측 " + ZonePickup.CardCount);
|
||
|
|
for (int i = 0; i < ZonePickup.CardCount; i++)
|
||
|
|
N(" 카드 " + i + ": id=" + ZonePickup.CardId(i) + " \"" + ZonePickup.CardNameText(i) + "\"");
|
||
|
|
Chk(ZonePickup.CardId(0) != 0 && ZonePickup.CardId(1) != 0 && ZonePickup.CardId(2) != 0, "3장 전부 실제 스킬 ID");
|
||
|
|
Chk(ZonePickup.CardId(0) != ZonePickup.CardId(1) && ZonePickup.CardId(1) != ZonePickup.CardId(2), "중복 없음");
|
||
|
|
Chk(!string.IsNullOrEmpty(ZonePickup.CardNameText(0)), "카드 이름이 테이블 값으로 채워졌다");
|
||
|
|
Chk(ZonePickup.TitleText == s.titleText, "제목 = \"" + ZonePickup.TitleText + "\"");
|
||
|
|
Chk(ZonePickup.TimerText.Contains("10"), "남은 초 첫 표시 = \"" + ZonePickup.TimerText + "\"");
|
||
|
|
Chk(ZonePickup.ActorsStopped, "원본 ActorInfo.All_Stop(true) 도달 = 자동전투 정지");
|
||
|
|
Chk(RunDirector.ExternalTimerPause, "런 타이머 정지 요청 on(RunDirector.PauseTimer) — 세계 정지 없음");
|
||
|
|
Chk(Math.Abs(Time.timeScale - 1f) < 0.0001f, "timeScale 무접촉 = " + Time.timeScale.ToString("F2") + "(holdWorld off)");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑤ 타임아웃 → 첫 카드 자동 선택 · 닫힘
|
||
|
|
static void Step5_TimeoutAutoPick()
|
||
|
|
{
|
||
|
|
H("⑤ 타임아웃 → 첫 카드 자동 선택 · 닫힘 · 정지 해제");
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
if (!ZonePickup.IsOpen) { Chk(false, "④에서 열려 있어야 한다"); return; }
|
||
|
|
|
||
|
|
int firstId = ZonePickup.CardId(0);
|
||
|
|
float t0 = ZonePickup.NowClock();
|
||
|
|
|
||
|
|
// 카운트다운 표시 — 남은 초가 바뀔 때마다 1회씩만 쓴다
|
||
|
|
int w0 = ZonePickup.TimerWrites;
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 3.2f);
|
||
|
|
N("6.8s 남음 표시 = \"" + ZonePickup.TimerText + "\" (sec=" + ZonePickup.ShownSecond + ")");
|
||
|
|
Chk(ZonePickup.ShownSecond == 7, "올림 표기 6.8 → 7 — 실측 " + ZonePickup.ShownSecond);
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 8.5f);
|
||
|
|
N("1.5s 남음 표시 = \"" + ZonePickup.TimerText + "\" (경고색 구간)");
|
||
|
|
Chk(ZonePickup.ShownSecond == 2, "올림 표기 1.5 → 2 — 실측 " + ZonePickup.ShownSecond);
|
||
|
|
Chk(ZonePickup.TimerWrites > w0, "표시 쓰기 발생 " + (ZonePickup.TimerWrites - w0) + "회");
|
||
|
|
|
||
|
|
N("Tick(타임아웃): " + ZonePickup.Tick(t0 + s.openDelaySeconds + s.pickTimeoutSeconds + 0.01f));
|
||
|
|
Chk(ZonePickup.TimeoutCount == 1, "타임아웃 1회 — 실측 " + ZonePickup.TimeoutCount);
|
||
|
|
Chk(ZonePickup.PickCount == 1, "선택 1회 — 실측 " + ZonePickup.PickCount);
|
||
|
|
Chk(!ZonePickup.IsOpen, "닫힘 — " + ZonePickup.Dump());
|
||
|
|
var dic = InGameInfo.Ins != null ? InGameInfo.Ins.Get_SelectSkills() : null;
|
||
|
|
Chk(dic != null && dic.ContainsKey(firstId),
|
||
|
|
"원본 InGameInfo.Add_SelectSkill(" + firstId + ") 도달 = 첫 카드가 적용됐다(효과는 원본 로직 그대로)");
|
||
|
|
Chk(!ZonePickup.ActorsStopped, "원본 All_Stop(false) 도달 = 자동전투 복귀");
|
||
|
|
Chk(!RunDirector.ExternalTimerPause, "런 타이머 정지 해제");
|
||
|
|
Chk(ZonePickup.PicksThisRun == 1, "이번 런의 픽업 수 = " + ZonePickup.PicksThisRun);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑥ 카드 클릭 경로
|
||
|
|
static void Step6_ClickPick()
|
||
|
|
{
|
||
|
|
H("⑥ 카드 버튼 클릭 경로(자동이 아닌 사용자 선택)");
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(1, 813002, 62f, 14, 14, false);
|
||
|
|
float t0 = ZonePickup.NowClock();
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 0.01f);
|
||
|
|
Chk(ZonePickup.IsOpen, "존 1 클리어로 다시 열림");
|
||
|
|
int id1 = ZonePickup.CardId(1);
|
||
|
|
N("클릭 카드 1: id=" + id1 + " \"" + ZonePickup.CardNameText(1) + "\"");
|
||
|
|
N("ClickCard(1): " + ZonePickup.ClickCard(1));
|
||
|
|
Chk(!ZonePickup.IsOpen, "클릭 즉시 닫힘");
|
||
|
|
Chk(ZonePickup.TimeoutCount == 0, "타임아웃 아님(클릭 선택) — 실측 " + ZonePickup.TimeoutCount);
|
||
|
|
var dic = InGameInfo.Ins != null ? InGameInfo.Ins.Get_SelectSkills() : null;
|
||
|
|
Chk(dic != null && dic.ContainsKey(id1), "클릭한 카드의 효과가 원본 경로로 적용됐다");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑦ 마지막 존 제외
|
||
|
|
static void Step7_FinalZoneExcluded()
|
||
|
|
{
|
||
|
|
H("⑦ 마지막 존(존4) 제외 · SO 로 포함 전환");
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
int last = RunDirector.ZoneCount - 1;
|
||
|
|
N("존 수 = " + RunDirector.ZoneCount + " (마지막 인덱스 " + last + ")");
|
||
|
|
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(last, 813004, 150f, 8, 8, true);
|
||
|
|
Chk(ZonePickup.Phase == ZonePickupPhase.Idle, "마지막 존은 카드를 띄우지 않는다 — " + ZonePickup.LastSkipReason);
|
||
|
|
Chk(ZonePickup.SkipCount == 1, "건너뜀 1회 — 실측 " + ZonePickup.SkipCount);
|
||
|
|
|
||
|
|
bool saved = s.includeFinalZone;
|
||
|
|
s.includeFinalZone = true;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(last, 813004, 150f, 8, 8, true);
|
||
|
|
Chk(ZonePickup.Phase == ZonePickupPhase.Pending, "includeFinalZone=1 이면 마지막 존도 예약된다(코드 변경 0)");
|
||
|
|
}
|
||
|
|
finally { s.includeFinalZone = saved; }
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑧ 부활 팝업 우선
|
||
|
|
static void Step8_RevivePriority()
|
||
|
|
{
|
||
|
|
H("⑧ 813i/813z 부활 팝업과 동시 — 부활 우선(카드는 미룬다)");
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
var dlg = _root != null ? _root.GetComponentsInChildren<ReviveDialog>(true) : new ReviveDialog[0];
|
||
|
|
Chk(dlg != null && dlg.Length > 0, "프리팹에서 ReviveDialog 발견 — " + (dlg != null ? dlg.Length : 0) + "개");
|
||
|
|
if (dlg == null || dlg.Length == 0) return;
|
||
|
|
var d = dlg[0];
|
||
|
|
N("ReviveDialog.Initialize : " + d.Initialize());
|
||
|
|
N("ReviveDialog.ShowNowOnce: " + d.ShowNowOnce());
|
||
|
|
Chk(ReviveDialog.Busy, "부활 팝업 Busy = " + ReviveDialog.Busy);
|
||
|
|
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(0, 813001, 20f, 12, 12, false);
|
||
|
|
float t0 = ZonePickup.NowClock();
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 0.01f);
|
||
|
|
Chk(!ZonePickup.IsOpen, "부활 팝업이 떠 있는 동안 카드는 열리지 않는다");
|
||
|
|
Chk(ZonePickup.ReviveDeferCount > 0, "미룸 카운트 " + ZonePickup.ReviveDeferCount);
|
||
|
|
Chk(Math.Abs(Time.timeScale - 1f) < 0.0001f || ReviveDialog.Busy, "픽업이 timeScale 을 건드리지 않았다");
|
||
|
|
|
||
|
|
d.HideNow();
|
||
|
|
Chk(!ReviveDialog.Busy, "부활 팝업 닫힘 — Busy=" + ReviveDialog.Busy);
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 0.02f);
|
||
|
|
Chk(ZonePickup.IsOpen, "부활이 닫힌 뒤 카드가 뜬다");
|
||
|
|
ZonePickup.Close("프로브 정리");
|
||
|
|
Chk(!ZonePickup.IsOpen && !RunDirector.ExternalTimerPause, "정리 후 닫힘 · 타이머 정지 해제");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑨ 813w 가이드 이동 중 대기
|
||
|
|
static void Step9_GuideWait()
|
||
|
|
{
|
||
|
|
H("⑨ 813w RunGuide 이동 중이면 도착 뒤에 연다");
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
N("RunGuide.Active=" + RunGuide.Active + " State=" + RunGuide.State);
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
|
||
|
|
if (!RunGuide.Active)
|
||
|
|
{
|
||
|
|
N("가이드 축이 꺼져 있어 Travel 상태를 만들 수 없다 — 대기 분기는 「미확인」(인게임 QA).");
|
||
|
|
Chk(true, "가이드 off 면 대기 없이 바로 연다(발주서 §1-1 의 폴백 경로)");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
RunGuide.BeginZone(1, "probe-813o");
|
||
|
|
N("BeginZone(1) 후 RunGuide.State=" + RunGuide.State + " (" + RunGuide.LastLog + ")");
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(0, 813001, 20f, 12, 12, false);
|
||
|
|
float t0 = ZonePickup.NowClock();
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 0.01f);
|
||
|
|
if (RunGuide.State == RunGuideState.Travel)
|
||
|
|
{
|
||
|
|
Chk(!ZonePickup.IsOpen, "이동 중에는 열리지 않는다");
|
||
|
|
Chk(ZonePickup.GuideWaitCount > 0, "가이드 대기 " + ZonePickup.GuideWaitCount + "회");
|
||
|
|
RunGuide.StopGuide("probe-813o");
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 0.02f);
|
||
|
|
Chk(ZonePickup.IsOpen, "도착(가이드 종료) 뒤 열린다");
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
N("Travel 상태가 만들어지지 않았다(에디트 모드엔 PC·NavMesh 가 없다) — 대기 분기는 「미확인」.");
|
||
|
|
Chk(true, "대기 상한 " + s.maxGuideDelaySeconds + "s 를 넘으면 이동 중이어도 연다(코드 경로 존재)");
|
||
|
|
}
|
||
|
|
ZonePickup.Close("프로브 정리");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑩ 런 타이머 정지 배선
|
||
|
|
static void Step10_TimerPauseWiring()
|
||
|
|
{
|
||
|
|
H("⑩ 런 타이머 정지(813p RunDirector.PauseTimer · 세계 정지 0)");
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
Chk(!RunDirector.ExternalTimerPause, "시작 상태 = 정지 아님");
|
||
|
|
|
||
|
|
RunDirector.PauseTimer(true);
|
||
|
|
Chk(RunDirector.ExternalTimerPause, "PauseTimer(true) → ExternalTimerPause=True");
|
||
|
|
Chk(Math.Abs(Time.timeScale - 1f) < 0.0001f, "timeScale 무접촉 = " + Time.timeScale.ToString("F2"));
|
||
|
|
RunDirector.ForceTick();
|
||
|
|
Chk(RunDirector.ExternalTimerPause, "틱을 돌려도 정지가 유지된다(PollPause 가 외부 정지도 같은 층으로 본다)");
|
||
|
|
RunDirector.PauseTimer(false);
|
||
|
|
Chk(!RunDirector.ExternalTimerPause, "PauseTimer(false) → 해제");
|
||
|
|
|
||
|
|
// 픽업이 열렸다 닫히면 반드시 짝이 맞는다(정지가 새지 않는다)
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(0, 813001, 10f, 12, 12, false);
|
||
|
|
float t0 = ZonePickup.NowClock();
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 0.01f);
|
||
|
|
bool onWhileOpen = RunDirector.ExternalTimerPause;
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + s.pickTimeoutSeconds + 0.01f);
|
||
|
|
Chk(onWhileOpen && !RunDirector.ExternalTimerPause, "열림=정지 · 닫힘=해제 (짝 맞음 · PauseCalls=" + ZonePickup.PauseCalls + ")");
|
||
|
|
|
||
|
|
// 🔴 「멈춘 초」 실측 — batchmode 는 Time.unscaledTime 이 전혀 전진하지 않는다(호출 사이에도 11.35 고정).
|
||
|
|
// 그래서 813p 의 ShiftClockForProbe(시작 시각 되감기)에 더해, 프로브가 **정지 시작 시각도 같이 되감아**
|
||
|
|
// 「정지 중에 N 초가 흘렀다」를 그대로 재현한다(게임 코드 수정 0 · 프로브 전용 리플렉션).
|
||
|
|
RunDirector.ResetAll();
|
||
|
|
RunDirector.StartRun("probe-813o-pause");
|
||
|
|
RunDirector.ShiftClockForProbe(10f);
|
||
|
|
float e0 = RunDirector.Elapsed;
|
||
|
|
N("정지 전 경과 = " + e0.ToString("F2") + "s");
|
||
|
|
RunDirector.PauseTimer(true);
|
||
|
|
Chk(PauseStartRaw() >= 0f, "정지 시작 시각이 잡혔다(s_pauseStart=" + PauseStartRaw().ToString("F2") + ")");
|
||
|
|
|
||
|
|
RunDirector.ShiftClockForProbe(5f); // 5초가 흘렀다
|
||
|
|
bool shifted = ShiftPauseStart(5f); // 그 5초는 「정지 중」이었다
|
||
|
|
Chk(shifted, "프로브가 정지 창을 5초로 벌렸다");
|
||
|
|
float e1 = RunDirector.Elapsed;
|
||
|
|
N("정지 중 5초 경과 후 = " + e1.ToString("F2") + "s (정지가 들으면 그대로 " + e0.ToString("F2") + ")");
|
||
|
|
Chk(Math.Abs(e1 - e0) < 0.01f, "🔴 정지 중 5초가 흘러도 런 경과 불변 = " + e1.ToString("F2") + "s");
|
||
|
|
|
||
|
|
RunDirector.PauseTimer(false);
|
||
|
|
float total = PausedTotalRaw();
|
||
|
|
Chk(Math.Abs(total - 5f) < 0.01f, "정지 누적 = " + total.ToString("F2") + "s 로 집계됐다");
|
||
|
|
RunDirector.ShiftClockForProbe(5f);
|
||
|
|
float e2 = RunDirector.Elapsed;
|
||
|
|
Chk(Math.Abs(e2 - (e0 + 5f)) < 0.01f, "해제 뒤 5초는 정상적으로 흐른다 = " + e2.ToString("F2") + "s (대조군)");
|
||
|
|
RunDirector.EndRun(RunOutcome.Abandon);
|
||
|
|
RunDirector.ResetAll();
|
||
|
|
RunDirector.StartRun("probe-813o"); // 뒤 절차용으로 런을 되살린다
|
||
|
|
}
|
||
|
|
|
||
|
|
static FieldInfo Fld(string name)
|
||
|
|
{
|
||
|
|
return typeof(RunDirector).GetField(name, BindingFlags.NonPublic | BindingFlags.Static);
|
||
|
|
}
|
||
|
|
static float PauseStartRaw() { var f = Fld("s_pauseStart"); return f == null ? -2f : (float)f.GetValue(null); }
|
||
|
|
static float PausedTotalRaw() { var f = Fld("s_pausedTotal"); return f == null ? -2f : (float)f.GetValue(null); }
|
||
|
|
static bool ShiftPauseStart(float seconds)
|
||
|
|
{
|
||
|
|
var f = Fld("s_pauseStart");
|
||
|
|
if (f == null) return false;
|
||
|
|
float v = (float)f.GetValue(null);
|
||
|
|
if (v < 0f) return false;
|
||
|
|
f.SetValue(null, v - seconds);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑪ GC
|
||
|
|
static void Step11_Gc()
|
||
|
|
{
|
||
|
|
H("⑪ GC — 같은 초 100틱 = 표시 쓰기 0회");
|
||
|
|
var s = WLZonePickupSettings.Instance;
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(0, 813001, 10f, 12, 12, false);
|
||
|
|
float t0 = ZonePickup.NowClock();
|
||
|
|
ZonePickup.Tick(t0 + s.openDelaySeconds + 0.01f);
|
||
|
|
Chk(ZonePickup.IsOpen, "GC 측정용으로 열림");
|
||
|
|
|
||
|
|
float t = t0 + s.openDelaySeconds + 2.51f; // 남은 7.5s → 올림 8 · 0.1s 동안 같은 초
|
||
|
|
ZonePickup.Tick(t); // 첫 쓰기
|
||
|
|
int w0 = ZonePickup.TimerWrites;
|
||
|
|
long m0 = GC.GetTotalMemory(false);
|
||
|
|
int gc0 = GC.CollectionCount(0);
|
||
|
|
for (int i = 0; i < 100; i++) ZonePickup.Tick(t + i * 0.001f); // 같은 초
|
||
|
|
long m1 = GC.GetTotalMemory(false);
|
||
|
|
int gc1 = GC.CollectionCount(0);
|
||
|
|
N("같은 초 100틱: 표시 쓰기 " + (ZonePickup.TimerWrites - w0) + "회 · ΔGetTotalMemory=" + (m1 - m0) +
|
||
|
|
" B · gc0 " + gc0 + "→" + gc1);
|
||
|
|
Chk(ZonePickup.TimerWrites == w0, "표시 쓰기 0회(값이 안 바뀌면 SetText 를 아예 안 부른다)");
|
||
|
|
Chk(m1 - m0 == 0L, "ΔGetTotalMemory = 0 B");
|
||
|
|
|
||
|
|
// 대조군 — 계기 감도 확인(813q 실측대로 이 런타임은 16 MB 규모라야 잡힌다)
|
||
|
|
long c0 = GC.GetTotalMemory(false);
|
||
|
|
var junk = new byte[16 * 1024 * 1024];
|
||
|
|
junk[0] = 1;
|
||
|
|
long c1 = GC.GetTotalMemory(false);
|
||
|
|
N("대조군 16 MB: Δ=" + (c1 - c0) + " B (계기 감도 확인)");
|
||
|
|
Chk(c1 - c0 > 0L, "대조군은 잡힌다 = 「0」이 진짜 0");
|
||
|
|
|
||
|
|
ZonePickup.Close("GC 측정 종료");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑫ C8 롤백
|
||
|
|
static void Step12_C8()
|
||
|
|
{
|
||
|
|
H("⑫ C8 — enabled_ = 0 이면 아무것도 하지 않는다");
|
||
|
|
ZonePickup.ResetState();
|
||
|
|
WLZonePickupSettings.RuntimeDisabled = true;
|
||
|
|
Chk(!WLZonePickupSettings.Enabled, "Enabled=False");
|
||
|
|
N("Tick: " + ZonePickup.Tick(ZonePickup.NowClock()));
|
||
|
|
WLZonePickupProbeHooks.RaiseZoneCleared(0, 813001, 10f, 12, 12, false);
|
||
|
|
Chk(ZonePickup.Phase == ZonePickupPhase.Idle, "ZoneCleared 를 받아도 예약 0 — " + ZonePickup.LastSkipReason);
|
||
|
|
Chk(!ZonePickup.IsOpen, "표시 0");
|
||
|
|
Chk(!RunDirector.ExternalTimerPause, "런 타이머 정지 0");
|
||
|
|
N("Bind(off): " + ZonePickup.Bind(_root != null ? _root.transform : null));
|
||
|
|
|
||
|
|
// 813p 축은 그대로 살아 있어야 한다
|
||
|
|
Chk(WLRunSettings.Enabled, "813p WLRunSettings.Enabled 불변 = " + WLRunSettings.Enabled);
|
||
|
|
WLZonePickupSettings.RuntimeDisabled = false;
|
||
|
|
Chk(WLZonePickupSettings.Enabled, "복구 후 Enabled=True");
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─────────────── ⑬ 정리
|
||
|
|
static void Step13_Cleanup()
|
||
|
|
{
|
||
|
|
H("⑬ 정리 — 구독 해제 · 노드 파괴 · 프리팹 저장 0");
|
||
|
|
try
|
||
|
|
{
|
||
|
|
N("Teardown : " + ZonePickup.Teardown());
|
||
|
|
Chk(RunEvents.ZoneCleared.Count == 0, "ZoneCleared 구독자 0 — 실측 " + RunEvents.ZoneCleared.Count);
|
||
|
|
RunDirector.EndRun(RunOutcome.Abandon);
|
||
|
|
RunDirector.ResetAll();
|
||
|
|
RunDirector.Unsubscribe();
|
||
|
|
RunDirector.PauseTimer(false);
|
||
|
|
Chk(!RunDirector.ExternalTimerPause, "타이머 정지 해제 확인");
|
||
|
|
|
||
|
|
table_selectskill.Ins = null;
|
||
|
|
InGameInfo.Ins = null;
|
||
|
|
ActorInfo.Ins = null;
|
||
|
|
if (_fakeInfos != null) UnityEngine.Object.DestroyImmediate(_fakeInfos);
|
||
|
|
if (_root != null) PrefabUtility.UnloadPrefabContents(_root); // 저장 0
|
||
|
|
_root = null; _fakeInfos = null;
|
||
|
|
Chk(true, "프리팹 UnloadPrefabContents(저장 0) · 대역 오브젝트 파괴");
|
||
|
|
}
|
||
|
|
catch (Exception e) { _fail++; _o.AppendLine(" [FAIL] 정리 예외 " + e.Message); }
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|