Project_WL/AgentScripts/WL813t4_Probe.cs

313 lines
20 KiB
C#
Raw Permalink Normal View History

// 발주서 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") + ")");
}
}