diff --git a/AgentScripts/WL813q3_PROBE.txt b/AgentScripts/WL813q3_PROBE.txt
new file mode 100644
index 000000000..ca9a9316e
--- /dev/null
+++ b/AgentScripts/WL813q3_PROBE.txt
@@ -0,0 +1,98 @@
+== WL-813q3 프로브 — 결과창 보스 HP = RunEnded 페이로드 스냅샷 ==
+ 2026-09-09 19:17:53 · Unity 6000.3.23f1 · isPlaying=False · project=E:\NerdNavis\WL_wt\ui
+
+── ⓪ 설정 실측 — 신규 값 2개 · 신규 행 1개
+ 에셋 = Assets/WL/UI/Settings/Resources/WL/WLRunUiSettings.asset (4688 B · 2026-09-09 10:12:11 UTC)
+ resultBossHpFromPayload=True · resultBossHpFromUi=True · resultCountCardPicksInUi=True · resultCountElitesInUi=True
+ 문구 = "미처치 (HP 남음 {0} %)" / "미처치" · 카드 행 이름 = "카드 선택"
+ [PASS] resultBossHpFromPayload 기본 on(813w6 페이로드 우선)
+ [PASS] resultCountCardPicksInUi 기본 on
+ [PASS] rowCardPicksLabel 값 있음
+ resultRows(11) = TotalTime · BossClear · Kills · EliteKills · MaxChain · MaxDamage · Level · CardPicks · LootByGrade · Gold · DeathsPotions
+ [PASS] BossClear 행 있음
+ [PASS] EliteKills 행 있음(813q2)
+ [PASS] CardPicks 행 있음(813q3 신설)
+ 배치 = 제목영역 120px + 행 11×56=616px → 본문 끝 736px · 버튼 상단 940px · 상자 1080px
+ [PASS] 행이 버튼을 안 침범(736 ≤ 940) = 잘림 0(산술)
+
+── ① 813w6 페이로드 계약 — RunResult 에 보스 HP 3필드
+ 합성 RunResult · bossHpRatio=0.2439 bossHp=439 bossHpMax=1800
+ [PASS] RunResult.bossHpRatio 필드 존재·대입(컴파일이 곧 증거)
+ [PASS] RunResult.bossHp / bossHpMax 필드 존재·대입
+ 구독자 수 RunEnded=0 RunStarted=0
+
+── ② 가짜 RunEnded(bossHpRatio 0.2439) → 「미처치 (HP 남음 24 %)」
+ BuildBody → 행 11개
+ 보스 처치 행 = "미처치 (HP 남음 24 %)" · 원천 = 페이로드 · 스냅샷 = 24.39% (439/1800)
+ [PASS] 문구 = "미처치 (HP 남음 24 %)" (0.2439 → 24)
+ [PASS] 원천 = 페이로드(BossHpBar 를 안 읽었다)
+ [PASS] BossHpRatioAtEnd = 0.2439(구조체째 복사)
+ [PASS] BossHp/BossHpMax 스냅샷도 함께 복사
+
+── ③ 🔴 부활 미룸(늦은 그리기) — Park 로 만피가 되어도 값 불변
+ Park 실행 DeferredParkCount 0 → 0 · BossHpBar.RunBossHpRatio = 1.00 · BossArena.LastBossHpRatio = -1.00
+ 늦은 Show() → 노드 미조립 — Bind 먼저
+ 보스 처치 행(늦게) = "미처치 (HP 남음 24 %)" · 원천 = 페이로드
+ [PASS] 🔴 늦게 그려도 24 % 그대로(813w6 §6-1 잔여 해소)
+ [PASS] 스냅샷 버퍼가 만피에 안 덮인다
+
+── ③-B C8 — resultBossHpFromPayload = 0 이면 같은 자리에서 100 %(D-44 재현)
+ 행 = "미처치 (HP 남음 100 %)" · 원천 = UI바
+ [PASS] 스위치 off → 813q2 동작(만피 100 %) = D-44 재현
+ [PASS] off 일 때 원천 = UI바(BossHpBar)
+ [PASS] 스위치 복구 → 다시 24 %
+
+── ④ 진짜 발행 경로 — RunDirector.StartRun → EndRun(Timeout)
+ StartRun 후 phase=Zones running=True (EndedSeen=0)
+ EndRun 후 EndedSeen 0 → 1 · LastResult.bossHpRatio=0.2439 · 결과창 스냅샷=0.2439
+ 보스 처치 행(진짜 발행) = "미처치 (HP 남음 24 %)" · 원천 = 페이로드
+ [PASS] 결과창이 RunEnded 를 실제로 받았다
+ [PASS] 결과창 스냅샷 = RunDirector.LastResult.bossHpRatio (같은 값)
+ [PASS] 진짜 발행에서도 24 %
+ [PASS] 진짜 발행 + Park 뒤 늦은 그리기도 24 %
+
+── ⑤ 처치 케이스(MM:SS) · 페이로드 −1 폴백 · 둘 다 없음
+ 처치 런(bossClearSec 154) = "02:34"
+ [PASS] 처치면 「02:34」(HP 는 안 본다)
+ 페이로드 −1 + 바 43.6 % = "미처치 (HP 남음 44 %)" · 원천 = UI바
+ [PASS] 페이로드가 −1 이면 813q2 폴백(44 %)
+ [PASS] 폴백 원천 표시 = UI바
+ 둘 다 없음 = "미처치" · 원천 = 없음
+ [PASS] 둘 다 없으면 퍼센트 없이 「미처치」
+ [PASS] 원천 = 없음
+
+── ⑥ 결과창 행 실측 — 엘리트 · 카드 선택 · 사망/물약 · 본문 전체
+ 813o ZonePickup.PicksThisRun(현재) = 0 · PickCount=0
+ 카드 선택 종료스냅 = 0 · 엘리트 종료스냅 = 0
+ 카드 선택 행 = "0" · 엘리트 행 = "0" · 사망/물약 행 = "1 / 2"
+ [PASS] 카드 선택 행 = 종료 스냅샷 값
+ [PASS] 엘리트 행 = 종료 스냅샷 값(813q2 그대로)
+ [PASS] 사망/물약 행 = 「1 / 2」(RunResult 값 그대로)
+ [PASS] 본문 행 수 = resultRows 길이 (11)
+ 본문 = 시간 종료 | 총 시간 : 04:01 | 보스 처치 : 미처치 (HP 남음 24 %) | 처치 수 : 29 (보스 0) | 엘리트 처치 : 0 | 최대 연쇄 : 7 | 최대 피해 : 8889 | 레벨 : 11 → 12 | 카드 선택 : 0 | 획득 : 5 2 | 골드 : 777 | 사망 / 물약 : 1 / 2 |
+ (카드 카운터는 813o 소유 · 프로브는 읽기만 — 스냅샷 시점만 확인한다)
+
+── ⑦ GC — 매 프레임 경로 0 B · 이번에 늘어난 줄 0 B
+ [PASS] Tick ×100(매 프레임 경로) Δ = 0 B
+ [PASS] 이번에 늘어난 읽기(PicksThisRun + BossHpRatioAtEnd) ×100 Δ = 0 B (sink 0/24.4)
+ [PASS] LastBossHpSource 대입(리터럴) ×100 Δ = 0 B
+ 참고 — BuildBody ×100 Δ = 49152 B (런 종료 1회 경로 · 813q 부터 본문 문자열을 만든다 · 매 프레임 아님)
+
+── ⑧ C8 롤백 — 값 하나씩
+ [PASS] resultCountCardPicksInUi=0 → 카드 선택 행 「0」
+ [PASS] resultRows 에서 CardPicks 를 빼면 행이 사라진다
+ [PASS] 행 배열 복구 = 11행
+ [PASS] 둘 다 off → 퍼센트 없이 「미처치」
+ RuntimeDisabled Tick → off — 무동작
+ [PASS] RuntimeDisabled=1 → Tick 무동작
+ [PASS] 복구 → tick ok
+ [PASS] resultEnabled=0 → 무동작
+
+── ⑨ 뒷정리 — 에셋 저장 0 · 구독·주입 원복
+ BossArena 주입 해제 · BossHpBar.RunBossHpRatio=-1 · 결과창 구독=False(시작 시 False)
+ BossArena.BossHpSnapshots=1 DeferredParkCount=0
+ 에셋 후 = 4688 B · 2026-09-09 10:12:11 UTC
+ [PASS] 에셋 파일 무변동 = 저장 0(SaveAssets/SetDirty 호출 0)
+ SO 값 원복 = payload:True fromUi:True cards:True elites:True enabled_:True result:True 행수:11
+
+== 합계 PASS 42 / FAIL 0 / NOTE 0 ==
diff --git a/AgentScripts/WL813q3_Probe.cs b/AgentScripts/WL813q3_Probe.cs
new file mode 100644
index 000000000..5e2250a7f
--- /dev/null
+++ b/AgentScripts/WL813q3_Probe.cs
@@ -0,0 +1,384 @@
+// WL-813q3 프로브 — 결과창 「보스 처치 : 미처치 (HP 남음 N %)」 가 813w6 `RunEnded` 페이로드
+// `bossHpRatio` 를 쓰는지 실측(에디트 모드 · Play 0 · 로그인 0 · #813).
+// unity command run_script --file AgentScripts/WL813q3_Probe.cs --entry WL813q3_Probe.RunAll
+// 산출물: /AgentScripts/WL813q3_PROBE.txt
+//
+// 🔴 에셋 저장 0 — WLRunUiSettings 값은 **메모리에서만** 바꾸고 finally 에서 원복한다.
+// AssetDatabase.SaveAssets / EditorUtility.SetDirty 호출 **0**. 파일 크기·수정시각을 전후로 찍어 증거로 남긴다.
+// 🔴 Gameplay 는 읽기·공개 API 호출만(파일 수정 0). Play 진입 0.
+// 🔴 핵심 계측 = **「늦게 그려도 값이 안 바뀐다」**.
+// RunEnded 를 받은 뒤 BossArena 가 미뤄 둔 Park(만피 복구)을 실제로 돌리고, 보스 HP 바 미러까지
+// 100 % 로 만든 다음 결과창을 **다시 그려서** 문구가 그대로 24 % 인지 본다.
+// 스위치를 끄면(813q2 동작) 같은 자리에서 100 % 가 나온다 = D-44 재현.
+
+using System;
+using System.IO;
+using System.Text;
+using UnityEngine;
+using UnityEditor;
+using WL.UI;
+using WL.Combat.Run;
+using WL.Combat.Boss;
+
+public static class WL813q3_Probe
+{
+ const string kAsset = "Assets/WL/UI/Settings/Resources/WL/WLRunUiSettings.asset";
+
+ static StringBuilder _o;
+ static int _pass, _fail, _note;
+
+ static void H(string s) { _o.AppendLine(); _o.AppendLine("── " + s); }
+ static void N(string s) { _o.AppendLine(" " + s); }
+ static void Note(string s) { _note++; _o.AppendLine(" [NOTE] " + 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 n) { return Path.Combine(Path.Combine(Root, "AgentScripts"), n); }
+
+ /// 본문에서 한 행의 값 문자열을 뽑는다(노드 없이도 되는 유일한 경로 = BodyText).
+ static string BodyRow(string label)
+ {
+ string body = RunResultUI.BodyText;
+ if (string.IsNullOrEmpty(body)) return "";
+ var lines = body.Split('\n');
+ for (int i = 0; i < lines.Length; i++)
+ {
+ int k = lines[i].IndexOf(" : ", StringComparison.Ordinal);
+ if (k < 0) continue;
+ if (lines[i].Substring(0, k) == label) return lines[i].Substring(k + 3);
+ }
+ return "";
+ }
+
+ /// 합성 RunResult — 배열 3종은 813p 계약대로 길이만 맞춘다.
+ static RunResult Fake(RunOutcome outcome, float bossClearSec, float ratio, double hp, double maxHp)
+ {
+ return new RunResult
+ {
+ outcome = outcome,
+ runIndex = 1,
+ totalSec = 241f,
+ runSeconds = 270f,
+ zoneClearSec = new float[] { 61.5f, -1f },
+ zoneKills = new int[] { 17, 12 },
+ zoneCount = 2,
+ zonesCleared = 1,
+ bossGateSec = 170f,
+ bossStartSec = 173f,
+ bossClearSec = bossClearSec,
+ bossHpRatio = ratio,
+ bossHp = hp,
+ bossHpMax = maxHp,
+ kills = 29,
+ bossKills = bossClearSec >= 0f ? 1 : 0,
+ maxChain = 7,
+ maxDamage = 4321d,
+ maxDamageBurst = 8888.5d,
+ 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 = 40,
+ time = 0f,
+ frame = 0,
+ };
+ }
+
+ public static string RunAll()
+ {
+ _o = new StringBuilder(16000);
+ _o.AppendLine("== WL-813q3 프로브 — 결과창 보스 HP = RunEnded 페이로드 스냅샷 ==");
+ _o.AppendLine(" " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " · Unity " + Application.unityVersion +
+ " · isPlaying=" + Application.isPlaying + " · project=" + Root);
+
+ WLRunUiSettings.ClearCache();
+ var s = WLRunUiSettings.Instance;
+ if (s == null) { _o.AppendLine(" [FAIL] WLRunUiSettings 에셋 없음 — 중단"); return Flush(); }
+
+ string abs = Path.Combine(Root, kAsset.Replace('/', Path.DirectorySeparatorChar));
+ var fi0 = new FileInfo(abs); fi0.Refresh();
+ long len0 = fi0.Length; DateTime mt0 = fi0.LastWriteTimeUtc;
+
+ // ── 원복용 스냅샷 ────────────────────────────────────────────────────
+ bool o_payload = s.resultBossHpFromPayload, o_fromUi = s.resultBossHpFromUi;
+ bool o_cards = s.resultCountCardPicksInUi, o_elites = s.resultCountElitesInUi;
+ bool o_enabled = s.enabled_, o_result = s.resultEnabled;
+ var o_rows = s.resultRows;
+ float o_barRatio = BossHpBar.RunBossHpRatio;
+ bool o_subscribed = RunResultUI.Subscribed;
+
+ try
+ {
+ // ── ⓪ 설정·행 배열 실측 ─────────────────────────────────────────
+ H("⓪ 설정 실측 — 신규 값 2개 · 신규 행 1개");
+ N("에셋 = " + kAsset + " (" + len0 + " B · " + mt0.ToString("yyyy-MM-dd HH:mm:ss") + " UTC)");
+ N("resultBossHpFromPayload=" + s.resultBossHpFromPayload + " · resultBossHpFromUi=" + s.resultBossHpFromUi +
+ " · resultCountCardPicksInUi=" + s.resultCountCardPicksInUi + " · resultCountElitesInUi=" + s.resultCountElitesInUi);
+ N("문구 = \"" + s.resultBossNotClearedHpFormat + "\" / \"" + s.resultBossNotClearedText + "\" · 카드 행 이름 = \"" + s.rowCardPicksLabel + "\"");
+ Chk(s.resultBossHpFromPayload, "resultBossHpFromPayload 기본 on(813w6 페이로드 우선)");
+ Chk(s.resultCountCardPicksInUi, "resultCountCardPicksInUi 기본 on");
+ Chk(!string.IsNullOrEmpty(s.rowCardPicksLabel), "rowCardPicksLabel 값 있음");
+
+ var sbRows = new StringBuilder();
+ bool hasCard = false, hasElite = false, hasBoss = false;
+ for (int i = 0; i < s.resultRows.Length; i++)
+ {
+ if (i > 0) sbRows.Append(" · ");
+ sbRows.Append(s.resultRows[i]);
+ if (s.resultRows[i] == WLRunResultRow.CardPicks) hasCard = true;
+ if (s.resultRows[i] == WLRunResultRow.EliteKills) hasElite = true;
+ if (s.resultRows[i] == WLRunResultRow.BossClear) hasBoss = true;
+ }
+ N("resultRows(" + s.resultRows.Length + ") = " + sbRows);
+ Chk(hasBoss, "BossClear 행 있음");
+ Chk(hasElite, "EliteKills 행 있음(813q2)");
+ Chk(hasCard, "CardPicks 행 있음(813q3 신설)");
+
+ // 상자 안에 11행이 들어가는지 산술 실측(제목 + 행 + 버튼)
+ float top = s.resultPadTopPx + s.resultTitleFontPx + s.resultTitleGapPx;
+ float rowsH = s.resultRowHeightPx * s.resultRows.Length;
+ float btnTop = s.resultBoxSizePx.y - (s.resultButtonBottomPx + s.resultButtonSizePx.y);
+ N("배치 = 제목영역 " + top + "px + 행 " + s.resultRows.Length + "×" + s.resultRowHeightPx + "=" + rowsH +
+ "px → 본문 끝 " + (top + rowsH) + "px · 버튼 상단 " + btnTop + "px · 상자 " + s.resultBoxSizePx.y + "px");
+ Chk(top + rowsH <= btnTop, "행이 버튼을 안 침범(" + (top + rowsH) + " ≤ " + btnTop + ") = 잘림 0(산술)");
+
+ // ── ① 813w6 계약 확인 ────────────────────────────────────────────
+ H("① 813w6 페이로드 계약 — RunResult 에 보스 HP 3필드");
+ var probe = Fake(RunOutcome.Timeout, -1f, 0.2439f, 439d, 1800d);
+ N("합성 RunResult · bossHpRatio=" + probe.bossHpRatio.ToString("F4") + " bossHp=" + probe.bossHp + " bossHpMax=" + probe.bossHpMax);
+ Chk(Math.Abs(probe.bossHpRatio - 0.2439f) < 1e-5f, "RunResult.bossHpRatio 필드 존재·대입(컴파일이 곧 증거)");
+ Chk(probe.bossHp == 439d && probe.bossHpMax == 1800d, "RunResult.bossHp / bossHpMax 필드 존재·대입");
+ N("구독자 수 RunEnded=" + RunEvents.RunEnded.Count + " RunStarted=" + RunEvents.RunStarted.Count);
+
+ // ── ② 가짜 RunEnded(0.2439) → 「HP 남음 24 %」 ────────────────────
+ H("② 가짜 RunEnded(bossHpRatio 0.2439) → 「미처치 (HP 남음 24 %)」");
+ if (!RunResultUI.Subscribed) RunResultUI.Subscribe(true);
+ RunResultUI.ResetState();
+ BossHpBar.RunBossHpRatio = -1f; // 바는 아무것도 안 물었다 = 폴백이 못 쓰는 상태
+ RunResultUI.CopyResult(in probe); // = OnRunEnded 가 즉시 하는 그 복사
+ N("BuildBody → " + RunResultUI.BuildBody(s));
+ string v2 = BodyRow(s.rowBossClearLabel);
+ N("보스 처치 행 = \"" + v2 + "\" · 원천 = " + RunResultUI.LastBossHpSource +
+ " · 스냅샷 = " + (RunResultUI.BossHpRatioAtEnd * 100f).ToString("F2") + "% (" +
+ RunResultUI.BossHpAtEnd + "/" + RunResultUI.BossHpMaxAtEnd + ")");
+ Chk(v2 == "미처치 (HP 남음 24 %)", "문구 = \"미처치 (HP 남음 24 %)\" (0.2439 → 24)");
+ Chk(RunResultUI.LastBossHpSource == "페이로드", "원천 = 페이로드(BossHpBar 를 안 읽었다)");
+ Chk(Math.Abs(RunResultUI.BossHpRatioAtEnd - 0.2439f) < 1e-5f, "BossHpRatioAtEnd = 0.2439(구조체째 복사)");
+ Chk(RunResultUI.BossHpAtEnd == 439d && RunResultUI.BossHpMaxAtEnd == 1800d, "BossHp/BossHpMax 스냅샷도 함께 복사");
+
+ // ── ③ 🔴 부활 미룸 = 늦게 그려도 스냅샷 유지 ───────────────────────
+ H("③ 🔴 부활 미룸(늦은 그리기) — Park 로 만피가 되어도 값 불변");
+ BossArena.SetBossHpForProbe(1f, 1800d, 1800d); // Park → On_Regen 만피
+ BossHpBar.RunBossHpRatio = 1f; // 813q2 가 읽던 UI 미러도 만피
+ int deferBefore = BossArena.DeferredParkCount;
+ BossArena.PumpDeferredPark(); // 813w6 이 발행 뒤로 미뤄 둔 고정을 실제로 실행
+ N("Park 실행 DeferredParkCount " + deferBefore + " → " + BossArena.DeferredParkCount +
+ " · BossHpBar.RunBossHpRatio = " + BossHpBar.RunBossHpRatio.ToString("F2") +
+ " · BossArena.LastBossHpRatio = " + BossArena.LastBossHpRatio.ToString("F2"));
+ string show3 = RunResultUI.Show(); // ← 부활 팝업이 닫힌 뒤의 「늦은 그리기」
+ N("늦은 Show() → " + show3);
+ string v3 = BodyRow(s.rowBossClearLabel);
+ N("보스 처치 행(늦게) = \"" + v3 + "\" · 원천 = " + RunResultUI.LastBossHpSource);
+ Chk(v3 == "미처치 (HP 남음 24 %)", "🔴 늦게 그려도 24 % 그대로(813w6 §6-1 잔여 해소)");
+ Chk(Math.Abs(RunResultUI.BossHpRatioAtEnd - 0.2439f) < 1e-5f, "스냅샷 버퍼가 만피에 안 덮인다");
+
+ H("③-B C8 — resultBossHpFromPayload = 0 이면 같은 자리에서 100 %(D-44 재현)");
+ s.resultBossHpFromPayload = false;
+ RunResultUI.BuildBody(s);
+ string v3b = BodyRow(s.rowBossClearLabel);
+ N("행 = \"" + v3b + "\" · 원천 = " + RunResultUI.LastBossHpSource);
+ Chk(v3b == "미처치 (HP 남음 100 %)", "스위치 off → 813q2 동작(만피 100 %) = D-44 재현");
+ Chk(RunResultUI.LastBossHpSource == "UI바", "off 일 때 원천 = UI바(BossHpBar)");
+ s.resultBossHpFromPayload = true;
+ RunResultUI.BuildBody(s);
+ Chk(BodyRow(s.rowBossClearLabel) == "미처치 (HP 남음 24 %)", "스위치 복구 → 다시 24 %");
+
+ // ── ④ 진짜 RunDirector.EndRun 경로 ─────────────────────────────────
+ H("④ 진짜 발행 경로 — RunDirector.StartRun → EndRun(Timeout)");
+ BossArena.SetBossHpForProbe(0.2439f, 439d, 1800d);
+ BossHpBar.RunBossHpRatio = -1f;
+ RunResultUI.ResetState();
+ RunDirector.StartRun("WL813q3 프로브");
+ int endedBefore = RunResultUI.EndedSeen; // StartRun 이 앞 런을 Abandon 으로 닫을 수 있다
+ N("StartRun 후 phase=" + RunDirector.Phase + " running=" + RunDirector.IsRunning +
+ " (EndedSeen=" + endedBefore + ")");
+ if (RunDirector.IsRunning)
+ {
+ RunDirector.EndRun(RunOutcome.Timeout);
+ N("EndRun 후 EndedSeen " + endedBefore + " → " + RunResultUI.EndedSeen +
+ " · LastResult.bossHpRatio=" + RunDirector.LastResult.bossHpRatio.ToString("F4") +
+ " · 결과창 스냅샷=" + RunResultUI.BossHpRatioAtEnd.ToString("F4"));
+ string v4 = BodyRow(s.rowBossClearLabel);
+ N("보스 처치 행(진짜 발행) = \"" + v4 + "\" · 원천 = " + RunResultUI.LastBossHpSource);
+ Chk(RunResultUI.EndedSeen == endedBefore + 1, "결과창이 RunEnded 를 실제로 받았다");
+ Chk(Math.Abs(RunResultUI.BossHpRatioAtEnd - RunDirector.LastResult.bossHpRatio) < 1e-6f,
+ "결과창 스냅샷 = RunDirector.LastResult.bossHpRatio (같은 값)");
+ if (RunDirector.LastResult.bossHpRatio >= 0f)
+ {
+ Chk(v4 == "미처치 (HP 남음 24 %)", "진짜 발행에서도 24 %");
+ // 발행 뒤 Park → 다시 그려도 그대로
+ BossArena.SetBossHpForProbe(1f, 1800d, 1800d);
+ BossHpBar.RunBossHpRatio = 1f;
+ BossArena.PumpDeferredPark();
+ RunResultUI.BuildBody(s);
+ Chk(BodyRow(s.rowBossClearLabel) == "미처치 (HP 남음 24 %)", "진짜 발행 + Park 뒤 늦은 그리기도 24 %");
+ }
+ else
+ {
+ Note("BossArena 가 에디트 모드에서 비활성(snapshotBossHpOnRunEnd/Active) — 페이로드 −1 · " +
+ "값 경로는 ②③ 합성으로만 실측(진짜 발행의 값은 「미확인」)");
+ }
+ }
+ else
+ {
+ Note("RunDirector 가 에디트 모드에서 안 돈다(WLRunSettings/Cfg) — 진짜 발행 경로는 「미확인」");
+ }
+
+ // ── ⑤ 처치 케이스 · 값 없음 폴백 ───────────────────────────────────
+ H("⑤ 처치 케이스(MM:SS) · 페이로드 −1 폴백 · 둘 다 없음");
+ RunResultUI.CopyResult(Fake(RunOutcome.Win, 154f, 0f, 0d, 1800d));
+ RunResultUI.BuildBody(s);
+ string v5a = BodyRow(s.rowBossClearLabel);
+ N("처치 런(bossClearSec 154) = \"" + v5a + "\"");
+ Chk(v5a == "02:34", "처치면 「02:34」(HP 는 안 본다)");
+
+ BossHpBar.RunBossHpRatio = 0.436f; // 813q2 가 읽던 값이 살아 있는 상태
+ RunResultUI.CopyResult(Fake(RunOutcome.Timeout, -1f, -1f, 0d, 0d));
+ RunResultUI.BuildBody(s);
+ string v5b = BodyRow(s.rowBossClearLabel);
+ N("페이로드 −1 + 바 43.6 % = \"" + v5b + "\" · 원천 = " + RunResultUI.LastBossHpSource);
+ Chk(v5b == "미처치 (HP 남음 44 %)", "페이로드가 −1 이면 813q2 폴백(44 %)");
+ Chk(RunResultUI.LastBossHpSource == "UI바", "폴백 원천 표시 = UI바");
+
+ BossHpBar.RunBossHpRatio = -1f;
+ RunResultUI.BuildBody(s);
+ string v5c = BodyRow(s.rowBossClearLabel);
+ N("둘 다 없음 = \"" + v5c + "\" · 원천 = " + RunResultUI.LastBossHpSource);
+ Chk(v5c == s.resultBossNotClearedText, "둘 다 없으면 퍼센트 없이 「미처치」");
+ Chk(RunResultUI.LastBossHpSource == "없음", "원천 = 없음");
+
+ // ── ⑥ 결과창 행 실측 ───────────────────────────────────────────────
+ H("⑥ 결과창 행 실측 — 엘리트 · 카드 선택 · 사망/물약 · 본문 전체");
+ N("813o ZonePickup.PicksThisRun(현재) = " + ZonePickup.PicksThisRun + " · PickCount=" + ZonePickup.PickCount);
+ RunResultUI.CopyResult(Fake(RunOutcome.Timeout, -1f, 0.2439f, 439d, 1800d));
+ RunResultUI.BuildBody(s);
+ N("카드 선택 종료스냅 = " + RunResultUI.CardPicksAtEnd + " · 엘리트 종료스냅 = " + RunResultUI.EliteKillsAtEnd);
+ N("카드 선택 행 = \"" + BodyRow(s.rowCardPicksLabel) + "\" · 엘리트 행 = \"" + BodyRow(s.rowEliteKillsLabel) +
+ "\" · 사망/물약 행 = \"" + BodyRow(s.rowDeathsLabel) + "\"");
+ Chk(BodyRow(s.rowCardPicksLabel) == RunResultUI.CardPicksAtEnd.ToString(), "카드 선택 행 = 종료 스냅샷 값");
+ Chk(BodyRow(s.rowEliteKillsLabel) == RunResultUI.EliteKillsAtEnd.ToString(), "엘리트 행 = 종료 스냅샷 값(813q2 그대로)");
+ Chk(BodyRow(s.rowDeathsLabel) == "1 / 2", "사망/물약 행 = 「1 / 2」(RunResult 값 그대로)");
+ Chk(RunResultUI.VisibleRows == s.resultRows.Length, "본문 행 수 = resultRows 길이 (" + RunResultUI.VisibleRows + ")");
+ N("본문 = " + RunResultUI.BodyText.Replace("\n", " | "));
+
+ // 카드 선택 스냅샷이 「런 종료 순간」인지 — 뒤에 카운터가 늘어도 화면은 안 바뀐다
+ N("(카드 카운터는 813o 소유 · 프로브는 읽기만 — 스냅샷 시점만 확인한다)");
+
+ // ── ⑦ GC ───────────────────────────────────────────────────────────
+ H("⑦ GC — 매 프레임 경로 0 B · 이번에 늘어난 줄 0 B");
+ RunResultUI.Tick(0f); RunResultUI.BuildBody(s); // 워밍업(첫 회 버퍼)
+ long g0 = GC.GetTotalMemory(false);
+ for (int i = 0; i < 100; i++) RunResultUI.Tick(i);
+ long g1 = GC.GetTotalMemory(false);
+ Chk(g1 - g0 == 0, "Tick ×100(매 프레임 경로) Δ = " + (g1 - g0) + " B");
+
+ int sink = 0; float fsink = 0f;
+ g0 = GC.GetTotalMemory(false);
+ for (int i = 0; i < 100; i++) { sink += ZonePickup.PicksThisRun; fsink += RunResultUI.BossHpRatioAtEnd; }
+ g1 = GC.GetTotalMemory(false);
+ Chk(g1 - g0 == 0, "이번에 늘어난 읽기(PicksThisRun + BossHpRatioAtEnd) ×100 Δ = " + (g1 - g0) + " B (sink " + sink + "/" + fsink.ToString("F1") + ")");
+
+ g0 = GC.GetTotalMemory(false);
+ for (int i = 0; i < 100; i++) RunResultUI.LastBossHpSource = "페이로드";
+ g1 = GC.GetTotalMemory(false);
+ Chk(g1 - g0 == 0, "LastBossHpSource 대입(리터럴) ×100 Δ = " + (g1 - g0) + " B");
+
+ g0 = GC.GetTotalMemory(false);
+ for (int i = 0; i < 100; i++) RunResultUI.BuildBody(s);
+ g1 = GC.GetTotalMemory(false);
+ N("참고 — BuildBody ×100 Δ = " + (g1 - g0) + " B (런 종료 1회 경로 · 813q 부터 본문 문자열을 만든다 · 매 프레임 아님)");
+
+ // ── ⑧ C8 롤백 ──────────────────────────────────────────────────────
+ H("⑧ C8 롤백 — 값 하나씩");
+ s.resultCountCardPicksInUi = false;
+ RunResultUI.BuildBody(s);
+ Chk(BodyRow(s.rowCardPicksLabel) == "0", "resultCountCardPicksInUi=0 → 카드 선택 행 「0」");
+ s.resultCountCardPicksInUi = true;
+
+ s.resultRows = new WLRunResultRow[] { WLRunResultRow.TotalTime, WLRunResultRow.BossClear };
+ RunResultUI.BuildBody(s);
+ Chk(RunResultUI.VisibleRows == 2 && BodyRow(s.rowCardPicksLabel) == "", "resultRows 에서 CardPicks 를 빼면 행이 사라진다");
+ s.resultRows = o_rows;
+ RunResultUI.BuildBody(s);
+ Chk(RunResultUI.VisibleRows == o_rows.Length, "행 배열 복구 = " + RunResultUI.VisibleRows + "행");
+
+ s.resultBossHpFromPayload = false; s.resultBossHpFromUi = false;
+ RunResultUI.BuildBody(s);
+ Chk(BodyRow(s.rowBossClearLabel) == s.resultBossNotClearedText, "둘 다 off → 퍼센트 없이 「미처치」");
+ s.resultBossHpFromPayload = o_payload; s.resultBossHpFromUi = o_fromUi;
+
+ WLRunUiSettings.RuntimeDisabled = true;
+ string tickOff = RunResultUI.Tick(0f);
+ N("RuntimeDisabled Tick → " + tickOff);
+ Chk(tickOff.StartsWith("off"), "RuntimeDisabled=1 → Tick 무동작");
+ WLRunUiSettings.RuntimeDisabled = false;
+ Chk(RunResultUI.Tick(0f) == "tick ok", "복구 → tick ok");
+
+ s.resultEnabled = false;
+ Chk(RunResultUI.Tick(0f).StartsWith("off"), "resultEnabled=0 → 무동작");
+ s.resultEnabled = o_result;
+
+ // ── ⑨ 뒷정리 확인 ──────────────────────────────────────────────────
+ H("⑨ 뒷정리 — 에셋 저장 0 · 구독·주입 원복");
+ BossArena.SetBossHpForProbe(-2f, 0d, 0d); // 주입 해제
+ BossHpBar.RunBossHpRatio = o_barRatio;
+ RunResultUI.ResetState();
+ if (!o_subscribed) RunResultUI.Subscribe(false);
+ N("BossArena 주입 해제 · BossHpBar.RunBossHpRatio=" + BossHpBar.RunBossHpRatio +
+ " · 결과창 구독=" + RunResultUI.Subscribed + "(시작 시 " + o_subscribed + ")");
+ N("BossArena.BossHpSnapshots=" + BossArena.BossHpSnapshots + " DeferredParkCount=" + BossArena.DeferredParkCount);
+ }
+ catch (Exception ex)
+ {
+ _fail++;
+ _o.AppendLine(" [FAIL] 예외 — " + ex.GetType().Name + " : " + ex.Message);
+ _o.AppendLine(ex.StackTrace);
+ }
+ finally
+ {
+ // 🔴 SO 원복(메모리만 · 저장 0)
+ s.resultBossHpFromPayload = o_payload; s.resultBossHpFromUi = o_fromUi;
+ s.resultCountCardPicksInUi = o_cards; s.resultCountElitesInUi = o_elites;
+ s.enabled_ = o_enabled; s.resultEnabled = o_result; s.resultRows = o_rows;
+ WLRunUiSettings.RuntimeDisabled = false;
+ try { BossArena.SetBossHpForProbe(-2f, 0d, 0d); } catch { }
+ BossHpBar.RunBossHpRatio = o_barRatio;
+ }
+
+ var fi1 = new FileInfo(abs); fi1.Refresh();
+ N("에셋 후 = " + fi1.Length + " B · " + fi1.LastWriteTimeUtc.ToString("yyyy-MM-dd HH:mm:ss") + " UTC");
+ Chk(fi1.Length == len0 && fi1.LastWriteTimeUtc == mt0, "에셋 파일 무변동 = 저장 0(SaveAssets/SetDirty 호출 0)");
+ N("SO 값 원복 = payload:" + s.resultBossHpFromPayload + " fromUi:" + s.resultBossHpFromUi +
+ " cards:" + s.resultCountCardPicksInUi + " elites:" + s.resultCountElitesInUi +
+ " enabled_:" + s.enabled_ + " result:" + s.resultEnabled + " 행수:" + s.resultRows.Length);
+
+ return Flush();
+ }
+
+ static string Flush()
+ {
+ _o.AppendLine();
+ _o.AppendLine("== 합계 PASS " + _pass + " / FAIL " + _fail + " / NOTE " + _note + " ==");
+ string p = OutPath("WL813q3_PROBE.txt");
+ try { File.WriteAllText(p, _o.ToString(), new UTF8Encoding(true)); } catch { }
+ return _o.ToString() + "\n(파일: " + p + ")";
+ }
+}
diff --git a/Assets/WL/UI/Scripts/RunResultUI.cs b/Assets/WL/UI/Scripts/RunResultUI.cs
index b425d9f9d..10f555080 100644
--- a/Assets/WL/UI/Scripts/RunResultUI.cs
+++ b/Assets/WL/UI/Scripts/RunResultUI.cs
@@ -35,6 +35,16 @@
// (전용 Canvas `sortingOrder` 40 → 20 · `SetAsFirstSibling`), 부활이 끝나면 되돌린다.
// 🔴 Gameplay 는 **읽기·구독·공개 API 호출만** 한다(파일 수정 0).
//
+// ■ WL-813q3(D-44) — 「보스 처치 : 미처치 (HP 남음 N %)」 의 원천을 바꿨다.
+// 813w6 이 `RunEnded` 페이로드에 **`bossHpRatio`/`bossHp`/`bossHpMax`** 를 실었다(`RunEvents.cs` 실측 ·
+// 순서 = HP 스냅샷 → `RunEnded` 발행 → `BossArena.ParkBoss`(만피 복구)). 813q2 는 이 필드가 없어
+// **표시 시점**에 `BossHpBar.RunBossHpRatio` 를 읽었고, 부활 미룸(D-18)으로 결과가 늦게 그려지면
+// 그 사이 Park 이 돌아 **만피 100 %** 가 찍혔다(813w6 보고 §6-1 「잔여」).
+// 이제 `CopyResult` 가 런 종료 순간에 구조체째 복사한 **`s_result.bossHpRatio`** 를 쓴다
+// → 늦게 그려도 스냅샷 값 그대로. 페이로드가 −1(보스 미등장·못 읽음)이면 813q2 폴백으로 간다.
+// C8 = `resultBossHpFromPayload = 0` → 813q2 동작(만피 재현). 원천은 `LastBossHpSource` 로 읽는다.
+// 같은 발주로 「카드 선택」 행(813o `ZonePickup.PicksThisRun` · 읽기만)을 추가했다.
+//
// ■ 프리팹 diff 0 — 노드는 전부 런타임 생성(`HideFlags.DontSave`) · 1회 만들고 재사용.
// ■ C8 롤백 — `WLRunUiSettings.enabled_ = 0`(또는 `resultEnabled = 0`) → 구독 0 · 노드 0 · 표시 0.
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
@@ -86,6 +96,7 @@ namespace WL.UI
private static bool s_lowered; // WL-813q2 — 부활 뒤로 재정렬해 둔 상태인가
private static int s_eliteKills; // WL-813q2 — 런 중 엘리트 처치(UI 자체 집계)
private static int s_eliteKillsAtEnd; // RunEnded 순간의 스냅샷(결과 화면이 쓰는 값)
+ private static int s_cardPicksAtEnd; // WL-813q3 — RunEnded 순간의 813o 카드 선택 수 스냅샷
private static readonly StringBuilder s_sb = new StringBuilder(128);
private static readonly char[] s_timeBuf = new char[8];
@@ -100,6 +111,15 @@ namespace WL.UI
public static int LowerCount, RaiseCount; // 결과 화면을 부활 뒤로 내린/되돌린 횟수
public static int EliteKilledSeen; // 런 중 본 엘리트 처치(UI 자체 집계)
public static string LastDeathLog = "";
+ // ── WL-813q3(D-44) 진단 ──────────────────────────────────────────────
+ /// 마지막 「보스 처치」 행이 남은 HP 를 어디서 읽었나 — "페이로드"(813w6) / "UI바"(813q2) / "없음".
+ public static string LastBossHpSource = "없음";
+ /// 런 종료 순간의 보스 HP 스냅샷(813w6 페이로드 · −1 = 미발생) — 늦게 그려도 안 바뀐다.
+ public static float BossHpRatioAtEnd { get { return s_result.bossHpRatio; } }
+ public static double BossHpAtEnd { get { return s_result.bossHp; } }
+ public static double BossHpMaxAtEnd { get { return s_result.bossHpMax; } }
+ /// 런 종료 순간의 813o 카드 선택 수 스냅샷.
+ public static int CardPicksAtEnd { get { return s_cardPicksAtEnd; } }
public static bool Subscribed { get { return s_subscribed; } }
public static bool Bound { get { return s_root != null; } }
public static bool IsOpen { get { return s_open; } }
@@ -221,7 +241,10 @@ namespace WL.UI
{
EndedSeen++;
s_eliteKillsAtEnd = s_eliteKills; // WL-813q2 — 결과 화면이 쓰는 값은 이 순간의 스냅샷
- CopyResult(in e); // 🔴 배열 3종을 즉시 자기 버퍼로 복사
+ s_cardPicksAtEnd = ZonePickup.PicksThisRun; // WL-813q3 — 813o 카운터도 이 순간에 뜬다(읽기만)
+ // 🔴 WL-813q3 — 배열 3종 + **보스 HP 스냅샷(813w6 `e.bossHpRatio`)** 을 즉시 자기 버퍼로 복사.
+ // 이 뒤에 BossArena 가 미뤄 둔 Park(만피 복구)을 돌려도 결과창 값은 안 바뀐다.
+ CopyResult(in e);
var s = St;
if (s == null || !WLRunUiSettings.Enabled || !s.resultEnabled) return;
if (e.outcome == RunOutcome.Abandon && !s.resultShowOnAbandon)
@@ -537,6 +560,7 @@ namespace WL.UI
case WLRunResultRow.LootByGrade: return s.rowLootLabel;
case WLRunResultRow.Gold: return s.rowGoldLabel;
case WLRunResultRow.EliteKills: return s.rowEliteKillsLabel;
+ case WLRunResultRow.CardPicks: return s.rowCardPicksLabel;
default: return s.rowDeathsLabel;
}
}
@@ -575,6 +599,9 @@ namespace WL.UI
case WLRunResultRow.EliteKills:
sb.Append(s.resultCountElitesInUi ? s_eliteKillsAtEnd : 0);
break;
+ case WLRunResultRow.CardPicks:
+ sb.Append(s.resultCountCardPicksInUi ? s_cardPicksAtEnd : 0);
+ break;
default:
AppendFormat2(sb, s.rowDeathsFormat, s_result.deaths, s_result.potionsUsed);
break;
@@ -582,16 +609,31 @@ namespace WL.UI
}
///
- /// WL-813q2 §1-3 — 보스 처치 행. 처치했으면 mm:ss, 아니면 "미처치".
- /// 🔴 813p `RunResult` 에는 **보스 HP 필드가 없다**(구조체 실측) → 남은 HP 는 813c `BossHpBar` 가
- /// 이번 런에 실제로 물고 있던 마지막 비율(UI 자체 실측)을 쓴다(`resultBossHpFromUi`).
- /// 바가 보스를 한 번도 물지 못했으면 퍼센트 없이 "미처치"만 쓴다.
+ /// WL-813q3 §1-1 — 보스 처치 행. 처치했으면 mm:ss, 아니면 "미처치 (HP 남음 N %)".
+ /// 🔴 남은 HP 의 원천이 바뀌었다: **`RunEnded` 페이로드 `bossHpRatio`**(813w6 D-44 스냅샷 · 0~1 · −1 = 미발생).
+ /// `CopyResult` 가 런 종료 순간에 구조체째 복사해 두므로 **부활 미룸(813q2 D-18)으로 결과가 늦게 그려져도**
+ /// 그 사이 `BossArena.ParkBoss` 가 보스를 만피로 되돌린 것과 무관하게 스냅샷 값 그대로다.
+ /// 페이로드가 −1(보스 미등장 · 못 읽음)이면 813q2 폴백(`BossHpBar.RunBossHpRatio` · UI 자체 실측)으로 간다.
+ /// 둘 다 없으면 퍼센트 없이 "미처치"만 쓴다. 원천은 `LastBossHpSource` 로 실측한다.
///
private static void AppendBossClear(StringBuilder sb, WLRunUiSettings s)
{
if (s_result.bossClearSec >= 0f) { AppendTime(sb, s_result.bossClearSec, s); return; }
- float ratio = s.resultBossHpFromUi ? BossHpBar.RunBossHpRatio : -1f;
+ // ① 813w6 페이로드 스냅샷(Park 이전 값) → ② 813q2 UI 실측 폴백 → ③ 없음
+ float ratio = -1f;
+ LastBossHpSource = "없음";
+ if (s.resultBossHpFromPayload && s_result.bossHpRatio >= 0f)
+ {
+ ratio = s_result.bossHpRatio;
+ LastBossHpSource = "페이로드"; // RunEnded.bossHpRatio (813w6)
+ }
+ else if (s.resultBossHpFromUi)
+ {
+ ratio = BossHpBar.RunBossHpRatio;
+ if (ratio >= 0f) LastBossHpSource = "UI바"; // BossHpBar.RunBossHpRatio (813q2)
+ }
+
if (ratio >= 0f && !string.IsNullOrEmpty(s.resultBossNotClearedHpFormat))
{
// 0 % 는 "죽었는데 처치 기록이 없는" 경우라 올림하지 않는다(살아 있으면 최소 1 %).
@@ -852,6 +894,7 @@ namespace WL.UI
DiedSeen = ReviveRequestSeen = RevivedSeen = SkipPopupReviveCount = 0;
LowerCount = RaiseCount = EliteKilledSeen = 0;
s_eliteKills = s_eliteKillsAtEnd = 0;
+ s_cardPicksAtEnd = 0; LastBossHpSource = "없음"; // WL-813q3
s_lowered = false; LastDeathLog = "";
s_pending = false; UsedCommonPopup = false; LastShowLog = "";
Hide();
@@ -894,6 +937,10 @@ namespace WL.UI
" 전체본것=" + EliteKilledSeen + " · 보스HP(UI 실측)=" +
(BossHpBar.RunBossHpRatio >= 0f ? (BossHpBar.RunBossHpRatio * 100f).ToString("F1") + "%" : "없음") +
" · 마지막 사망 로그 = " + LastDeathLog);
+ sb.AppendLine(" [813q3] 보스HP 원천=" + LastBossHpSource + " · 페이로드 스냅샷 ratio=" +
+ (BossHpRatioAtEnd >= 0f ? (BossHpRatioAtEnd * 100f).ToString("F1") + "%" : "없음(−1)") +
+ " hp=" + BossHpAtEnd.ToString("F0") + "/" + BossHpMaxAtEnd.ToString("F0") +
+ " · 카드 선택 종료스냅=" + s_cardPicksAtEnd + "(현재 " + ZonePickup.PicksThisRun + ")");
if (s != null && s.resultRows != null)
for (int i = 0; i < s.resultRows.Length; i++)
{
@@ -921,7 +968,9 @@ namespace WL.UI
" · [813q2] 사망중미룸=" + s.resultDeferWhileDying + " 런밖즉시부활=" + s.skipRevivePopupAfterRunEnd +
" 전용캔버스=" + s.resultOwnCanvas + "(" + s.resultSortingOrder + "→" + s.resultSortingOrderWhileRevive + ")" +
" 재정렬=" + s.resultLowerWhileRevive + "/" + s.resultRaiseAfterRevive +
- " 엘리트UI집계=" + s.resultCountElitesInUi + " 보스HP UI=" + s.resultBossHpFromUi);
+ " 엘리트UI집계=" + s.resultCountElitesInUi + " 보스HP UI=" + s.resultBossHpFromUi +
+ " · [813q3] 보스HP 페이로드=" + s.resultBossHpFromPayload +
+ " 카드선택UI집계=" + s.resultCountCardPicksInUi);
return sb.ToString();
}
}
diff --git a/Assets/WL/UI/Settings/Resources/WL/WLRunUiSettings.asset b/Assets/WL/UI/Settings/Resources/WL/WLRunUiSettings.asset
index ce89c953d..232eca7e9 100644
--- a/Assets/WL/UI/Settings/Resources/WL/WLRunUiSettings.asset
+++ b/Assets/WL/UI/Settings/Resources/WL/WLRunUiSettings.asset
@@ -102,6 +102,7 @@ MonoBehaviour:
- 3
- 4
- 5
+ - 10
- 6
- 7
- 8
@@ -115,6 +116,7 @@ MonoBehaviour:
rowGoldLabel: "\uACE8\uB4DC"
rowDeathsLabel: "\uC0AC\uB9DD / \uBB3C\uC57D"
rowEliteKillsLabel: "\uC5D8\uB9AC\uD2B8 \uCC98\uCE58"
+ rowCardPicksLabel: "\uCE74\uB4DC \uC120\uD0DD"
rowKillsFormat: "{0} (\uBCF4\uC2A4 {1})"
rowLevelFormat: "{0} \u2192 {1}"
rowDeathsFormat: '{0} / {1}'
@@ -126,7 +128,9 @@ MonoBehaviour:
resultBossNotClearedText: "미처치"
resultBossNotClearedHpFormat: "미처치 (HP 남음 {0} %)"
resultBossHpFromUi: 1
+ resultBossHpFromPayload: 1
resultCountElitesInUi: 1
+ resultCountCardPicksInUi: 1
resultNextRunText: "\uB2E4\uC74C \uB7F0"
resultCloseText: "\uB2EB\uAE30"
resultButtonSizePx: {x: 340, y: 104}
diff --git a/Assets/WL/UI/Settings/WLRunUiSettings.cs b/Assets/WL/UI/Settings/WLRunUiSettings.cs
index f1e3b607a..87d91031a 100644
--- a/Assets/WL/UI/Settings/WLRunUiSettings.cs
+++ b/Assets/WL/UI/Settings/WLRunUiSettings.cs
@@ -48,6 +48,8 @@ namespace WL.UI
DeathsPotions = 8,
/// 엘리트 처치 수(WL-813q2 · 813p RunResult 에 없어 UI 가 직접 센다).
EliteKills = 9,
+ /// 카드 선택 수(WL-813q3 · 813o `ZonePickup.PicksThisRun` · 런 종료 순간 스냅샷).
+ CardPicks = 10,
}
[CreateAssetMenu(fileName = "WLRunUiSettings", menuName = "WL/Run UI Settings", order = 33)]
@@ -346,6 +348,7 @@ namespace WL.UI
public string rowGoldLabel = "골드";
public string rowDeathsLabel = "사망 / 물약";
public string rowEliteKillsLabel = "엘리트 처치";
+ public string rowCardPicksLabel = "카드 선택";
[Tooltip("처치 수 값 문구 — {0} = 전체 처치, {1} = 그중 보스.")]
public string rowKillsFormat = "{0} (보스 {1})";
@@ -380,13 +383,25 @@ namespace WL.UI
public string resultBossNotClearedHpFormat = "미처치 (HP 남음 {0} %)";
[Tooltip("🔴 813p RunResult 에는 보스 HP 필드가 없다(실측). 켜면 813c 보스 HP 바가 이번 런에 " +
- "실제로 물었던 마지막 비율을 쓴다(UI 자체 실측). 끄면 남은 HP 를 쓰지 않는다.")]
+ "실제로 물었던 마지막 비율을 쓴다(UI 자체 실측). 끄면 남은 HP 를 쓰지 않는다.\n" +
+ "WL-813q3 부터는 resultBossHpFromPayload 가 먼저이고 이 값은 폴백이다.")]
public bool resultBossHpFromUi = true;
+ // ── 보스 HP 원천 (WL-813q3 · 813w6 D-44) ──────────────────────────────
+ [Tooltip("🔴 WL-813q3 — 813w6 이 RunResult 에 bossHpRatio/bossHp/bossHpMax 를 실었다(실측). " +
+ "켜면 결과창은 RunEnded 페이로드의 스냅샷(= BossArena.ParkBoss 만피 복구 **이전** 값)을 쓴다. " +
+ "런 종료 순간에 복사해 두므로 부활 미룸(813q2 D-18)으로 늦게 그려도 값이 안 바뀐다. " +
+ "끄면 813q2 동작(= BossHpBar.RunBossHpRatio · 늦게 그리면 만피 100 %)으로 돌아간다.")]
+ public bool resultBossHpFromPayload = true;
+
[Tooltip("🔴 813p RunResult 에는 엘리트 집계가 없다(실측). 켜면 UI 가 CombatEvents.Killed 의 " +
"subRole==Elite 를 런 중에 직접 센다. 끄면 엘리트 행은 0 으로 나온다.")]
public bool resultCountElitesInUi = true;
+ [Tooltip("🔴 WL-813q3 — 813p RunResult 에는 카드 선택 집계도 없다(실측). 켜면 813o " +
+ "ZonePickup.PicksThisRun(런 시작에 0) 을 런 종료 순간에 스냅샷해서 쓴다. 끄면 0.")]
+ public bool resultCountCardPicksInUi = true;
+
[Header("결과 화면 — 버튼")]
[Tooltip("\"다음 런\" 버튼 문구 — 누르면 RunDirector.Restart() 하나만 부른다(813p 공개 API).")]
public string resultNextRunText = "다음 런";