Project_WL/AgentScripts/WL813w6_Probe.cs

574 lines
30 KiB
C#
Raw Permalink Normal View History

// WL813w6_Probe.cs — #813w6(재스폰 보스 고정 D-43 · 결과 HP 스냅샷 순서 D-44) 프로브
// 에디트 모드 전용 · Play 불필요 · 로그인 0 · 씬/에셋 무수정(임시 GameObject·임시 SO 는 즉시 파괴/회수)
//
// 상주 에디터:
// unity command run_script --project-path <wt> --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<RunResult> 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); }
/// <summary>실에셋을 복사한 임시 SO(실에셋은 절대 안 건드린다). 쓰고 나면 DestroyImmediate + 캐시 원복.</summary>
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;
}
/// <summary>가짜 보스(살아 있는 것으로 보이는 MobActor · m_Stat 은 없다).</summary>
static MobActor MakeBoss(string name)
{
var go = new GameObject(name);
var m = go.AddComponent<MobActor>();
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<PCActor>();
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<FieldBossData>();
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) 동작 그대로다");
}
}