// WL813w6_Probe.cs — #813w6(재스폰 보스 고정 D-43 · 결과 HP 스냅샷 순서 D-44) 프로브 // 에디트 모드 전용 · Play 불필요 · 로그인 0 · 씬/에셋 무수정(임시 GameObject·임시 SO 는 즉시 파괴/회수) // // 상주 에디터: // unity command run_script --project-path --file AgentScripts/WL813w6_Probe.cs --entry WL813w6_Probe.RunAll --timeout 300 // 결과: Console + AgentScripts/WL813w6_PROBE.txt (RESULT PASS / RESULT FAIL n) // // 무엇을 증명하나 (발주서 WL-813w6 §1 · 근거 = qa/W2-9_재채점_v7.md D-43 · D-44) // ① 에셋·SOT — 813w6 신규 6필드 값 실측 · 정합성 · 코드 상수 0 // ② D-43 — 런 밖 가짜 `Spawned(isBoss)` → **즉시 고정 + 홀드 무장** · 게이트 개방 뒤 등장은 무개입 // ③ D-43 — 홀드가 「게이트 개방까지」 유지되는가(시간제 만료 뒤에도) · 게이트가 열리면 해제되는가 // ④ D-43 — 게이트 개방 전 런 중 이탈 → **회복 없이** 되돌림(PreGateLeashResets) // ⑤ D-44 — 진짜 `RunDirector.EndRun` 발행 시점의 페이로드가 **Park 이전 HP**(24.4 %) 인가 · // 발행 도중 `ParkCount` 불변 · 발행 뒤 `PumpDeferredPark` 로 고정 // ⑥ GC 0 — 스냅샷 ×100 · 홀드 판정 ×100 · 미뤄둔 고정 펌프 ×100 · 게이트 전 틱 ×100 // ⑦ C8 롤백 — 신규 6스위치 0 → 813w5 동작 그대로 · RuntimeDisabled → 무동작 // // 에디트 모드 한계(보고서 「미확인」) // · 살아 있는 MobActor(m_Stat·Animator·NavMeshAgent)가 없어 ParkBoss 의 실제 워프·만피는 못 돌린다 // (813d/813w3/813w5 와 같은 한계) → 고정 「판단·홀드 무장」까지를 증명한다. // · 같은 이유로 실제 보스 HP 읽기(Get_HP/Get_MaxHP)는 주입값(SetBossHpForProbe)으로 대체한다. // · Time.unscaledTime 이 스크립트 실행 중 멈춰 있다 → 시간 의존 판정은 BossArena.SetProbeTime 으로 먹인다. using System; using System.IO; using System.Text; using UnityEngine; using WL.Combat.Boss; using WL.Combat.Core; using WL.Combat.Run; public static class WL813w6_Probe { const string OutCommit = "AgentScripts/WL813w6_PROBE.txt"; static StringBuilder sb; static int s_fail; static GameObject s_pcGo, s_fbdGo; static PCActor s_pc, s_prevMyPc; static FieldBossData s_fbd; // ⑤ 가 쓰는 「발행 시점」 기록 static float s_seenRatio; static double s_seenHp, s_seenMax; static int s_seenParkCount, s_seenDeferred; static bool s_seenPending; static int s_seenCount; static readonly CombatHandler h_seen = OnRunEndedSeen; public static object RunAll() { sb = new StringBuilder(); s_fail = 0; sb.AppendLine("# WL813w6 Probe " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " (edit mode · Play 0 · 로그인 0)"); sb.AppendLine("playMode=" + Application.isPlaying); try { Setup(); Sec1_Asset(); Sec2_RespawnPark(); Sec3_HoldUntilGate(); Sec4_PreGateLeash(); Sec5_HpSnapshotOrder(); Sec6_GC(); Sec7_C8(); } catch (Exception ex) { L("[FAIL] 예외 — " + ex.GetType().Name + ": " + ex.Message); L(ex.StackTrace); s_fail++; } finally { Teardown(); } L(""); L(s_fail == 0 ? "RESULT PASS" : "RESULT FAIL " + s_fail); var text = sb.ToString(); try { File.WriteAllText(OutCommit, text, new UTF8Encoding(false)); } catch { } Debug.Log(text); return text; } // ───────────────────────────────────────── 유틸 static void L(string m) { sb.AppendLine(m); } static string P(bool ok) { if (!ok) s_fail++; return ok ? "[PASS]" : "[FAIL]"; } static long Alloc() { return GC.GetTotalMemory(false); } /// 실에셋을 복사한 임시 SO(실에셋은 절대 안 건드린다). 쓰고 나면 DestroyImmediate + 캐시 원복. static WLBossArenaSettings Tmp() { var t = UnityEngine.Object.Instantiate(WLBossArenaSettings.Instance); t.hideFlags = HideFlags.HideAndDontSave; return t; } static void UseTmp(WLBossArenaSettings t) { WLBossArenaSettings.SetInstanceForProbe(t); } static void UseReal() { WLBossArenaSettings.SetInstanceForProbe(null); } static void OnRunEndedSeen(in RunResult e) { s_seenCount++; s_seenRatio = e.bossHpRatio; s_seenHp = e.bossHp; s_seenMax = e.bossHpMax; s_seenParkCount = BossArena.ParkCount; s_seenDeferred = BossArena.DeferredParkCount; s_seenPending = BossArena.ParkPending; } /// 가짜 보스(살아 있는 것으로 보이는 MobActor · m_Stat 은 없다). static MobActor MakeBoss(string name) { var go = new GameObject(name); var m = go.AddComponent(); m.m_Role = eRole.Mob; m.m_SubRole = eSubRol.Boss; return m; } static void KillBoss(MobActor m) { if (m == null) return; BossArena.SetBossForProbe(null); UnityEngine.Object.DestroyImmediate(m.gameObject); } static void Setup() { s_prevMyPc = MyValue.MyPC; s_pcGo = new GameObject("__WL813w6_PC"); s_pc = s_pcGo.AddComponent(); s_pc.m_Role = eRole.PC; s_pc.m_SubRole = eSubRol.None; MyValue.MyPC = s_pc; WLBossArenaSettings.RuntimeDisabled = false; BossArena.ResetAll(); BossArena.EnsureSubscribed(); BossArena.EnsureRunSubscribed(); RunDirector.ResetAll(); // 게이트 무장 — 원본 훅과 같은 진입점(HoldSpawn)으로 Arm 한다. var cfg = WLBossArenaSettings.Instance; s_fbdGo = new GameObject("__WL813w6_FBD"); s_fbd = s_fbdGo.AddComponent(); s_fbd.n_SpawnerId = cfg != null ? cfg.bossSpawnerId : 813010; s_fbd.n_MonsterID = cfg != null ? cfg.bossMonsterId : 10006; bool held = BossArena.HoldSpawn(s_fbd, NoopResume); L(""); L("── ⓪ 준비"); L(" " + P(WLBossArenaSettings.Instance != null) + " WLBossArenaSettings 로드 · Active=" + WLBossArenaSettings.Active); L(" " + P(BossArena.Subscribed) + " BossArena.Subscribed(CombatEvents)"); L(" " + P(BossArena.RunSubscribed) + " BossArena.RunSubscribed(RunEvents.RunEnded · 813w5)"); L(" " + P(held && BossArena.GateArmed && !BossArena.GateOpen) + " 🔴 HoldSpawn(FieldBossData 813010) → 게이트 무장(armed=" + BossArena.GateArmed + " open=" + BossArena.GateOpen + ")"); } static void NoopResume() { } static void Teardown() { try { UseReal(); BossArena.SetProbeTime(-1f); BossArena.SetBossHpForProbe(-2f, 0d, 0d); BossArena.ClearBossHold(); BossArena.SetRunActiveForProbe(0); BossArena.SetBossForProbe(null); RunEvents.RunEnded.Remove(h_seen); BossArena.ResetAll(); RunDirector.ResetAll(); WLBossArenaSettings.RuntimeDisabled = false; MyValue.MyPC = s_prevMyPc; if (s_pcGo != null) UnityEngine.Object.DestroyImmediate(s_pcGo); if (s_fbdGo != null) UnityEngine.Object.DestroyImmediate(s_fbdGo); } catch { } } // ───────────────────────────────────────────────────────────────────── // ① 에셋 · SOT // ───────────────────────────────────────────────────────────────────── static void Sec1_Asset() { L(""); L("── ① 에셋 (Assets/WL/Combat/Settings/Resources/WL/WLBossArenaSettings.asset · 값 전부 SO = C45)"); var b = WLBossArenaSettings.Instance; if (b == null) { L(" [FAIL] 에셋 없음"); s_fail++; return; } L(" 신규 6필드: parkBossOnRespawn=" + b.parkBossOnRespawn + " bossHoldUntilGateOpen=" + b.bossHoldUntilGateOpen + " preGateLeashDistanceOnly=" + b.preGateLeashDistanceOnly + " parkBossOnRunEndDeferred=" + b.parkBossOnRunEndDeferred + " parkBossOnRunEndDelaySeconds=" + b.parkBossOnRunEndDelaySeconds + " snapshotBossHpOnRunEnd=" + b.snapshotBossHpOnRunEnd); L(" " + P(b.parkBossOnRespawn) + " parkBossOnRespawn=1 (D-43 재스폰 즉시 고정)"); L(" " + P(b.bossHoldUntilGateOpen) + " bossHoldUntilGateOpen=1 (발주서 §1-1 기본 = 게이트 개방까지)"); L(" " + P(b.preGateLeashDistanceOnly) + " preGateLeashDistanceOnly=1 (게이트 전 거리 판정 · 회복 0)"); L(" " + P(b.parkBossOnRunEndDeferred) + " parkBossOnRunEndDeferred=1 (D-44 스냅샷 → 발행 → Park)"); L(" " + P(Mathf.Approximately(b.parkBossOnRunEndDelaySeconds, 0f)) + " parkBossOnRunEndDelaySeconds=0 (다음 Tick)"); L(" " + P(b.snapshotBossHpOnRunEnd) + " snapshotBossHpOnRunEnd=1 (RunResult.bossHpRatio 채움)"); L(" 기존 값(813w5) 대조: nextRunBossHoldSeconds=" + b.nextRunBossHoldSeconds + " gateMinSeconds=" + b.gateMinSeconds + " arenaRadius=" + b.arenaRadius + " leashMargin=" + b.leashMargin); L(" " + P(b.parkBossOnRunEndDelaySeconds < b.nextRunBossHoldSeconds) + " 지연(" + b.parkBossOnRunEndDelaySeconds + "s) < 홀드(" + b.nextRunBossHoldSeconds + "s) — 고정 전 공백이 홀드보다 짧다"); L(" " + P(b.parkBossOnRunEnd) + " 813w5 parkBossOnRunEnd=1 유지(런 종료 고정 경로 자체는 그대로)"); } // ───────────────────────────────────────────────────────────────────── // ② D-43 — 런 밖 재스폰 보스 즉시 고정 // ───────────────────────────────────────────────────────────────────── static void Sec2_RespawnPark() { L(""); L("── ② D-43 재스폰 보스 고정 (Q9 R2n: ParkCount=0 · 홀드 미무장 · t=41.9 mobStatus=Chase)"); var cfg = WLBossArenaSettings.Instance; BossArena.ClearBossHold(); BossArena.SetRunActiveForProbe(-1); // 런 밖(결과창·다음 런 대기) L(" " + P(!BossArena.RunActive) + " RunActive=false 강제 · GateOpen=" + BossArena.GateOpen); var boss = MakeBoss("__WL813w6_Boss_A"); try { BossArena.SetBossForProbe(boss); L(" " + P(!boss.IsDead()) + " 가짜 보스 살아 있음(activeInHierarchy=true)"); L(" " + P(!BossArena.BossHeld) + " 스폰 전 홀드 없음(BossHeld=false)"); int a0 = BossArena.RespawnParkAttempts; BossArena.NoteSpawn(cfg.bossSpawnerId, true); // = CombatEvents.Spawned(isBoss) 가 들어오는 그 경로 L(" " + P(BossArena.RespawnParkAttempts == a0 + 1) + " 🔴 Spawned(isBoss · 게이트 닫힘) → 고정 시도 +1 (attempts=" + BossArena.RespawnParkAttempts + " · 실제 워프 성공 " + BossArena.RespawnParkCount + "회 = 에디트 모드 한계)"); L(" " + P(BossArena.BossHeld) + " 🔴 **홀드 무장됨**(BossHeld=true) — D-43 의 본질(홀드 미무장)이 사라졌다"); L(" " + P(BossArena.HoldUntilGate) + " 🔴 홀드 모드 = 「게이트 개방까지」(HoldUntilGate=true)"); L(" " + P(BossArena.BossSpawned) + " BossSpawned=true(813p 폴백이 BossStarted 를 낼 수 있다 · D-27ⓒ 유지)"); // 게이트가 열린 뒤의 등장(= 정상 보스전)은 건드리지 않는다 BossArena.ClearBossHold(); BossArena.ForceOpenGate(); int a1 = BossArena.RespawnParkAttempts; BossArena.NoteSpawn(cfg.bossSpawnerId, true); L(" " + P(BossArena.GateOpen && BossArena.RespawnParkAttempts == a1) + " 게이트 개방 뒤 Spawned(isBoss) → 고정 0(정상 보스전 · attempts 불변 " + BossArena.RespawnParkAttempts + ")"); L(" " + P(!BossArena.BossHeld) + " 그 보스는 홀드 없이 싸운다(BossHeld=false)"); // 잡몹 스폰은 이 경로에 들어오지 않는다 int a2 = BossArena.RespawnParkAttempts; BossArena.NoteSpawn(cfg.zoneSpawnerIds[0], false); L(" " + P(BossArena.RespawnParkAttempts == a2) + " 잡몹 Spawned(isBoss=false) → 고정 0"); } finally { KillBoss(boss); } } // ───────────────────────────────────────────────────────────────────── // ③ D-43 — 홀드가 「게이트 개방까지」 유지되는가 // ───────────────────────────────────────────────────────────────────── static void Sec3_HoldUntilGate() { L(""); L("── ③ 홀드 = 「게이트 개방까지」 (813w5 는 30 s 뒤 풀려 R1n t=77 에 중심 21.89 m 까지 왔다)"); var cfg = WLBossArenaSettings.Instance; BossArena.ResetAll(); BossArena.EnsureSubscribed(); BossArena.HoldSpawn(s_fbd, NoopResume); BossArena.SetRunActiveForProbe(-1); BossArena.SetProbeTime(1000f); BossArena.ArmBossHoldForProbe(); L(" arm@t=1000 → BossHoldUntil=" + BossArena.BossHoldUntil.ToString("F1") + " (= 1000 + nextRunBossHoldSeconds " + cfg.nextRunBossHoldSeconds + ")"); L(" " + P(BossArena.HoldUntilGate) + " HoldUntilGate=true(bossHoldUntilGateOpen=1)"); BossArena.SetProbeTime(1000f + cfg.nextRunBossHoldSeconds - 1f); L(" " + P(BossArena.BossHeld) + " t=만료 −1s → 홀드 유지(시간제 구간 · 813w5 와 동일)"); BossArena.SetProbeTime(1000f + cfg.nextRunBossHoldSeconds + 1f); L(" " + P(BossArena.BossHeld) + " 🔴 t=만료 +1s → **여전히 홀드**(813w5 라면 여기서 풀려 존0 로 걸어 나갔다)"); BossArena.SetProbeTime(1000f + 10000f); L(" " + P(BossArena.BossHeld) + " 🔴 t=만료 +9970s → 여전히 홀드(게이트가 닫혀 있는 한 유지)"); int hc0 = BossArena.HoldClearedByGate; BossArena.ForceOpenGate(); L(" " + P(BossArena.HoldClearedByGate == hc0 + 1) + " 🔴 게이트 개방(Release) → 홀드 해제 +1 (" + BossArena.HoldClearedByGate + "회)"); L(" " + P(!BossArena.BossHeld && !BossArena.HoldUntilGate && BossArena.BossHoldUntil < 0f) + " 홀드 완전 해제(BossHeld=false · HoldUntilGate=false · BossHoldUntil=" + BossArena.BossHoldUntil.ToString("F1") + ") = 정상 보스전"); BossArena.SetProbeTime(-1f); } // ───────────────────────────────────────────────────────────────────── // ④ D-43 — 게이트 개방 전 거리 판정(회복 0) // ───────────────────────────────────────────────────────────────────── static void Sec4_PreGateLeash() { L(""); L("── ④ 게이트 개방 전 리쉬 = 거리 판정만 (런 중이라도 회복 0 · D-27 ③ 재발 방지)"); var b = WLBossArenaSettings.Instance; float lim = b.arenaRadius + b.leashMargin; L(" 아레나 중심=" + BossArena.Center.ToString("F1") + " 반경=" + b.arenaRadius + " leashMargin=" + b.leashMargin + " → 되돌림 경계 " + lim + "m"); BossArena.ResetAll(); BossArena.EnsureSubscribed(); BossArena.HoldSpawn(s_fbd, NoopResume); BossArena.SetRunActiveForProbe(1); // 런 중 강제 BossArena.ClearBossHold(); // 홀드 없이(= C8 로 홀드를 꺼도 거리는 지킨다) L(" " + P(BossArena.RunActive && !BossArena.GateOpen && !BossArena.BossHeld) + " RunActive=true · GateOpen=false · BossHeld=false (D-43 의 그 구간)"); int r0 = BossArena.PreGateLeashResets, h0 = BossArena.LeashHealCount; BossArena.LeashTick(); L(" " + P(BossArena.PreGateLeashResets == r0) + " 보스 없음 → 보스 가드에서 먼저 끊긴다(카운터 불변)"); var boss = MakeBoss("__WL813w6_Boss_B"); try { // 중심에서 100 m 밖에 세운다(= 존0 쪽으로 걸어 나간 상태 · Q9 R2n 은 중심 12.7~19.0 m 였다) boss.transform.position = BossArena.Center + new Vector3(100f, 0f, 0f); BossArena.SetBossForProbe(boss); float d = Vector3.Distance(new Vector3(boss.transform.position.x, 0f, boss.transform.position.z), new Vector3(BossArena.Center.x, 0f, BossArena.Center.z)); L(" " + P(d > lim) + " 가짜 보스 위치 " + boss.transform.position.ToString("F1") + " → 중심 거리 " + d.ToString("F1") + "m > 경계 " + lim + "m"); string err = null; try { BossArena.LeashTick(); } catch (Exception ex) { err = ex.GetType().Name; } // Set_Warp 는 NavMeshAgent 가 필요하다(에디트 모드 한계) L(" " + P(BossArena.PreGateLeashResets == r0 + 1) + " 🔴 게이트 전 이탈 → 되돌림 경로 진입 +1 (PreGateLeashResets=" + BossArena.PreGateLeashResets + (err != null ? " · 워프 본체는 " + err + " = 에디트 모드 한계)" : ")")); L(" " + P(BossArena.LeashHealCount == h0) + " 🔴 **회복 0**(LeashHealCount 불변 " + BossArena.LeashHealCount + ") — D-27 ③(런 밖 회복으로 HP 되돌아옴) 재발 없음"); // C8 — preGateLeashDistanceOnly=0 → 813w5 그대로(경로 미진입) var t = Tmp(); try { t.preGateLeashDistanceOnly = false; UseTmp(t); int r1 = BossArena.PreGateLeashResets; try { BossArena.LeashTick(); } catch { } L(" " + P(BossArena.PreGateLeashResets == r1) + " C8 preGateLeashDistanceOnly=0 → 게이트 전 경로 미진입(813w5 그대로)"); } finally { UseReal(); UnityEngine.Object.DestroyImmediate(t); } } finally { KillBoss(boss); BossArena.SetRunActiveForProbe(0); } } // ───────────────────────────────────────────────────────────────────── // ⑤ D-44 — HP 스냅샷 → RunEnded 발행 → Park 순서 // ───────────────────────────────────────────────────────────────────── static void Sec5_HpSnapshotOrder() { L(""); L("── ⑤ D-44 결과 페이로드 보스 HP (Q9 R3: 실제 24.4 % = 439/1800 인데 성적표는 100 %)"); BossArena.ResetAll(); BossArena.EnsureSubscribed(); BossArena.EnsureRunSubscribed(); BossArena.HoldSpawn(s_fbd, NoopResume); RunDirector.ResetAll(); // Q9 R3 실측값을 그대로 주입한다(에디트 모드에는 m_Stat 이 없다). BossArena.SetBossHpForProbe(439f / 1800f, 439d, 1800d); float ratio; double hp, max; BossArena.SnapshotBossHp(out ratio, out hp, out max); L(" " + P(Mathf.Abs(ratio - 0.2439f) < 0.001f && hp == 439d && max == 1800d) + " SnapshotBossHp → ratio=" + ratio.ToString("F4") + " hp=" + hp + " max=" + max + " (= 24.4 %)"); RunEvents.RunEnded.Add(h_seen); s_seenCount = 0; try { var boss = MakeBoss("__WL813w6_Boss_C"); try { BossArena.SetBossForProbe(boss); int p0 = BossArena.ParkCount, d0 = BossArena.DeferredParkCount; RunDirector.StartRun("probe w6"); RunDirector.EndRun(RunOutcome.Timeout); // 🔴 진짜 발행(CombatEventList.Dispatch 는 internal) L(" " + P(s_seenCount == 1) + " RunEnded 1회 수신(구독자 시점 기록)"); L(" " + P(Mathf.Abs(s_seenRatio - 0.2439f) < 0.001f) + " 🔴 **발행 시점 페이로드 bossHpRatio=" + s_seenRatio.ToString("F4") + "** (= Park 이전 24.4 %) · hp=" + s_seenHp + "/" + s_seenMax); L(" " + P(s_seenParkCount == p0 && s_seenDeferred == d0) + " 🔴 발행 도중 고정 0(ParkCount " + s_seenParkCount + " · DeferredParkCount " + s_seenDeferred + ") — 만피 복구가 결과창보다 **뒤**에 온다"); L(" " + P(s_seenPending) + " 발행 시점에 「미뤄 둔 고정」 예약됨(ParkPending=true)"); L(" " + P(Mathf.Abs(RunDirector.LastResult.bossHpRatio - 0.2439f) < 0.001f) + " RunDirector.LastResult.bossHpRatio=" + RunDirector.LastResult.bossHpRatio.ToString("F4") + " (결과창이 늦게 떠도 읽을 수 있다 · 813p 계약)"); BossArena.PumpDeferredPark(); L(" " + P(BossArena.DeferredParkCount == d0 + 1 && !BossArena.ParkPending) + " 🔴 발행 뒤 Tick → 미뤄 둔 고정 실행(DeferredParkCount=" + BossArena.DeferredParkCount + " · ParkPending=" + BossArena.ParkPending + ")"); L(" " + P(BossArena.BossHeld) + " 고정 실패(에디트 모드 워프 불가)해도 **홀드는 무장**(BossHeld=true)"); BossArena.PumpDeferredPark(); L(" " + P(BossArena.DeferredParkCount == d0 + 1) + " 두 번째 펌프 → 중복 실행 0"); } finally { KillBoss(boss); } // 보스가 없으면 −1(813p 계약 = 「값 없음」) BossArena.SetBossHpForProbe(-2f, 0d, 0d); BossArena.SetBossForProbe(null); BossArena.SnapshotBossHp(out ratio, out hp, out max); L(" " + P(ratio < 0f && hp == 0d && max == 0d) + " 보스 없음 → ratio=−1 · hp/max=0 (결과창은 「미처치」만 쓴다)"); // C8 — snapshotBossHpOnRunEnd=0 var t = Tmp(); try { t.snapshotBossHpOnRunEnd = false; UseTmp(t); BossArena.SetBossHpForProbe(0.5f, 900d, 1800d); BossArena.SnapshotBossHp(out ratio, out hp, out max); L(" " + P(ratio < 0f) + " C8 snapshotBossHpOnRunEnd=0 → ratio=−1(813p 계약 그대로 · 필드는 비어 있다)"); } finally { UseReal(); UnityEngine.Object.DestroyImmediate(t); BossArena.SetBossHpForProbe(-2f, 0d, 0d); } // C8 — parkBossOnRunEndDeferred=0 → 813w5 그대로(발행 도중 즉시 고정) var t2 = Tmp(); try { t2.parkBossOnRunEndDeferred = false; UseTmp(t2); RunDirector.ResetAll(); int d1 = BossArena.DeferredParkCount, n1 = BossArena.RunEndNotices; RunDirector.StartRun("probe w6 c8"); RunDirector.EndRun(RunOutcome.Abandon); L(" " + P(BossArena.RunEndNotices == n1 + 1 && !BossArena.ParkPending && BossArena.DeferredParkCount == d1) + " C8 parkBossOnRunEndDeferred=0 → 발행 도중 즉시 고정(예약 0 · 813w5 그대로)"); } finally { UseReal(); UnityEngine.Object.DestroyImmediate(t2); } } finally { RunEvents.RunEnded.Remove(h_seen); RunDirector.ResetAll(); } } // ───────────────────────────────────────────────────────────────────── // ⑥ GC 0 // ───────────────────────────────────────────────────────────────────── static void Sec6_GC() { L(""); L("── ⑥ GC (모바일 · 매 프레임 도는 경로는 0 B 여야 한다)"); BossArena.ResetAll(); BossArena.EnsureSubscribed(); BossArena.HoldSpawn(s_fbd, NoopResume); BossArena.SetRunActiveForProbe(1); BossArena.SetBossForProbe(null); BossArena.SetBossHpForProbe(0.244f, 439d, 1800d); float r; double h, m; BossArena.SnapshotBossHp(out r, out h, out m); // 워밍업 long a0 = Alloc(); for (int i = 0; i < 100; i++) BossArena.SnapshotBossHp(out r, out h, out m); long a1 = Alloc(); L(" " + P(a1 - a0 == 0) + " SnapshotBossHp ×100 → Δ " + (a1 - a0) + " B"); BossArena.SetProbeTime(500f); BossArena.ArmBossHoldForProbe(); bool held = BossArena.BossHeld; a0 = Alloc(); for (int i = 0; i < 100; i++) held = BossArena.BossHeld; a1 = Alloc(); L(" " + P(a1 - a0 == 0) + " BossHeld(게이트 개방까지 판정 포함) ×100 → Δ " + (a1 - a0) + " B · held=" + held); a0 = Alloc(); for (int i = 0; i < 100; i++) BossArena.PumpDeferredPark(); // 예약 없음 = 즉시 return a1 = Alloc(); L(" " + P(a1 - a0 == 0) + " PumpDeferredPark(예약 없음) ×100 → Δ " + (a1 - a0) + " B"); BossArena.ClearBossHold(); BossArena.LeashTick(); a0 = Alloc(); for (int i = 0; i < 100; i++) BossArena.LeashTick(); // 게이트 전 경로(보스 없음 = 가드) a1 = Alloc(); L(" " + P(a1 - a0 == 0) + " LeashTick(게이트 전 경로) ×100 → Δ " + (a1 - a0) + " B"); BossArena.SetProbeTime(-1f); BossArena.SetBossHpForProbe(-2f, 0d, 0d); BossArena.SetRunActiveForProbe(0); } // ───────────────────────────────────────────────────────────────────── // ⑦ C8 롤백 // ───────────────────────────────────────────────────────────────────── static void Sec7_C8() { L(""); L("── ⑦ C8 롤백 (신규 6스위치 0 → 813w5 동작 그대로 · RuntimeDisabled → 무동작)"); var cfg = WLBossArenaSettings.Instance; BossArena.ResetAll(); BossArena.EnsureSubscribed(); BossArena.HoldSpawn(s_fbd, NoopResume); BossArena.SetRunActiveForProbe(-1); // parkBossOnRespawn=0 var t = Tmp(); var boss = MakeBoss("__WL813w6_Boss_D"); try { t.parkBossOnRespawn = false; UseTmp(t); BossArena.SetBossForProbe(boss); BossArena.ClearBossHold(); int a0 = BossArena.RespawnParkAttempts; BossArena.NoteSpawn(cfg.bossSpawnerId, true); L(" " + P(BossArena.RespawnParkAttempts == a0 && !BossArena.BossHeld) + " C8 parkBossOnRespawn=0 → 재스폰 고정 0 · 홀드 0(813w5 그대로)"); // bossHoldUntilGateOpen=0 → 시간제 홀드만(만료되면 풀린다) t.parkBossOnRespawn = true; t.bossHoldUntilGateOpen = false; BossArena.SetProbeTime(2000f); BossArena.ClearBossHold(); BossArena.NoteSpawn(cfg.bossSpawnerId, true); L(" " + P(BossArena.BossHeld && !BossArena.HoldUntilGate) + " C8 bossHoldUntilGateOpen=0 → 시간제 홀드만(HoldUntilGate=false · BossHoldUntil=" + BossArena.BossHoldUntil.ToString("F1") + ")"); BossArena.SetProbeTime(2000f + cfg.nextRunBossHoldSeconds + 1f); L(" " + P(!BossArena.BossHeld) + " C8 만료 뒤 홀드 해제 = 813w5 동작(30 s 뒤 풀린다)"); BossArena.SetProbeTime(-1f); } finally { KillBoss(boss); UseReal(); UnityEngine.Object.DestroyImmediate(t); } // RuntimeDisabled — 전부 무동작 var boss2 = MakeBoss("__WL813w6_Boss_E"); try { BossArena.SetBossForProbe(boss2); BossArena.ClearBossHold(); WLBossArenaSettings.RuntimeDisabled = true; L(" " + P(!WLBossArenaSettings.Active) + " RuntimeDisabled=1 → Active=false"); int a0 = BossArena.RespawnParkAttempts; BossArena.NoteSpawn(cfg.bossSpawnerId, true); L(" " + P(BossArena.RespawnParkAttempts == a0 && !BossArena.BossHeld) + " → 재스폰 고정 0 · 홀드 0"); float r; double h, m; BossArena.SetBossHpForProbe(0.5f, 900d, 1800d); BossArena.SnapshotBossHp(out r, out h, out m); L(" " + P(r < 0f) + " → HP 스냅샷 −1(RunResult 필드는 비어 있다)"); int n0 = BossArena.RunEndNotices; RunDirector.ResetAll(); RunDirector.StartRun("probe c8 off"); RunDirector.EndRun(RunOutcome.Win); L(" " + P(BossArena.RunEndNotices == n0 && !BossArena.ParkPending) + " → RunEnded 통지 0 · 예약 0 · 예외 0"); } finally { WLBossArenaSettings.RuntimeDisabled = false; BossArena.SetBossHpForProbe(-2f, 0d, 0d); KillBoss(boss2); BossArena.SetRunActiveForProbe(0); RunDirector.ResetAll(); } L(" " + P(true) + " 복구 확인 — Active=" + WLBossArenaSettings.Active + " · 신규 스위치 6개를 전부 0 으로 두면 813w5 판(292ccf674) 동작 그대로다"); } }