Project_WL/AgentScripts/WL813t4_Probe.cs

313 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 발주서 WL-813t4 — 프로브 (에디트 모드 · Play 0 · 로그인 0)
//
// unity command --project-path E:\NerdNavis\WL_wt\systems run_script --file AgentScripts/WL813t4_Probe.cs --entry WL813t4_Probe.Run --timeout 900
//
// ① 값 실측 — arenaCenter · 스테이지 5·10 앵커 · 813w WLRunGuideSettings 의 아레나 참조
// ② Telegraph.StageScale 연결(입장 → def.telegraphScale · 이탈/실패/클리어/Abort → 1)
// ③ 리스폰 0 / 배치 / 회수 seam 재실측(815b StopRespawnOverride·DeployOverride·RecallOverride)
// ④ C8 진리표(값 원복 = 813d 동작)
// ⑤ GC Δ
public static class WL813t4_Probe
{
static System.Text.StringBuilder s;
static int s_pass, s_fail;
static void L(string t) { s.AppendLine(t); UnityEngine.Debug.Log("[WL813t4P] " + t); }
static void Chk(string name, bool ok, string got)
{
if (ok) { s_pass++; L(" [PASS] " + name + " — " + got); }
else { s_fail++; L(" [FAIL] " + name + " — " + got); }
}
static string Abs(string rel)
{
return System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(),
rel.Replace('/', System.IO.Path.DirectorySeparatorChar));
}
static readonly UnityEngine.Vector3 kNew = new UnityEngine.Vector3(179f, 0.01f, 4f);
static readonly UnityEngine.Vector3 kOld = new UnityEngine.Vector3(139f, 0.8f, -20f);
public static string Run()
{
s = new System.Text.StringBuilder(); s_pass = 0; s_fail = 0;
L("===== WL-813t4 프로브 · " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " =====");
var arena = UnityEngine.Resources.Load<WL.Combat.Boss.WLBossArenaSettings>("WL/WLBossArenaSettings");
var tbl = UnityEngine.Resources.Load<WL.Combat.Stage.WLStageTable>("WL/WLStageTable");
var guide = UnityEngine.Resources.Load<WL.Combat.Run.WLRunGuideSettings>("WL/WLRunGuideSettings");
var stg = UnityEngine.Resources.Load<WL.Combat.Stage.WLStageSettings>("WL/WLStageSettings");
if (arena == null || tbl == null || stg == null) { L("[에러] SO 로드 실패"); return Flush(); }
Section1_Values(arena, tbl, guide, stg);
Section2_StageScale(tbl);
Section3_Spawner(tbl);
Section4_C8(arena, stg, tbl);
Section5_GC();
L("");
L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL") + " — PASS " + s_pass + " / FAIL " + s_fail);
return Flush();
}
static string Flush()
{
try { System.IO.File.WriteAllText(Abs("AgentScripts/WL813t4_PROBE.txt"), s.ToString(), new System.Text.UTF8Encoding(true)); }
catch (System.Exception e) { UnityEngine.Debug.LogWarning("[WL813t4P] 저장 실패: " + e.Message); }
return s.ToString();
}
static bool Near(UnityEngine.Vector3 a, UnityEngine.Vector3 b) { return UnityEngine.Vector3.Distance(a, b) < 0.005f; }
// ─────────────────────────────────────────────────────────────────────────
static void Section1_Values(WL.Combat.Boss.WLBossArenaSettings arena, WL.Combat.Stage.WLStageTable tbl,
WL.Combat.Run.WLRunGuideSettings guide, WL.Combat.Stage.WLStageSettings stg)
{
L("");
L("── ① 값 실측 (F-28) ──────────────────────────────────────────────");
Chk("WLBossArenaSettings.arenaCenter", Near(arena.arenaCenter, kNew), arena.arenaCenter.ToString("F2"));
Chk("useFieldBossPositionAsCenter 유지 0", !arena.useFieldBossPositionAsCenter, arena.useFieldBossPositionAsCenter ? "1" : "0");
Chk("arenaRadius 20 유지", UnityEngine.Mathf.Abs(arena.arenaRadius - 20f) < 0.01f, arena.arenaRadius.ToString("F1"));
Chk("BossArena.Center 가 새 좌표를 읽는다", Near(WL.Combat.Boss.BossArena.Center, kNew), WL.Combat.Boss.BossArena.Center.ToString("F2"));
var d5 = tbl.Get(4); var d10 = tbl.Get(9);
Chk("WLStageTable 스테이지 5 anchor", d5 != null && Near(d5.anchor, kNew), d5 != null ? d5.anchor.ToString("F2") + " r" + d5.barrierRadius.ToString("F0") : "null");
Chk("스테이지 5 barrierRadius 20 유지", d5 != null && UnityEngine.Mathf.Abs(d5.barrierRadius - 20f) < 0.01f, d5 != null ? d5.barrierRadius.ToString("F1") : "null");
Chk("WLStageTable 스테이지 10 anchor", d10 != null && Near(d10.anchor, kNew), d10 != null ? d10.anchor.ToString("F2") + " r" + d10.barrierRadius.ToString("F0") : "null");
Chk("스테이지 10 barrierRadius 20 유지", d10 != null && UnityEngine.Mathf.Abs(d10.barrierRadius - 20f) < 0.01f, d10 != null ? d10.barrierRadius.ToString("F1") : "null");
// 나머지 8스테이지 앵커는 손대지 않았다
bool others = true; string ol = "";
for (int i = 0; i < tbl.Count; i++)
{
if (i == 4 || i == 9) continue;
var d = tbl.Get(i);
if (d == null) continue;
if (Near(d.anchor, kNew)) { others = false; ol += " #" + d.id; }
}
Chk("스테이지 5·10 외 앵커 무변경", others, others ? "8스테이지 그대로" : "바뀐 것" + ol);
// 813w WLRunGuideSettings — 아레나 좌표 필드가 따로 있는가(있으면 동기 필요)
L(" [813w 아레나 참조 실측] WLRunGuideSettings 의 Vector3 계열 필드 전수:");
int v3 = 0; bool sep = false;
var ft = typeof(WL.Combat.Run.WLRunGuideSettings).GetFields(
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
for (int i = 0; i < ft.Length; i++)
{
var f = ft[i];
if (f.FieldType != typeof(UnityEngine.Vector3) && f.FieldType != typeof(UnityEngine.Vector3[])) continue;
v3++;
string val = "";
if (guide != null)
{
if (f.FieldType == typeof(UnityEngine.Vector3)) val = ((UnityEngine.Vector3)f.GetValue(guide)).ToString("F2");
else { var arr = f.GetValue(guide) as UnityEngine.Vector3[]; val = "["; if (arr != null) for (int k = 0; k < arr.Length; k++) val += (k > 0 ? ", " : "") + arr[k].ToString("F2"); val += "]"; }
}
L(" · " + f.Name + " : " + f.FieldType.Name + " = " + val);
if (f.Name.IndexOf("arena", System.StringComparison.OrdinalIgnoreCase) >= 0
|| f.Name.IndexOf("boss", System.StringComparison.OrdinalIgnoreCase) >= 0) sep = true;
}
Chk("813w 에 아레나 좌표 별도 필드 없음(= BossArena.Center 를 읽는다 · 동기 불필요)", !sep,
"Vector3 계열 " + v3 + "개 · 아레나/보스 좌표 필드 " + (sep ? "있음" : "0") + " · 보스 유도는 반경만(bossArriveRadius "
+ (guide != null ? guide.bossArriveRadius.ToString("F0") : "?") + " / bossHoldRadius " + (guide != null ? guide.bossHoldRadius.ToString("F0") : "?") + ")");
Chk("WLStageSettings.Enabled", WL.Combat.Stage.WLStageSettings.Enabled, WL.Combat.Stage.WLStageSettings.Enabled ? "1" : "0");
L(" 참고 — 스테이지 배치 산포 반경 = barrierRadius × spawnScatterFactor " + stg.spawnScatterFactor.ToString("F2")
+ " = " + (20f * stg.spawnScatterFactor).ToString("F1") + " m · recallExcess " + (stg.recallExcess ? "1" : "0"));
}
// ─────────────────────────────────────────────────────────────────────────
static void Section2_StageScale(WL.Combat.Stage.WLStageTable tbl)
{
L("");
L("── ② Telegraph.StageScale 연결 (StageDirector 2줄) ─────────────────");
WL.Combat.Stage.StageDirector.ResetAll();
WL.Combat.Telegraph.Telegraph.StageScale = 1f;
Chk("초기값", UnityEngine.Mathf.Abs(WL.Combat.Telegraph.Telegraph.StageScale - 1f) < 0.0001f,
WL.Combat.Telegraph.Telegraph.StageScale.ToString("F2"));
// 입장 → def.telegraphScale
int[] idx = { 0, 4, 9 };
for (int i = 0; i < idx.Length; i++)
{
var def = tbl.Get(idx[i]);
WL.Combat.Stage.StageDirector.Enter(idx[i], "probe");
float got = WL.Combat.Telegraph.Telegraph.StageScale;
Chk("입장 스테이지 " + (def != null ? def.id : 0) + " → StageScale " + (def != null ? def.telegraphScale.ToString("F2") : "?"),
def != null && UnityEngine.Mathf.Abs(got - def.telegraphScale) < 0.0001f, got.ToString("F2")
+ " · 상태 " + WL.Combat.Stage.StageDirector.State);
if (i == 0)
{
// 이탈(Abort) → 1
WL.Combat.Stage.StageDirector.Abort("probe 이탈");
Chk("이탈(Abort) → StageScale 1", UnityEngine.Mathf.Abs(WL.Combat.Telegraph.Telegraph.StageScale - 1f) < 0.0001f,
WL.Combat.Telegraph.Telegraph.StageScale.ToString("F2") + " · 상태 " + WL.Combat.Stage.StageDirector.State);
}
}
// 클리어 · 실패 — private 이라 리플렉션으로 같은 경로를 태운다(SetState 를 지난다)
var t = typeof(WL.Combat.Stage.StageDirector);
var bf = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static;
WL.Combat.Stage.StageDirector.Enter(0, "probe clear");
float before = WL.Combat.Telegraph.Telegraph.StageScale;
var mClear = t.GetMethod("Clear", bf);
if (mClear != null) mClear.Invoke(null, null);
Chk("클리어 → StageScale 1 (전 " + before.ToString("F2") + ")",
UnityEngine.Mathf.Abs(WL.Combat.Telegraph.Telegraph.StageScale - 1f) < 0.0001f,
WL.Combat.Telegraph.Telegraph.StageScale.ToString("F2") + " · 상태 " + WL.Combat.Stage.StageDirector.State);
WL.Combat.Stage.StageDirector.Enter(0, "probe fail");
before = WL.Combat.Telegraph.Telegraph.StageScale;
var mFail = t.GetMethod("Fail", bf);
if (mFail != null) mFail.Invoke(null, new object[] { WL.Combat.Stage.StageFailReason.TimeLimit });
Chk("실패 → StageScale 1 (전 " + before.ToString("F2") + ")",
UnityEngine.Mathf.Abs(WL.Combat.Telegraph.Telegraph.StageScale - 1f) < 0.0001f,
WL.Combat.Telegraph.Telegraph.StageScale.ToString("F2") + " · 상태 " + WL.Combat.Stage.StageDirector.State);
// 로비 이탈
WL.Combat.Stage.StageDirector.Enter(9, "probe lobby");
before = WL.Combat.Telegraph.Telegraph.StageScale;
WL.Combat.Stage.StageDirector.ToLobby();
Chk("로비 이탈 → StageScale 1 (전 " + before.ToString("F2") + ")",
UnityEngine.Mathf.Abs(WL.Combat.Telegraph.Telegraph.StageScale - 1f) < 0.0001f,
WL.Combat.Telegraph.Telegraph.StageScale.ToString("F2") + " · 상태 " + WL.Combat.Stage.StageDirector.State);
// 재도전 → 다시 걸린다
WL.Combat.Stage.StageDirector.Enter(0, "probe retry");
WL.Combat.Telegraph.Telegraph.StageScale = 1f; // 일부러 흐트러뜨린다
WL.Combat.Stage.StageDirector.Retry();
var d1 = tbl.Get(0);
Chk("재도전 → StageScale 다시 " + (d1 != null ? d1.telegraphScale.ToString("F2") : "?"),
d1 != null && UnityEngine.Mathf.Abs(WL.Combat.Telegraph.Telegraph.StageScale - d1.telegraphScale) < 0.0001f,
WL.Combat.Telegraph.Telegraph.StageScale.ToString("F2"));
WL.Combat.Stage.StageDirector.Abort("probe 정리");
WL.Combat.Stage.StageDirector.ResetAll();
WL.Combat.Telegraph.Telegraph.StageScale = 1f;
}
// ─────────────────────────────────────────────────────────────────────────
static void Section3_Spawner(WL.Combat.Stage.WLStageTable tbl)
{
L("");
L("── ③ 리스폰 0 · 배치 · 회수 seam (815b 프로브 이음매 재사용) ────────");
var stops = new System.Collections.Generic.List<int>();
var deploys = new System.Collections.Generic.List<string>();
var recalls = new System.Collections.Generic.List<int>();
WL.Combat.Stage.StageSpawner.StopRespawnOverride = delegate (WL.Combat.Stage.StageDef d, int sid) { stops.Add(sid); return true; };
WL.Combat.Stage.StageSpawner.DeployOverride = delegate (WL.Combat.Stage.StageDef d, WL.Combat.Stage.StageWave w, int want) { deploys.Add(w.spawnerId + "×" + want); return want; };
WL.Combat.Stage.StageSpawner.RecallOverride = delegate (WL.Combat.Stage.StageDef d, int sid) { recalls.Add(sid); return 1; };
try
{
var def10 = tbl.Get(9);
stops.Clear(); deploys.Clear();
WL.Combat.Stage.StageDirector.ResetAll();
WL.Combat.Stage.StageDirector.Enter(9, "probe spawner");
string sl = ""; for (int i = 0; i < stops.Count; i++) sl += (i > 0 ? ", " : "") + stops[i];
int distinct = 0;
for (int i = 0; i < stops.Count; i++) { bool dup = false; for (int j = 0; j < i; j++) if (stops[j] == stops[i]) dup = true; if (!dup) distinct++; }
Chk("스테이지 10 스포너 5기 리젠 차단 1회씩", stops.Count == 5 && distinct == 5, "차단 호출 " + stops.Count + "회 · 스포너 " + distinct + "기 [" + sl + "]");
string dl = ""; int sum = 0;
for (int i = 0; i < deploys.Count; i++) dl += (i > 0 ? ", " : "") + deploys[i];
for (int i = 0; def10 != null && def10.waves != null && i < def10.waves.Length; i++) sum += def10.waves[i].count;
Chk("고정 수 배치 = 표 합계 " + (def10 != null ? def10.TotalCount : 0),
def10 != null && WL.Combat.Stage.StageSpawner.Deployed >= def10.TotalCount && sum == def10.TotalCount,
"묶음 " + deploys.Count + "개 [" + dl + "] · Deployed " + WL.Combat.Stage.StageSpawner.Deployed);
// 초과분 회수 경로 — Recall 이 스포너마다 1회
recalls.Clear();
int n = WL.Combat.Stage.StageSpawner.Recall(def10, "probe 초과분 회수");
int rd = 0;
for (int i = 0; i < recalls.Count; i++) { bool dup = false; for (int j = 0; j < i; j++) if (recalls[j] == recalls[i]) dup = true; if (!dup) rd++; }
string rl = ""; for (int i = 0; i < recalls.Count; i++) rl += (i > 0 ? ", " : "") + recalls[i];
Chk("초과분 회수 — 스포너 5기 1회씩", recalls.Count == 5 && rd == 5, "회수 호출 " + recalls.Count + "회 · 스포너 " + rd + "기 · 반환 " + n + " [" + rl + "]");
// 같은 스포너를 두 번 쓰는 표에서도 1회만 — AlreadyRequested
var dup2 = new WL.Combat.Stage.StageDef();
dup2.id = 999; dup2.anchor = kNew; dup2.barrierRadius = 20f;
var w1 = new WL.Combat.Stage.StageWave(); w1.spawnerId = 813001; w1.count = 3;
var w2 = new WL.Combat.Stage.StageWave(); w2.spawnerId = 813001; w2.count = 4;
dup2.waves = new WL.Combat.Stage.StageWave[] { w1, w2 };
stops.Clear();
bool all; WL.Combat.Stage.StageSpawner.StopRespawns(dup2, out all);
Chk("같은 스포너 2묶음 → 차단 1회(중복 방지)", stops.Count == 1, "차단 " + stops.Count + "회 · allStopped " + all);
WL.Combat.Stage.StageDirector.Abort("probe 정리");
WL.Combat.Stage.StageDirector.ResetAll();
}
finally
{
WL.Combat.Stage.StageSpawner.StopRespawnOverride = null;
WL.Combat.Stage.StageSpawner.DeployOverride = null;
WL.Combat.Stage.StageSpawner.RecallOverride = null;
}
L(" 🔴 실기(Play) 리젠 0 · On_Regen/Off 실제 동작은 이 세션에서 미확인(815b §7-1·2 한계 그대로 · 살아 있는 MobActor 필요).");
}
// ─────────────────────────────────────────────────────────────────────────
static void Section4_C8(WL.Combat.Boss.WLBossArenaSettings arena, WL.Combat.Stage.WLStageSettings stg,
WL.Combat.Stage.WLStageTable tbl)
{
L("");
L("── ④ C8 진리표 (값 원복 = 813d 동작) ──────────────────────────────");
WL.Combat.Telegraph.Telegraph.StageScale = 1f;
WL.Combat.Stage.StageDirector.ResetAll();
// ⓐ arenaCenter 를 옛 값으로 되돌리면 BossArena.Center 도 그대로 813d 동작
var keep = arena.arenaCenter;
arena.arenaCenter = kOld;
Chk("arenaCenter 원복 → BossArena.Center 813d 값", Near(WL.Combat.Boss.BossArena.Center, kOld), WL.Combat.Boss.BossArena.Center.ToString("F2"));
arena.arenaCenter = keep;
Chk("복구", Near(WL.Combat.Boss.BossArena.Center, kNew), WL.Combat.Boss.BossArena.Center.ToString("F2"));
// ⓑ arenaEnabled 0 → 813d 아레나 자체가 꺼진다(중심 값은 남는다)
bool ka = arena.arenaEnabled; arena.arenaEnabled = false;
Chk("arenaEnabled 0 → 아레나 off (값은 남는다)", !arena.arenaEnabled, "arenaEnabled 0 · Center " + WL.Combat.Boss.BossArena.Center.ToString("F2"));
arena.arenaEnabled = ka;
// ⓒ 스테이지 축 off → 입장 자체가 안 된다 = StageScale 무변경(815f 병합 전과 동일)
WL.Combat.Stage.WLStageSettings.RuntimeDisabled = true;
bool entered = WL.Combat.Stage.StageDirector.Enter(0, "probe c8");
Chk("WLStageSettings off → 입장 false · StageScale 1 유지",
!entered && UnityEngine.Mathf.Abs(WL.Combat.Telegraph.Telegraph.StageScale - 1f) < 0.0001f,
"Enter=" + entered + " · StageScale " + WL.Combat.Telegraph.Telegraph.StageScale.ToString("F2"));
WL.Combat.Stage.WLStageSettings.RuntimeDisabled = false;
// ⓓ 815f 예고 축 off → StageScale 이 걸려도 예고 자체가 안 붙는다
var tel = UnityEngine.Resources.Load<WL.Combat.Telegraph.WLTelegraphSettings>("WL/WLTelegraphSettings");
Chk("815f WLTelegraphSettings.enabled(0 이면 예고 전부 off = 코드 무수정 롤백)", tel != null, tel != null ? "enabled " + (tel.enabled ? "1" : "0") : "SO 없음");
// ⓔ telegraphScale 을 1 로 두면 배수 없음
var d = tbl.Get(2);
Chk("표의 telegraphScale 1 인 스테이지(3) → 배수 없음", d != null && UnityEngine.Mathf.Abs(d.telegraphScale - 1f) < 0.0001f,
d != null ? "스테이지 " + d.id + " telegraphScale " + d.telegraphScale.ToString("F2") : "null");
WL.Combat.Stage.StageDirector.ResetAll();
WL.Combat.Telegraph.Telegraph.StageScale = 1f;
}
// ─────────────────────────────────────────────────────────────────────────
static void Section5_GC()
{
L("");
L("── ⑤ GC ──────────────────────────────────────────────────────────");
var c = WL.Combat.Boss.BossArena.Center; if (c.x == 12345f) L("(warmup)");
System.GC.Collect();
long a = System.GC.GetTotalMemory(true);
float acc = 0f;
for (int i = 0; i < 1000; i++)
{
var p = WL.Combat.Boss.BossArena.Center;
WL.Combat.Telegraph.Telegraph.StageScale = 1f + (i % 3) * 0.1f;
acc += p.x;
}
WL.Combat.Telegraph.Telegraph.StageScale = 1f;
long b = System.GC.GetTotalMemory(true);
Chk("BossArena.Center + StageScale ×1000 GC Δ", b - a <= 0, "Δ " + (b - a) + " B (acc " + acc.ToString("F0") + ")");
}
}