From 3fc3307d0ecc69b1408bf4b7a52abe42eaef6bbf Mon Sep 17 00:00:00 2001 From: swrring Date: Wed, 9 Sep 2026 19:19:34 +0900 Subject: [PATCH] =?UTF-8?q?[WL-813q3]=20=EA=B2=B0=EA=B3=BC=EC=B0=BD=20?= =?UTF-8?q?=EB=B3=B4=EC=8A=A4=20HP=20=EC=8A=A4=EB=83=85=EC=83=B7=20?= =?UTF-8?q?=EA=B0=92=20=EC=82=AC=EC=9A=A9=20(#813)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AgentScripts/WL813q3_PROBE.txt | 98 +++++ AgentScripts/WL813q3_Probe.cs | 384 ++++++++++++++++++ Assets/WL/UI/Scripts/RunResultUI.cs | 63 ++- .../Resources/WL/WLRunUiSettings.asset | 4 + Assets/WL/UI/Settings/WLRunUiSettings.cs | 17 +- 5 files changed, 558 insertions(+), 8 deletions(-) create mode 100644 AgentScripts/WL813q3_PROBE.txt create mode 100644 AgentScripts/WL813q3_Probe.cs 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 = "다음 런";