Project_WL/AgentScripts/WL813l2_Probe.cs

427 lines
25 KiB
C#
Raw Normal View History

// 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 원래 산식과 동일 = 값 하나로 되돌린다)");
}
}