589 lines
33 KiB
C#
589 lines
33 KiB
C#
// WL813w7_Probe.cs — #813w7(다음 런 존 재무장 · 결과창 중 PC 정지 D-45) 프로브
|
||
// 에디트 모드 전용 · Play 불필요 · 로그인 0 · 씬/에셋 무수정(임시 GameObject·임시 SO 는 즉시 파괴/회수)
|
||
//
|
||
// 상주 에디터:
|
||
// unity command run_script --project-path <wt> --file AgentScripts/WL813w7_Probe.cs --entry WL813w7_Probe.RunAll --timeout 300
|
||
// 결과: Console + AgentScripts/WL813w7_PROBE.txt (RESULT PASS / RESULT FAIL n)
|
||
//
|
||
// 무엇을 증명하나 (발주서 WL-813w7 §1 · 근거 = qa/W2-10_재채점_v8.md D-45)
|
||
// ① 에셋·SOT — 813w7 신규 12필드 값 실측 · 이중 스폰 방지 부등식 · 코드 상수 0
|
||
// ② 원인 산식 — 존0 앵커 ↔ 아레나 중심 24.1 m ≤ arenaRadius+zoneSpawnHoldMargin 30 m
|
||
// = 813w3 리젠 차단의 사정권 안이다(D-45 의 직접 원인 · 값으로 재현)
|
||
// ③ 수리 ① — 가짜 `RunEnded` → PC 조종 잡힘(JoystickStatus 1) · 타깃 해제 ·
|
||
// 가짜 `RunStarted("restart")` → 조종 원복(0) · 상한 만료 자동 해제
|
||
// ④ 수리 ②-a — 리젠 차단 판정(ShouldHoldZoneSpawn) 진리표: 게이트 전 0 · 런 존 0 · 그 밖 1
|
||
// ⑤ 수리 ②-b — 빈 존 감시 타이밍(8 s) · Restart 즉시 재무장 · 1웨이브 마리 수(WaveSize)
|
||
// ⑥ 수리 ③ — 런 밖 처치는 다음 런 존 진행에 안 센다(StartRun 스냅샷 차감)
|
||
// ⑦ GC 0 — 정지 틱 ×100 · 빈 존 감시 ×100 · 차단 판정 ×100 · 런 존 판정 ×100
|
||
// ⑧ C8 롤백 — 신규 스위치 0 → 813w6 동작 그대로 · RuntimeDisabled → 무동작
|
||
//
|
||
// 에디트 모드 한계(보고서 「미확인」)
|
||
// · `ActorInfo.Ins` 도 `table_monsterappear.Ins` 도 없다(플레이 모드 전용) → 실제 `On_Regen()` 재무장은
|
||
// 못 돌린다. 「언제 재무장을 트리거하는가」와 「1웨이브 마리 수 산정」까지를 증명한다.
|
||
// · `Time.unscaledTime` 이 스크립트 실행 중 멈춰 있다 → 시간 판정은 전부 인자로 먹인다.
|
||
// · PC 에 살아 있는 타깃(m_Target)을 붙일 수 없다(Set_Outline 이 렌더러를 만진다) → 타깃 해제는
|
||
// 「isTarget 플래그가 내려가는가」로 증명한다.
|
||
|
||
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 WL813w7_Probe
|
||
{
|
||
const string OutCommit = "AgentScripts/WL813w7_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;
|
||
|
||
public static object RunAll()
|
||
{
|
||
sb = new StringBuilder();
|
||
s_fail = 0;
|
||
sb.AppendLine("# WL813w7 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_Cause();
|
||
Sec3_RunEndHold();
|
||
Sec4_HoldDecision();
|
||
Sec5_ZoneRearm();
|
||
Sec6_OutsideRunKills();
|
||
Sec7_GC();
|
||
Sec8_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); }
|
||
|
||
static WLBossArenaSettings TmpB()
|
||
{
|
||
var t = UnityEngine.Object.Instantiate(WLBossArenaSettings.Instance);
|
||
t.hideFlags = HideFlags.HideAndDontSave;
|
||
return t;
|
||
}
|
||
static WLRunGuideSettings TmpG()
|
||
{
|
||
var t = UnityEngine.Object.Instantiate(WLRunGuideSettings.Instance);
|
||
t.hideFlags = HideFlags.HideAndDontSave;
|
||
return t;
|
||
}
|
||
static void UseTmpB(WLBossArenaSettings t) { WLBossArenaSettings.SetInstanceForProbe(t); }
|
||
static void UseRealB() { WLBossArenaSettings.SetInstanceForProbe(null); }
|
||
static void UseTmpG(WLRunGuideSettings t) { WLRunGuideSettings.SetInstanceForProbe(t); }
|
||
static void UseRealG() { WLRunGuideSettings.SetInstanceForProbe(null); }
|
||
|
||
static float DistXZ(Vector3 a, Vector3 b)
|
||
{
|
||
float dx = a.x - b.x, dz = a.z - b.z;
|
||
return Mathf.Sqrt(dx * dx + dz * dz);
|
||
}
|
||
|
||
static void Setup()
|
||
{
|
||
s_prevMyPc = MyValue.MyPC;
|
||
|
||
s_pcGo = new GameObject("__WL813w7_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;
|
||
WLRunGuideSettings.RuntimeDisabled = false;
|
||
BossArena.ResetAll();
|
||
BossArena.EnsureSubscribed();
|
||
RunDirector.ResetAll();
|
||
RunGuide.EnsureSubscribed();
|
||
RunGuide.ResetDiagnostics();
|
||
|
||
// 게이트 무장 — 원본 훅과 같은 진입점(HoldSpawn)으로 Arm 한다(s_zones 에 존 id 가 들어간다).
|
||
var cfg = WLBossArenaSettings.Instance;
|
||
s_fbdGo = new GameObject("__WL813w7_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(WLRunGuideSettings.Instance != null) + " WLRunGuideSettings 로드 · Enabled=" + WLRunGuideSettings.Enabled);
|
||
L(" " + P(WLRunSettings.Instance != null) + " WLRunSettings 로드 · Enabled=" + WLRunSettings.Enabled);
|
||
L(" " + P(RunGuide.Subscribed) + " RunGuide.Subscribed(RunEvents)");
|
||
L(" " + P(held && BossArena.GateArmed && !BossArena.GateOpen) +
|
||
" HoldSpawn(FieldBossData) → 게이트 무장(armed=" + BossArena.GateArmed + " open=" + BossArena.GateOpen + ")");
|
||
}
|
||
|
||
static void NoopResume() { }
|
||
|
||
static void Teardown()
|
||
{
|
||
try
|
||
{
|
||
UseRealB(); UseRealG();
|
||
BossArena.SetProbeTime(-1f);
|
||
BossArena.SetRunActiveForProbe(0);
|
||
BossArena.SetBossForProbe(null);
|
||
BossArena.ResetAll();
|
||
RunGuide.EndRunEndHold("teardown");
|
||
RunGuide.ResetDiagnostics();
|
||
RunDirector.ResetAll();
|
||
WLBossArenaSettings.RuntimeDisabled = false;
|
||
WLRunGuideSettings.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 (C45 — 값은 전부 .asset)
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
static void Sec1_Asset()
|
||
{
|
||
L("");
|
||
L("── ① 에셋 (813w7 신규 12필드 · 값 전부 SO = C45)");
|
||
|
||
var g = WLRunGuideSettings.Instance;
|
||
var b = WLBossArenaSettings.Instance;
|
||
if (g == null || b == null) { L(" [FAIL] 에셋 없음"); s_fail++; return; }
|
||
|
||
L(" RunGuide: holdPcOnRunEnd=" + g.holdPcOnRunEnd +
|
||
" runEndHoldMaxSeconds=" + g.runEndHoldMaxSeconds +
|
||
" runEndHoldClearsTarget=" + g.runEndHoldClearsTarget +
|
||
" runEndHoldStopsAgent=" + g.runEndHoldStopsAgent);
|
||
L(" " + P(g.holdPcOnRunEnd) + " holdPcOnRunEnd=1 (발주서 §1-2 ① 기본값)");
|
||
L(" " + P(g.runEndHoldMaxSeconds > 0f) + " runEndHoldMaxSeconds>0 (결과창을 닫고 방치해도 스스로 풀린다)");
|
||
L(" " + P(g.runEndHoldClearsTarget) + " runEndHoldClearsTarget=1 (자동 스킬 루프 차단 · PCActor.cs:93 isTarget 조건)");
|
||
L(" " + P(g.runEndHoldStopsAgent) + " runEndHoldStopsAgent=1 (잔여 경로 제거)");
|
||
|
||
L(" BossArena: holdZoneSpawnOnlyAfterGate=" + b.holdZoneSpawnOnlyAfterGate +
|
||
" holdZoneSpawnSkipsRunZones=" + b.holdZoneSpawnSkipsRunZones +
|
||
" rearmZoneSpawnsEnabled=" + b.rearmZoneSpawnsEnabled +
|
||
" rearmZoneEmptySeconds=" + b.rearmZoneEmptySeconds +
|
||
" rearmZoneCheckSeconds=" + b.rearmZoneCheckSeconds +
|
||
" rearmZoneRegenMax=" + b.rearmZoneRegenMax +
|
||
" rearmZoneRunZonesOnly=" + b.rearmZoneRunZonesOnly +
|
||
" rearmZoneSpawnsOnRestart=" + b.rearmZoneSpawnsOnRestart);
|
||
L(" " + P(b.holdZoneSpawnOnlyAfterGate) + " holdZoneSpawnOnlyAfterGate=1 (차단을 보스전으로 좁힌다)");
|
||
L(" " + P(b.holdZoneSpawnSkipsRunZones) + " holdZoneSpawnSkipsRunZones=1 (런 사냥 존은 대상 제외)");
|
||
L(" " + P(b.rearmZoneSpawnsEnabled) + " rearmZoneSpawnsEnabled=1");
|
||
L(" " + P(b.rearmZoneSpawnsOnRestart) + " rearmZoneSpawnsOnRestart=1 ([다음 런] 즉시 1회)");
|
||
L(" " + P(b.rearmZoneRegenMax > 0) + " rearmZoneRegenMax>0 (테이블 실패 시 폴백·상한)");
|
||
|
||
// 🔴 이중 스폰 방지 부등식 — 원본 리듬 = 시체 3.0 s(Actor.cs:631) + f_SpawnerDelay 2.0 s(813e2 표 A) = 5.0 s
|
||
const float kOriginalRegenRhythm = 5.0f; // 게임 수치가 아니라 813e2 실측 리듬(검증 기준값)
|
||
L(" " + P(b.rearmZoneEmptySeconds > kOriginalRegenRhythm) +
|
||
" 🔴 rearmZoneEmptySeconds(" + b.rearmZoneEmptySeconds + "s) > 원본 리듬 " + kOriginalRegenRhythm +
|
||
"s — 멀쩡한 스포너와 겹치지 않는다(이중 스폰 방지)");
|
||
L(" " + P(b.rearmZoneCheckSeconds > 0f && b.rearmZoneCheckSeconds < b.rearmZoneEmptySeconds) +
|
||
" 감시 주기(" + b.rearmZoneCheckSeconds + "s) < 임계(" + b.rearmZoneEmptySeconds + "s)");
|
||
L(" " + P(b.holdZoneSpawnNearArena) + " 813w3 holdZoneSpawnNearArena=1 유지(차단 경로 자체는 그대로 · 조건만 좁혔다)");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// ② 원인 산식 — 존0 이 리젠 차단 사정권 안이다
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
static void Sec2_Cause()
|
||
{
|
||
L("");
|
||
L("── ② 🔴 D-45 원인 산식 (Q10: R1n 0/18 · R2n 1/18 PC (112.93,0.06,−16.60) 고정 무피해 · R3n 14/18 정지)");
|
||
|
||
var b = WLBossArenaSettings.Instance;
|
||
var g = WLRunGuideSettings.Instance;
|
||
var rs = WLRunSettings.Instance;
|
||
if (b == null || g == null || rs == null) { L(" [FAIL] 에셋 없음"); s_fail++; return; }
|
||
|
||
Vector3 center = b.arenaCenter;
|
||
float lim = b.arenaRadius + b.zoneSpawnHoldMargin;
|
||
L(" 아레나 중심 " + center.ToString("F2") + " · 차단 반경 = arenaRadius " + b.arenaRadius +
|
||
" + zoneSpawnHoldMargin " + b.zoneSpawnHoldMargin + " = " + lim.ToString("F0") + " m");
|
||
|
||
var fb = g.zoneAnchorFallbacks;
|
||
for (int i = 0; i < rs.zoneSpawnerIds.Length; i++)
|
||
{
|
||
if (fb == null || i >= fb.Length) continue;
|
||
float d = DistXZ(fb[i], center);
|
||
L(" 존" + i + "(id " + rs.zoneSpawnerIds[i] + ") 앵커 " + fb[i].ToString("F2") +
|
||
" → 중심 " + d.ToString("F1") + " m · 차단 사정권 " + (d <= lim ? "안 🔴" : "밖"));
|
||
}
|
||
|
||
float d0 = fb != null && fb.Length > 0 ? DistXZ(fb[0], center) : -1f;
|
||
L(" " + P(d0 >= 0f && d0 <= lim) +
|
||
" 🔴 존0 이 차단 사정권 안(" + d0.ToString("F1") + " m ≤ " + lim.ToString("F0") + " m) = D-45 의 직접 원인");
|
||
|
||
// PC 고정 좌표(Q10 R2n 실측)가 존0 앵커에 도착한 자리인가 = 「PC 는 존에 있는데 몹이 없다」
|
||
var pcSeen = new Vector3(112.93f, 0.06f, -16.60f);
|
||
float dPc = fb != null && fb.Length > 0 ? DistXZ(pcSeen, fb[0]) : -1f;
|
||
L(" " + P(dPc >= 0f && dPc <= g.zoneArriveRadius) +
|
||
" Q10 R2n PC 좌표 " + pcSeen.ToString("F2") + " ↔ 존0 앵커 " + dPc.ToString("F1") +
|
||
" m ≤ zoneArriveRadius " + g.zoneArriveRadius + " = **도착(Hold)했는데 때릴 몹이 없었다**");
|
||
|
||
L(" 차단 방식 = MobControlMgr.StopAllCoroutines() → PhaseAllKill 의 `killmobcount = 0`(MobControlMgr.cs:218)이");
|
||
L(" 함께 끊겨 카운터가 n_MaxMonsterCount 에 굳는다 → `==` 판정(:214)이 두 번 다시 참이 안 된다 = 영구 소진.");
|
||
L(" (MyCoroutine.Set_Coroutine = StartCoroutine(MyCoroutine.cs) 이므로 StopAllCoroutines 에 걸린다 · 실측)");
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// ③ 수리 ① — 결과창 중 PC 정지
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
static void Sec3_RunEndHold()
|
||
{
|
||
L("");
|
||
L("── ③ 수리 ① 결과창 중 PC 정지 (D-45 원인 = 결과창 100 s 사이 z0 처치 26 → 47)");
|
||
|
||
var g = WLRunGuideSettings.Instance;
|
||
RunGuide.ResetDiagnostics();
|
||
s_pc.Set_JoystickStatus(3);
|
||
s_pc.isTarget = false;
|
||
|
||
int holds0 = RunGuide.RunEndHolds;
|
||
RunGuide.ProbeRunEnded(RunOutcome.Win);
|
||
RunGuide.SetRunEndHoldUntilForProbe(-1f); // 에디트 모드는 Time 이 안 흘러 상한 판정을 따로 검증한다(아래 ⓑ)
|
||
L(" " + P(RunGuide.RunEndHeld && RunGuide.RunEndHolds == holds0 + 1) +
|
||
" 가짜 RunEnded(Win) → 정지 시작(RunEndHeld=" + RunGuide.RunEndHeld + " RunEndHolds=" + RunGuide.RunEndHolds + ")");
|
||
L(" " + P(RunGuide.State == RunGuideState.Off && RunGuide.DestKind == RunGuideDest.None) +
|
||
" 가이드는 꺼진다(State=" + RunGuide.State + " Dest=" + RunGuide.DestKind + ") — 813w6 계약 유지");
|
||
|
||
// 정지 1틱 — 조종을 잡고 타깃을 뗀다
|
||
s_pc.isTarget = true;
|
||
int clears0 = RunGuide.RunEndTargetClears;
|
||
RunGuide.RunEndHoldTick(g, s_pc, 100f);
|
||
L(" " + P(s_pc.JoystickStatus == 1 && RunGuide.Steering) +
|
||
" 🔴 정지 틱 → JoystickStatus=" + s_pc.JoystickStatus + "(1 = 원본 자동 교전 접근 루프 차단 · PCActor.cs:139) Steering=" + RunGuide.Steering);
|
||
L(" " + P(!s_pc.isTarget && RunGuide.RunEndTargetClears == clears0 + 1) +
|
||
" 🔴 정지 틱 → 타깃 해제(isTarget=" + s_pc.isTarget + " RunEndTargetClears=" + RunGuide.RunEndTargetClears +
|
||
") = 자동 스킬 루프(PCActor.cs:93)도 돌지 않는다");
|
||
|
||
int ticks0 = RunGuide.RunEndHoldTicks;
|
||
RunGuide.RunEndHoldTick(g, s_pc, 101f);
|
||
RunGuide.RunEndHoldTick(g, s_pc, 102f);
|
||
L(" " + P(RunGuide.RunEndHoldTicks == ticks0 + 2 && RunGuide.RunEndHeld) +
|
||
" 정지가 이어진다(틱 " + RunGuide.RunEndHoldTicks + " · 상한 전)");
|
||
|
||
// 복귀 ⓐ — 다음 런 시작(RunDirector.Restart → StartRun("restart"))
|
||
int rel0 = RunGuide.RunEndHoldReleases;
|
||
RunGuide.ProbeRunStarted(2, "restart");
|
||
L(" " + P(!RunGuide.RunEndHeld && RunGuide.RunEndHoldReleases == rel0 + 1) +
|
||
" 🔴 가짜 RunStarted(\"restart\") → 정지 해제(RunEndHeld=" + RunGuide.RunEndHeld +
|
||
" 해제 " + RunGuide.RunEndHoldReleases + " 사유 '" + RunGuide.LastRunEndHoldReason + "')");
|
||
L(" " + P(s_pc.JoystickStatus == 0 && !RunGuide.Steering) +
|
||
" 🔴 조종 원복 JoystickStatus=" + s_pc.JoystickStatus + "(0 = 원본 그대로) Steering=" + RunGuide.Steering);
|
||
L(" " + P(RunGuide.RestartGuides >= 1) +
|
||
" 813w4 재유도는 그대로 산다(RestartGuides=" + RunGuide.RestartGuides + ") — 정지 해제가 유도보다 앞");
|
||
|
||
// 복귀 ⓑ — 상한 만료(결과창을 [닫기]로 닫고 런을 다시 시작하지 않는 경우)
|
||
RunGuide.StopGuide("프로브");
|
||
RunGuide.BeginRunEndHold("상한 시험");
|
||
RunGuide.SetRunEndHoldUntilForProbe(200f);
|
||
RunGuide.RunEndHoldTick(g, s_pc, 199f);
|
||
bool stillHeld = RunGuide.RunEndHeld;
|
||
RunGuide.RunEndHoldTick(g, s_pc, 201f);
|
||
L(" " + P(stillHeld && !RunGuide.RunEndHeld) +
|
||
" 🔴 상한 만료 자동 해제(199 s 유지 → 201 s 해제 · 사유 '" + RunGuide.LastRunEndHoldReason + "')");
|
||
L(" " + P(s_pc.JoystickStatus == 0) + " 만료 해제에서도 조종 원복(JoystickStatus=" + s_pc.JoystickStatus + ")");
|
||
|
||
// 이미 스킬 이동 잠금(2)이면 건드리지 않는다
|
||
RunGuide.ResetDiagnostics();
|
||
s_pc.Set_JoystickStatus(2);
|
||
RunGuide.BeginRunEndHold("잠금 시험");
|
||
RunGuide.SetRunEndHoldUntilForProbe(-1f);
|
||
RunGuide.RunEndHoldTick(g, s_pc, 300f);
|
||
L(" " + P(s_pc.JoystickStatus == 2 && !RunGuide.Steering) +
|
||
" JoystickStatus=2(스킬 이동 잠금)는 건드리지 않는다(=" + s_pc.JoystickStatus + " Steering=" + RunGuide.Steering + ")");
|
||
RunGuide.EndRunEndHold("프로브 정리");
|
||
s_pc.Set_JoystickStatus(3);
|
||
RunGuide.ResetDiagnostics();
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// ④ 수리 ②-a — 리젠 차단 판정 진리표
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
static void Sec4_HoldDecision()
|
||
{
|
||
L("");
|
||
L("── ④ 수리 ②-a 리젠 차단 판정(ShouldHoldZoneSpawn · 순수 함수)");
|
||
|
||
var rs = WLRunSettings.Instance;
|
||
int runZone = rs != null && rs.zoneSpawnerIds != null && rs.zoneSpawnerIds.Length > 0 ? rs.zoneSpawnerIds[0] : 813001;
|
||
int other = 813005;
|
||
|
||
L(" " + P(BossArena.IsRunZoneSpawner(runZone)) + " IsRunZoneSpawner(" + runZone + ")=true (런 사냥 존)");
|
||
L(" " + P(!BossArena.IsRunZoneSpawner(other)) + " IsRunZoneSpawner(" + other + ")=false (런 밖 존)");
|
||
|
||
var t = TmpB();
|
||
try
|
||
{
|
||
UseTmpB(t);
|
||
t.holdZoneSpawnNearArena = true;
|
||
t.holdZoneSpawnOnlyAfterGate = true;
|
||
t.holdZoneSpawnSkipsRunZones = true;
|
||
|
||
L(" " + P(!BossArena.ShouldHoldZoneSpawn(t, other, false)) +
|
||
" 🔴 게이트 **닫힘** + 런 밖 존 → 차단 안 함(= 813w6 이 만든 「다음 런 내내 차단」이 사라진다)");
|
||
L(" " + P(!BossArena.ShouldHoldZoneSpawn(t, runZone, true)) +
|
||
" 🔴 게이트 열림 + **런 사냥 존** → 차단 안 함(D-45 재발 차단)");
|
||
L(" " + P(BossArena.ShouldHoldZoneSpawn(t, other, true)) +
|
||
" 게이트 열림 + 런 밖 존 → 차단(813w3 잡몹 유입 차단 의도 유지)");
|
||
|
||
// C8 — 두 스위치를 끄면 813w3 그대로(보스 생존 = 상시 차단)
|
||
t.holdZoneSpawnOnlyAfterGate = false;
|
||
t.holdZoneSpawnSkipsRunZones = false;
|
||
L(" " + P(BossArena.ShouldHoldZoneSpawn(t, runZone, false)) +
|
||
" C8: 두 스위치 0 → 게이트 전 + 런 존도 차단 = 813w3 동작 그대로");
|
||
|
||
t.holdZoneSpawnNearArena = false;
|
||
L(" " + P(!BossArena.ShouldHoldZoneSpawn(t, other, true)) + " C8: holdZoneSpawnNearArena=0 → 언제나 차단 0");
|
||
}
|
||
finally { UseRealB(); UnityEngine.Object.DestroyImmediate(t); }
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// ⑤ 수리 ②-b — 빈 존 감시 · 재무장
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
static void Sec5_ZoneRearm()
|
||
{
|
||
L("");
|
||
L("── ⑤ 수리 ②-b 존 재무장(원본 리셋 API 없음 → MobActor.On_Regen = MobControlMgr.cs:221 과 같은 호출)");
|
||
|
||
var b = WLBossArenaSettings.Instance;
|
||
var rs = WLRunSettings.Instance;
|
||
int z0 = rs != null && rs.zoneSpawnerIds != null && rs.zoneSpawnerIds.Length > 0 ? rs.zoneSpawnerIds[0] : 813001;
|
||
|
||
// 감시 목록 — 런 존만
|
||
BossArena.RebuildRearmZonesForProbe();
|
||
L(" 감시 대상 " + BossArena.RearmZoneCount + "개: " +
|
||
(BossArena.RearmZoneCount > 0 ? BossArena.RearmZoneIdAt(0).ToString() : "-") +
|
||
(BossArena.RearmZoneCount > 1 ? " / " + BossArena.RearmZoneIdAt(1) : ""));
|
||
L(" " + P(rs != null && BossArena.RearmZoneCount == rs.zoneSpawnerIds.Length) +
|
||
" 대상 = WLRunSettings.zoneSpawnerIds 전부(rearmZoneRunZonesOnly=1)");
|
||
L(" " + P(BossArena.RearmZoneCount > 0 && BossArena.RearmZoneIdAt(0) == z0) + " 첫 대상 = 존0(" + z0 + ")");
|
||
|
||
// 1웨이브 마리 수 — 에디트 모드엔 table_monsterappear 가 없어 SO 폴백을 쓴다
|
||
int wave = BossArena.WaveSize(b, z0);
|
||
L(" " + P(wave > 0 && wave <= b.rearmZoneRegenMax) +
|
||
" WaveSize(" + z0 + ")=" + wave + " (테이블 n_MaxMonsterCount → 없으면 rearmZoneRegenMax " + b.rearmZoneRegenMax + " · 상한도 그 값)");
|
||
|
||
// 타이밍 — 살아 있으면 타이머가 안 돈다 / 비면 임계까지 기다렸다 트리거
|
||
BossArena.SetRunActiveForProbe(1);
|
||
BossArena.NoteSpawn(z0, false); // alive 1
|
||
BossArena.ZoneRearmTick(b, 100f);
|
||
L(" " + P(BossArena.AliveOf(z0) > 0 && BossArena.RearmZoneEmptySinceAt(0) < 0f) +
|
||
" alive=" + BossArena.AliveOf(z0) + " → 빈 시각 미기록(" + BossArena.RearmZoneEmptySinceAt(0) + ")");
|
||
|
||
BossArena.NoteKill(z0, false); // alive 0
|
||
BossArena.ZoneRearmTick(b, 100f);
|
||
L(" " + P(BossArena.AliveOf(z0) == 0 && Mathf.Approximately(BossArena.RearmZoneEmptySinceAt(0), 100f)) +
|
||
" alive=0 → 빈 시각 " + BossArena.RearmZoneEmptySinceAt(0) + " 기록");
|
||
|
||
BossArena.ZoneRearmTick(b, 100f + b.rearmZoneEmptySeconds - 1f);
|
||
L(" " + P(Mathf.Approximately(BossArena.RearmZoneEmptySinceAt(0), 100f)) +
|
||
" 임계(" + b.rearmZoneEmptySeconds + "s) 전 → 트리거 없음(빈 시각 그대로 " + BossArena.RearmZoneEmptySinceAt(0) + ")");
|
||
|
||
float fire = 100f + b.rearmZoneEmptySeconds + 0.5f;
|
||
BossArena.ZoneRearmTick(b, fire);
|
||
L(" " + P(Mathf.Approximately(BossArena.RearmZoneEmptySinceAt(0), fire)) +
|
||
" 🔴 임계 경과 → 재무장 트리거(빈 시각이 " + BossArena.RearmZoneEmptySinceAt(0) + " 로 갱신 = RearmZone 호출)");
|
||
L(" (에디트 모드는 ActorInfo.Ins 가 없어 실제 On_Regen 은 0마리 — 「언제 트리거되는가」까지 증명 · §미확인)");
|
||
|
||
// 런 밖에서는 감시하지 않는다(결과창 동안 몹을 우리가 늘리지 않는다)
|
||
BossArena.SetRunActiveForProbe(-1);
|
||
float before = BossArena.RearmZoneEmptySinceAt(0);
|
||
BossArena.ZoneRearmTick(b, fire + 100f);
|
||
L(" " + P(Mathf.Approximately(BossArena.RearmZoneEmptySinceAt(0), before)) +
|
||
" 런 밖(결과창)에서는 감시 정지(빈 시각 " + BossArena.RearmZoneEmptySinceAt(0) + " 불변)");
|
||
BossArena.SetRunActiveForProbe(1);
|
||
|
||
// [다음 런] 즉시 재무장 진입점
|
||
int n = BossArena.RearmZoneSpawns("프로브 다음 런");
|
||
L(" " + P(BossArena.RearmZoneCount > 0) +
|
||
" RearmZoneSpawns() 진입점 동작(재등장 " + n + "마리 · 에디트 모드라 0 · 목록 " + BossArena.RearmZoneCount + "개 재구축)");
|
||
BossArena.SetRunActiveForProbe(0);
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// ⑥ 수리 ③ — 런 밖 처치는 다음 런 존 진행에 안 센다
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
static void Sec6_OutsideRunKills()
|
||
{
|
||
L("");
|
||
L("── ⑥ 수리 ③ 런 밖 처치가 존 카운터에 안 실린다(Q10: 결과창 동안 z0 26 → 47)");
|
||
|
||
var rs = WLRunSettings.Instance;
|
||
int z0 = rs.zoneSpawnerIds[0];
|
||
|
||
RunDirector.ResetAll();
|
||
RunDirector.EnsureSubscribed();
|
||
BossArena.SetRunActiveForProbe(-1); // 런 밖(결과창)
|
||
|
||
int arena0 = BossArena.KillsOf(z0);
|
||
int gateEvals0 = BossArena.GateSuspendedEvals;
|
||
for (int i = 0; i < 21; i++) // Q10 실측과 같은 21마리
|
||
{
|
||
RunDirector.NoteKill(z0, false); // 런 밖 → IsRunning=false → 무시
|
||
BossArena.NoteKill(z0, false); // 누적값은 813p 계약대로 계속 센다
|
||
}
|
||
L(" " + P(BossArena.KillsOf(z0) == arena0 + 21) +
|
||
" BossArena 누적은 계속 센다(" + arena0 + " → " + BossArena.KillsOf(z0) + ") = 813p 계약(리셋 금지) 유지");
|
||
L(" " + P(BossArena.GateSuspendedEvals > gateEvals0) +
|
||
" 런 밖 게이트 평가는 건너뛴다(GateSuspendedEvals " + gateEvals0 + " → " + BossArena.GateSuspendedEvals + ")");
|
||
|
||
BossArena.SetRunActiveForProbe(1);
|
||
RunDirector.StartRun("probe restart"); // ← 여기서 s_zoneBase 를 다시 잡는다
|
||
L(" " + P(RunDirector.ZoneProgress(0) == 0) +
|
||
" 🔴 다음 런 시작 직후 존0 진행 = " + RunDirector.ZoneProgress(0) + "/" + RunDirector.ZoneRequired(0) +
|
||
" — 런 밖 21처치가 한 건도 안 실렸다(StartRun 스냅샷 차감)");
|
||
|
||
RunDirector.NoteKill(z0, false);
|
||
L(" " + P(RunDirector.ZoneProgress(0) == 1) +
|
||
" 런 안 처치 1건은 정상 반영(" + RunDirector.ZoneProgress(0) + "/" + RunDirector.ZoneRequired(0) + ")");
|
||
|
||
RunDirector.ResetAll();
|
||
BossArena.SetRunActiveForProbe(0);
|
||
RunGuide.ResetDiagnostics();
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// ⑦ GC 0
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
static void Sec7_GC()
|
||
{
|
||
L("");
|
||
L("── ⑦ GC 0 (틱 경로 · 각 100회)");
|
||
|
||
var g = WLRunGuideSettings.Instance;
|
||
var b = WLBossArenaSettings.Instance;
|
||
var rs = WLRunSettings.Instance;
|
||
int z0 = rs.zoneSpawnerIds[0];
|
||
int other = 813005;
|
||
|
||
// ⓐ 결과창 정지 틱
|
||
RunGuide.ResetDiagnostics();
|
||
s_pc.Set_JoystickStatus(3);
|
||
RunGuide.BeginRunEndHold("GC");
|
||
RunGuide.SetRunEndHoldUntilForProbe(-1f);
|
||
RunGuide.RunEndHoldTick(g, s_pc, 10f); // 워밍업(조종 획득 1회)
|
||
long a0 = Alloc();
|
||
for (int i = 0; i < 100; i++) RunGuide.RunEndHoldTick(g, s_pc, 10f + i);
|
||
long a1 = Alloc();
|
||
L(" " + P(a1 - a0 <= 0) + " 정지 틱 ×100 = " + (a1 - a0) + " B");
|
||
RunGuide.EndRunEndHold("GC 끝");
|
||
s_pc.Set_JoystickStatus(3);
|
||
|
||
// ⓑ 빈 존 감시
|
||
BossArena.SetRunActiveForProbe(1);
|
||
BossArena.ZoneRearmTick(b, 500f);
|
||
long b0 = Alloc();
|
||
for (int i = 0; i < 100; i++) BossArena.ZoneRearmTick(b, 500f + i * 0.01f);
|
||
long b1 = Alloc();
|
||
L(" " + P(b1 - b0 <= 0) + " 빈 존 감시 ×100 = " + (b1 - b0) + " B");
|
||
BossArena.SetRunActiveForProbe(0);
|
||
|
||
// ⓒ 차단 판정 · 런 존 판정
|
||
long c0 = Alloc();
|
||
bool acc = false;
|
||
for (int i = 0; i < 100; i++) acc ^= BossArena.ShouldHoldZoneSpawn(b, i % 2 == 0 ? z0 : other, i % 2 == 0);
|
||
long c1 = Alloc();
|
||
L(" " + P(c1 - c0 <= 0) + " 차단 판정 ×100 = " + (c1 - c0) + " B (acc=" + acc + ")");
|
||
|
||
long d0 = Alloc();
|
||
for (int i = 0; i < 100; i++) acc ^= BossArena.IsRunZoneSpawner(i % 2 == 0 ? z0 : other);
|
||
long d1 = Alloc();
|
||
L(" " + P(d1 - d0 <= 0) + " 런 존 판정 ×100 = " + (d1 - d0) + " B (acc=" + acc + ")");
|
||
|
||
RunGuide.ResetDiagnostics();
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
// ⑧ C8 롤백
|
||
// ─────────────────────────────────────────────────────────────────────
|
||
static void Sec8_C8()
|
||
{
|
||
L("");
|
||
L("── ⑧ C8 롤백 (신규 스위치 0 → 813w6 동작 그대로)");
|
||
|
||
// ⓐ holdPcOnRunEnd = 0 → 런 종료 정지 경로가 통째로 죽는다
|
||
var tg = TmpG();
|
||
try
|
||
{
|
||
UseTmpG(tg);
|
||
tg.holdPcOnRunEnd = false;
|
||
RunGuide.ResetDiagnostics();
|
||
s_pc.Set_JoystickStatus(3);
|
||
RunGuide.ProbeRunEnded(RunOutcome.Timeout);
|
||
L(" " + P(!RunGuide.RunEndHeld && RunGuide.RunEndHolds == 0) +
|
||
" holdPcOnRunEnd=0 → 정지 0(RunEndHeld=" + RunGuide.RunEndHeld + " RunEndHolds=" + RunGuide.RunEndHolds + ")");
|
||
L(" " + P(s_pc.JoystickStatus == 0) + " 조종도 잡지 않는다(JoystickStatus=" + s_pc.JoystickStatus + ") = 813w6 그대로");
|
||
|
||
// 정지 중에 스위치를 끄면 그 자리에서 풀린다
|
||
tg.holdPcOnRunEnd = true;
|
||
RunGuide.BeginRunEndHold("C8");
|
||
tg.holdPcOnRunEnd = false;
|
||
RunGuide.RunEndHoldTick(tg, s_pc, 900f);
|
||
L(" " + P(!RunGuide.RunEndHeld) + " 정지 중 스위치 off → 즉시 해제(사유 '" + RunGuide.LastRunEndHoldReason + "')");
|
||
}
|
||
finally { UseRealG(); UnityEngine.Object.DestroyImmediate(tg); RunGuide.ResetDiagnostics(); }
|
||
|
||
// ⓑ rearmZoneSpawnsEnabled = 0 → 감시·재무장 경로가 통째로 죽는다
|
||
var tb = TmpB();
|
||
try
|
||
{
|
||
UseTmpB(tb);
|
||
BossArena.SetRunActiveForProbe(1);
|
||
BossArena.RebuildRearmZonesForProbe();
|
||
tb.rearmZoneSpawnsEnabled = false;
|
||
BossArena.ZoneRearmTick(tb, 1000f);
|
||
L(" " + P(BossArena.RearmZoneEmptySinceAt(0) < 0f) +
|
||
" rearmZoneSpawnsEnabled=0 → 감시 무동작(빈 시각 " + BossArena.RearmZoneEmptySinceAt(0) + ")");
|
||
L(" " + P(BossArena.RearmZoneSpawns("C8") == 0) + " RearmZoneSpawns() 도 0");
|
||
|
||
tb.rearmZoneSpawnsEnabled = true;
|
||
tb.rearmZoneSpawnsOnRestart = false;
|
||
L(" " + P(BossArena.RearmZoneSpawns("C8") == 0) + " rearmZoneSpawnsOnRestart=0 → [다음 런] 즉시 재무장 0");
|
||
}
|
||
finally { UseRealB(); BossArena.SetRunActiveForProbe(0); UnityEngine.Object.DestroyImmediate(tb); }
|
||
|
||
// ⓒ RuntimeDisabled — 축 전체 off
|
||
WLRunGuideSettings.RuntimeDisabled = true;
|
||
RunGuide.ResetDiagnostics();
|
||
RunGuide.BeginRunEndHold("RuntimeDisabled");
|
||
RunGuide.ForceTick();
|
||
L(" " + P(!WLRunGuideSettings.Enabled && !RunGuide.Active) + " RunGuide RuntimeDisabled → Active=false");
|
||
L(" " + P(!RunGuide.RunEndHeld) + " Tick 이 정지를 풀고 원본으로 돌려준다(RunEndHeld=" + RunGuide.RunEndHeld + ")");
|
||
WLRunGuideSettings.RuntimeDisabled = false;
|
||
|
||
WLBossArenaSettings.RuntimeDisabled = true;
|
||
var b2 = WLBossArenaSettings.Instance;
|
||
BossArena.SetRunActiveForProbe(1);
|
||
BossArena.RebuildRearmZonesForProbe();
|
||
BossArena.ZoneRearmTick(b2, 2000f);
|
||
L(" " + P(BossArena.RearmZoneEmptySinceAt(0) < 0f) + " BossArena RuntimeDisabled → 감시 무동작");
|
||
WLBossArenaSettings.RuntimeDisabled = false;
|
||
BossArena.SetRunActiveForProbe(0);
|
||
RunGuide.ResetDiagnostics();
|
||
}
|
||
}
|