// 발주서 WL-813t3 프로브 — 에디트 모드 · Play 0 · 로그인 0 // // unity command --project-path E:\NerdNavis\WL_wt\data run_script --file AgentScripts/WL813t3_Probe.cs --entry WL813t3_Probe.Run --timeout 900 // // ① 시야 3 m (가짜 적 2.5 / 3.5 m → 유지 / 놓음 · 락 · 이동 정지 경로) // ② 보스 평지 스폰 (ResolveBossPin 실호출 · 아레나 앵커 · y 컷) // ③ C8 진리표 (값 원복 = 813t2 동작) · GC // // 🔴 using 금지(CLAUDE.md §1) — 네임스페이스를 풀어 쓴다. public static class WL813t3_Probe { const string kOut = "AgentScripts/staging/WL813t3/PROBE.txt"; static System.Text.StringBuilder sb; static int s_fail; static void L(string m) { sb.AppendLine(m); } static string P(bool ok) { if (!ok) s_fail++; return ok ? "[PASS]" : "[FAIL]"; } static string V(UnityEngine.Vector3 v) { return v.ToString("F2"); } static UnityEngine.GameObject s_pcGo, s_nearGo, s_farGo, s_lockGo; static PCActor s_pc; static Actor s_near, s_far, s_lock; static PCActor s_prevPc; public static string Run() { sb = new System.Text.StringBuilder(); s_fail = 0; L("===== WL-813t3 프로브 · " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " ====="); L("에디트 모드 · Play 0 · 로그인 0 · 실 에셋은 읽기만 한다."); try { Setup(); Sec1_Decide(); Sec2_Live(); Sec3_BossPin(); Sec4_C8(); Sec5_GC(); } catch (System.Exception e) { L("[에러] " + e.GetType().Name + " — " + e.Message); L(e.StackTrace); s_fail++; } finally { Teardown(); } L(""); L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL(" + s_fail + ")")); try { System.IO.Directory.CreateDirectory(System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "AgentScripts/staging/WL813t3")); System.IO.File.WriteAllText(System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), kOut.Replace('/', System.IO.Path.DirectorySeparatorChar)), sb.ToString(), new System.Text.UTF8Encoding(true)); } catch (System.Exception e) { UnityEngine.Debug.LogWarning("[WL813t3] 프로브 저장 실패: " + e.Message); } return sb.ToString(); } // ───────────────────────────────────────────────────────────────────────── static Actor MakeActor(string name, eRole role, eSubRol sub, UnityEngine.Vector3 pos, out UnityEngine.GameObject go) { go = new UnityEngine.GameObject("__WL813t3_" + name); go.transform.position = pos; var a = go.AddComponent(); a.m_Role = role; a.m_SubRole = sub; return a; } static void Setup() { s_prevPc = MyValue.MyPC; s_pcGo = new UnityEngine.GameObject("__WL813t3_PC"); s_pcGo.transform.position = UnityEngine.Vector3.zero; s_pc = s_pcGo.AddComponent(); s_pc.m_Role = eRole.PC; s_pc.m_SubRole = eSubRol.None; MyValue.MyPC = s_pc; s_near = MakeActor("near", eRole.Mob, eSubRol.None, new UnityEngine.Vector3(2.5f, 0f, 0f), out s_nearGo); s_far = MakeActor("far", eRole.Mob, eSubRol.None, new UnityEngine.Vector3(3.5f, 0f, 0f), out s_farGo); s_lock = MakeActor("lockedBoss", eRole.Mob, eSubRol.Boss, new UnityEngine.Vector3(9f, 0f, 0f), out s_lockGo); s_lock.m_IsLockTarget = true; WL.Settings.WLTargetingSettings.ClearCache(); WL.Combat.Auto.SightRangeGuard.RuntimeDisabled = false; WL.Combat.Auto.SightRangeGuard.ResetDiagnostics(); L(""); L("── ⓪ 준비"); L(" " + P(s_pc.IsMainPC()) + " 가짜 메인 PC · MyValue.MyPC 등록"); var t = WL.Settings.WLTargetingSettings.Instance; L(" " + P(t != null) + " WLTargetingSettings 에셋 로드"); if (t != null) L(" 실값 retargetSearchRange=" + t.retargetSearchRange + " · chaseRange=" + t.chaseRange + " · chaseDropIgnoresLock=" + t.chaseDropIgnoresLock + " · stopChaseMoveOnDrop=" + t.stopChaseMoveOnDrop + " · chaseCheckSeconds=" + t.chaseCheckSeconds); L(" " + P(WL.Combat.Auto.SightRangeGuard.Active) + " SightRangeGuard.Active (chaseRange > 0)"); L(" PC Agent = " + (s_pc.Get_Agent() == null ? "없음(에디트 모드 — 이동 정지 경로는 미확인)" : "있음")); } static void Teardown() { try { WL.Settings.WLTargetingSettings.SetInstanceForProbe(null); WL.Settings.WLTargetingSettings.ClearCache(); WL.Combat.Nav.WLTerrainGuardSettings.ClearCache(); WL.Combat.Auto.SightRangeGuard.RuntimeDisabled = false; WL.Combat.Auto.SightRangeGuard.ResetDiagnostics(); WL.Combat.Nav.SpawnPositionGuard.ResetDiagnostics(); MyValue.MyPC = s_prevPc; Kill(s_pcGo); Kill(s_nearGo); Kill(s_farGo); Kill(s_lockGo); } catch { } } static void Kill(UnityEngine.GameObject go) { if (go != null) UnityEngine.Object.DestroyImmediate(go); } // ───────────────────────────────────────────────────────────────────────── // ① 순수 판정 (Decide) // ───────────────────────────────────────────────────────────────────────── static void Sec1_Decide() { L(""); L("── ① 시야 3 m 판정표 (SightRangeGuard.Decide · 순수 함수)"); L(" | 거리 | 락 | ignoreLock | 판정 |"); Row(3f, 0.0f, false, false, WL.Combat.Auto.SightRangeGuard.KeepTarget, "0.0 m(붙어 있음)"); Row(3f, 2.5f, false, false, WL.Combat.Auto.SightRangeGuard.KeepTarget, "2.5 m"); Row(3f, 3.0f, false, false, WL.Combat.Auto.SightRangeGuard.KeepTarget, "3.0 m(경계 = 유지)"); Row(3f, 3.01f, false, false, WL.Combat.Auto.SightRangeGuard.DropTarget, "3.01 m"); Row(3f, 3.5f, false, false, WL.Combat.Auto.SightRangeGuard.DropTarget, "3.5 m"); Row(3f, 14f, false, false, WL.Combat.Auto.SightRangeGuard.DropTarget, "14 m(813w 보스 홀드 반경)"); Row(3f, 3.5f, true, false, WL.Combat.Auto.SightRangeGuard.KeepByLock, "3.5 m · 락 · 락 존중"); Row(3f, 3.5f, true, true, WL.Combat.Auto.SightRangeGuard.DropTarget, "3.5 m · 락 · 락 무시"); Row(0f, 100f, false, false, WL.Combat.Auto.SightRangeGuard.KeepDisabled, "chaseRange 0 = 축 off(C8)"); } static void Row(float range, float dist, bool locked, bool ignore, int expect, string label) { int got = WL.Combat.Auto.SightRangeGuard.Decide(range, dist, locked, ignore); L(" " + P(got == expect) + " " + label + " → " + Name(got) + " (기대 " + Name(expect) + ")"); } static string Name(int v) { if (v == WL.Combat.Auto.SightRangeGuard.KeepTarget) return "유지"; if (v == WL.Combat.Auto.SightRangeGuard.DropTarget) return "놓음"; if (v == WL.Combat.Auto.SightRangeGuard.KeepByLock) return "락유지"; return "축off"; } // ───────────────────────────────────────────────────────────────────────── // ② 실제 액터로 CheckOnce // ───────────────────────────────────────────────────────────────────────── static void Sec2_Live() { L(""); L("── ② 가짜 적으로 실호출 (SightRangeGuard.CheckOnce · 원본 Del_Target 경로)"); WL.Combat.Auto.SightRangeGuard.ResetDiagnostics(); s_pc.Set_Target(s_near); bool dropped = WL.Combat.Auto.SightRangeGuard.CheckOnce(); L(" " + P(!dropped && s_pc.isTarget && object.ReferenceEquals(s_pc.Get_Target(), s_near)) + " 2.5 m 적 → 타깃 유지 (거리 실측 " + WL.Combat.Auto.SightRangeGuard.LastDistance.ToString("F2") + " m)"); s_pc.Set_Target(s_far); dropped = WL.Combat.Auto.SightRangeGuard.CheckOnce(); L(" " + P(dropped && !s_pc.isTarget) + " 3.5 m 적 → **타깃 해제** (거리 실측 " + WL.Combat.Auto.SightRangeGuard.LastDistance.ToString("F2") + " m · Dropped=" + WL.Combat.Auto.SightRangeGuard.Dropped + " · 마지막 " + WL.Combat.Auto.SightRangeGuard.LastDroppedName + ")"); // 락 타깃(813w 보스) — 기본값(락 존중)에서는 유지된다 s_pc.Set_Target(s_lock); dropped = WL.Combat.Auto.SightRangeGuard.CheckOnce(); bool lockKept = !dropped && s_pc.isTarget; L(" " + P(lockKept) + " 9 m 락 보스 → 유지(원본 락 존중 · LockKept=" + WL.Combat.Auto.SightRangeGuard.LockKept + ")" + " · SO `bossLockTarget` 을 0 으로 두면 813w 가 애초에 락을 걸지 않는다"); // 락 무시 스위치 var tmp = MakeTemp(); tmp.chaseRange = 3f; tmp.chaseDropIgnoresLock = true; tmp.stopChaseMoveOnDrop = false; WL.Settings.WLTargetingSettings.SetInstanceForProbe(tmp); s_pc.Set_Target(s_lock); dropped = WL.Combat.Auto.SightRangeGuard.CheckOnce(); L(" " + P(dropped && !s_pc.isTarget) + " chaseDropIgnoresLock=1 → 락 보스도 해제"); WL.Settings.WLTargetingSettings.SetInstanceForProbe(null); UnityEngine.Object.DestroyImmediate(tmp); s_pc.Del_Target(true); L(" 이동 정지 경로(MoveStopped=" + WL.Combat.Auto.SightRangeGuard.MoveStopped + " · MoveSkipped=" + WL.Combat.Auto.SightRangeGuard.MoveSkipped + ") — 🔴 에디트 모드엔 NavMeshAgent·애니메이터가 없어 **실이동 정지는 미확인**(§QA 로 확인)."); } static WL.Settings.WLTargetingSettings MakeTemp() { var so = UnityEngine.ScriptableObject.CreateInstance(); var real = UnityEngine.Resources.Load("WL/WLTargetingSettings"); if (real != null) { so.frontConeDeg = real.frontConeDeg; so.facingWeight = real.facingWeight; so.attackStartRetarget = real.attackStartRetarget; so.retargetOnKill = real.retargetOnKill; so.immediateAttackMargin = real.immediateAttackMargin; so.retargetSearchRange = real.retargetSearchRange; so.pcAggroOnHit = real.pcAggroOnHit; so.autoEngage = real.autoEngage; so.chaseRange = real.chaseRange; so.chaseDropIgnoresLock = real.chaseDropIgnoresLock; so.stopChaseMoveOnDrop = real.stopChaseMoveOnDrop; so.chaseCheckSeconds = real.chaseCheckSeconds; } return so; } // ───────────────────────────────────────────────────────────────────────── // ③ 보스 평지 스폰 // ───────────────────────────────────────────────────────────────────────── static void Sec3_BossPin() { L(""); L("── ③ 보스 평지 스폰 (SpawnPositionGuard.ResolveBossPin · 순수 함수)"); var cfg = WL.Combat.Nav.WLTerrainGuardSettings.Instance; L(" " + P(cfg != null) + " WLTerrainGuardSettings 에셋 로드"); if (cfg == null) return; L(" 실값 bossSpawnPinEnabled=" + cfg.bossSpawnPinEnabled + " · bossSpawnPinRadius=" + cfg.bossSpawnPinRadius + " · bossSpawnPinTolerance=" + cfg.bossSpawnPinTolerance + " · maxWalkableY=" + cfg.maxWalkableY + " · dwellSeconds=" + cfg.dwellSeconds); var arena = WL.Combat.Boss.BossArena.Center; L(" BossArena.Center(에디트 모드 · 보류 목록 비어 있음 → SO arenaCenter) = " + V(arena)); // 실 NavMesh 자산을 잠시 올린다(WL_Nature 재베이크본) — 표집이 실제로 돌게. var data = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/WL/Settings/WL_Nature_NavMesh.asset"); UnityEngine.AI.NavMeshDataInstance inst = default(UnityEngine.AI.NavMeshDataInstance); if (data != null) inst = UnityEngine.AI.NavMesh.AddNavMeshData(data); L(" " + P(data != null) + " WL_Nature_NavMesh.asset 로드·부착(삼각형 " + (UnityEngine.AI.NavMesh.CalculateTriangulation().indices.Length / 3) + ")"); try { UnityEngine.Vector3 outp; int r1 = WL.Combat.Nav.SpawnPositionGuard.ResolveBossPin(cfg, new UnityEngine.Vector3(139f, 6f, -20f), out outp); L(" " + P(r1 == WL.Combat.Nav.SpawnPositionGuard.BossPinMoved) + " 언덕 위 가짜 보스 (139, 6.00, -20) → " + PinName(r1) + " " + V(outp) + " · y 컷(" + cfg.maxWalkableY + ") 이하 " + (outp.y <= cfg.maxWalkableY)); int r2 = WL.Combat.Nav.SpawnPositionGuard.ResolveBossPin(cfg, arena, out outp); L(" " + P(r2 == WL.Combat.Nav.SpawnPositionGuard.BossPinAlready) + " 아레나 제자리 " + V(arena) + " → " + PinName(r2) + " " + V(outp)); int r3 = WL.Combat.Nav.SpawnPositionGuard.ResolveBossPin(cfg, new UnityEngine.Vector3(128f, 8f, -58f), out outp); L(" " + P(r3 == WL.Combat.Nav.SpawnPositionGuard.BossPinMoved) + " D-48 언덕 (128, 8.00, -58) → " + PinName(r3) + " " + V(outp)); int r5 = WL.Combat.Nav.SpawnPositionGuard.ResolveBossPin(cfg, new UnityEngine.Vector3(147f, 6f, 30f), out outp); L(" " + P(r5 == WL.Combat.Nav.SpawnPositionGuard.BossPinMoved) + " D-48 언덕 (147, 6.00, 30) → " + PinName(r5) + " " + V(outp)); var off = UnityEngine.ScriptableObject.CreateInstance(); off.bossSpawnPinEnabled = false; int r4 = WL.Combat.Nav.SpawnPositionGuard.ResolveBossPin(off, new UnityEngine.Vector3(139f, 6f, -20f), out outp); L(" " + P(r4 == WL.Combat.Nav.SpawnPositionGuard.BossPinNoAnchor) + " C8: bossSpawnPinEnabled=0 → 손대지 않음(" + PinName(r4) + ")"); UnityEngine.Object.DestroyImmediate(off); } finally { if (inst.valid) UnityEngine.AI.NavMesh.RemoveNavMeshData(inst); } } static string PinName(int v) { if (v == WL.Combat.Nav.SpawnPositionGuard.BossPinNoAnchor) return "앵커없음(무개입)"; if (v == WL.Combat.Nav.SpawnPositionGuard.BossPinAlready) return "이미 제자리"; if (v == WL.Combat.Nav.SpawnPositionGuard.BossPinMoved) return "**옮김**"; return "앵커 부적격"; } // ───────────────────────────────────────────────────────────────────────── // ④ C8 진리표 // ───────────────────────────────────────────────────────────────────────── static void Sec4_C8() { L(""); L("── ④ C8 진리표 (값 원복 = 813t2 동작)"); var t0 = MakeTemp(); t0.chaseRange = 0f; WL.Settings.WLTargetingSettings.SetInstanceForProbe(t0); bool active0 = WL.Combat.Auto.SightRangeGuard.Active; s_pc.Set_Target(s_far); bool drop0 = WL.Combat.Auto.SightRangeGuard.CheckOnce(); L(" " + P(!active0 && !drop0 && s_pc.isTarget) + " chaseRange=0 → Active=false · 3.5 m 적 유지(= 813t3 이전 동작 100%)"); s_pc.Del_Target(true); WL.Settings.WLTargetingSettings.SetInstanceForProbe(null); UnityEngine.Object.DestroyImmediate(t0); WL.Combat.Auto.SightRangeGuard.RuntimeDisabled = true; bool active1 = WL.Combat.Auto.SightRangeGuard.Active; WL.Combat.Auto.SightRangeGuard.RuntimeDisabled = false; L(" " + P(!active1) + " RuntimeDisabled=true → Active=false"); var t1 = MakeTemp(); t1.retargetSearchRange = 0f; WL.Settings.WLTargetingSettings.SetInstanceForProbe(t1); L(" " + P(WL.Settings.WLTargetingSettings.RetargetSearchRange == 0f) + " retargetSearchRange=0 → MyActor.Get_WLFindRange() 가 원본 f_FindEnemyRange(table 6 m + AttackRange_Up)로 되돌아간다"); WL.Settings.WLTargetingSettings.SetInstanceForProbe(null); UnityEngine.Object.DestroyImmediate(t1); var g0 = UnityEngine.ScriptableObject.CreateInstance(); g0.enabled = false; WL.Combat.Nav.WLTerrainGuardSettings.SetInstanceForProbe(g0); L(" " + P(!WL.Combat.Nav.WLTerrainGuardSettings.Active && !WL.Combat.Nav.SpawnPositionGuard.Active) + " WLTerrainGuardSettings.enabled=0 → 813t2 러너·스폰 보정 전부 off(보스 고정 포함)"); WL.Combat.Nav.WLTerrainGuardSettings.SetInstanceForProbe(null); WL.Combat.Nav.WLTerrainGuardSettings.ClearCache(); UnityEngine.Object.DestroyImmediate(g0); L(" " + P(WL.Combat.Nav.WLTerrainGuardSettings.Instance != null) + " 실 에셋 복구 확인(프로브가 실 에셋을 쓰지 않았다)"); var b = UnityEngine.Resources.Load("WL/WLBossArenaSettings"); L(" " + P(b != null && !b.useFieldBossPositionAsCenter) + " WLBossArenaSettings.useFieldBossPositionAsCenter=0 → 아레나 중심이 **SO 값**(139, 0.8, -20)으로 고정 (1 로 되돌리면 스포너 노드 위치)"); var rg = UnityEngine.Resources.Load("WL/WLRunGuideSettings"); L(" " + P(rg != null && !rg.bossLockTarget) + " WLRunGuideSettings.bossLockTarget=0 → 813w 보스 락타깃 off(1 로 되돌리면 원상)"); } // ───────────────────────────────────────────────────────────────────────── // ⑤ GC // ───────────────────────────────────────────────────────────────────────── static void Sec5_GC() { L(""); L("── ⑤ GC (틱 · 판정 반복)"); s_pc.Set_Target(s_near); // 유지 경로(로그·문자열 없음)로 돈다 WL.Combat.Auto.SightRangeGuard.ResetDiagnostics(); for (int i = 0; i < 50; i++) WL.Combat.Auto.SightRangeGuard.CheckOnce(); System.GC.Collect(); long a = System.GC.GetTotalMemory(false); for (int i = 0; i < 1000; i++) WL.Combat.Auto.SightRangeGuard.CheckOnce(); var cfg = WL.Combat.Nav.WLTerrainGuardSettings.Instance; UnityEngine.Vector3 o; for (int i = 0; i < 1000; i++) WL.Combat.Nav.SpawnPositionGuard.ResolveBossPin(cfg, new UnityEngine.Vector3(139f, 6f, -20f), out o); for (int i = 0; i < 1000; i++) WL.Combat.Auto.SightRangeGuard.Decide(3f, 3.5f, false, false); long b = System.GC.GetTotalMemory(false); L(" " + P(b - a <= 0) + " CheckOnce ×1000 + ResolveBossPin ×1000 + Decide ×1000 → Δ " + (b - a) + " B"); s_pc.Del_Target(true); } }