// WL813l2_Probe.cs — #813l2(Q6 D-28 「전투력 토스트가 한 번도 안 뜸 · LastPower 52 불변」 수리) 에디트 모드 프로브 // 에디트 모드 전용 · Play 불필요 · 로그인 0 · 씬 무수정(임시 GameObject 는 즉시 파괴) · 에셋 쓰기 0 // // 상주 에디터: // unity command run_script --project-path --file AgentScripts/WL813l2_Probe.cs --entry WL813l2_Probe.RunAll --timeout 600 // 결과: Console + AgentScripts/WL813l2_PROBE.txt (RESULT PASS / RESULT FAIL n) // // 검사 (발주서 WL-813l2 §1) // ⓪ 설정 에셋 · 새 성장 입력 값 전수 // ① 원인 실증 — 원본 스탯은 레벨업으로 안 움직인다 / 미사용 포인트만 움직인다(원본 Get_Point() 와 대조) // ② 813k 실 Dispatch — LevelUp(prev ATK 100 → new 112) → 토스트 "+N %" · LastPower 변화 // ③ 🔴 D-28 재현·수리 — 스탯 prev == new(원본 기본 동작)인데도 표시된다 / statPointWeight=0 이면 다시 0회 // ④ 팝업 전투력 줄 — 813y StatPopup 에 「전투력 A → B (+N %)」 실제로 붙는가(레벨업 프레임 스탬프) // ⑤ GC 0 — Tick × 1000 · 페이로드 Dispatch × 1000 · ApplyGrowth × 1000(원본 LINQ Get_Point 대조군) // ⑥ C8 — enabled_=0 → 표시 0 / statPointWeight=0·levelWeight=0 → 813l 원래 산식 그대로 // // 에디트 모드 한계(보고서 「미확인」) // · 진짜 PC 는 ServerData 없이 m_Stat 이 안 만들어진다(813l 보고 §5-3) → 실전 전투력 자릿수는 여전히 미확인. // · Canvas 가 없어 UnitsPerPx = 1(px == 유닛). using System; using System.IO; using System.Text; using UnityEngine; using UnityEngine.UI; using TMPro; using WL.Combat.Growth; using WL.UI; public static class WL813l2_Probe { const string OutCommit = "AgentScripts/WL813l2_PROBE.txt"; static StringBuilder sb; static int s_fail; static GameObject s_canvasGo; static StatPopup s_popup; static PCActor s_prevMyPc; static bool s_prevBMyPc; // 에셋 값 임시 변경분 원복용(에셋 저장 0) static bool s_prevSuppress; static float s_prevPointWeight, s_prevLevelWeight; static bool s_prevUsePayload; public static object RunAll() { sb = new StringBuilder(); s_fail = 0; sb.AppendLine("# WL813l2 Probe " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " (edit mode · Play 0 · 로그인 0)"); sb.AppendLine("playMode=" + Application.isPlaying); try { Setup(); Sec0_Asset(); Sec1_Cause(); Sec2_Payload(); Sec3_D28(); Sec4_PopupLine(); Sec5_Gc(); Sec6_Rollback(); } catch (Exception ex) { s_fail++; L("EXCEPTION " + ex); } finally { Teardown(); } sb.AppendLine(); sb.AppendLine(s_fail == 0 ? "RESULT PASS" : "RESULT FAIL " + s_fail); string text = sb.ToString(); try { File.WriteAllText(Path.Combine(Directory.GetCurrentDirectory(), OutCommit), text, new UTF8Encoding(true)); } catch { } Debug.Log(text); return text; } static void L(string s) { sb.AppendLine(s); } static string P(bool ok) { if (!ok) s_fail++; return ok ? "[OK]" : "[FAIL]"; } static bool Near(double a, double b) { return Math.Abs(a - b) < 0.01d; } // ───────────────────────────────────────── 준비 static void Setup() { s_prevMyPc = MyValue.MyPC; s_prevBMyPc = MyValue.bMyPC; MyValue.MyPC = null; // 에디트 모드 = 월드에 PC 없음(페이로드만으로 도는 경로) WLPowerDeltaSettings.RuntimeDisabled = false; WLPowerDeltaSettings.ClearCache(); var s = WLPowerDeltaSettings.Instance; if (s != null) { s_prevSuppress = s.suppressToastOnLevelUp; s_prevPointWeight = s.statPointWeight; s_prevLevelWeight = s.levelWeight; s_prevUsePayload = s.useLevelUpPayload; } PowerDeltaToast.ResetDiagnostics(); PowerDeltaToast.Subscribe(false); PowerDeltaToast.Subscribe(true); L(""); L("── 준비"); L(" " + P(PowerDeltaToast.Subscribed) + " 구독 ON · MyValue.bMyPC=" + MyValue.bMyPC + "(월드 PC 없음 = 로비·에디트 경로)"); } static void Teardown() { try { var s = WLPowerDeltaSettings.Instance; if (s != null) { s.suppressToastOnLevelUp = s_prevSuppress; s.statPointWeight = s_prevPointWeight; s.levelWeight = s_prevLevelWeight; s.useLevelUpPayload = s_prevUsePayload; } if (s_popup != null) s_popup.Subscribe(false); PowerDeltaToast.Subscribe(false); PowerDeltaToast.ResetDiagnostics(); WLPowerDeltaSettings.RuntimeDisabled = false; MyValue.MyPC = s_prevMyPc; MyValue.bMyPC = s_prevBMyPc; if (s_canvasGo != null) UnityEngine.Object.DestroyImmediate(s_canvasGo); } catch { } } // ───────────────────────────────────────── ⓪ 에셋 static void Sec0_Asset() { L(""); L("── ⓪ 설정 에셋 · 성장 입력 값 전수 (C45 · 코드 상수 0)"); var s = WLPowerDeltaSettings.Instance; L(" " + P(s != null) + " WLPowerDeltaSettings 로드"); if (s == null) return; L(" " + P(WLPowerDeltaSettings.Enabled) + " Enabled · enabled_=" + s.enabled_); L(" 성장 입력: useLevelUpPayload=" + s.useLevelUpPayload + " readGrowthFromServerData=" + s.readGrowthFromServerData + " statPointWeight=" + s.statPointWeight + " atkGain=" + s.statPointAtkGain + " hpGain=" + s.statPointHpGain + " perLevel=" + s.statPointsPerLevel + " levelWeight=" + s.levelWeight + " levelBase=" + s.levelBase); L(" 기존 산식: atk=" + s.atkStat + "(폴백 " + s.atkFallbackStat + ") hp=" + s.hpStat + " hpWeight=" + s.hpWeight + " powerScale=" + s.powerScale + " 최소 표시 " + s.minPercentToShow + "%" + " suppressToastOnLevelUp=" + s.suppressToastOnLevelUp); L(" " + P(s.statPointsPerLevel == 5) + " statPointsPerLevel=5 (원본 실측 ServerClass.cs:2770 Get_Point() = (Lv−1)*5 − 사용분)"); L(" " + P(Near(s.statPointAtkGain, 1d) && Near(s.statPointHpGain, 22d)) + " 포인트 환산 ATK +" + s.statPointAtkGain + " / MaxHP +" + s.statPointHpGain + " (원본 실측 MyValue.cs:335-336 dic_MainStatMultiplier[STR] = ATK 1 · MaxHP 22)"); } // ───────────────────────────────────────── ① 원인 실증 static void Sec1_Cause() { L(""); L("── ① 🔴 D-28 원인 실증 — 원본 스탯은 레벨업으로 안 움직인다 · 움직이는 것은 미사용 포인트뿐"); var s = WLPowerDeltaSettings.Instance; // (a) 원본 규약 대조 — SD_PC 를 직접 만들어 레벨만 올려 본다(원본 API 만 · 쓰기 0) var pc1 = new SD_PC(); // Lv=1 · Stat={0,0,0,0} var pc16 = new SD_PC(); pc16.Lv = 16; // 레벨만 1 → 16 L(" Lv 1 → 16 · 스탯 포인트 사용 0 · 장비 변화 0 일 때 원본이 주는 것:"); L(" " + P(pc1.Get_Point() == 0 && pc16.Get_Point() == 75) + " Get_Point() " + pc1.Get_Point() + " → " + pc16.Get_Point() + " (= (Lv−1)*5 · 미사용 포인트만 늘어난다)"); L(" " + P(pc1.Get_Stat(eStat.STR) == 0 && pc16.Get_Stat(eStat.STR) == 0) + " SD_PC.Get_Stat(STR) " + pc1.Get_Stat(eStat.STR) + " → " + pc16.Get_Stat(eStat.STR) + " (= **사용한** 포인트만 셈 · table_classconfig.Get_PureStat:246 이 읽는 값) → ActorStatInfo 는 그대로"); L(" ⇒ `ActorStatInfo.Reset`(MyValue.cs:286-453)은 SD_PC.Lv 를 한 번도 안 읽는다 = 813l 이 폴링해도 값이 상수"); L(" (Q6 D-28 `PollCount 1573` 인데 `LastPower 52.000000` 불변 · 레벨 1→16)"); // (b) 내 포인트 산식이 원본 Get_Point() 와 값이 같은가(GC 0 판 · LINQ 없음) var pcx = new SD_PC(); pcx.Lv = 16; pcx.Stat[0] = 3; pcx.Stat[1] = 2; // 5점 사용 WLPowerMeter.Sample g = default(WLPowerMeter.Sample); g.aspd = s.attackSpeedBase; WLPowerMeter.ApplyGrowth(pcx, s, ref g); L(" " + P((int)g.statPoints == pcx.Get_Point() && (int)g.level == 16) + " ApplyGrowth ↔ 원본 Get_Point() 대조 — 내 값 " + g.statPoints.ToString("F0") + " · 원본 " + pcx.Get_Point() + " · level=" + g.level.ToString("F0") + " (사용 5점 반영)"); // (c) 813l 원래 산식 회귀 — 성장 입력 0 이면 값이 그대로여야 한다 double p1 = WLPowerMeter.Compute(100d, 0d, 0d, 1d, 1000d, s); double p2 = WLPowerMeter.Compute(100d, 0.2d, 0.5d, 1d, 1000d, s); double p3 = WLPowerMeter.Compute(100d, 0d, 0d, 1.3d, 1000d, s); double p4 = WLPowerMeter.Compute(200d, 0.2d, 0.5d, 1.3d, 2000d, s); L(" " + P(Near(p1, 200d) && Near(p2, 210d) && Near(p3, 230d) && Near(p4, 486d)) + " 813l 산식 회귀 4건(성장 입력 0) — " + p1.ToString("F2") + " / " + p2.ToString("F2") + " / " + p3.ToString("F2") + " / " + p4.ToString("F2") + " (기대 200 / 210 / 230 / 486)"); // (d) 성장 입력이 붙으면 값이 움직인다 — 손계산 대조 // (100 + 5×1×1) × 1 × 1 + (1000 + 5×1×22) × 0.1 = 105 + 111.0 = 216.0 double g5 = WLPowerMeter.Compute(100d, 0d, 0d, 1d, 1000d, 2d, 5d, s); L(" " + P(Near(g5, 216d)) + " 포인트 5점 → " + g5.ToString("F2") + " (기대 216.00 = 105 + 111.0)"); double g10 = WLPowerMeter.Compute(100d, 0d, 0d, 1d, 1000d, 3d, 10d, s); L(" " + P(Near(g10, 232d)) + " 포인트 10점 → " + g10.ToString("F2") + " (기대 232.00 · 레벨당 +16.0)"); L(" " + P(WLPowerMeter.PercentDelta(p1, g5) > 7.9d && WLPowerMeter.PercentDelta(p1, g5) < 8.1d) + " 레벨업 1회 증가율 " + WLPowerMeter.PercentDelta(p1, g5).ToString("F2") + "% (최소 표시 " + s.minPercentToShow + "% 를 넘는다)"); // (e) 포인트를 실제로 찍어도 전투력이 튀지 않는다(잠재 → 실현 연속) // 5점 미사용: (100+5)×1 + (1000+110)×0.1 = 216.0 // 5점 사용 후: atk 100+5 · hp 1000+110 · 포인트 0 → 216.0 double spent = WLPowerMeter.Compute(105d, 0d, 0d, 1d, 1110d, 2d, 0d, s); L(" " + P(Near(spent, g5)) + " 포인트 사용 전/후 연속 — 미사용 " + g5.ToString("F2") + " ↔ 사용 후 " + spent.ToString("F2") + " (statPointWeight=1 의 뜻)"); } // ───────────────────────────────────────── ② 813k 실 Dispatch (스탯이 실제로 바뀐 경우) static void Sec2_Payload() { L(""); L("── ② 813k GrowthEvents.LevelUp 실 Dispatch — prev ATK 100 → new 112 (발주서 §1-3)"); var s = WLPowerDeltaSettings.Instance; PowerDeltaToast.ResetDiagnostics(); // (a) 에셋 기본(suppressToastOnLevelUp=1) — 팝업 줄이 표시하므로 토스트는 생략된다 s.suppressToastOnLevelUp = true; string d1 = PowerDeltaToast.RaiseFakeLevelUpForProbe(6, 7, 30, eStat.ATK, 100d, 112d, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d); L(" " + d1); // 기대: new = (112+30)×1×1 + (1000+30×22)×0.1 = 142 + 166 = 308 // prev= (100+25)×1×1 + (1000+25×22)×0.1 = 125 + 155 = 280 (prev 포인트 = 30 − 5) L(" " + P(PowerDeltaToast.LevelUpSeen == 1 && PowerDeltaToast.PayloadUsed == 1) + " 수신 " + PowerDeltaToast.LevelUpSeen + "회 · 소스 \"" + PowerDeltaToast.LastPowerSource + "\""); L(" " + P(Near(PowerDeltaToast.LastPayloadPrev, 280d) && Near(PowerDeltaToast.LastPayloadNew, 308d)) + " 페이로드 전투력 prev=" + PowerDeltaToast.LastPayloadPrev.ToString("F2") + " new=" + PowerDeltaToast.LastPayloadNew.ToString("F2") + " (기대 280.00 / 308.00 · 손계산)"); L(" " + P(PowerDeltaToast.HasPower && Near(PowerDeltaToast.LastPower, 308d)) + " 🔴 LastPower 0 → " + PowerDeltaToast.LastPower.ToString("F2") + " (D-28 = 여기가 52 로 고정이었다)"); L(" " + P(PowerDeltaToast.SuppressedByPopup == 1 && PowerDeltaToast.PowerToastCount == 0) + " 레벨업 프레임 토스트 생략(Suppressed=" + PowerDeltaToast.SuppressedByPopup + " Toast=" + PowerDeltaToast.PowerToastCount + ") — 같은 값을 팝업 줄이 보여준다"); string line = PowerDeltaToast.BuildStatPopupLine(); L(" " + P(line != null && line.Contains("280") && line.Contains("308") && line.Contains("10.0")) + " 팝업 줄 \"" + line + "\" (기대 280 → 308 · +10.0%)"); // (b) 토스트 자체 — 생략 스위치를 끄고 같은 페이로드를 다시 흘린다 PowerDeltaToast.ResetDiagnostics(); s.suppressToastOnLevelUp = false; PowerDeltaToast.RaiseFakeLevelUpForProbe(6, 7, 30, eStat.ATK, 100d, 112d, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d); L(" " + P(PowerDeltaToast.PowerToastCount == 1 && PowerDeltaToast.LastText.Contains("10.0")) + " 토스트 " + PowerDeltaToast.PowerToastCount + "회 · 문구 \"" + PowerDeltaToast.LastText + "\" (기대 \"전투력 +10.0%\")"); s.suppressToastOnLevelUp = s_prevSuppress; // (c) 페이로드를 끄면(=813l 원래 동작) 에디트 모드에서는 읽을 스탯이 없어 표시 0 — D-28 의 모양 PowerDeltaToast.ResetDiagnostics(); s.useLevelUpPayload = false; PowerDeltaToast.RaiseFakeLevelUpForProbe(6, 7, 30, eStat.ATK, 100d, 112d, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d); L(" " + P(PowerDeltaToast.PayloadUsed == 0 && PowerDeltaToast.PowerToastCount == 0 && !PowerDeltaToast.HasPower) + " useLevelUpPayload=0 → 소스 \"" + PowerDeltaToast.LastPowerSource + "\" · 토스트 " + PowerDeltaToast.PowerToastCount + "회 · LastPower 없음(= 813l 원래 경로)"); s.useLevelUpPayload = s_prevUsePayload; } // ───────────────────────────────────────── ③ D-28 재현 · 수리 static void Sec3_D28() { L(""); L("── ③ 🔴 D-28 재현 · 수리 — 스탯이 **하나도 안 바뀌는** 원본 기본 동작(813k 보고 ①)에서"); var s = WLPowerDeltaSettings.Instance; // (a) 수리 후 — 스탯 prev == new 인데도 표시된다(포인트가 늘었으니까) PowerDeltaToast.ResetDiagnostics(); s.suppressToastOnLevelUp = false; string d = PowerDeltaToast.RaiseFakeLevelUpForProbe(7, 8, 35, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d, eStat.ATK, 100d, 100d); L(" " + d + " · 스탯 3종 prev == new (원본 기본값)"); // 기대: new = (100+35)×1 + (1000+35×22)×0.1 = 135 + 177 = 312 // prev= (100+30)×1 + (1000+30×22)×0.1 = 130 + 166 = 296 → +5.41% L(" " + P(Near(PowerDeltaToast.LastPayloadPrev, 296d) && Near(PowerDeltaToast.LastPayloadNew, 312d)) + " 전투력 " + PowerDeltaToast.LastPayloadPrev.ToString("F2") + " → " + PowerDeltaToast.LastPayloadNew.ToString("F2") + " (기대 296.00 → 312.00)"); L(" " + P(PowerDeltaToast.PowerToastCount == 1 && PowerDeltaToast.LastText.Contains("5.4")) + " ✅ 수리 후 — 토스트 " + PowerDeltaToast.PowerToastCount + "회 \"" + PowerDeltaToast.LastText + "\""); // (b) 수리 전 모양 — 성장 입력을 0 으로 두면 그대로 D-28(값 불변 · 표시 0) PowerDeltaToast.ResetDiagnostics(); s.statPointWeight = 0f; s.levelWeight = 0f; PowerDeltaToast.RaiseFakeLevelUpForProbe(7, 8, 35, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d, eStat.ATK, 100d, 100d); double p0 = PowerDeltaToast.LastPayloadPrev, n0 = PowerDeltaToast.LastPayloadNew; PowerDeltaToast.RaiseFakeLevelUpForProbe(8, 9, 40, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d, eStat.ATK, 100d, 100d); L(" " + P(Near(p0, n0) && Near(PowerDeltaToast.LastPayloadNew, n0) && PowerDeltaToast.PowerToastCount == 0) + " ❌ 재현(statPointWeight=0) — 레벨 7→8→9 인데 전투력 " + p0.ToString("F2") + " 불변 · 토스트 " + PowerDeltaToast.PowerToastCount + "회 = Q6 D-28 그대로"); s.statPointWeight = s_prevPointWeight; s.levelWeight = s_prevLevelWeight; s.suppressToastOnLevelUp = s_prevSuppress; // (c) 다단 레벨업(levelsGained > 1) 도 prev 포인트를 제대로 역산하는가 PowerDeltaToast.ResetDiagnostics(); PowerDeltaToast.RaiseFakeLevelUpForProbe(5, 8, 35, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d, eStat.ATK, 100d, 100d); // prev 포인트 = 35 − 3×5 = 20 → prev = (100+20)×1 + (1000+440)×0.1 = 120 + 144 = 264 L(" " + P(Near(PowerDeltaToast.LastPayloadPrev, 264d)) + " 다단(5→8 · 3레벨) prev 포인트 역산 → prev 전투력 " + PowerDeltaToast.LastPayloadPrev.ToString("F2") + " (기대 264.00 = 포인트 20점)"); } // ───────────────────────────────────────── ④ 팝업 줄 (813y StatPopup 왕복) static void Sec4_PopupLine() { L(""); L("── ④ 레벨업 팝업 전투력 줄 — 813y StatPopup 왕복(구독 → Dispatch → 줄)"); var s = WLPowerDeltaSettings.Instance; s_canvasGo = new GameObject("__WL813l2_Canvas", typeof(Canvas), typeof(CanvasScaler)); s_canvasGo.layer = 5; var croot = (RectTransform)s_canvasGo.transform; croot.sizeDelta = new Vector2(1080f, 1920f); var prt = new GameObject("WL_StatPopup", typeof(RectTransform)); prt.layer = 5; prt.transform.SetParent(croot, false); s_popup = prt.AddComponent(); L(" init: " + s_popup.Initialize()); L(" " + P(StatPopup.PowerLineOn) + " StatPopup.PowerLineOn"); L(" " + P(s_popup.Subscribed) + " StatPopup 구독=" + s_popup.Subscribed + " · GrowthEvents.LevelUp 구독자=" + GrowthEvents.LevelUp.Count + "(StatPopup + PowerDeltaToast)"); PowerDeltaToast.ResetDiagnostics(); PowerDeltaToast.RaiseFakeLevelUpForProbe(9, 10, 45, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d, eStat.ATK, 100d, 100d); // new = (100+45) + (1000+990)×0.1 = 145 + 199 = 344 · prev = (100+40) + (1000+880)×0.1 = 140 + 188 = 328 L(" 팝업 표시: rows=" + s_popup.LastRowCount + " level=" + s_popup.LastLevel + " text=\"" + s_popup.LastText + "\""); L(" " + P(!string.IsNullOrEmpty(s_popup.LastPowerLine) && s_popup.LastPowerLine.Contains("328") && s_popup.LastPowerLine.Contains("344")) + " 🔴 팝업 전투력 줄 \"" + s_popup.LastPowerLine + "\" (기대 328 → 344 · +4.9%)"); L(" " + P(PowerDeltaToast.SuppressedByPopup + PowerDeltaToast.PowerToastCount == 1) + " 중복 표시 0 — Suppressed=" + PowerDeltaToast.SuppressedByPopup + " Toast=" + PowerDeltaToast.PowerToastCount); // 구독 순서 무관 — StatPopup 이 먼저 구독돼 있어도 같은 값이 나온다(프레임 스탬프) L(" " + P(PowerDeltaToast.LastPayloadPrev > 0d) + " 프레임 스탬프 prev=" + PowerDeltaToast.LastPayloadPrev.ToString("F2") + " new=" + PowerDeltaToast.LastPayloadNew.ToString("F2") + " → 팝업/토스트가 같은 값을 본다(구독 순서 무관)"); } // ───────────────────────────────────────── ⑤ GC static void Sec5_Gc() { L(""); L("── ⑤ GC 0 (폴링 · 페이로드 · 포인트 산식)"); var s = WLPowerDeltaSettings.Instance; PowerDeltaToast.ResetDiagnostics(); PowerDeltaToast.Observe(1000d, 0f, "base"); long a0 = Gc(), b0 = Prof(); for (int i = 0; i < 1000; i++) PowerDeltaToast.Tick(3000f + i * 0.001f, 0.001f); long a1 = Gc(), b1 = Prof(); L(" " + P(a1 - a0 <= 0 && b1 - b0 <= 0) + " Tick × 1000 → GetTotalMemory " + (a1 - a0) + " B · MonoUsedSize " + (b1 - b0) + " B (폴링 " + PowerDeltaToast.PollCount + "회)"); // 내 포인트 산식(for 루프 · 할당 0) vs 원본 Get_Point()(LINQ Select+Sum · 할당 있음) var pcx = new SD_PC(); pcx.Lv = 30; pcx.Stat[0] = 7; WLPowerMeter.Sample g = default(WLPowerMeter.Sample); g.aspd = s.attackSpeedBase; WLPowerMeter.ApplyGrowth(pcx, s, ref g); // 워밍업 long c0 = Gc(); for (int i = 0; i < 1000; i++) WLPowerMeter.ApplyGrowth(pcx, s, ref g); long c1 = Gc(); int sink = 0; long d0 = Gc(); for (int i = 0; i < 1000; i++) sink += pcx.Get_Point(); long d1 = Gc(); L(" " + P(c1 - c0 <= 0) + " ApplyGrowth × 1000 = " + (c1 - c0) + " B ↔ 원본 Get_Point() × 1000 = " + (d1 - d0) + " B (원본은 LINQ Select+Sum · sink=" + sink + ")"); // 레벨업 1회당 비용 — 프레임 경로가 아니라 **이벤트 1회** 경로다. // 기준선(A) = 프로브 헬퍼의 반환 문자열만(구독 off) · 측정(B) = 거기에 내 페이로드 경로가 얹힌 것. if (s_popup != null) s_popup.Subscribe(false); // 813y 팝업 비용은 이 측정에서 뺀다 PowerDeltaToast.Subscribe(false); long e0 = Gc(); for (int i = 0; i < 1000; i++) PowerDeltaToast.RaiseFakeLevelUpForProbe(7, 8, 35, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d, eStat.ATK, 100d, 100d); long e1 = Gc(); PowerDeltaToast.Subscribe(true); PowerDeltaToast.ResetDiagnostics(); long f0 = Gc(); for (int i = 0; i < 1000; i++) PowerDeltaToast.RaiseFakeLevelUpForProbe(7, 8, 35, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d, eStat.ATK, 100d, 100d); long f1 = Gc(); L(" 레벨업 Dispatch × 1000 — 헬퍼만 " + (e0 == e1 ? "0" : (e1 - e0).ToString()) + " B · 구독 포함 " + (f1 - f0) + " B → 내 경로 몫 " + ((f1 - f0) - (e1 - e0)) + " B / 1000회 (813l 때부터 있던 진단 문자열 · **프레임 경로 아님**)"); long h0 = Gc(); var keep = new object[1000]; for (int i = 0; i < 1000; i++) keep[i] = new object(); long h1 = Gc(); L(" 대조군 new object() × 1000 = " + (h1 - h0) + " B (0 이 아니어야 측정이 살아 있다) · keep=" + keep.Length); PowerDeltaToast.ResetDiagnostics(); } static long Gc() { return GC.GetTotalMemory(false); } static long Prof() { return UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong(); } // ───────────────────────────────────────── ⑥ C8 static void Sec6_Rollback() { L(""); L("── ⑥ C8 롤백"); var s = WLPowerDeltaSettings.Instance; PowerDeltaToast.ResetDiagnostics(); WLPowerDeltaSettings.RuntimeDisabled = true; L(" " + P(!WLPowerDeltaSettings.Enabled) + " Enabled=false"); PowerDeltaToast.RaiseFakeLevelUpForProbe(7, 8, 35, eStat.MaxHP, 1000d, 1000d, eStat.MaxMP, 200d, 200d, eStat.ATK, 100d, 100d); L(" " + P(PowerDeltaToast.LevelUpSeen == 0 && PowerDeltaToast.PowerToastCount == 0) + " 레벨업 무시(LevelUpSeen=" + PowerDeltaToast.LevelUpSeen + " Toast=" + PowerDeltaToast.PowerToastCount + ")"); L(" " + P(PowerDeltaToast.BuildStatPopupLine() == null) + " BuildStatPopupLine()=null"); WLPowerDeltaSettings.RuntimeDisabled = false; L(" " + P(WLPowerDeltaSettings.Enabled) + " 해제 후 Enabled=true"); // 성장 입력만 끄면 813l 원래 산식 그대로(= 이번 변경 전 동작) s.statPointWeight = 0f; s.levelWeight = 0f; double r1 = WLPowerMeter.Compute(100d, 0d, 0d, 1d, 1000d, 16d, 75d, s); s.statPointWeight = s_prevPointWeight; s.levelWeight = s_prevLevelWeight; L(" " + P(Near(r1, 200d)) + " statPointWeight=0 · levelWeight=0 → 레벨 16 · 포인트 75 여도 " + r1.ToString("F2") + " (813l 원래 산식과 동일 = 값 하나로 되돌린다)"); } }