Project_WL/AgentScripts/WL816m_Probe.cs

505 lines
26 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// WL-816m 프로브 — 던전 결과창(던전 이름 + 획득 골드) 실측 (에디트 모드 · Play 0 · 로그인 0).
// unity command run_script --file AgentScripts/WL816m_Probe.cs --entry WL816m_Probe.SaveAsset
// unity command run_script --file AgentScripts/WL816m_Probe.cs --entry WL816m_Probe.RunAll
// unity command run_script --file AgentScripts/WL816m_Probe.cs --entry WL816m_Probe.Capture
// 산출물: <worktree>/AgentScripts/WL816m_PROBE.txt · Screenshots_WL/WL816m/*.png
// 🔴 NewGameUI.prefab 은 LoadPrefabContents 로 열고 저장하지 않는다(UnloadPrefabContents).
// 🔴 PlayerPrefs 는 건드리지 않는다 — 에디트 모드라 WLIslandBridge 는 설치되지 않는다(전후 값 실측 대조).
using System;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEditor;
using WL.UI;
using WL.Island;
public static class WL816m_Probe
{
const string kPrefab = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
static StringBuilder _o;
static int _pass, _fail;
static string Root { get { return Directory.GetParent(Application.dataPath).FullName; } }
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;
}
// ═════════════════════════════════════════════════════════════════════
// ① SO 에 새 필드를 직렬화한다(에셋 YAML 에 값이 보이도록 · C45)
// ═════════════════════════════════════════════════════════════════════
public static string SaveAsset()
{
var g = WLStageUiSettings.Instance;
if (g == null) return "WLStageUiSettings 에셋 없음";
EditorUtility.SetDirty(g);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
return "저장 — 던전모드=" + g.dungeonModeEnabled + " 제목=\"" + g.dungeonClearTitleFormat +
"\" 라벨=\"" + g.dungeonLabelFormat + "\" 골드행=" + g.dungeonGoldRowEnabled +
" \"" + g.rowDungeonGoldLabel + "\" \"" + g.rowDungeonGoldFormat + "\"";
}
// ═════════════════════════════════════════════════════════════════════
// ② 실측
// ═════════════════════════════════════════════════════════════════════
public static string RunAll()
{
_o = new StringBuilder();
_pass = _fail = 0;
_o.AppendLine("# WL816m Probe " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " (edit mode · Play 0 · 로그인 0)");
_o.AppendLine("playMode=" + Application.isPlaying);
var cfg = WLIslandSettings.Instance;
string prefsKey = cfg != null ? cfg.pendingPrefsKey : "WL_Island_PendingGold";
int prefsBefore = PlayerPrefs.GetInt(prefsKey, -1);
var savedCurrent = WLDungeonSceneSetup.Current;
GameObject prefabRoot = null;
try
{
Step0_Settings(cfg);
prefabRoot = PrefabUtility.LoadPrefabContents(kPrefab);
BuildRig(prefabRoot);
Step1_Detect(cfg);
Step2_Gold(cfg);
Step3_Result(cfg, prefabRoot);
Step4_Hud(cfg, prefabRoot);
Step5_StageUnchanged(prefabRoot);
Step6_Rollback(cfg, prefabRoot);
}
catch (Exception e)
{
_fail++;
_o.AppendLine(" [FAIL] 예외 " + e.GetType().Name + " : " + e.Message);
_o.AppendLine(e.StackTrace);
}
finally
{
try { StageUiBridge.Teardown(); RunHud.Teardown(); RunResultUI.Teardown(); } catch { }
DestroyRig();
WLDungeonSceneSetup.Current = savedCurrent;
if (prefabRoot != null) PrefabUtility.UnloadPrefabContents(prefabRoot);
}
int prefsAfter = PlayerPrefs.GetInt(prefsKey, -1);
H("⑦ PlayerPrefs 무변경 (섬 축은 에디트 모드에 설치되지 않는다)");
N("키 \"" + prefsKey + "\" 전=" + prefsBefore + " 후=" + prefsAfter);
Chk(prefsBefore == prefsAfter, "보류 보상 PlayerPrefs 무변경");
_o.AppendLine();
_o.AppendLine("RESULT " + (_fail == 0 ? "PASS" : "FAIL") + " (pass=" + _pass + " fail=" + _fail + ")");
string path = Path.Combine(Path.Combine(Root, "AgentScripts"), "WL816m_PROBE.txt");
File.WriteAllText(path, _o.ToString(), new UTF8Encoding(true));
return _o.ToString();
}
static void Step0_Settings(WLIslandSettings cfg)
{
H("① 설정 실값 (SO · 하드코딩 0)");
var g = WLStageUiSettings.Instance;
Chk(g != null, "WLStageUiSettings 에셋 로드");
if (g == null) return;
N("던전모드=" + g.dungeonModeEnabled + " · 제목 \"" + g.dungeonClearTitleFormat + "\" · HUD \"" +
g.dungeonLabelFormat + "\" · 대체이름 \"" + g.dungeonNameFallbackFormat + "\"");
N("골드행=" + g.dungeonGoldRowEnabled + " · 라벨 \"" + g.rowDungeonGoldLabel + "\" · 값 \"" + g.rowDungeonGoldFormat + "\"");
N("스테이지 제목(불변) \"" + g.stageClearTitleFormat + "\" · 스테이지 HUD(불변) \"" + g.stageLabelFormat + "\"");
Chk(cfg != null && cfg.dungeons != null && cfg.dungeons.Length >= 3, "던전 표 3행 이상");
if (cfg == null || cfg.dungeons == null) return;
for (int i = 0; i < cfg.dungeons.Length; i++)
{
var d = cfg.dungeons[i];
int id = StageIdOf(d.stageIndex);
N("던전 표[" + i + "] id=" + d.id + " \"" + d.displayName + "\" stageIndex=" + d.stageIndex +
" → 스테이지 표 id=" + id + "(지금 화면에 나오던 숫자) · goldMultiplier=" +
WLIslandBridge.MultiplierOf(d.stageIndex).ToString("F2"));
}
N("보상 값 — clearBonus=" + cfg.goldClearBonus + " perKill=" + cfg.goldPerKill + " bossBonus=" +
cfg.goldBossBonus + " 배수적용=" + cfg.applyGoldMultiplier + " rewardEnabled=" + cfg.rewardEnabled);
}
static int StageIdOf(int index)
{
var t = WL.Combat.Stage.WLStageTable.Instance;
if (t == null) return -1;
var def = t.Get(index);
return def != null ? def.id : -1;
}
static void Step1_Detect(WLIslandSettings cfg)
{
H("② 던전 판정 = WLDungeonSceneSetup.Current 하나뿐");
WLDungeonSceneSetup.Current = null;
Chk(StageUiBridge.CurrentDungeon == null, "Current=null → 던전 아님(#815 스테이지 모드)");
Chk(StageUiBridge.DungeonNameOf(null) == null, "이름 null");
WLDungeonSceneSetup.Current = cfg.dungeons[0];
var d1 = StageUiBridge.CurrentDungeon;
Chk(d1 != null, "Current=던전1 행 → 던전으로 판정");
N("이름 = \"" + StageUiBridge.DungeonNameOf(d1) + "\"");
Chk(StageUiBridge.DungeonNameOf(d1) == cfg.dungeons[0].displayName, "이름 = 표의 displayName");
// displayName 이 비었을 때의 대체 이름
string saved = cfg.dungeons[0].displayName;
cfg.dungeons[0].displayName = "";
string fb = StageUiBridge.DungeonNameOf(StageUiBridge.CurrentDungeon);
cfg.dungeons[0].displayName = saved;
N("displayName 빈 경우 대체 이름 = \"" + fb + "\"");
Chk(fb == "던전 " + cfg.dungeons[0].id, "대체 이름 = dungeonNameFallbackFormat + id");
}
static void Step2_Gold(WLIslandSettings cfg)
{
H("③ 획득 골드 = 섬 축 WLIslandBridge.ComputeGold 재사용(식 1곳)");
var r1 = Cleared(cfg.dungeons[0].stageIndex, 3, false);
int g1 = StageUiBridge.DungeonGoldOf(in r1);
N("던전 1 (index " + cfg.dungeons[0].stageIndex + " · 처치 3) → " + g1 +
" / 섬 축 직접 = " + WLIslandBridge.ComputeGold(cfg, in r1));
Chk(g1 == 96, "던전 1 = 96 (816g §5 실측과 같음)");
var r3 = Cleared(cfg.dungeons[2].stageIndex, 3, false);
int g3 = StageUiBridge.DungeonGoldOf(in r3);
N("던전 3 (index " + cfg.dungeons[2].stageIndex + " · 처치 3) → " + g3);
Chk(g3 == 192, "던전 3 = 192 (816i2 §3 실측과 같음)");
Chk(g1 == WLIslandBridge.ComputeGold(cfg, in r1) && g3 == WLIslandBridge.ComputeGold(cfg, in r3),
"UI 표시값 == 섬 축 지급 계산값(두 곳 일치)");
}
static WL.Combat.Stage.StageClearResult Cleared(int index, int killed, bool isBoss)
{
return new WL.Combat.Stage.StageClearResult
{
index = index, stageId = StageIdOf(index), win = true,
failReason = WL.Combat.Stage.StageFailReason.None,
elapsedSec = 34.2f, timeLimit = 60f, killed = killed, total = killed, remaining = 0,
retries = 0, isLastStage = false, isBoss = isBoss,
time = Time.unscaledTime, frame = Time.frameCount,
};
}
// ── 목업 캔버스(815d 와 같은 방식) — TitleText·PhaseText 를 읽으려면 노드가 있어야 한다 ──
static GameObject _rig;
static Camera _cam;
static RectTransform _crt;
static ReviveDialog _revive;
static void BuildRig(GameObject prefabRoot)
{
_rig = new GameObject("WL816m_Rig");
_rig.hideFlags = HideFlags.HideAndDontSave;
var camGo = new GameObject("Cam");
camGo.transform.SetParent(_rig.transform, false);
_cam = camGo.AddComponent<Camera>();
_cam.clearFlags = CameraClearFlags.SolidColor;
_cam.backgroundColor = new Color(0.09f, 0.10f, 0.12f, 1f);
_cam.orthographic = true; _cam.orthographicSize = 960f;
_cam.nearClipPlane = 0.1f; _cam.farClipPlane = 5000f;
camGo.transform.position = new Vector3(0f, 0f, -1000f);
var canvasGo = new GameObject("Canvas", typeof(RectTransform));
canvasGo.transform.SetParent(_rig.transform, false);
canvasGo.layer = 5;
var canvas = canvasGo.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.worldCamera = _cam;
canvas.planeDistance = 500f;
var scaler = canvasGo.AddComponent<UnityEngine.UI.CanvasScaler>();
scaler.uiScaleMode = UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1080f, 1920f);
scaler.matchWidthOrHeight = 0.5f;
canvasGo.AddComponent<UnityEngine.UI.GraphicRaycaster>();
_crt = (RectTransform)canvasGo.transform;
_crt.sizeDelta = new Vector2(1080f, 1920f);
var ingame = NewRect(_crt, "IngameUIs");
var wlHud = NewRect(ingame, "WL_HUD");
var font = RunHud.BorrowFont(prefabRoot.transform);
RunHud.SetFont(font); RunResultUI.SetFont(font);
StageUiBridge.SetUiRootForProbe(prefabRoot.transform);
RunHud.Subscribe(true); RunResultUI.Subscribe(true); StageUiBridge.Subscribe(true);
RunHud.Bind(_crt); RunResultUI.Bind(_crt);
var dlgGo = new GameObject("WL_ReviveDialog", typeof(RectTransform));
dlgGo.layer = 5;
dlgGo.transform.SetParent(wlHud, false);
_revive = dlgGo.AddComponent<ReviveDialog>();
_revive.SetFont(font);
_revive.Initialize();
}
static void DestroyRig()
{
if (_rig != null) UnityEngine.Object.DestroyImmediate(_rig);
_rig = null; _cam = null; _crt = null; _revive = null;
}
static void Step3_Result(WLIslandSettings cfg, GameObject prefabRoot)
{
H("④ 결과창 — 던전 1 / 던전 3 (StageEvents 실제 Dispatch 경로)");
// 던전 1
WLDungeonSceneSetup.Current = cfg.dungeons[0];
int idx1 = cfg.dungeons[0].stageIndex;
WLStageUiProbeHooks.RaiseEntered(idx1, StageIdOf(idx1), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
StageUiBridge.ProbeSetInStage(true);
WLStageUiProbeHooks.RaiseCleared(idx1, StageIdOf(idx1), 34.2f, 60f, 3, 3, 0, false, false);
N("제목 \"" + RunResultUI.TitleText + "\" · 행 " + RunResultUI.VisibleRows + "개 · 던전=" +
RunResultUI.IsDungeonResult + " \"" + RunResultUI.DungeonName + "\" 골드=" + RunResultUI.DungeonGold);
N("본문 ⤵\n" + Indent(RunResultUI.BodyText));
Chk(RunResultUI.TitleText == "던전 1 클리어!", "제목 = \"던전 1 클리어!\" (지금까지는 \"스테이지 11 클리어!\")");
Chk(RunResultUI.BodyText.Contains("획득 골드 : +96"), "행 = \"획득 골드 : +96\"");
Chk(RunResultUI.VisibleRows == 7, "행 6 + 획득 골드 1 = 7");
// 던전 3
RunResultUI.Hide();
WLDungeonSceneSetup.Current = cfg.dungeons[2];
int idx3 = cfg.dungeons[2].stageIndex;
WLStageUiProbeHooks.RaiseEntered(idx3, StageIdOf(idx3), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
StageUiBridge.ProbeSetInStage(true);
WLStageUiProbeHooks.RaiseCleared(idx3, StageIdOf(idx3), 41.5f, 60f, 3, 3, 0, false, false);
N("제목 \"" + RunResultUI.TitleText + "\" · 골드=" + RunResultUI.DungeonGold);
Chk(RunResultUI.TitleText == "던전 3 클리어!", "제목 = \"던전 3 클리어!\"");
Chk(RunResultUI.BodyText.Contains("획득 골드 : +192"), "행 = \"획득 골드 : +192\"");
}
static string Indent(string s)
{
if (string.IsNullOrEmpty(s)) return " (빈 본문)";
var lines = s.Replace("\r", "").Split('\n');
var sb = new StringBuilder();
for (int i = 0; i < lines.Length; i++)
if (lines[i].Length > 0) sb.AppendLine(" " + lines[i]);
return sb.ToString().TrimEnd();
}
static void Step4_Hud(WLIslandSettings cfg, GameObject prefabRoot)
{
H("⑤ HUD 단계 라벨 — 던전이면 「던전 N」");
WLDungeonSceneSetup.Current = cfg.dungeons[0];
int idx1 = cfg.dungeons[0].stageIndex;
WLStageUiProbeHooks.RaiseEntered(idx1, StageIdOf(idx1), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
N("던전 1 → 단계 \"" + RunHud.PhaseText + "\" · 남은 적 \"" + RunHud.EnemyText + "\"");
Chk(RunHud.PhaseText == "던전 1", "HUD 라벨 = \"던전 1\" (지금까지는 \"스테이지 11\")");
WLDungeonSceneSetup.Current = cfg.dungeons[2];
int idx3 = cfg.dungeons[2].stageIndex;
WLStageUiProbeHooks.RaiseStarted(idx3, StageIdOf(idx3), 3, 60f, 0);
N("던전 3 → 단계 \"" + RunHud.PhaseText + "\"");
Chk(RunHud.PhaseText == "던전 3", "HUD 라벨 = \"던전 3\"");
int w0 = RunHud.StageLabelWrites;
WLStageUiProbeHooks.RaiseStarted(idx3, StageIdOf(idx3), 3, 60f, 0);
Chk(RunHud.StageLabelWrites == w0, "같은 이름 재입력 시 쓰기 0회(GC 0)");
}
static void Step5_StageUnchanged(GameObject prefabRoot)
{
H("⑥ #815 스테이지 모드 무변경 (Current = null)");
WLDungeonSceneSetup.Current = null;
RunResultUI.Hide();
WLStageUiProbeHooks.RaiseEntered(2, 3, 12, 60f, Vector3.zero, 14f, 1.5f, 0);
WLStageUiProbeHooks.RaiseEnemyCount(2, 5, 12);
WLStageUiProbeHooks.RaiseTime(2, 8f, 60f);
N("단계 \"" + RunHud.PhaseText + "\" · 시간 \"" + RunHud.TimerText + "\" · 남은 적 \"" + RunHud.EnemyText + "\"");
Chk(RunHud.PhaseText == "스테이지 3", "HUD = \"스테이지 3\" 그대로");
StageUiBridge.ProbeSetInStage(true);
WLStageUiProbeHooks.RaiseCleared(2, 3, 34.2f, 60f, 12, 12, 0, false, false);
N("제목 \"" + RunResultUI.TitleText + "\" · 행 " + RunResultUI.VisibleRows + "개 · 던전=" + RunResultUI.IsDungeonResult);
N("본문 ⤵\n" + Indent(RunResultUI.BodyText));
Chk(RunResultUI.TitleText == "스테이지 3 클리어!", "제목 = \"스테이지 3 클리어!\" 그대로");
Chk(RunResultUI.VisibleRows == 6, "행 6개 그대로(획득 골드 없음)");
Chk(!RunResultUI.BodyText.Contains("획득 골드"), "본문에 「획득 골드」 0건");
Chk(!RunResultUI.IsDungeonResult && RunResultUI.DungeonGold == -1, "던전 문맥 없음");
// 구 경로(4인자 = 816m 이전 동작)와 같은 본문인가 — 진짜 before/after 대조
string after = RunResultUI.BodyText;
var r = Cleared(2, 12, false);
RunResultUI.Hide();
RunResultUI.SetStageMode(true);
RunResultUI.ShowStage(in r, 0, 0, 0); // 816m 이전과 같은 진입점
RunResultUI.BuildBody(WLRunUiSettings.Instance);
N("구 경로 본문 ⤵\n" + Indent(RunResultUI.BodyText));
Chk(RunResultUI.BodyText == after, "구 4인자 경로(=816m 이전) == 새 경로 본문 문자열 동일");
}
static void Step6_Rollback(WLIslandSettings cfg, GameObject prefabRoot)
{
H("⑦ C8 롤백 — dungeonModeEnabled = 0 이면 던전도 지금까지 그대로");
var g = WLStageUiSettings.Instance;
bool saved = g.dungeonModeEnabled;
try
{
g.dungeonModeEnabled = false;
WLDungeonSceneSetup.Current = cfg.dungeons[0];
int idx1 = cfg.dungeons[0].stageIndex;
RunResultUI.Hide();
WLStageUiProbeHooks.RaiseEntered(idx1, StageIdOf(idx1), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
StageUiBridge.ProbeSetInStage(true);
WLStageUiProbeHooks.RaiseCleared(idx1, StageIdOf(idx1), 34.2f, 60f, 3, 3, 0, false, false);
N("롤백 제목 \"" + RunResultUI.TitleText + "\" · HUD \"" + RunHud.PhaseText + "\" · 행 " + RunResultUI.VisibleRows);
Chk(RunResultUI.TitleText == "스테이지 11 클리어!", "롤백 = \"스테이지 11 클리어!\"(PD 가 본 화면)");
Chk(RunHud.PhaseText == "스테이지 11", "롤백 HUD = \"스테이지 11\"");
Chk(RunResultUI.VisibleRows == 6, "롤백 행 6개");
}
finally { g.dungeonModeEnabled = saved; }
}
// ═════════════════════════════════════════════════════════════════════
// ③ 캡처 — 1080×1920 목업(실 노드 · 실 값 · RenderTexture · 815d 와 같은 방식)
// ═════════════════════════════════════════════════════════════════════
public static string Capture()
{
var sb = new StringBuilder();
string dir = Path.Combine(Path.Combine(Root, "Screenshots_WL"), "WL816m");
Directory.CreateDirectory(dir);
var cfg = WLIslandSettings.Instance;
var savedCurrent = WLDungeonSceneSetup.Current;
GameObject prefabRoot = null;
try
{
prefabRoot = PrefabUtility.LoadPrefabContents(kPrefab);
BuildRig(prefabRoot);
var cam = _cam;
var revive = _revive;
var rt = new RenderTexture(1080, 1920, 24, RenderTextureFormat.ARGB32);
cam.targetTexture = rt;
int idx1 = cfg.dungeons[0].stageIndex, idx3 = cfg.dungeons[2].stageIndex;
// ⓒ HUD — 던전 1
WLDungeonSceneSetup.Current = cfg.dungeons[0];
WLStageUiProbeHooks.RaiseEntered(idx1, StageIdOf(idx1), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
WLStageUiProbeHooks.RaiseEnemyCount(idx1, 3, 3);
WLStageUiProbeHooks.RaiseTime(idx1, 42f, 60f);
RunHud.ApplyLayout(); RunResultUI.Hide(); revive.HideNow();
sb.AppendLine("ⓒ HUD 던전1 \"" + RunHud.PhaseText + "\" \"" + RunHud.TimerText + "\" \"" + RunHud.EnemyText + "\"");
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "c_hud_dungeon1.png")));
// ⓐ 던전 1 클리어
StageUiBridge.ProbeSetInStage(true);
WLStageUiProbeHooks.RaiseCleared(idx1, StageIdOf(idx1), 34.2f, 60f, 3, 3, 0, false, false);
RunResultUI.ApplyLayout();
sb.AppendLine("ⓐ 던전1 \"" + RunResultUI.TitleText + "\" 행=" + RunResultUI.VisibleRows +
" 골드=" + RunResultUI.DungeonGold);
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "a_dungeon1_clear.png")));
// ⓑ 던전 3 클리어
RunResultUI.Hide();
WLDungeonSceneSetup.Current = cfg.dungeons[2];
WLStageUiProbeHooks.RaiseEntered(idx3, StageIdOf(idx3), 3, 60f, Vector3.zero, 13f, 1.5f, 0);
StageUiBridge.ProbeSetInStage(true);
WLStageUiProbeHooks.RaiseCleared(idx3, StageIdOf(idx3), 41.5f, 60f, 3, 3, 0, false, false);
RunResultUI.ApplyLayout();
sb.AppendLine("ⓑ 던전3 \"" + RunResultUI.TitleText + "\" 골드=" + RunResultUI.DungeonGold);
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "b_dungeon3_clear.png")));
// ⓓ 스테이지 모드 대조 — 새 경로(Current=null)
RunResultUI.Hide();
WLDungeonSceneSetup.Current = null;
WLStageUiProbeHooks.RaiseEntered(2, 3, 12, 60f, Vector3.zero, 14f, 1.5f, 0);
WLStageUiProbeHooks.RaiseEnemyCount(2, 5, 12);
WLStageUiProbeHooks.RaiseTime(2, 8f, 60f);
RunHud.ApplyLayout();
sb.AppendLine("ⓓ HUD 스테이지 \"" + RunHud.PhaseText + "\" \"" + RunHud.TimerText + "\" \"" + RunHud.EnemyText + "\"");
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "d1_hud_stage_new.png")));
StageUiBridge.ProbeSetInStage(true);
WLStageUiProbeHooks.RaiseCleared(2, 3, 34.2f, 60f, 12, 12, 0, false, false);
RunResultUI.ApplyLayout();
sb.AppendLine("ⓓ 스테이지 클리어(새 경로) \"" + RunResultUI.TitleText + "\" 행=" + RunResultUI.VisibleRows);
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "d1_stage_clear_new.png")));
// ⓓ 대조군 — 816m 이전과 같은 4인자 진입점
RunResultUI.Hide();
RunHud.WriteStageLabel(3);
RunHud.ApplyLayout();
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "d2_hud_stage_legacy.png")));
var r = Cleared(2, 12, false);
RunResultUI.SetStageMode(true);
RunResultUI.ShowStage(in r, 0, 0, 0);
RunResultUI.ApplyLayout();
sb.AppendLine("ⓓ 스테이지 클리어(구 4인자) \"" + RunResultUI.TitleText + "\" 행=" + RunResultUI.VisibleRows);
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "d2_stage_clear_legacy.png")));
// ⓔ 실패 팝업 (ReviveDialog.cs 0줄 변경)
RunResultUI.Hide();
StageUiBridge.ProbeSetInStage(true);
WLStageUiProbeHooks.RaiseFailed(2, 3, WL.Combat.Stage.StageFailReason.TimeLimit, 4, 12, 60f, 0);
revive.ApplyLayout();
sb.AppendLine("ⓔ 실패 \"" + revive.LastMessage.Replace("\n", " / ") + "\" 버튼=[" +
revive.PrimaryButtonText + "] [" + revive.SecondaryButtonText + "]");
sb.AppendLine(Shot(cam, rt, Path.Combine(dir, "e_fail.png")));
cam.targetTexture = null;
UnityEngine.Object.DestroyImmediate(rt);
sb.AppendLine();
sb.AppendLine("픽셀 대조 (MD5) — 새 경로 vs 816m 이전 경로");
sb.AppendLine(" HUD " + SameFile(Path.Combine(dir, "d1_hud_stage_new.png"), Path.Combine(dir, "d2_hud_stage_legacy.png")));
sb.AppendLine(" 결과창 " + SameFile(Path.Combine(dir, "d1_stage_clear_new.png"), Path.Combine(dir, "d2_stage_clear_legacy.png")));
}
catch (Exception e) { sb.AppendLine("예외 " + e.GetType().Name + " : " + e.Message + "\n" + e.StackTrace); }
finally
{
try { StageUiBridge.Teardown(); RunHud.Teardown(); RunResultUI.Teardown(); } catch { }
DestroyRig();
WLDungeonSceneSetup.Current = savedCurrent;
if (prefabRoot != null) PrefabUtility.UnloadPrefabContents(prefabRoot);
}
string path = Path.Combine(Path.Combine(Root, "AgentScripts"), "WL816m_CAPTURE.txt");
File.WriteAllText(path, sb.ToString(), new UTF8Encoding(true));
return sb.ToString();
}
static string SameFile(string a, string b)
{
if (!File.Exists(a) || !File.Exists(b)) return "파일 없음";
string ha = Md5(a), hb = Md5(b);
return (ha == hb ? "동일 " : "다름 ") + ha.Substring(0, 12) + " / " + hb.Substring(0, 12);
}
static string Md5(string path)
{
using (var md5 = System.Security.Cryptography.MD5.Create())
using (var fs = File.OpenRead(path))
return BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "").ToLowerInvariant();
}
static RectTransform NewRect(RectTransform parent, string name)
{
var go = new GameObject(name, typeof(RectTransform));
go.layer = 5;
var rt = (RectTransform)go.transform;
rt.SetParent(parent, false);
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero;
rt.localScale = Vector3.one;
return rt;
}
static string Shot(Camera cam, RenderTexture rt, string path)
{
Canvas.ForceUpdateCanvases();
cam.Render();
var prev = RenderTexture.active;
RenderTexture.active = rt;
var tex = new Texture2D(rt.width, rt.height, TextureFormat.RGBA32, false);
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
tex.Apply();
RenderTexture.active = prev;
var png = tex.EncodeToPNG();
UnityEngine.Object.DestroyImmediate(tex);
File.WriteAllBytes(path, png);
return " → " + path + " (" + png.Length + " B · " + rt.width + "×" + rt.height + ")";
}
}