701 lines
38 KiB
C#
701 lines
38 KiB
C#
|
|
// WL-813q2 프로브 — 결과 화면 핫픽스 실측(에디트 모드 · Play 0 · #813).
|
|||
|
|
// run_script --file AgentScripts/WL813q2_Probe.cs --entry WL813q2_Probe.<Entry>
|
|||
|
|
// Overlap · Watchdog · Reset · Rows · Gc · All
|
|||
|
|
// 로그는 <worktree>/Logs/WL813q2_*.txt (Assets 밖 · 미추적).
|
|||
|
|
//
|
|||
|
|
// 🔴 에디트 모드에서 `Time.timeScale` 에 쓰지 않는다(ProjectSettings/TimeManager.asset 오염 · 811b-fix).
|
|||
|
|
// DeathFlow.HoldWorld 도 `Application.isPlaying` 가드가 있어 에디트 모드에선 대입하지 않는다.
|
|||
|
|
// 🔴 씬·프리팹을 저장하지 않는다. 만든 노드는 finally 에서 전부 DestroyImmediate.
|
|||
|
|
// 🔴 SO 값은 메모리에서만 바꾸고 finally 에서 원복한다(AssetDatabase.SaveAssets 호출 0).
|
|||
|
|
// 🔴 Gameplay 는 읽기·구독·공개 API 호출만 한다(파일 수정 0). `RunDirector.s_phase` 는 리플렉션 **읽기/쓰기**로
|
|||
|
|
// "런 중/런 밖"만 흉내 낸다(런 시작·종료 부작용을 피하려고 StartRun/EndRun 을 쓰지 않는다).
|
|||
|
|
|
|||
|
|
using System;
|
|||
|
|
using System.IO;
|
|||
|
|
using System.Reflection;
|
|||
|
|
using System.Text;
|
|||
|
|
using TMPro;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.UI;
|
|||
|
|
using UnityEditor;
|
|||
|
|
using WL.Combat.Core;
|
|||
|
|
using WL.Combat.Run;
|
|||
|
|
using WL.Combat.Survival;
|
|||
|
|
using WL.UI;
|
|||
|
|
|
|||
|
|
public static class WL813q2_Probe
|
|||
|
|
{
|
|||
|
|
const BindingFlags NP = BindingFlags.Instance | BindingFlags.NonPublic;
|
|||
|
|
const BindingFlags SP = BindingFlags.Static | BindingFlags.NonPublic;
|
|||
|
|
|
|||
|
|
static int s_pass, s_fail;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────── 공통
|
|||
|
|
|
|||
|
|
static string LogDir
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
string d = Path.Combine(Directory.GetParent(Application.dataPath).FullName, "Logs");
|
|||
|
|
if (!Directory.Exists(d)) Directory.CreateDirectory(d);
|
|||
|
|
return d;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string Write(string name, string body)
|
|||
|
|
{
|
|||
|
|
string p = Path.Combine(LogDir, name);
|
|||
|
|
File.WriteAllText(p, body, new UTF8Encoding(false));
|
|||
|
|
return p;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static string Chk(StringBuilder sb, bool ok, string what)
|
|||
|
|
{
|
|||
|
|
if (ok) s_pass++; else s_fail++;
|
|||
|
|
sb.AppendLine((ok ? " PASS " : " **FAIL** ") + what);
|
|||
|
|
return what;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>세로 1080×1920 기준 합성 캔버스 + `IngameUIs/WL_HUD` 대역(SO 경로와 같게).</summary>
|
|||
|
|
static Transform BuildUiRoot(out GameObject root)
|
|||
|
|
{
|
|||
|
|
root = new GameObject("__WL813q2_Canvas", typeof(RectTransform));
|
|||
|
|
root.hideFlags = HideFlags.DontSave;
|
|||
|
|
var canvas = root.AddComponent<Canvas>();
|
|||
|
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|||
|
|
var scaler = root.AddComponent<CanvasScaler>();
|
|||
|
|
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|||
|
|
scaler.referenceResolution = new Vector2(1920f, 1080f);
|
|||
|
|
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
|
|||
|
|
root.AddComponent<GraphicRaycaster>();
|
|||
|
|
|
|||
|
|
var ing = new GameObject("IngameUIs", typeof(RectTransform)).GetComponent<RectTransform>();
|
|||
|
|
ing.gameObject.hideFlags = HideFlags.DontSave;
|
|||
|
|
ing.SetParent(root.transform, false);
|
|||
|
|
var hud = new GameObject("WL_HUD", typeof(RectTransform)).GetComponent<RectTransform>();
|
|||
|
|
hud.gameObject.hideFlags = HideFlags.DontSave;
|
|||
|
|
hud.SetParent(ing, false);
|
|||
|
|
hud.anchorMin = hud.anchorMax = new Vector2(0.5f, 0.5f);
|
|||
|
|
hud.pivot = new Vector2(0.5f, 0.5f);
|
|||
|
|
hud.sizeDelta = new Vector2(1080f, 1920f);
|
|||
|
|
// 결과 패널이 형제 맨 뒤/맨 앞으로 움직이는 것을 볼 수 있게 미끼 형제 2개
|
|||
|
|
for (int i = 0; i < 2; i++)
|
|||
|
|
{
|
|||
|
|
var d = new GameObject("Decoy" + i, typeof(RectTransform)).GetComponent<RectTransform>();
|
|||
|
|
d.gameObject.hideFlags = HideFlags.DontSave;
|
|||
|
|
d.SetParent(hud, false);
|
|||
|
|
}
|
|||
|
|
return root.transform;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>813z 프로브와 같은 가짜 메인 PC(사망 상태).</summary>
|
|||
|
|
static PCActor MakePC()
|
|||
|
|
{
|
|||
|
|
var go = new GameObject("__WL813q2_PC");
|
|||
|
|
go.hideFlags = HideFlags.DontSave;
|
|||
|
|
var pc = go.AddComponent<PCActor>();
|
|||
|
|
pc.m_Role = eRole.PC; pc.m_SubRole = eSubRol.None;
|
|||
|
|
var stat = new ActorStatInfo(eRole.PC);
|
|||
|
|
stat.Set_Stat(eStat.MaxHP, 1000d); stat.Set_Stat(eStat.HP, 0d);
|
|||
|
|
typeof(Actor).GetField("m_Stat", NP).SetValue(pc, stat);
|
|||
|
|
typeof(Actor).GetField("m_Enemy", NP).SetValue(pc, false);
|
|||
|
|
typeof(Actor).GetField("DeadStatus", NP).SetValue(pc, true);
|
|||
|
|
return pc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void SetPhase(RunPhase p)
|
|||
|
|
{
|
|||
|
|
typeof(RunDirector).GetField("s_phase", SP).SetValue(null, p);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static RunPhase GetPhase()
|
|||
|
|
{
|
|||
|
|
return (RunPhase)typeof(RunDirector).GetField("s_phase", SP).GetValue(null);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void OnReq(in ReviveRequestEvent e) { s_reqSeen++; }
|
|||
|
|
static int s_reqSeen;
|
|||
|
|
|
|||
|
|
static void Kill(GameObject go) { if (go != null) UnityEngine.Object.DestroyImmediate(go); }
|
|||
|
|
|
|||
|
|
static RunResult FakeResult(RunOutcome outcome, float total, float bossClear, int kills, int bossKills)
|
|||
|
|
{
|
|||
|
|
return new RunResult
|
|||
|
|
{
|
|||
|
|
outcome = outcome, runIndex = 1, totalSec = total, runSeconds = 240f,
|
|||
|
|
zoneClearSec = new float[] { 40f, -1f }, zoneKills = new int[] { 24, 11 },
|
|||
|
|
zoneCount = 2, zonesCleared = 1,
|
|||
|
|
bossGateSec = -1f, bossStartSec = -1f, bossClearSec = bossClear,
|
|||
|
|
kills = kills, bossKills = bossKills, maxChain = 7,
|
|||
|
|
maxDamage = 1234.4, maxDamageBurst = 8888.5,
|
|||
|
|
levelFrom = 11, levelTo = 12,
|
|||
|
|
lootByGrade = new int[] { 0, 5, 2, 0, 0, 0, 0, 0, 0, 0 }, lootTotal = 7, gold = 777,
|
|||
|
|
deaths = 1, potionsUsed = 2, skillCasts = 9,
|
|||
|
|
time = Time.unscaledTime, frame = Time.frameCount,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ────────────────────────────────────────── ① D-18 결과 위 부활 겹침
|
|||
|
|
|
|||
|
|
public static string Overlap()
|
|||
|
|
{
|
|||
|
|
s_pass = s_fail = 0;
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("=== WL-813q2 ① D-18 결과 화면 위 부활 팝업 겹침 (에디트 모드 · Play 0) ===");
|
|||
|
|
|
|||
|
|
var ui = WLRunUiSettings.Instance;
|
|||
|
|
var sv = WLSurvivalSettings.Instance;
|
|||
|
|
if (ui == null || sv == null) { string b = sb.AppendLine("**FAIL** 설정 에셋 없음").ToString(); return b + "\n→ " + Write("WL813q2_overlap.txt", b); }
|
|||
|
|
|
|||
|
|
GameObject root = null; PCActor pc = null;
|
|||
|
|
var phase0 = GetPhase();
|
|||
|
|
int ov0 = WLSurvivalSettings.RuntimeOverride;
|
|||
|
|
var ovr0 = DeathFlow.ReviveOverride;
|
|||
|
|
bool skip0 = ui.skipRevivePopupAfterRunEnd, dying0 = ui.resultDeferWhileDying, lower0 = ui.resultLowerWhileRevive;
|
|||
|
|
|
|||
|
|
sb.AppendLine("근거: Q5 캡처 PD2_07 — 제목 \"시간 종료\"(= 결과가 **먼저** 떴다) 위에 부활 팝업이 겹쳐 4행이 가려졌다.");
|
|||
|
|
sb.AppendLine("실측 전제: 813z 부활 팝업 sortingOrder = " + sv.reviveSortingOrder +
|
|||
|
|
" · 결과 화면 SO sortingOrder = " + ui.resultSortingOrder + "(부활 중 " + ui.resultSortingOrderWhileRevive + ")");
|
|||
|
|
sb.AppendLine();
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
WLSurvivalSettings.RuntimeOverride = 1;
|
|||
|
|
DeathFlow.ReviveOverride = (p, r) => { };
|
|||
|
|
DeathFlow.ReviveRequested.Add(OnReq);
|
|||
|
|
s_reqSeen = 0;
|
|||
|
|
|
|||
|
|
var uiRoot = BuildUiRoot(out root);
|
|||
|
|
sb.AppendLine("Bind: " + RunResultUI.Bind(uiRoot));
|
|||
|
|
RunResultUI.Subscribe(true);
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
DeathFlow.ResetDiagnostics();
|
|||
|
|
pc = MakePC();
|
|||
|
|
|
|||
|
|
Chk(sb, RunResultUI.Bound, "결과 화면 노드 조립됨");
|
|||
|
|
Chk(sb, RunResultUI.SortingOrder == ui.resultSortingOrder,
|
|||
|
|
"전용 Canvas sortingOrder = " + RunResultUI.SortingOrder + " (SO " + ui.resultSortingOrder +
|
|||
|
|
") < 부활 " + sv.reviveSortingOrder);
|
|||
|
|
|
|||
|
|
// ── A. 캡처와 같은 순서: RunEnded(결과 표시) → 그 뒤 사망 ────────
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("A. 캡처 순서 재현 — RunEnded → 결과 표시 → **그 뒤** 사망(런 밖)");
|
|||
|
|
SetPhase(RunPhase.Zones);
|
|||
|
|
var r = FakeResult(RunOutcome.Timeout, 241f, -1f, 29, 0);
|
|||
|
|
SetPhase(RunPhase.Ended); // EndRun 과 같은 상태(IsRunning=false)
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
Chk(sb, RunResultUI.IsOpen, "RunEnded 로 결과 화면이 떴다(open=" + RunResultUI.IsOpen + " 제목 \"" + RunResultUI.TitleText + "\")");
|
|||
|
|
Chk(sb, !RunDirector.IsRunning, "런 밖 상태(IsRunning=" + RunDirector.IsRunning + ")");
|
|||
|
|
|
|||
|
|
float t = 1000f;
|
|||
|
|
DeathFlow.Tick(t);
|
|||
|
|
DeathFlow.OnPCDied(pc); // ← 결과가 떠 있는 상태에서 사망
|
|||
|
|
sb.AppendLine(" 사망 직후: DeathFlow=" + DeathFlow.State + " · 즉시부활 호출 " + RunResultUI.SkipPopupReviveCount +
|
|||
|
|
"회 · " + RunResultUI.LastDeathLog);
|
|||
|
|
Chk(sb, RunResultUI.SkipPopupReviveCount >= 1, "런 밖 사망 → 팝업 생략 즉시 부활을 불렀다(skipRevivePopupAfterRunEnd=1)");
|
|||
|
|
|
|||
|
|
int reqBefore = s_reqSeen;
|
|||
|
|
DeathFlow.Tick(t + sv.deathDisplaySeconds + 0.01f); // 연출 끝 → acceptEarly 로 바로 부활
|
|||
|
|
sb.AppendLine(" 연출 끝: DeathFlow=" + DeathFlow.State + " · ReviveRequested " + (s_reqSeen - reqBefore) +
|
|||
|
|
"회(팝업이 1프레임 안에 닫힌다) · Revived=" + RunResultUI.RevivedSeen);
|
|||
|
|
Chk(sb, DeathFlow.State == DeathState.None, "부활 완료 — DeathFlow.State=None(부활 대기 0 = 팝업이 남지 않는다)");
|
|||
|
|
Chk(sb, DeathFlow.ReviveCount >= 1, "DeathFlow.ReviveCount=" + DeathFlow.ReviveCount);
|
|||
|
|
Chk(sb, RunResultUI.IsOpen && !RunResultUI.Lowered,
|
|||
|
|
"결과 화면은 그대로 · 재정렬 없음(open=" + RunResultUI.IsOpen + " lowered=" + RunResultUI.Lowered + ")");
|
|||
|
|
|
|||
|
|
// ── B. 스위치를 끄면 813q 동작(팝업이 뜬다) → 재정렬로 막는다 ──
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("B. skipRevivePopupAfterRunEnd=0 (C8 롤백) — 팝업이 뜨는 경우 결과를 부활 **뒤로 재정렬**");
|
|||
|
|
ui.skipRevivePopupAfterRunEnd = false;
|
|||
|
|
DeathFlow.ResetDiagnostics();
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
SetPhase(RunPhase.Ended);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
Chk(sb, RunResultUI.IsOpen, "결과 화면 재표시(open=" + RunResultUI.IsOpen + ")");
|
|||
|
|
int sib0 = RunResultUI.SiblingIndex;
|
|||
|
|
|
|||
|
|
t = 2000f; DeathFlow.Tick(t); DeathFlow.OnPCDied(pc);
|
|||
|
|
sb.AppendLine(" 사망 후: " + RunResultUI.LastDeathLog);
|
|||
|
|
Chk(sb, RunResultUI.SkipPopupReviveCount == 0, "즉시 부활 호출 0(스위치 off = 813q 동작)");
|
|||
|
|
Chk(sb, RunResultUI.Lowered && RunResultUI.SortingOrder == ui.resultSortingOrderWhileRevive,
|
|||
|
|
"결과를 부활 뒤로 재정렬 sortingOrder " + ui.resultSortingOrder + " → " + RunResultUI.SortingOrder +
|
|||
|
|
" (부활 " + sv.reviveSortingOrder + " 아래)");
|
|||
|
|
Chk(sb, RunResultUI.SiblingIndex == 0 && sib0 != 0,
|
|||
|
|
"형제 순서 " + sib0 + " → " + RunResultUI.SiblingIndex + " (SetAsFirstSibling)");
|
|||
|
|
|
|||
|
|
DeathFlow.Tick(t + sv.deathDisplaySeconds + 0.01f); // AwaitingRevive
|
|||
|
|
Chk(sb, DeathFlow.State == DeathState.AwaitingRevive, "부활 대기 상태(DeathFlow=" + DeathFlow.State + ")");
|
|||
|
|
Chk(sb, RunResultUI.ReviveBusy, "「부활 우선」 판정 ON — 이유 = " + RunResultUI.ReviveBusyReason);
|
|||
|
|
|
|||
|
|
DeathFlow.Revive();
|
|||
|
|
Chk(sb, !RunResultUI.Lowered && RunResultUI.SortingOrder == ui.resultSortingOrder,
|
|||
|
|
"부활 완료 → 결과 화면 복귀 sortingOrder=" + RunResultUI.SortingOrder + " 형제=" + RunResultUI.SiblingIndex);
|
|||
|
|
|
|||
|
|
// ── C. 813q 원래 판정(ReviveDialog.Busy)만으로는 못 잡는다는 증명 ──
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("C. 왜 813q 판정이 못 잡았나 — RunEnded 시점에는 아무도 죽지 않아 Busy=false 였다");
|
|||
|
|
DeathFlow.ResetDiagnostics();
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
SetPhase(RunPhase.Ended);
|
|||
|
|
Chk(sb, !ReviveDialog.Busy && DeathFlow.State == DeathState.None,
|
|||
|
|
"RunEnded 직전 실측: ReviveDialog.Busy=" + ReviveDialog.Busy + " · DeathFlow=" + DeathFlow.State +
|
|||
|
|
" → 813q 의 미룸 조건은 어느 쪽으로도 걸리지 않는다");
|
|||
|
|
ui.skipRevivePopupAfterRunEnd = true;
|
|||
|
|
|
|||
|
|
// ── D. 반대 순서(사망 먼저 → RunEnded)는 813q2 에서도 미뤄진다 ──
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("D. 반대 순서 — 사망(런 중) → RunEnded → 부활 뒤에 결과가 뜬다");
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
DeathFlow.ResetDiagnostics();
|
|||
|
|
SetPhase(RunPhase.Zones);
|
|||
|
|
t = 3000f; DeathFlow.Tick(t); DeathFlow.OnPCDied(pc);
|
|||
|
|
Chk(sb, DeathFlow.State == DeathState.Dying, "사망 연출 중(DeathFlow=" + DeathFlow.State + ")");
|
|||
|
|
Chk(sb, RunResultUI.ReviveBusy, "「부활 우선」 ON — " + RunResultUI.ReviveBusyReason);
|
|||
|
|
SetPhase(RunPhase.Ended);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
Chk(sb, !RunResultUI.IsOpen && RunResultUI.Pending,
|
|||
|
|
"결과 화면 미룸(open=" + RunResultUI.IsOpen + " pending=" + RunResultUI.Pending +
|
|||
|
|
" defer=" + RunResultUI.DeferredCount + ")");
|
|||
|
|
DeathFlow.Tick(t + sv.deathDisplaySeconds + 0.01f);
|
|||
|
|
DeathFlow.Revive();
|
|||
|
|
RunResultUI.Tick(Time.unscaledTime);
|
|||
|
|
Chk(sb, RunResultUI.IsOpen && !RunResultUI.Pending,
|
|||
|
|
"부활 뒤 결과 화면 표시(open=" + RunResultUI.IsOpen + " shows=" + RunResultUI.ShownCount + ")");
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine(RunResultUI.Dump());
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) { s_fail++; sb.AppendLine("**FAIL** 예외 " + ex.GetType().Name + " — " + ex.Message + "\n" + ex.StackTrace); }
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
ui.skipRevivePopupAfterRunEnd = skip0; ui.resultDeferWhileDying = dying0; ui.resultLowerWhileRevive = lower0;
|
|||
|
|
DeathFlow.ReviveRequested.Remove(OnReq);
|
|||
|
|
DeathFlow.ReviveOverride = ovr0;
|
|||
|
|
DeathFlow.Abort("probe cleanup");
|
|||
|
|
WLSurvivalSettings.RuntimeOverride = ov0;
|
|||
|
|
RunResultUI.Teardown();
|
|||
|
|
Kill(pc != null ? pc.gameObject : null);
|
|||
|
|
Kill(root);
|
|||
|
|
SetPhase(phase0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("① 결과 = " + s_pass + " PASS / " + s_fail + " FAIL");
|
|||
|
|
string body = sb.ToString();
|
|||
|
|
return body + "\n→ " + Write("WL813q2_overlap.txt", body);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ────────────────────────────────────── ② 워치독 자동 부활 경로(813z)
|
|||
|
|
|
|||
|
|
public static string Watchdog()
|
|||
|
|
{
|
|||
|
|
s_pass = s_fail = 0;
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("=== WL-813q2 ② 워치독 자동 부활 경로에서도 「부활 우선」이 걸리는가 (813z §1) ===");
|
|||
|
|
|
|||
|
|
var ui = WLRunUiSettings.Instance;
|
|||
|
|
var sv = WLSurvivalSettings.Instance;
|
|||
|
|
if (ui == null || sv == null) { string b = sb.AppendLine("**FAIL** 설정 에셋 없음").ToString(); return b + "\n→ " + Write("WL813q2_watchdog.txt", b); }
|
|||
|
|
|
|||
|
|
GameObject root = null; PCActor pc = null;
|
|||
|
|
var phase0 = GetPhase();
|
|||
|
|
int ov0 = WLSurvivalSettings.RuntimeOverride;
|
|||
|
|
var ovr0 = DeathFlow.ReviveOverride;
|
|||
|
|
bool dying0 = ui.resultDeferWhileDying;
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
WLSurvivalSettings.RuntimeOverride = 1;
|
|||
|
|
DeathFlow.ReviveOverride = (p, r) => { };
|
|||
|
|
DeathFlow.ReviveRequested.Add(OnReq);
|
|||
|
|
s_reqSeen = 0;
|
|||
|
|
|
|||
|
|
var uiRoot = BuildUiRoot(out root);
|
|||
|
|
RunResultUI.Bind(uiRoot);
|
|||
|
|
RunResultUI.Subscribe(true);
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
DeathFlow.ResetDiagnostics();
|
|||
|
|
pc = MakePC();
|
|||
|
|
|
|||
|
|
sb.AppendLine("워치독 SO 실측: reviveWatchdogSeconds=" + sv.reviveWatchdogSeconds.ToString("F1") + "s");
|
|||
|
|
SetPhase(RunPhase.Zones);
|
|||
|
|
float t = 5000f;
|
|||
|
|
DeathFlow.Tick(t); DeathFlow.OnPCDied(pc);
|
|||
|
|
float req = t + sv.deathDisplaySeconds + 0.01f;
|
|||
|
|
DeathFlow.Tick(req);
|
|||
|
|
Chk(sb, DeathFlow.State == DeathState.AwaitingRevive, "부활 대기(DeathFlow=" + DeathFlow.State + ")");
|
|||
|
|
|
|||
|
|
// 런 종료가 부활 대기 중에 온다 → 결과는 미뤄진다(813q 는 Busy 로만 봤다)
|
|||
|
|
SetPhase(RunPhase.Ended);
|
|||
|
|
var r = FakeResult(RunOutcome.Timeout, 240f, -1f, 31, 0);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
Chk(sb, !RunResultUI.IsOpen && RunResultUI.Pending,
|
|||
|
|
"결과 미룸 — 이유 = " + RunResultUI.ReviveBusyReason + " (ReviveDialog.Busy=" + ReviveDialog.Busy +
|
|||
|
|
" 이라 813q 판정만으로는 못 막는다)");
|
|||
|
|
|
|||
|
|
// 워치독 시각 직전/직후
|
|||
|
|
DeathFlow.Tick(req + sv.reviveWatchdogSeconds - 0.1f);
|
|||
|
|
RunResultUI.Tick(Time.unscaledTime);
|
|||
|
|
Chk(sb, !RunResultUI.IsOpen, "워치독 직전까지 계속 미룸(defer=" + RunResultUI.DeferredCount + ")");
|
|||
|
|
|
|||
|
|
DeathFlow.Tick(req + sv.reviveWatchdogSeconds + 0.02f);
|
|||
|
|
sb.AppendLine(" 워치독: WatchdogCount=" + DeathFlow.WatchdogCount + " 대기 " + DeathFlow.LastWatchdogSeconds.ToString("F2") +
|
|||
|
|
"s · auto 부활=" + DeathFlow.AutoReviveCount + " · RunResultUI.Revived=" + RunResultUI.RevivedSeen);
|
|||
|
|
Chk(sb, DeathFlow.WatchdogCount == 1 && DeathFlow.State == DeathState.None, "워치독 자동 부활 1회 · 상태 None");
|
|||
|
|
Chk(sb, RunResultUI.RevivedSeen >= 1, "RunResultUI 가 Revived 를 받았다(워치독 경로 포함)");
|
|||
|
|
RunResultUI.Tick(Time.unscaledTime);
|
|||
|
|
Chk(sb, RunResultUI.IsOpen && !RunResultUI.Pending, "워치독 부활 뒤 결과 화면 표시(shows=" + RunResultUI.ShownCount + ")");
|
|||
|
|
|
|||
|
|
// C8 — resultDeferWhileDying=0 이면 813q 동작(미룸 없음)
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("C8 롤백 — resultDeferWhileDying=0 (813q 동작 복귀)");
|
|||
|
|
ui.resultDeferWhileDying = false;
|
|||
|
|
RunResultUI.ResetState(); DeathFlow.ResetDiagnostics();
|
|||
|
|
SetPhase(RunPhase.Zones);
|
|||
|
|
t = 6000f; DeathFlow.Tick(t); DeathFlow.OnPCDied(pc);
|
|||
|
|
DeathFlow.Tick(t + sv.deathDisplaySeconds + 0.01f);
|
|||
|
|
Chk(sb, DeathFlow.State == DeathState.AwaitingRevive && !RunResultUI.ReviveBusy,
|
|||
|
|
"DeathFlow=" + DeathFlow.State + " 인데 부활우선=" + RunResultUI.ReviveBusy + " (스위치 off = 813q 판정만)");
|
|||
|
|
DeathFlow.Revive();
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) { s_fail++; sb.AppendLine("**FAIL** 예외 " + ex.GetType().Name + " — " + ex.Message + "\n" + ex.StackTrace); }
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
ui.resultDeferWhileDying = dying0;
|
|||
|
|
DeathFlow.ReviveRequested.Remove(OnReq);
|
|||
|
|
DeathFlow.ReviveOverride = ovr0;
|
|||
|
|
DeathFlow.Abort("probe cleanup");
|
|||
|
|
WLSurvivalSettings.RuntimeOverride = ov0;
|
|||
|
|
RunResultUI.Teardown();
|
|||
|
|
Kill(pc != null ? pc.gameObject : null);
|
|||
|
|
Kill(root);
|
|||
|
|
SetPhase(phase0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("② 결과 = " + s_pass + " PASS / " + s_fail + " FAIL");
|
|||
|
|
string body2 = sb.ToString();
|
|||
|
|
return body2 + "\n→ " + Write("WL813q2_watchdog.txt", body2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────── ③ D-23 「다음 런」 뒤 상태 리셋
|
|||
|
|
|
|||
|
|
public static string Reset()
|
|||
|
|
{
|
|||
|
|
s_pass = s_fail = 0;
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("=== WL-813q2 ③ D-23 「다음 런」 뒤 타이머 색 · 보스 바 · 배너 리셋 ===");
|
|||
|
|
|
|||
|
|
var ui = WLRunUiSettings.Instance;
|
|||
|
|
if (ui == null) { string b = sb.AppendLine("**FAIL** WLRunUiSettings 없음").ToString(); return b + "\n→ " + Write("WL813q2_reset.txt", b); }
|
|||
|
|
|
|||
|
|
GameObject root = null, barGo = null, bannerGo = null;
|
|||
|
|
bool reset0 = ui.timerResetColorOnRunStart;
|
|||
|
|
var phase0 = GetPhase();
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var uiRoot = BuildUiRoot(out root);
|
|||
|
|
sb.AppendLine("RunHud Bind: " + RunHud.Bind(uiRoot));
|
|||
|
|
RunHud.Subscribe(true);
|
|||
|
|
RunHud.ResetState();
|
|||
|
|
|
|||
|
|
string warnHex = ColorUtility.ToHtmlStringRGB(ui.timerWarnColor);
|
|||
|
|
string normHex = ColorUtility.ToHtmlStringRGB(ui.timerColor);
|
|||
|
|
sb.AppendLine("SO 색 실측: 평상 #" + normHex + " · 경고 #" + warnHex + " (경고 구간 " + ui.timerWarnSeconds + "s)");
|
|||
|
|
|
|||
|
|
// 런 1 — 시작 → 마지막 30 s 경고색
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(1, 240f, 2, "probe");
|
|||
|
|
Chk(sb, ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) == normHex,
|
|||
|
|
"런 1 시작 타이머 색 #" + ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) + " = 평상색 · 표시 \"" + RunHud.TimerText + "\"");
|
|||
|
|
WLRunUiProbeHooks.RaiseRunTick(215f, 25f, 1, 3, 14, 1, 2, RunPhase.Zones, 1);
|
|||
|
|
Chk(sb, ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) == warnHex && RunHud.TimerWarn,
|
|||
|
|
"남은 25 s → 경고색 #" + ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) + " · 표시 \"" + RunHud.TimerText + "\"");
|
|||
|
|
|
|||
|
|
var r = FakeResult(RunOutcome.Timeout, 240f, -1f, 29, 0);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
|
|||
|
|
// 런 2 (= 「다음 런」) — 색이 평상색으로 돌아와야 한다
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(2, 240f, 2, "restart");
|
|||
|
|
Chk(sb, ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) == normHex,
|
|||
|
|
"🔴 D-23 — 다음 런 시작 타이머 색 #" + ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) +
|
|||
|
|
" = 평상색 · 표시 \"" + RunHud.TimerText + "\" (색 쓰기 " + RunHud.TimerColorWrites + "회)");
|
|||
|
|
bool zonesZero = true;
|
|||
|
|
for (int i = 0; i < RunHud.ZoneCells; i++) if (RunHud.ZoneFill(i) > 0.0005f) zonesZero = false;
|
|||
|
|
Chk(sb, zonesZero && RunHud.ZoneCells == 2, "존 게이지 리셋 — 칸 " + RunHud.ZoneCells + "개 전부 fill 0");
|
|||
|
|
|
|||
|
|
// C8 — 스위치를 끄면 813q 버그가 그대로 재현된다(= 이 스위치가 원인 지점이다)
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("C8 롤백 — timerResetColorOnRunStart=0 (813q 동작 = D-23 재현)");
|
|||
|
|
ui.timerResetColorOnRunStart = false;
|
|||
|
|
WLRunUiProbeHooks.RaiseRunTick(215f, 25f, 1, 3, 14, 1, 2, RunPhase.Zones, 2);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(3, 240f, 2, "restart");
|
|||
|
|
Chk(sb, ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) == warnHex,
|
|||
|
|
"스위치 off → 이전 런의 경고색 #" + ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) +
|
|||
|
|
" 이 그대로 남는다 = D-23 원인 지점 확정");
|
|||
|
|
ui.timerResetColorOnRunStart = true;
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(4, 240f, 2, "restart");
|
|||
|
|
Chk(sb, ColorUtility.ToHtmlStringRGB(RunHud.TimerColor) == normHex, "스위치 on 복귀 → 평상색");
|
|||
|
|
|
|||
|
|
// ── 보스 HP 바
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("보스 HP 바 — 이전 런의 바가 남는가");
|
|||
|
|
barGo = new GameObject("__WL813q2_BossBar", typeof(RectTransform));
|
|||
|
|
barGo.hideFlags = HideFlags.DontSave;
|
|||
|
|
barGo.transform.SetParent(root.transform, false);
|
|||
|
|
var bar = barGo.AddComponent<BossHpBar>();
|
|||
|
|
sb.AppendLine(" " + bar.Initialize());
|
|||
|
|
sb.AppendLine(" " + bar.Bind(null, "테스트 보스", 200f)); // 합성(actor=null) 경로
|
|||
|
|
Chk(sb, bar.Visible && bar.LastRatio > 0.99f, "바 표시 · ratio=" + bar.LastRatio.ToString("F3"));
|
|||
|
|
bar.SetRatio(0.37f);
|
|||
|
|
Chk(sb, Mathf.Abs(BossHpBar.RunBossHpRatio - 0.37f) < 0.001f,
|
|||
|
|
"런 단위 보스 HP 실측값 = " + (BossHpBar.RunBossHpRatio * 100f).ToString("F1") + "% (결과 화면이 읽는 값)");
|
|||
|
|
|
|||
|
|
int reset1 = BossHpBar.RunResetCount;
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(5, 240f, 2, "restart");
|
|||
|
|
sb.AppendLine(" RunStarted 뒤: 리셋 " + (BossHpBar.RunResetCount - reset1) + "회 · visible=" + bar.Visible +
|
|||
|
|
" · 런 HP=" + BossHpBar.RunBossHpRatio.ToString("F2") + " · 늦은구독 스캔=" + bar.LateScanCount);
|
|||
|
|
Chk(sb, BossHpBar.RunResetCount == reset1 + 1, "RunStarted 를 받아 리셋했다");
|
|||
|
|
Chk(sb, !bar.Visible, "🔴 D-23 — 이전 런의 보스 바가 사라졌다(visible=" + bar.Visible + ")");
|
|||
|
|
Chk(sb, BossHpBar.RunBossHpRatio < 0f, "런 단위 보스 HP 값도 비워졌다(" + BossHpBar.RunBossHpRatio.ToString("F2") + ")");
|
|||
|
|
|
|||
|
|
ui.bossResetOnRunStart = false;
|
|||
|
|
bar.Bind(null, "테스트 보스", 200f);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(6, 240f, 2, "restart");
|
|||
|
|
Chk(sb, bar.Visible, "C8 롤백 bossResetOnRunStart=0 → 813q 동작(바가 남는다 · visible=" + bar.Visible + ")");
|
|||
|
|
ui.bossResetOnRunStart = true;
|
|||
|
|
|
|||
|
|
// ── 보스 배너(재트리거 가드)
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("보스 등장 배너 — 재트리거 가드가 다음 런에서 풀리는가");
|
|||
|
|
bannerGo = new GameObject("__WL813q2_BossBanner", typeof(RectTransform));
|
|||
|
|
bannerGo.hideFlags = HideFlags.DontSave;
|
|||
|
|
bannerGo.transform.SetParent(root.transform, false);
|
|||
|
|
var banner = bannerGo.AddComponent<BossBanner>();
|
|||
|
|
sb.AppendLine(" " + banner.Initialize());
|
|||
|
|
sb.AppendLine(" " + banner.Trigger("테스트 보스", true));
|
|||
|
|
int show1 = banner.ShowCount;
|
|||
|
|
sb.AppendLine(" 같은 런에서 재트리거: " + banner.Trigger("테스트 보스", true));
|
|||
|
|
Chk(sb, banner.ShowCount == show1, "같은 런에서는 가드가 막는다(shows=" + banner.ShowCount + ")");
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(7, 240f, 2, "restart");
|
|||
|
|
Chk(sb, !banner.Visible, "다음 런 시작 → 배너 숨김(alpha=" + banner.BannerAlpha.ToString("F2") + ")");
|
|||
|
|
sb.AppendLine(" 다음 런에서 재트리거: " + banner.Trigger("테스트 보스", true));
|
|||
|
|
Chk(sb, banner.ShowCount == show1 + 1, "다음 런에서는 다시 뜬다 = 가드 해제됨(shows=" + banner.ShowCount + ")");
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine(RunHud.Dump());
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) { s_fail++; sb.AppendLine("**FAIL** 예외 " + ex.GetType().Name + " — " + ex.Message + "\n" + ex.StackTrace); }
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
ui.timerResetColorOnRunStart = reset0;
|
|||
|
|
ui.bossResetOnRunStart = true;
|
|||
|
|
BossHpBar.RunBossHpRatio = -1f; BossHpBar.RunBossSeen = false;
|
|||
|
|
RunHud.Teardown();
|
|||
|
|
Kill(barGo); Kill(bannerGo); Kill(root);
|
|||
|
|
SetPhase(phase0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("③ 결과 = " + s_pass + " PASS / " + s_fail + " FAIL");
|
|||
|
|
string body3 = sb.ToString();
|
|||
|
|
return body3 + "\n→ " + Write("WL813q2_reset.txt", body3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────── ④ 결과 화면 행 보강(보스 미처치 · 엘리트)
|
|||
|
|
|
|||
|
|
public static string Rows()
|
|||
|
|
{
|
|||
|
|
s_pass = s_fail = 0;
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("=== WL-813q2 ④ 결과 화면 행 — 보스 미처치 문구 · 엘리트 처치 수 ===");
|
|||
|
|
|
|||
|
|
var ui = WLRunUiSettings.Instance;
|
|||
|
|
if (ui == null) { string b = sb.AppendLine("**FAIL** WLRunUiSettings 없음").ToString(); return b + "\n→ " + Write("WL813q2_rows.txt", b); }
|
|||
|
|
|
|||
|
|
GameObject root = null, barGo = null;
|
|||
|
|
var phase0 = GetPhase();
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var uiRoot = BuildUiRoot(out root);
|
|||
|
|
RunResultUI.Bind(uiRoot);
|
|||
|
|
RunResultUI.Subscribe(true);
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
BossHpBar.RunBossHpRatio = -1f; BossHpBar.RunBossSeen = false; // 앞 항목의 잔값 제거
|
|||
|
|
|
|||
|
|
sb.AppendLine("SO 행 배열 = " + ui.resultRows.Length + "개 · 엘리트 행 포함=" + ui.HasRow(WLRunResultRow.EliteKills));
|
|||
|
|
Chk(sb, ui.HasRow(WLRunResultRow.EliteKills), "엘리트 처치 행이 SO 배열에 있다");
|
|||
|
|
|
|||
|
|
// 엘리트 집계 — 런 중에만 센다
|
|||
|
|
SetPhase(RunPhase.Zones);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(1, 240f, 2, "probe");
|
|||
|
|
for (int i = 0; i < 3; i++) WLRunUiProbeHooks.RaiseKilled(eSubRol.Elite);
|
|||
|
|
for (int i = 0; i < 5; i++) WLRunUiProbeHooks.RaiseKilled(eSubRol.None);
|
|||
|
|
WLRunUiProbeHooks.RaiseKilled(eSubRol.Boss);
|
|||
|
|
Chk(sb, RunResultUI.EliteKills == 3, "런 중 엘리트 처치 " + RunResultUI.EliteKills + "건(일반 5 · 보스 1 은 세지 않는다)");
|
|||
|
|
|
|||
|
|
SetPhase(RunPhase.Ended);
|
|||
|
|
WLRunUiProbeHooks.RaiseKilled(eSubRol.Elite); // 런 밖 처치는 세지 않는다
|
|||
|
|
Chk(sb, RunResultUI.EliteKills == 3, "런 밖 엘리트 처치는 무시(" + RunResultUI.EliteKills + ")");
|
|||
|
|
|
|||
|
|
// 보스 미처치 + HP 를 모르는 경우
|
|||
|
|
var r = FakeResult(RunOutcome.Timeout, 240f, -1f, 29, 0);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
string boss1 = RunResultUI.RowValueText(WLRunResultRow.BossClear);
|
|||
|
|
string elite1 = RunResultUI.RowValueText(WLRunResultRow.EliteKills);
|
|||
|
|
sb.AppendLine(" 보스 처치 행 = \"" + boss1 + "\" · 엘리트 처치 행 = \"" + elite1 + "\"");
|
|||
|
|
Chk(sb, boss1 == ui.resultBossNotClearedText, "보스 미처치(HP 모름) → \"" + boss1 + "\"");
|
|||
|
|
Chk(sb, elite1 == "3", "엘리트 처치 행 = \"" + elite1 + "\" (RunEnded 순간 스냅샷)");
|
|||
|
|
|
|||
|
|
// 보스 미처치 + 813c 바가 남긴 HP 실측값
|
|||
|
|
barGo = new GameObject("__WL813q2_BossBar2", typeof(RectTransform));
|
|||
|
|
barGo.hideFlags = HideFlags.DontSave;
|
|||
|
|
barGo.transform.SetParent(root.transform, false);
|
|||
|
|
var bar = barGo.AddComponent<BossHpBar>();
|
|||
|
|
bar.Initialize();
|
|||
|
|
bar.Bind(null, "테스트 보스", 200f);
|
|||
|
|
bar.SetRatio(0.436f);
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
string boss2 = RunResultUI.RowValueText(WLRunResultRow.BossClear);
|
|||
|
|
sb.AppendLine(" 바 실측 HP 43.6% 일 때 보스 처치 행 = \"" + boss2 + "\"");
|
|||
|
|
Chk(sb, boss2.Contains("44"), "보스 미처치 + HP 남음 44 % 표기 — \"" + boss2 + "\"");
|
|||
|
|
|
|||
|
|
// 보스 처치한 런은 시간이 그대로 나온다
|
|||
|
|
var r2 = FakeResult(RunOutcome.Win, 154f, 154f, 99, 1);
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r2);
|
|||
|
|
string boss3 = RunResultUI.RowValueText(WLRunResultRow.BossClear);
|
|||
|
|
Chk(sb, boss3 == "02:34", "보스 처치 런 = \"" + boss3 + "\" (mm:ss 그대로 · 813q 동작 불변)");
|
|||
|
|
|
|||
|
|
// C8 — UI 집계·UI HP 를 끄면 이전 동작
|
|||
|
|
ui.resultBossHpFromUi = false; ui.resultCountElitesInUi = false;
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
Chk(sb, RunResultUI.RowValueText(WLRunResultRow.BossClear) == ui.resultBossNotClearedText &&
|
|||
|
|
RunResultUI.RowValueText(WLRunResultRow.EliteKills) == "0",
|
|||
|
|
"C8 롤백 — 보스 행 \"" + RunResultUI.RowValueText(WLRunResultRow.BossClear) + "\" · 엘리트 행 \"" +
|
|||
|
|
RunResultUI.RowValueText(WLRunResultRow.EliteKills) + "\"");
|
|||
|
|
ui.resultBossHpFromUi = true; ui.resultCountElitesInUi = true;
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine(RunResultUI.Dump());
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) { s_fail++; sb.AppendLine("**FAIL** 예외 " + ex.GetType().Name + " — " + ex.Message + "\n" + ex.StackTrace); }
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
ui.resultBossHpFromUi = true; ui.resultCountElitesInUi = true;
|
|||
|
|
RunResultUI.Teardown();
|
|||
|
|
Kill(barGo); Kill(root);
|
|||
|
|
SetPhase(phase0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("④ 결과 = " + s_pass + " PASS / " + s_fail + " FAIL");
|
|||
|
|
string body4 = sb.ToString();
|
|||
|
|
return body4 + "\n→ " + Write("WL813q2_rows.txt", body4);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ────────────────────────────────────────────────── ⑤ GC 0 · C8
|
|||
|
|
|
|||
|
|
public static string Gc()
|
|||
|
|
{
|
|||
|
|
s_pass = s_fail = 0;
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("=== WL-813q2 ⑤ GC 0 · C8 ===");
|
|||
|
|
|
|||
|
|
var ui = WLRunUiSettings.Instance;
|
|||
|
|
if (ui == null) { string b = sb.AppendLine("**FAIL** WLRunUiSettings 없음").ToString(); return b + "\n→ " + Write("WL813q2_gc.txt", b); }
|
|||
|
|
|
|||
|
|
GameObject root = null;
|
|||
|
|
var phase0 = GetPhase();
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var uiRoot = BuildUiRoot(out root);
|
|||
|
|
RunHud.Bind(uiRoot); RunHud.Subscribe(true); RunHud.ResetState();
|
|||
|
|
RunResultUI.Bind(uiRoot); RunResultUI.Subscribe(true); RunResultUI.ResetState();
|
|||
|
|
|
|||
|
|
SetPhase(RunPhase.Zones);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunStarted(1, 240f, 2, "probe");
|
|||
|
|
WLRunUiProbeHooks.RaiseRunTick(60f, 180f, 0, 5, 24, 0, 2, RunPhase.Zones, 1);
|
|||
|
|
|
|||
|
|
// 같은 초 100틱 = 표시 쓰기 0회 · 델타 0 B
|
|||
|
|
GC.Collect(); GC.WaitForPendingFinalizers();
|
|||
|
|
long m0 = GC.GetTotalMemory(true);
|
|||
|
|
int tw0 = RunHud.TimerWrites, cw0 = RunHud.TimerColorWrites;
|
|||
|
|
for (int i = 0; i < 100; i++) WLRunUiProbeHooks.RaiseRunTick(60f, 180f, 0, 5, 24, 0, 2, RunPhase.Zones, 1);
|
|||
|
|
long m1 = GC.GetTotalMemory(false);
|
|||
|
|
Chk(sb, RunHud.TimerWrites == tw0 && RunHud.TimerColorWrites == cw0,
|
|||
|
|
"같은 초 100틱 — 타이머 쓰기 " + (RunHud.TimerWrites - tw0) + "회 · 색 쓰기 " + (RunHud.TimerColorWrites - cw0) + "회");
|
|||
|
|
Chk(sb, m1 - m0 == 0, "Δ GetTotalMemory = " + (m1 - m0) + " B");
|
|||
|
|
|
|||
|
|
// Killed 100건(엘리트 집계 경로) — 문자열 0
|
|||
|
|
GC.Collect(); GC.WaitForPendingFinalizers();
|
|||
|
|
long k0 = GC.GetTotalMemory(true);
|
|||
|
|
for (int i = 0; i < 100; i++) WLRunUiProbeHooks.RaiseKilled(i % 2 == 0 ? eSubRol.Elite : eSubRol.None);
|
|||
|
|
long k1 = GC.GetTotalMemory(false);
|
|||
|
|
Chk(sb, k1 - k0 == 0, "Killed 100건(엘리트 50) Δ = " + (k1 - k0) + " B · 집계=" + RunResultUI.EliteKills);
|
|||
|
|
|
|||
|
|
// 재정렬 100회 왕복 — 노드 재생성 0(Canvas·Raycaster 는 1개씩 재사용)
|
|||
|
|
// 🔴 재정렬 함수는 진단 문자열을 돌려주므로 그 자체는 할당이 있다. 다만 **매 프레임 경로가 아니다**
|
|||
|
|
// (사망 1회 · 부활 1회). 여기서는 "노드를 다시 만들지 않는다"만 PASS 기준으로 둔다.
|
|||
|
|
long l0 = GC.GetTotalMemory(true);
|
|||
|
|
for (int i = 0; i < 100; i++) { RunResultUI.LowerBehindRevive(); RunResultUI.RaiseToFront(); }
|
|||
|
|
long l1 = GC.GetTotalMemory(false);
|
|||
|
|
var resRoot = root.transform.Find("IngameUIs/WL_HUD/WL_RunResult");
|
|||
|
|
int canvasN = resRoot != null ? resRoot.GetComponents<Canvas>().Length : -1;
|
|||
|
|
int rayN = resRoot != null ? resRoot.GetComponents<GraphicRaycaster>().Length : -1;
|
|||
|
|
Chk(sb, RunResultUI.LowerCount == 100 && RunResultUI.RaiseCount == 100, "재정렬 왕복 100회(내림 " +
|
|||
|
|
RunResultUI.LowerCount + " / 복귀 " + RunResultUI.RaiseCount + ") · Δ = " + (l1 - l0) + " B(진단 문자열분)");
|
|||
|
|
Chk(sb, canvasN == 1 && rayN == 1, "노드 재생성 0 — Canvas " + canvasN + "개 · GraphicRaycaster " + rayN + "개");
|
|||
|
|
|
|||
|
|
// C8 — 전체 off
|
|||
|
|
sb.AppendLine();
|
|||
|
|
WLRunUiSettings.RuntimeDisabled = true;
|
|||
|
|
RunResultUI.ResetState();
|
|||
|
|
var r = FakeResult(RunOutcome.Timeout, 240f, -1f, 29, 0);
|
|||
|
|
SetPhase(RunPhase.Ended);
|
|||
|
|
WLRunUiProbeHooks.RaiseRunEnded(in r);
|
|||
|
|
RunResultUI.Tick(Time.unscaledTime);
|
|||
|
|
Chk(sb, !RunResultUI.IsOpen, "C8 RuntimeDisabled — 결과 화면 표시 0(open=" + RunResultUI.IsOpen + ")");
|
|||
|
|
Chk(sb, !RunResultUI.ReviveBusy, "C8 중 부활우선 판정도 무동작(" + RunResultUI.ReviveBusyReason + ")");
|
|||
|
|
WLRunUiSettings.RuntimeDisabled = false;
|
|||
|
|
Chk(sb, WLRunUiSettings.Enabled, "C8 복구 — Enabled=" + WLRunUiSettings.Enabled);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) { s_fail++; sb.AppendLine("**FAIL** 예외 " + ex.GetType().Name + " — " + ex.Message + "\n" + ex.StackTrace); }
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
WLRunUiSettings.RuntimeDisabled = false;
|
|||
|
|
RunHud.Teardown(); RunResultUI.Teardown();
|
|||
|
|
Kill(root);
|
|||
|
|
SetPhase(phase0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sb.AppendLine();
|
|||
|
|
sb.AppendLine("⑤ 결과 = " + s_pass + " PASS / " + s_fail + " FAIL");
|
|||
|
|
string body5 = sb.ToString();
|
|||
|
|
return body5 + "\n→ " + Write("WL813q2_gc.txt", body5);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ───────────────────────────────────────────────────────── 전체
|
|||
|
|
|
|||
|
|
public static string All()
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
int pass = 0, fail = 0;
|
|||
|
|
string[] parts = new string[5];
|
|||
|
|
parts[0] = Overlap(); pass += s_pass; fail += s_fail;
|
|||
|
|
parts[1] = Watchdog(); pass += s_pass; fail += s_fail;
|
|||
|
|
parts[2] = Reset(); pass += s_pass; fail += s_fail;
|
|||
|
|
parts[3] = Rows(); pass += s_pass; fail += s_fail;
|
|||
|
|
parts[4] = Gc(); pass += s_pass; fail += s_fail;
|
|||
|
|
for (int i = 0; i < parts.Length; i++) sb.AppendLine(parts[i]).AppendLine();
|
|||
|
|
sb.AppendLine("======================================================");
|
|||
|
|
sb.AppendLine("WL-813q2 전체 = " + pass + " PASS / " + fail + " FAIL");
|
|||
|
|
string body = sb.ToString();
|
|||
|
|
return "WL-813q2 전체 = " + pass + " PASS / " + fail + " FAIL\n→ " + Write("WL813q2_all.txt", body);
|
|||
|
|
}
|
|||
|
|
}
|