Project_WL/AgentScripts/WL814e_Probe.cs

348 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-814e 프로브 — EnvLook(814c) ↔ LookModeHub(814d) 브리지 + 프리셋 값 정정 실측
// unity command run_script --file AgentScripts/WL814e_Probe.cs --entry WL814e_Probe.RunAll --project-path E:\NerdNavis\WL_wt\ui
// 산출물: <worktree>/AgentScripts/WL814e_PROBE.txt
// 🔴 에디트 모드 전용 · Play 0 · 로그인 0 · 임시 씬(NewScene) 저장 0 · AssetDatabase.SaveAssets 0 · 에셋 파일 쓰기 0.
// 🔴 SO 필드를 잠시 바꾸는 구간은 전부 try/finally 로 원값 복구(SetDirty 0 이라 디스크에 안 쓰인다).
// 🔴 Critter 의 전역 네임스페이스 `Environment` 는 System.Environment 와 충돌 → global:: 로 명시.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.SceneManagement;
using WL.Look.Env;
using WL.Look.Toggle;
public static class WL814e_Probe
{
const string kTxt = "AgentScripts/WL814e_PROBE.txt";
const string kMapPrefab = "Assets/Res_Addr/Map/WL_Nature.prefab";
static StringBuilder s_sb;
static int s_pass, s_fail;
static void L(string s) { s_sb.AppendLine(s); }
static void Chk(string n, bool ok, string d) { if (ok) s_pass++; else s_fail++; L((ok ? " PASS " : " FAIL ") + n + " — " + d); }
// 표(발주서 WL-814e §1-2) — 모드별 h / d / up / ortho / px / envLookOn / 표기
static readonly float[] kH = { 76f, 76f, 76f, 76f };
static readonly float[] kD = { 3f, 3f, 3f, 3f };
static readonly float[] kUp = { 2.4f, 2.4f, 2.4f, 2.4f };
static readonly float[] kOrtho = { 0f, 3.4f, 4.6f, 4.6f }; // 0번은 리그 해제 = 미사용
static readonly int[] kPx = { 0, 480, 480, 360 };
static readonly bool[] kEnv = { false, true, true, true };
static readonly string[] kName = { "기본", "도트A", "도트B(넓게)", "도트C(굵게)" };
public static void RunAll()
{
s_sb = new StringBuilder(); s_pass = 0; s_fail = 0;
L("WL-814e 프로브 · " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " · Unity " + Application.unityVersion);
L("playMode=" + Application.isPlaying);
var cfg = WLEnvLookSettings.Instance;
bool savedFollow = cfg != null && cfg.followLookModeHub;
bool savedRtE = WLEnvLookSettings.RuntimeDisabled;
bool savedRtL = WLLookModeSettings.RuntimeDisabled;
Scene temp = default(Scene);
GameObject map = null;
try
{
A_Values();
temp = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
map = Inst(kMapPrefab);
L("");
L("임시 씬 '" + temp.name + "' · 맵 " + (map != null ? map.name : "(없음)") +
" · 렌더러 " + (map != null ? map.GetComponentsInChildren<Renderer>(true).Length : 0) + "개");
B_GateOff(map);
C_HubEvent(map);
D_FollowOff(map);
E_Unhook(map);
F_Gc();
G_C8(map);
}
catch (Exception ex) { s_fail++; L("EXCEPTION " + ex); }
finally
{
try { EnvLook.UnhookHub(); EnvLook.RestoreNow("probe-final"); EnvLook.ResetForProbe(); } catch { }
try { LookModeHub.SetEnvLook(false); LookModeHub.ResetAll(); } catch { }
WLEnvLookSettings.RuntimeDisabled = savedRtE;
WLLookModeSettings.RuntimeDisabled = savedRtL;
if (cfg != null) cfg.followLookModeHub = savedFollow; // 🔴 원값 복구(SetDirty 0)
if (map != null) UnityEngine.Object.DestroyImmediate(map);
// 🔴 뒤에 도는 프로브(814d)가 Camera.main 을 뺏기지 않도록 빈 씬으로 정리한다(저장 0).
if (temp.IsValid()) temp = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
L("");
L("SO 원값 복구 — followLookModeHub=" + (cfg != null ? cfg.followLookModeHub.ToString() : "?") +
" · enabled=" + (cfg != null ? cfg.enabled.ToString() : "?") +
" · RuntimeDisabled(Env/Look)=" + WLEnvLookSettings.RuntimeDisabled + "/" + WLLookModeSettings.RuntimeDisabled);
L("씬 dirty=" + (temp.IsValid() ? temp.isDirty.ToString() : "(무효)") + " · 씬 저장 0 · SaveAssets 0");
L("");
L("RESULT " + (s_fail == 0 ? "PASS" : "FAIL") + " " + s_pass + "/" + (s_pass + s_fail));
var p = Path.Combine(Directory.GetParent(Application.dataPath).FullName, kTxt);
File.WriteAllText(p, s_sb.ToString(), new UTF8Encoding(false));
Debug.Log("[WL814e] → " + p + " · " + (s_fail == 0 ? "PASS" : "FAIL") + " " + s_pass + "/" + (s_pass + s_fail));
}
}
// ── A. 값 표 (ⓔ 앞부분) ──────────────────────────────────────────────────
static void A_Values()
{
L("");
L("## A. SO 값 (WLLookModeSettings.asset · WLEnvLookSettings.asset · 전부 실측)");
var s = WLLookModeSettings.Instance;
var c = WLEnvLookSettings.Instance;
if (!Chk2("A0 두 SO 로드", s != null && c != null, "look=" + (s != null) + " env=" + (c != null))) return;
L(" | 모드 | 표기 | 리그 | SO구도 | h | d | up | ortho | px | envLookOn |");
for (int i = 0; i < 4; i++)
{
var p = s.Preset(i);
L(" | " + i + " | " + p.name + " | " + (p.pixelRig ? "직교" : "원근") + " | " + (p.useSoFraming ? "SO" : "프리셋") +
" | " + p.cameraHeight + " | " + p.distance + " | " + p.distanceUp +
" | " + p.orthoSize + " | " + p.pixelHeight + " | " + (p.envLookOn ? "1" : "0") + " |");
}
Chk("A1 프리셋 4개", s.ModeCount == 4, "ModeCount=" + s.ModeCount);
for (int i = 0; i < 4; i++)
{
var p = s.Preset(i);
bool ok = p.name == kName[i]
&& Mathf.Abs(p.cameraHeight - kH[i]) < 0.001f
&& Mathf.Abs(p.distance - kD[i]) < 0.001f
&& Mathf.Abs(p.distanceUp - kUp[i]) < 0.001f
&& p.envLookOn == kEnv[i]
&& (i == 0 || (Mathf.Abs(p.orthoSize - kOrtho[i]) < 0.001f && p.pixelHeight == kPx[i]));
Chk("A2." + i + " 모드 " + i + " = 표", ok,
p.name + " · h " + p.cameraHeight + " d " + p.distance + " up " + p.distanceUp +
" ortho " + p.orthoSize + " px " + p.pixelHeight + " env " + (p.envLookOn ? 1 : 0));
}
Chk("A3 0번 = 원근 + 811o SO 구도", !s.Preset(0).pixelRig && s.Preset(0).useSoFraming,
"pixelRig=" + s.Preset(0).pixelRig + " useSoFraming=" + s.Preset(0).useSoFraming);
Chk("A4 1~3번 = 도트(직교 리그)", s.Preset(1).pixelRig && s.Preset(2).pixelRig && s.Preset(3).pixelRig, "3/3");
Chk("A5 각도 4행 동일(76/3/2.4)",
s.Preset(0).cameraHeight == s.Preset(1).cameraHeight && s.Preset(1).cameraHeight == s.Preset(2).cameraHeight &&
s.Preset(2).cameraHeight == s.Preset(3).cameraHeight, "h " + s.Preset(0).cameraHeight + " 전부 동일");
Chk("A6 허브 유지 값", s.envFollowsCameraMode && !s.envLookOnAtStart && s.startMode == 0 && s.showButtons,
"envFollows=" + s.envFollowsCameraMode + " envAtStart=" + s.envLookOnAtStart +
" startMode=" + s.startMode + " showButtons=" + s.showButtons);
Chk("A7 WLEnvLookSettings enabled 1 · followLookModeHub 1 · swapCharacters 0",
c.enabled && c.followLookModeHub && !c.swapCharacters,
"enabled=" + c.enabled + " follow=" + c.followLookModeHub + " swapChars=" + c.swapCharacters +
" · grassDensity=" + c.grassDensity + "(유지)");
Chk("A8 Enabled 게이트 통과", WLEnvLookSettings.Enabled, "Enabled=" + WLEnvLookSettings.Enabled);
}
// ── B. ⓐ 허브 OFF → 맵 경계 → 적용 0 ─────────────────────────────────────
static void B_GateOff(GameObject map)
{
L("");
L("## B. ⓐ 허브 EnvLookOn=false → 맵 진입 경계 → 적용 0");
if (map == null) return;
EnvLook.UnhookHub();
EnvLook.ResetForProbe();
LookModeHub.SetEnvLook(false);
Chk("B0 허브 OFF · 추종 ON", !LookModeHub.EnvLookOn && EnvLook.FollowHub,
"EnvLookOn=" + LookModeHub.EnvLookOn + " FollowHub=" + EnvLook.FollowHub);
bool applied = EnvLook.MapEnter();
int grass = map.GetComponentsInChildren<global::Environment.Instancing.MeshInstancesBehaviour>(true).Length;
Chk("B1 경계 적용 0", !applied && !EnvLook.IsApplied, "MapEnter=" + applied + " IsApplied=" + EnvLook.IsApplied);
Chk("B2 스왑 0 · 잔디 0", EnvLook.SwappedRenderers == 0 && EnvLook.WaterSwapped == 0 && grass == 0,
"렌더러 " + EnvLook.SwappedRenderers + " · 물 " + EnvLook.WaterSwapped + " · 잔디 " + grass);
Chk("B3 게이트 카운터 +1", EnvLook.HubGated == 1, "HubGated=" + EnvLook.HubGated);
}
// ── C. ⓑ 허브 ON/OFF 이벤트 → 적용 / 원복 전부 일치 ──────────────────────
static void C_HubEvent(GameObject map)
{
L("");
L("## C. ⓑ LookModeHub.EnvLookChanged 구독 → ON 적용 · OFF 원복");
if (map == null) return;
EnvLook.UnhookHub();
EnvLook.ResetForProbe();
LookModeHub.SetEnvLook(false);
var before = Snapshot(map);
bool hooked = EnvLook.HookHub();
Chk("C1 구독 성공 · 현재 값(OFF) 1회 반영", hooked && EnvLook.HubHooked && EnvLook.HubEvents == 1 && !EnvLook.IsApplied,
"HookHub=" + hooked + " HubHooked=" + EnvLook.HubHooked + " HubEvents=" + EnvLook.HubEvents +
" IsApplied=" + EnvLook.IsApplied);
Chk("C2 중복 구독 0", EnvLook.HookHub() == false && EnvLook.HubEvents == 1, "두 번째 HookHub=false · HubEvents=" + EnvLook.HubEvents);
LookModeHub.SetEnvLook(true);
int grassOn = map.GetComponentsInChildren<global::Environment.Instancing.MeshInstancesBehaviour>(true).Length;
Chk("C3 ON → 적용", EnvLook.IsApplied && EnvLook.SwappedRenderers > 0 && EnvLook.HubEvents == 2,
"렌더러 " + EnvLook.SwappedRenderers + " · 슬롯 " + EnvLook.SwappedSlots + " · 물 " + EnvLook.WaterSwapped +
" · 잔디 " + grassOn + "(" + EnvLook.GrassInstances + "개) · HubEvents=" + EnvLook.HubEvents +
" · LastLog='" + EnvLook.LastLog + "'");
LookModeHub.SetEnvLook(false);
var back = Snapshot(map);
int mismatch = 0;
foreach (var kv in before) { string now; if (back.TryGetValue(kv.Key, out now) && now != kv.Value) mismatch++; }
int grassOff = map.GetComponentsInChildren<global::Environment.Instancing.MeshInstancesBehaviour>(true).Length;
Chk("C4 OFF → 원복 전부 일치", !EnvLook.IsApplied && mismatch == 0 && grassOff == 0 && EnvLook.HubEvents == 3,
"불일치 " + mismatch + "/" + before.Count + " · 복원 렌더러 " + EnvLook.RestoredRenderers +
" · 잔디 " + grassOff + " · HubEvents=" + EnvLook.HubEvents + " · LastLog='" + EnvLook.LastLog + "'");
Chk("C5 같은 값 재설정 → 이벤트 0", DoubleSet(false) && EnvLook.HubEvents == 3, "HubEvents=" + EnvLook.HubEvents);
}
static bool DoubleSet(bool on) { LookModeHub.SetEnvLook(on); return true; }
// ── D. ⓒ followLookModeHub 0 → 814c 원래 동작 ────────────────────────────
static void D_FollowOff(GameObject map)
{
L("");
L("## D. ⓒ followLookModeHub = 0 → 814c 원래 동작(경계 자동 적용 · 허브 참조 0)");
var c = WLEnvLookSettings.Instance;
if (map == null || c == null) return;
EnvLook.UnhookHub();
EnvLook.ResetForProbe();
LookModeHub.SetEnvLook(false); // 허브는 꺼 둔 채로
bool saved = c.followLookModeHub;
try
{
c.followLookModeHub = false;
Chk("D0 추종 off", !EnvLook.FollowHub, "FollowHub=" + EnvLook.FollowHub + " · 허브 EnvLookOn=" + LookModeHub.EnvLookOn);
bool applied = EnvLook.MapEnter();
int grass = map.GetComponentsInChildren<global::Environment.Instancing.MeshInstancesBehaviour>(true).Length;
Chk("D1 허브가 꺼져 있어도 경계에서 적용", applied && EnvLook.IsApplied && EnvLook.SwappedRenderers > 0,
"MapEnter=" + applied + " · 렌더러 " + EnvLook.SwappedRenderers + " · 물 " + EnvLook.WaterSwapped +
" · 잔디 " + grass + " · LastLog='" + EnvLook.LastLog + "'");
Chk("D2 게이트 0회", EnvLook.HubGated == 0, "HubGated=" + EnvLook.HubGated);
EnvLook.RestoreNow("probe-d");
Chk("D3 추종 off 면 구독도 0", EnvLook.HookHub() == false && !EnvLook.HubHooked && EnvLook.HubEvents == 0,
"HookHub=false · HubHooked=" + EnvLook.HubHooked + " · HubEvents=" + EnvLook.HubEvents);
}
finally { c.followLookModeHub = saved; }
Chk("D4 추종 원복", EnvLook.FollowHub, "FollowHub=" + EnvLook.FollowHub);
}
// ── E. ⓓ 구독 해제 뒤 이벤트 → 무동작(누수 0) ────────────────────────────
static void E_Unhook(GameObject map)
{
L("");
L("## E. ⓓ 구독 해제 뒤 이벤트 → 무동작(누수 0)");
if (map == null) return;
EnvLook.UnhookHub();
EnvLook.ResetForProbe();
LookModeHub.SetEnvLook(false);
EnvLook.HookHub();
int e0 = EnvLook.HubEvents;
Chk("E0 구독 상태", EnvLook.HubHooked && e0 == 1, "HubHooked=" + EnvLook.HubHooked + " HubEvents=" + e0);
bool un = EnvLook.UnhookHub();
Chk("E1 해제", un && !EnvLook.HubHooked, "UnhookHub=" + un + " HubHooked=" + EnvLook.HubHooked);
Chk("E2 이중 해제 무해", EnvLook.UnhookHub() == false, "두 번째 UnhookHub=false");
LookModeHub.SetEnvLook(true);
int grass = map.GetComponentsInChildren<global::Environment.Instancing.MeshInstancesBehaviour>(true).Length;
Chk("E3 해제 뒤 ON 이벤트 무동작", EnvLook.HubEvents == e0 && !EnvLook.IsApplied && EnvLook.SwappedRenderers == 0 && grass == 0,
"HubEvents=" + EnvLook.HubEvents + "(해제 시점 " + e0 + ") · IsApplied=" + EnvLook.IsApplied +
" · 렌더러 " + EnvLook.SwappedRenderers + " · 잔디 " + grass);
LookModeHub.SetEnvLook(false);
Chk("E4 해제 뒤 OFF 이벤트 무동작", EnvLook.HubEvents == e0, "HubEvents=" + EnvLook.HubEvents);
// 재구독 → 다시 산다(핸들러가 남아 두 번 불리지 않는다)
EnvLook.HookHub();
int e1 = EnvLook.HubEvents;
LookModeHub.SetEnvLook(true);
Chk("E5 재구독 → 이벤트 정확히 1회", EnvLook.HubEvents == e1 + 1, "HubEvents " + e1 + " → " + EnvLook.HubEvents);
LookModeHub.SetEnvLook(false);
EnvLook.UnhookHub();
EnvLook.ResetForProbe();
}
// ── F. ⓕ GC 0 ───────────────────────────────────────────────────────────
static void F_Gc()
{
L("");
L("## F. ⓕ GC — 브리지 게이트 경로 (허브 OFF · 맵 없음)");
EnvLook.UnhookHub();
EnvLook.ResetForProbe();
LookModeHub.SetEnvLook(false);
for (int i = 0; i < 1000; i++) { EnvLook.MapEnter(); EnvLook.TickForProbe(); } // 워밍업
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
long m0 = Profiler.GetMonoUsedSizeLong(), t0 = GC.GetTotalMemory(false);
int g0 = GC.CollectionCount(0);
for (int i = 0; i < 200000; i++) EnvLook.MapEnter();
for (int i = 0; i < 200000; i++) { if (EnvLook.FollowHub && EnvLook.HubHooked) { } }
for (int i = 0; i < 200000; i++) EnvLook.TickForProbe();
long m1 = Profiler.GetMonoUsedSizeLong(), t1 = GC.GetTotalMemory(false);
int g1 = GC.CollectionCount(0);
Chk("F1 MapEnter/FollowHub/Tick ×200k GC 0", (m1 - m0) == 0 && (t1 - t0) == 0 && (g1 - g0) == 0,
"monoΔ " + (m1 - m0) + " B · totalΔ " + (t1 - t0) + " B · gen0 " + (g1 - g0) + " · HubGated=" + EnvLook.HubGated);
EnvLook.ResetForProbe();
}
// ── G. ⓕ C8 (WLEnvLookSettings.enabled 0 → 브리지 무동작) ────────────────
static void G_C8(GameObject map)
{
L("");
L("## G. ⓕ C8 — WLEnvLookSettings.enabled = 0 → 브리지 무동작");
if (map == null) return;
EnvLook.UnhookHub();
EnvLook.ResetForProbe();
LookModeHub.SetEnvLook(false);
var before = Snapshot(map);
try
{
WLEnvLookSettings.RuntimeDisabled = true; // = 에셋 enabled 0 과 같은 게이트
Chk("G0 게이트 off", !WLEnvLookSettings.Enabled, "Enabled=" + WLEnvLookSettings.Enabled);
EnvLook.HookHub();
LookModeHub.SetEnvLook(true); // [배경] ON 상당
int grass = map.GetComponentsInChildren<global::Environment.Instancing.MeshInstancesBehaviour>(true).Length;
Chk("G1 이벤트를 받아도 적용 0", !EnvLook.IsApplied && EnvLook.SwappedRenderers == 0 && grass == 0,
"IsApplied=" + EnvLook.IsApplied + " · 렌더러 " + EnvLook.SwappedRenderers + " · 잔디 " + grass +
" · HubEvents=" + EnvLook.HubEvents);
Chk("G2 경계도 적용 0", EnvLook.MapEnter() == false && !EnvLook.IsApplied, "MapEnter=false");
var back = Snapshot(map);
int mismatch = 0;
foreach (var kv in before) { string now; if (back.TryGetValue(kv.Key, out now) && now != kv.Value) mismatch++; }
Chk("G3 머티리얼 무변경", mismatch == 0, "불일치 " + mismatch + "/" + before.Count);
EnvLook.EnsureRunnerForProbe();
int runners = 0;
foreach (var go in UnityEngine.Object.FindObjectsByType<GameObject>(FindObjectsInactive.Include, FindObjectsSortMode.None))
if (go.name.StartsWith("[WL814c]")) runners++;
Chk("G4 러너 오브젝트 0", runners == 0, runners + "개");
}
finally
{
WLEnvLookSettings.RuntimeDisabled = false;
LookModeHub.SetEnvLook(false);
EnvLook.UnhookHub();
EnvLook.ResetForProbe();
}
Chk("G5 게이트 복구", WLEnvLookSettings.Enabled, "Enabled=" + WLEnvLookSettings.Enabled);
}
// ── 헬퍼 ────────────────────────────────────────────────────────────────
static bool Chk2(string n, bool ok, string d) { Chk(n, ok, d); return ok; }
static Dictionary<int, string> Snapshot(GameObject root)
{
var d = new Dictionary<int, string>();
foreach (var r in root.GetComponentsInChildren<Renderer>(true))
{
var sb = new StringBuilder();
foreach (var m in r.sharedMaterials) sb.Append(m != null ? m.name : "(null)").Append('|');
d[r.GetInstanceID()] = sb.ToString();
}
return d;
}
static GameObject Inst(string path)
{
var src = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (src == null) { L(" 프리팹 없음 " + path); return null; }
var go = (GameObject)PrefabUtility.InstantiatePrefab(src);
go.transform.position = Vector3.zero;
return go;
}
}