Project_WL/AgentScripts/WL813l2_Probe.cs

427 lines
25 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.

// WL813l2_Probe.cs — #813l2(Q6 D-28 「전투력 토스트가 한 번도 안 뜸 · LastPower 52 불변」 수리) 에디트 모드 프로브
// 에디트 모드 전용 · Play 불필요 · 로그인 0 · 씬 무수정(임시 GameObject 는 즉시 파괴) · 에셋 쓰기 0
//
// 상주 에디터:
// unity command run_script --project-path <wt> --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() = (Lv1)*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() + " (= (Lv1)*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<StatPopup>();
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 원래 산식과 동일 = 값 하나로 되돌린다)");
}
}