Project_WL/Assets/WL/Combat/Diagnostics/WLErrorGuardSettings.cs

98 lines
5.9 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// WLErrorGuardSettings.cs — 인게임 에러 모달 차단 · 투사체 null 타깃 가드 값의 단일 출처(SOT · C45)
//
// PD 지시 #813 · 발주서 WL-813x2 §1-2/§1-3 (2026-09-09) · 근거 = Q7 D-35
//
// 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLErrorGuardSettings.asset
// → Resources.Load<WLErrorGuardSettings>("WL/WLErrorGuardSettings") 로 1회 로드 후 캐시(기존 WL 설정 SO 관례).
// 에셋이 없거나 enabled = false 면 Enabled == false → 훅 무접촉 · 투사체 가드 무동작 = 원본 동작 100%(C8 롤백 1순위).
//
// 🔴 발주서는 이 값들을 `WLCombatCoreSettings.asset` 에 넣으라고 했으나, 그 클래스는
// `Assets/WL/Combat/Core/**` 에 있고 완료 기준 ⓑ 가 **Core 0 줄**을 요구한다.
// → 충돌을 피해 같은 Resources/WL 폴더에 **신규 SO** 로 분리했다(보고서에 기재).
//
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것(Actor/ProjectileBase 가 Assembly-CSharp 에 있다).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
namespace WL.Combat.Diagnostics
{
[CreateAssetMenu(fileName = "WLErrorGuardSettings", menuName = "WL/Error Guard Settings", order = 360)]
public sealed class WLErrorGuardSettings : ScriptableObject
{
public const string ResourcesPath = "WL/WLErrorGuardSettings";
static WLErrorGuardSettings s_cached;
static bool s_lookupDone;
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null.</summary>
public static WLErrorGuardSettings Instance
{
get
{
if (!s_lookupDone)
{
s_cached = Resources.Load<WLErrorGuardSettings>(ResourcesPath);
s_lookupDone = true;
}
return s_cached;
}
}
/// <summary>진단·A/B 전용 런타임 스위치. Play 종료 시 도메인 리로드로 자동 false.</summary>
public static bool RuntimeDisabled;
/// <summary>기능이 살아 있는가 = 에셋 존재 · enabled · 런타임 스위치 off.</summary>
public static bool Enabled
{
get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled; }
}
/// <summary>테스트용: 캐시를 비워 다음 접근 때 다시 Resources.Load 한다.</summary>
public static void ClearCache() { s_cached = null; s_lookupDone = false; }
// ─────────────────────────────────────────── 전체 스위치
[Header("전체 스위치")]
[Tooltip("끄면 에러 훅 무접촉 · 투사체 가드 무동작 = 원본 100%. 롤백 1순위.")]
public bool enabled = true;
// ─────────────────────────────────────────── 시연용 모달 차단 (§1-3)
[Header("시연용 에러 모달 차단 (RunErrorModalGuard · Q7 D-35)")]
[Tooltip("런 중 Unity Error/Exception 이 나도 원본 ErrorLogHookManager 의 인게임 파란 모달을 띄우지 않는다. " +
"구현 = 훅 컴포넌트를 런 동안만 enabled=false 로 내려 OnDisable 이 Application.logMessageReceived 구독을 스스로 해제하게 한다" +
"(원본 파일 0 줄 수정). 런이 끝나면 다시 enabled=true 로 되돌린다.")]
public bool suppressErrorModalInRun = true;
[Tooltip("1 = 런 중(RunDirector.IsRunning)에만 차단 · 0 = Play 내내 차단. 시연 중 로비 오류까지 막으려면 0.")]
public bool suppressOnlyWhileRunning = true;
[Tooltip("차단한(그리고 차단하지 않은) Error/Exception 을 파일로 남긴다. 끄면 카운터만 센다.")]
public bool writeErrorFile = true;
[Tooltip("로그 파일 폴더(프로젝트 루트 기준 상대 · 에디터). 빌드에서는 persistentDataPath 아래에 같은 이름으로 만든다.")]
public string errorFileDir = "Screenshots_WL/WL813x2";
[Tooltip("스택트레이스도 같이 남긴다(발생원 추적용).")]
public bool includeStack = true;
[Tooltip("한 세션에 남길 최대 건수(로그 폭주 방지). 초과분은 카운터만 올라간다.")]
public int maxEntriesPerSession = 200;
[Tooltip("훅 상태 폴링 주기(초 · unscaled). 이 주기마다 런 진행 상태만 비교하고 바뀔 때만 손댄다(GC 0).")]
public float pollSeconds = 0.25f;
[Tooltip("훅 컴포넌트를 못 찾았을 때의 최대 재탐색 횟수(폴링 기준). 초과하면 탐색을 멈춘다(GC 0).")]
public int hookSearchTries = 40;
// ─────────────────────────────────────────── 투사체 null 타깃 가드 (§1-2)
[Header("투사체 null 타깃 가드 (WLProjectileStart · D-35 원인)")]
[Tooltip("ProjectileBase.Set 의 시작 위치 스위치에서 target 이 null 이면 shooter 위치로 대체한다. " +
"끄면 원본 그대로(= target 을 그대로 역참조 → NullReferenceException).")]
public bool projectileNullTargetGuard = true;
[Tooltip("가드가 실제로 null 을 잡았을 때 Debug.Log 1줄을 남긴다(Error 아님 = 모달 유발 0). 시연 빌드에서는 0 권장.")]
public bool projectileGuardLog = false;
}
}