diff --git a/AgentScripts/WL813w3_PROBE.txt b/AgentScripts/WL813w3_PROBE.txt index d5a1fc8c7..3ac406aab 100644 --- a/AgentScripts/WL813w3_PROBE.txt +++ b/AgentScripts/WL813w3_PROBE.txt @@ -1,4 +1,4 @@ -# WL813w3 Probe 2026-09-09 14:46:42 (edit mode · Play 0 · 로그인 0) +# WL813w3 Probe 2026-09-09 14:48:56 (edit mode · Play 0 · 로그인 0) playMode=False ── ⓪ 준비 @@ -20,6 +20,8 @@ playMode=False [PASS] D-27 스위치 3종 on ── ② 이탈 원인 진단 (§1-1 · Q7 이 읽는 줄) — PC 를 반경 밖으로 옮긴다 + [PASS] 🔴 도착 전 50 m — 이탈 0 · 진단 0(BossArrivedOnce=False) = 게이트 개방 직후 순간이동이 생기지 않는다 + [PASS] 아레나 도착 → 이탈 감시 개시(BossArrivedOnce=true) 시작 상태=Hold · 목적지=(139.0,0.8,-20.0) [PASS] 중심에서 Hold(도착 반경 10 m) [PASS] 반경 안 — 이탈 0(실측 0) @@ -27,19 +29,19 @@ playMode=False [PASS] 링버퍼 1칸(실측 1) [PASS] 되돌림도 그대로 1회(실측 1) — 813w2 경로 유지 진단 줄 원문(보스 생존): - #1 t=221.5s f=12 · 중심 37.0m · 보스 37.0m(보스-중심 0.0m 점프=N 락=Y) · 타깃=보스 · 조이스틱=0(조종 N) · 애님=Idle CC=N · 에이전트 v=0.0 정지=N NavMesh=N 목적지-중심=-1.0m · 가이드=Hold · 피격=없음 + #1 t=355.1s f=14 · 중심 37.0m · 보스 37.0m(보스-중심 0.0m 점프=N 락=Y) · 타깃=보스 · 조이스틱=0(조종 N) · 애님=Idle CC=N · 에이전트 v=0.0 정지=N NavMesh=N 목적지-중심=-1.0m · 가이드=Hold · 피격=없음 [PASS] 중심 거리 37.0 m [PASS] 타깃 종류 = 보스(실측 1) · 락=True — 보스 생존 중엔 락이 걸려 있으니 「보스를 쫓아 나간 것」으로 읽는다 [PASS] 보스는 중심(보스-중심 0.0 m) = 보스 점프 아님 → **잡몹/원본 이동이 끌고 나간 경우** [PASS] PC-보스 37.0 m [PASS] 가이드 상태 = Hold(이탈 순간) [PASS] 이 줄은 이탈 줄(워프 줄 아님) - [PASS] frame=12 · index=1 · 조이스틱=0 · 애님=Idle · 에이전트 NavMesh=False + [PASS] frame=14 · index=1 · 조이스틱=0 · 애님=Idle · 에이전트 NavMesh=False [PASS] 줄에 타깃·가이드 상태가 문자로 들어간다(Q7 이 로그만 읽는다) [PASS] 반경 안 복귀 → 이탈 타이머 해제(OutsideSince=-1.0) [PASS] 재진입해도 이탈 수는 그대로 1(에피소드당 1줄) 진단 줄 원문(보스 없음 · 잡몹 타깃): - #1 t=221.5s f=12 · 중심 37.0m · 보스 -1.0m(보스-중심 -1.0m 점프=N 락=N) · 타깃=잡몹 · 조이스틱=0(조종 N) · 애님=Idle CC=N · 에이전트 v=0.0 정지=N NavMesh=N 목적지-중심=-1.0m · 가이드=Hold · 피격=없음 + #1 t=355.1s f=14 · 중심 37.0m · 보스 -1.0m(보스-중심 -1.0m 점프=N 락=N) · 타깃=잡몹 · 조이스틱=0(조종 N) · 애님=Idle CC=N · 에이전트 v=0.0 정지=N NavMesh=N 목적지-중심=-1.0m · 가이드=Hold · 피격=없음 [PASS] 🔴 타깃 종류 = 잡몹(실측 2) — 「누가 끌고 나갔는가」 [PASS] 보스 없음 → 보스 필드 = −1(실측 -1.0) [PASS] 줄에 「타깃=잡몹」 @@ -51,7 +53,7 @@ playMode=False [PASS] 3 s 전 워프 → 쿨다운(5s)으로 보류 [PASS] 6 s 전 워프 → 쿨다운 지나 다시 워프 [PASS] 보스 곁 → 워프하지 않는다(정상 추적) - [PASS] 이탈 직후 폴링 — 워프 시도 0(1.5 s 대기 중) · OutsideSince=221.5 + [PASS] 이탈 직후 폴링 — 워프 시도 0(1.5 s 대기 중) · OutsideSince=355.1 [PASS] 가짜 시각 t=1000 — 이탈 기록 · OutsideSince=1000 [PASS] t=1001(+1.0 s < 1.5) — 워프 경로 미도달 [PASS] 🔴 t=1002(+2.0 s ≥ 1.5) — **워프 경로 도달**(워프 0 · NavMesh 지점 없음 1) diff --git a/AgentScripts/WL813w3_Probe.cs b/AgentScripts/WL813w3_Probe.cs index e80170fea..abeca2de3 100644 --- a/AgentScripts/WL813w3_Probe.cs +++ b/AgentScripts/WL813w3_Probe.cs @@ -198,9 +198,22 @@ public static class WL813w3_Probe Vector3 center = BossArena.Center; s_bossGo.transform.position = center; // 보스는 아레나 한복판 + + // 🔴 「가는 중」은 이탈이 아니다 — 게이트 개방 순간 PC 는 존에 있어 중심에서 50 m 밖일 수 있다. + MovePc(center + new Vector3(50f, 0f, 0f)); + RunGuide.BeginBoss("probe travel"); + for (int i = 0; i < 5; i++) RunGuide.ForceTick(); + L(" " + P(!RunGuide.BossArrivedOnce && RunGuide.ArenaExits == 0 && RunGuide.ExitLogCount == 0) + + " 🔴 도착 전 50 m — 이탈 0 · 진단 0(BossArrivedOnce=" + RunGuide.BossArrivedOnce + + ") = 게이트 개방 직후 순간이동이 생기지 않는다"); + RunGuide.StopGuide("probe travel end"); + RunGuide.ResetDiagnostics(); + RunGuide.SetBossForProbe(s_boss); + MovePc(center); RunGuide.BeginBoss("probe"); RunGuide.ForceTick(); // 중심 = 도착 → Hold + L(" " + P(RunGuide.BossArrivedOnce) + " 아레나 도착 → 이탈 감시 개시(BossArrivedOnce=true)"); L(" 시작 상태=" + RunGuide.State + " · 목적지=" + V(RunGuide.Destination)); L(" " + P(RunGuide.State == RunGuideState.Hold) + " 중심에서 Hold(도착 반경 " + c.bossArriveRadius + " m)"); L(" " + P(RunGuide.ArenaExits == 0) + " 반경 안 — 이탈 0(실측 " + RunGuide.ArenaExits + ")"); diff --git a/Assets/WL/Combat/Run/RunGuide.cs b/Assets/WL/Combat/Run/RunGuide.cs index aa0d93d36..face24759 100644 --- a/Assets/WL/Combat/Run/RunGuide.cs +++ b/Assets/WL/Combat/Run/RunGuide.cs @@ -239,6 +239,10 @@ namespace WL.Combat.Run // 813w3 §1-2 강제 복귀 — 이탈 시작 시각 · 마지막 워프 시각 · 다음 반복 진단 시각 static float s_outsideSince = -1f, s_lastWarp = -999f, s_nextExitDiag; + // 🔴 813w3: 아레나에 **한 번이라도 도착한 뒤**부터만 이탈로 센다. + // 게이트가 열린 순간 PC 는 존에 있어 중심에서 50 m 밖일 수 있는데, 그건 이탈이 아니라 「가는 중」이다. + // 이 가드가 없으면 게이트 개방 1.5 s 뒤에 PC 가 아레나로 순간이동해 이동 구간이 통째로 사라진다. + static bool s_bossArrivedOnce; // 존 앵커 캐시(스포너 노드 실좌표 · 없으면 SO 폴백) const int kMaxZones = 16; @@ -302,7 +306,7 @@ namespace WL.Combat.Run s_nextTickLog = 0f; s_lastAccumTime = -1f; LastTickLog = ""; // 813w3: 이탈·워프 집계도 런 단위(QA 가 런 1회 표를 읽는다). 링버퍼 기록은 남긴다. ArenaExits = ArenaWarps = WarpsSkippedCooldown = WarpsSkippedNearBoss = WarpsFailedNavMesh = 0; - s_outsideSince = -1f; s_lastWarp = -999f; s_nextExitDiag = 0f; + s_outsideSince = -1f; s_lastWarp = -999f; s_nextExitDiag = 0f; s_bossArrivedOnce = false; var cfg = Cfg; if (cfg != null && cfg.zoneGuideEnabled && cfg.guideOnRunStart) BeginZone(0, "런 시작"); } @@ -393,6 +397,7 @@ namespace WL.Combat.Run s_destKind = RunGuideDest.Boss; s_zoneIndex = -1; s_dest = BossArena.Center; + s_bossArrivedOnce = false; // 813w3: 새 보스 유도 = 「가는 중」부터 다시 시작 EnterTravel(); GuideBegun++; Log("보스 아레나 유도 시작(" + reason + ") → " + Fmt(s_dest)); @@ -410,6 +415,7 @@ namespace WL.Combat.Run s_hitUntil = 0f; s_stillSince = -1f; s_lastPosTime = -1f; s_outsideSince = -1f; s_nextExitDiag = 0f; // 813w3: 이탈 타이머는 목적지와 함께 끝난다 + s_bossArrivedOnce = false; if (had) Log("가이드 종료 — " + reason); } @@ -475,8 +481,9 @@ namespace WL.Combat.Run bool suspend = SuspendDecision(cfg, hit, s_state); // ── 813w3 §1-1·§1-2: 보스 구간 이탈 추적 → 진단 1줄 → (1.5 s 뒤에도 밖이면) 강제 워프. - // 상태(Travel/Hold)와 무관하게 돈다 — 되돌림 중(Travel)에 못 돌아오는 것이 D-22 재발의 실체다. - if (isBoss && TrackArenaExit(cfg, pc, pcPos, d, hold, now, hit)) return; // 워프했다 = 이 폴링 끝 + // ① 아레나에 한 번이라도 도착한 뒤부터(= 「가는 중」은 이탈이 아니다) + // ② 상태(Travel/Hold)와 무관하게 — 되돌림 중(Travel)에 못 돌아오는 것이 D-22 재발의 실체다. + if (isBoss && s_bossArrivedOnce && TrackArenaExit(cfg, pc, pcPos, d, hold, now, hit)) return; // 워프 = 폴링 끝 if (s_state == RunGuideState.Travel) { @@ -531,6 +538,7 @@ namespace WL.Combat.Run s_state = RunGuideState.Hold; ReleaseSteer(); s_stillSince = -1f; s_lastPosTime = -1f; + if (isBoss) s_bossArrivedOnce = true; // 813w3: 여기서부터 「이탈」을 센다 Log((isBoss ? "아레나" : "존 " + s_zoneIndex) + " " + why); } @@ -1026,7 +1034,7 @@ namespace WL.Combat.Run s_faulted = false; s_anchorCount = 0; // 813w3 ArenaExits = ArenaWarps = WarpsSkippedCooldown = WarpsSkippedNearBoss = WarpsFailedNavMesh = 0; - s_outsideSince = -1f; s_lastWarp = -999f; s_nextExitDiag = 0f; + s_outsideSince = -1f; s_lastWarp = -999f; s_nextExitDiag = 0f; s_bossArrivedOnce = false; s_exitLogNext = s_exitLogCount = 0; LastExitLog = ""; } @@ -1041,6 +1049,8 @@ namespace WL.Combat.Run public static float LastWarpTime { get { return s_lastWarp; } } /// 프로브용: 가이드 상태를 직접 세운다(에디트 모드에서 Hold 시나리오를 만들 때). public static void SetStateForProbe(RunGuideState st) { s_state = st; } + /// 아레나에 한 번이라도 도착했는가 — 이탈 감시는 이 뒤부터 돈다(진단·프로브). + public static bool BossArrivedOnce { get { return s_bossArrivedOnce; } } /// 프로브용: 조종 플래그를 직접 세운다(에디트 모드엔 NavMeshAgent 가 없어 Steer 가 되돌아간다). public static void SetSteeringForProbe(bool on) { s_steering = on; if (!on) { s_stillSince = -1f; s_lastPosTime = -1f; } }