646 lines
36 KiB
C#
646 lines
36 KiB
C#
|
|
// WL813w8_Probe.cs — #813w8(결과창 정지 중 PC 무적 D-46 · 게이트 전 보스 UI 숨김 D-23 재발) 프로브
|
|||
|
|
// 에디트 모드 전용 · Play 불필요 · 로그인 0 · 씬/에셋 무수정(임시 GameObject·임시 SO 는 즉시 파괴/회수)
|
|||
|
|
//
|
|||
|
|
// 상주 에디터:
|
|||
|
|
// unity command run_script --project-path <wt> --file AgentScripts/WL813w8_Probe.cs --entry WL813w8_Probe.RunAll --timeout 300
|
|||
|
|
// 결과: Console + AgentScripts/WL813w8_PROBE.txt (RESULT PASS / RESULT FAIL n)
|
|||
|
|
//
|
|||
|
|
// 무엇을 증명하나 (발주서 WL-813w8 §1 · 근거 = qa/W2-11_재채점_v9.md D-46 · D-23)
|
|||
|
|
// ① 에셋 — 신규 2필드(invincibleWhileRunEndHold · hideBossUiBeforeGate) + D-46 이 성립하던 조건 값
|
|||
|
|
// ② D-46 — 가짜 RunEnded → 정지 + 무적 래치 → 잡몹 연타 피해 0(대조군은 깎인다) → RunStarted 해제
|
|||
|
|
// · 사망 중 진입은 부활 우선(래치 0) · 부활 뒤 틱에서 래치 · 상한 만료에서 함께 해제
|
|||
|
|
// ③ D-46 보조 — 훅을 안 거치는 경로(도트)도 정지 중에는 전액 환급(DamageGuard 배율 0)
|
|||
|
|
// ④ D-23 — 게이트 전 보스 UI 숨김 판정(순수 함수 진리표 + 실제 게이트 상태)
|
|||
|
|
// ⑤ D-23 — 바·배너 4진입점(늦은 구독 · Spawned · 런 시작 재바인드 · 폴링) 전부 숨김 →
|
|||
|
|
// 게이트 개방(RunEvents.BossGateOpened) 순간 표시
|
|||
|
|
// ⑥ GC 0 — 정지 틱 · 무적 판정 · 피해 차단 · UI 숨김 판정 각 ×100
|
|||
|
|
// ⑦ C8 롤백 — 신규 2필드 0 → 813w7/813q2·813y 동작 그대로(= D-46 · D-23 재현)
|
|||
|
|
//
|
|||
|
|
// 에디트 모드 한계(보고서 「미확인」)
|
|||
|
|
// · Time 이 흐르지 않는다 → 시간 판정은 전부 인자로 먹인다(RunEndHoldTick(now) · SetRunEndHoldUntilForProbe).
|
|||
|
|
// · 사망은 GameObject 비활성으로 만든다(Actor.IsDead() = DeadStatus || !activeInHierarchy · Actor.cs:975).
|
|||
|
|
// · PC 스탯(m_Stat)은 protected 라 **프로브에서만** 리플렉션으로 주입한다(제품 코드 리플렉션 0).
|
|||
|
|
// · 화면 픽셀은 못 본다 — 바는 CanvasGroup.alpha(=BossHpBar.Visible)로 판정한다.
|
|||
|
|
|
|||
|
|
using System;
|
|||
|
|
using System.IO;
|
|||
|
|
using System.Reflection;
|
|||
|
|
using System.Text;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using WL.Combat.Boss;
|
|||
|
|
using WL.Combat.Core;
|
|||
|
|
using WL.Combat.Run;
|
|||
|
|
using WL.Combat.Survival;
|
|||
|
|
using WL.UI;
|
|||
|
|
|
|||
|
|
public static class WL813w8_Probe
|
|||
|
|
{
|
|||
|
|
const string OutCommit = "AgentScripts/WL813w8_PROBE.txt";
|
|||
|
|
const BindingFlags NP = BindingFlags.Instance | BindingFlags.NonPublic;
|
|||
|
|
|
|||
|
|
const double PcMaxHp = 310d; // Q11 실측 PC 최대 HP
|
|||
|
|
const double MobRaw = 6d; // Q11 death_*.txt: 잡몹 원 피해 6.0 → ×0.10 = 0.6~1.0 HP
|
|||
|
|
const int MobHitsPerResultScreen = 240; // 초당 4대 × 60 s (Q11: 55~80 s 에 사망)
|
|||
|
|
|
|||
|
|
static StringBuilder sb;
|
|||
|
|
static int s_fail;
|
|||
|
|
|
|||
|
|
static GameObject s_pcGo, s_fbdGo, s_bossGo, s_barGo, s_bannerGo;
|
|||
|
|
static PCActor s_pc, s_prevMyPc;
|
|||
|
|
static MobActor s_boss;
|
|||
|
|
static FieldBossData s_fbd;
|
|||
|
|
static BossHpBar s_bar;
|
|||
|
|
static BossBanner s_banner;
|
|||
|
|
|
|||
|
|
public static object RunAll()
|
|||
|
|
{
|
|||
|
|
sb = new StringBuilder();
|
|||
|
|
s_fail = 0;
|
|||
|
|
sb.AppendLine("# WL813w8 Probe " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " (edit mode · Play 0 · 로그인 0)");
|
|||
|
|
sb.AppendLine("playMode=" + Application.isPlaying);
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
Setup();
|
|||
|
|
Sec1_Asset();
|
|||
|
|
Sec2_RunEndInvincible();
|
|||
|
|
Sec3_DamageGuard();
|
|||
|
|
Sec4_GateDecision();
|
|||
|
|
Sec5_BossUi();
|
|||
|
|
Sec6_GC();
|
|||
|
|
Sec7_C8();
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) { L("[FAIL] 예외 — " + ex.GetType().Name + ": " + ex.Message); L(ex.StackTrace); s_fail++; }
|
|||
|
|
finally { Teardown(); }
|
|||
|
|
|
|||
|
|
L("");
|
|||
|
|
L(s_fail == 0 ? "RESULT PASS" : "RESULT FAIL " + s_fail);
|
|||
|
|
var text = sb.ToString();
|
|||
|
|
try { File.WriteAllText(OutCommit, text, new UTF8Encoding(false)); } catch { }
|
|||
|
|
Debug.Log(text);
|
|||
|
|
return text;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ───────────────────────────────────────── 유틸
|
|||
|
|
static void L(string m) { sb.AppendLine(m); }
|
|||
|
|
static string P(bool ok) { if (!ok) s_fail++; return ok ? "[PASS]" : "[FAIL]"; }
|
|||
|
|
static long Alloc() { return GC.GetTotalMemory(false); }
|
|||
|
|
|
|||
|
|
static WLRunGuideSettings TmpG()
|
|||
|
|
{
|
|||
|
|
var t = UnityEngine.Object.Instantiate(WLRunGuideSettings.Instance);
|
|||
|
|
t.hideFlags = HideFlags.HideAndDontSave;
|
|||
|
|
return t;
|
|||
|
|
}
|
|||
|
|
static WLBossArenaSettings TmpB()
|
|||
|
|
{
|
|||
|
|
var t = UnityEngine.Object.Instantiate(WLBossArenaSettings.Instance);
|
|||
|
|
t.hideFlags = HideFlags.HideAndDontSave;
|
|||
|
|
return t;
|
|||
|
|
}
|
|||
|
|
static void UseTmpG(WLRunGuideSettings t) { WLRunGuideSettings.SetInstanceForProbe(t); }
|
|||
|
|
static void UseRealG() { WLRunGuideSettings.SetInstanceForProbe(null); }
|
|||
|
|
static void UseTmpB(WLBossArenaSettings t) { WLBossArenaSettings.SetInstanceForProbe(t); }
|
|||
|
|
static void UseRealB() { WLBossArenaSettings.SetInstanceForProbe(null); }
|
|||
|
|
|
|||
|
|
/// <summary>가짜 base.Get_Damage = 원본 MyActor.Get_Damage(:301-309) 그대로(무적 플래그를 본다).</summary>
|
|||
|
|
static Action<DamageInfo> ApplyToPc(PCActor pc)
|
|||
|
|
{
|
|||
|
|
return d =>
|
|||
|
|
{
|
|||
|
|
s_flagDuringBase = WL.Settings.WLGameplaySettings.PlayerInvincible;
|
|||
|
|
if (s_flagDuringBase) return; // 원본 무적 경로 = 피해 0
|
|||
|
|
if (d != null) pc.Get_StatInfo().Set_Stat(false, eStat.HP, d.Damage);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
static bool s_flagDuringBase;
|
|||
|
|
|
|||
|
|
static DamageInfo Dmg(Actor beater, double dmg)
|
|||
|
|
{
|
|||
|
|
var d = new DamageInfo();
|
|||
|
|
d.Beater = beater; d.Damage = dmg;
|
|||
|
|
return d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void SetHp(Actor a, double hp)
|
|||
|
|
{
|
|||
|
|
var st = a.Get_StatInfo();
|
|||
|
|
st.Set_Stat(eStat.HP, hp);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Setup()
|
|||
|
|
{
|
|||
|
|
s_prevMyPc = MyValue.MyPC;
|
|||
|
|
|
|||
|
|
s_pcGo = new GameObject("__WL813w8_PC");
|
|||
|
|
s_pc = s_pcGo.AddComponent<PCActor>();
|
|||
|
|
s_pc.m_Role = eRole.PC; s_pc.m_SubRole = eSubRol.None;
|
|||
|
|
var stat = new ActorStatInfo(eRole.PC);
|
|||
|
|
stat.Set_Stat(eStat.MaxHP, PcMaxHp); stat.Set_Stat(eStat.HP, PcMaxHp);
|
|||
|
|
typeof(Actor).GetField("m_Stat", NP).SetValue(s_pc, stat); // 프로브 전용(제품 코드 0)
|
|||
|
|
typeof(Actor).GetField("m_Enemy", NP).SetValue(s_pc, false);
|
|||
|
|
MyValue.MyPC = s_pc;
|
|||
|
|
|
|||
|
|
s_bossGo = new GameObject("__WL813w8_BOSS");
|
|||
|
|
s_boss = s_bossGo.AddComponent<MobActor>();
|
|||
|
|
s_boss.m_Role = eRole.Mob; s_boss.m_SubRole = eSubRol.Boss;
|
|||
|
|
var bstat = new ActorStatInfo(eRole.Mob);
|
|||
|
|
bstat.Set_Stat(eStat.MaxHP, 1800d); bstat.Set_Stat(eStat.HP, 1800d); // Q11 D-23 실측(bhp=1800/1800)
|
|||
|
|
typeof(Actor).GetField("m_Stat", NP).SetValue(s_boss, bstat);
|
|||
|
|
typeof(Actor).GetField("m_Enemy", NP).SetValue(s_boss, true);
|
|||
|
|
|
|||
|
|
WLBossArenaSettings.RuntimeDisabled = false;
|
|||
|
|
WLRunGuideSettings.RuntimeDisabled = false;
|
|||
|
|
BossArena.ResetAll();
|
|||
|
|
BossArena.EnsureSubscribed();
|
|||
|
|
RunDirector.ResetAll();
|
|||
|
|
RunGuide.EnsureSubscribed();
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
Survival.ResetDiagnostics();
|
|||
|
|
DamageGuard.ResetDiagnostics();
|
|||
|
|
DeathDiag.ResetDiagnostics();
|
|||
|
|
|
|||
|
|
// 게이트 무장 — 원본 훅과 같은 진입점(HoldSpawn)
|
|||
|
|
var cfg = WLBossArenaSettings.Instance;
|
|||
|
|
s_fbdGo = new GameObject("__WL813w8_FBD");
|
|||
|
|
s_fbd = s_fbdGo.AddComponent<FieldBossData>();
|
|||
|
|
s_fbd.n_SpawnerId = cfg != null ? cfg.bossSpawnerId : 813010;
|
|||
|
|
s_fbd.n_MonsterID = cfg != null ? cfg.bossMonsterId : 10006;
|
|||
|
|
bool held = BossArena.HoldSpawn(s_fbd, NoopResume);
|
|||
|
|
|
|||
|
|
// 보스 UI 2벌(런타임 OnEnable 과 같은 경로 = Initialize)
|
|||
|
|
s_barGo = new GameObject("__WL813w8_BAR", typeof(RectTransform));
|
|||
|
|
s_bar = s_barGo.AddComponent<BossHpBar>();
|
|||
|
|
string barInit = s_bar.Initialize();
|
|||
|
|
s_bannerGo = new GameObject("__WL813w8_BANNER", typeof(RectTransform));
|
|||
|
|
s_banner = s_bannerGo.AddComponent<BossBanner>();
|
|||
|
|
string bannerInit = s_banner.Initialize();
|
|||
|
|
|
|||
|
|
L("");
|
|||
|
|
L("── ⓪ 준비");
|
|||
|
|
L(" " + P(WLRunGuideSettings.Instance != null) + " WLRunGuideSettings 로드 · Enabled=" + WLRunGuideSettings.Enabled);
|
|||
|
|
L(" " + P(WLBossArenaSettings.Instance != null) + " WLBossArenaSettings 로드 · Active=" + WLBossArenaSettings.Active);
|
|||
|
|
L(" " + P(WLSurvivalSettings.Instance != null && WLSurvivalSettings.Enabled) +
|
|||
|
|
" WLSurvivalSettings 로드 · Enabled=" + WLSurvivalSettings.Enabled + " (이게 켜져 있어야 D-46 이 성립한다)");
|
|||
|
|
L(" " + P(RunGuide.Subscribed) + " RunGuide.Subscribed(RunEvents)");
|
|||
|
|
L(" " + P(held && BossArena.GateArmed && !BossArena.GateOpen) +
|
|||
|
|
" HoldSpawn(FieldBossData) → 게이트 무장(armed=" + BossArena.GateArmed + " open=" + BossArena.GateOpen + ")");
|
|||
|
|
L(" PC hp=" + s_pc.Get_HP().ToString("F0") + "/" + s_pc.Get_MaxHP().ToString("F0") +
|
|||
|
|
" · 보스 hp=" + s_boss.Get_HP().ToString("F0") + "/" + s_boss.Get_MaxHP().ToString("F0"));
|
|||
|
|
L(" 바 초기화: " + barInit);
|
|||
|
|
L(" 배너 초기화: " + bannerInit);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void NoopResume() { }
|
|||
|
|
|
|||
|
|
static void Teardown()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
UseRealG(); UseRealB();
|
|||
|
|
RunGuide.EndRunEndHold("teardown");
|
|||
|
|
RunGuide.ClearRunEndInvincible("teardown");
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
Survival.SetHoldInvincible(null, false, "teardown");
|
|||
|
|
Survival.ResetDiagnostics();
|
|||
|
|
DamageGuard.ResetDiagnostics();
|
|||
|
|
DeathDiag.ResetDiagnostics();
|
|||
|
|
BossArena.SetProbeTime(-1f);
|
|||
|
|
BossArena.SetRunActiveForProbe(0);
|
|||
|
|
BossArena.SetBossForProbe(null);
|
|||
|
|
BossArena.ResetAll();
|
|||
|
|
RunDirector.ResetAll();
|
|||
|
|
WLBossArenaSettings.RuntimeDisabled = false;
|
|||
|
|
WLRunGuideSettings.RuntimeDisabled = false;
|
|||
|
|
BossHpBar.ClearFakeHp();
|
|||
|
|
BossHpBar.DebugFallbackMaxHp = 100f;
|
|||
|
|
BossHpBar.RunBossHpRatio = -1f; BossHpBar.RunBossSeen = false; BossHpBar.RunResetCount = 0;
|
|||
|
|
BossHpBar.GateSuppressCount = BossHpBar.GateHideCount = BossHpBar.GateOpenBinds = 0;
|
|||
|
|
BossBanner.GateSuppressCount = BossBanner.GateOpenTriggers = 0;
|
|||
|
|
BossBanner.RunResetCount = 0;
|
|||
|
|
if (s_bar != null) s_bar.Subscribe(false);
|
|||
|
|
if (s_banner != null) s_banner.Subscribe(false);
|
|||
|
|
MyValue.MyPC = s_prevMyPc;
|
|||
|
|
if (s_pcGo != null) UnityEngine.Object.DestroyImmediate(s_pcGo);
|
|||
|
|
if (s_bossGo != null) UnityEngine.Object.DestroyImmediate(s_bossGo);
|
|||
|
|
if (s_fbdGo != null) UnityEngine.Object.DestroyImmediate(s_fbdGo);
|
|||
|
|
if (s_barGo != null) UnityEngine.Object.DestroyImmediate(s_barGo);
|
|||
|
|
if (s_bannerGo != null) UnityEngine.Object.DestroyImmediate(s_bannerGo);
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
// ① 에셋 (값은 전부 SO = C45)
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
static void Sec1_Asset()
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("── ① 에셋 (813w8 신규 2필드 + D-46/D-23 이 성립하던 조건 값)");
|
|||
|
|
|
|||
|
|
var g = WLRunGuideSettings.Instance;
|
|||
|
|
var b = WLBossArenaSettings.Instance;
|
|||
|
|
var s = WLSurvivalSettings.Instance;
|
|||
|
|
if (g == null || b == null || s == null) { L(" [FAIL] 에셋 없음"); s_fail++; return; }
|
|||
|
|
|
|||
|
|
L(" " + P(g.invincibleWhileRunEndHold) + " 🔴 신규 invincibleWhileRunEndHold=" + g.invincibleWhileRunEndHold + " (발주서 §1-1 기본 1)");
|
|||
|
|
L(" " + P(b.hideBossUiBeforeGate) + " 🔴 신규 hideBossUiBeforeGate=" + b.hideBossUiBeforeGate + " (발주서 §1-2)");
|
|||
|
|
L(" " + P(g.holdPcOnRunEnd) + " 813w7 holdPcOnRunEnd=" + g.holdPcOnRunEnd + " 유지(무적은 이 정지와 같은 구간)");
|
|||
|
|
L(" " + P(g.runEndHoldMaxSeconds > 0f) + " runEndHoldMaxSeconds=" + g.runEndHoldMaxSeconds +
|
|||
|
|
"s — Q11 사망은 55~80 s 라 상한이 더 늦다(그래서 무적이 필요했다)");
|
|||
|
|
L(" " + P(!s.mobHitInvincible) + " 813i2 mobHitInvincible=" + s.mobHitInvincible +
|
|||
|
|
" (잡몹 연타를 끊는 창이 없다 = D-46 의 조건 · 이 발주는 이 값을 바꾸지 않는다)");
|
|||
|
|
L(" mob 배율=" + s.mobDamageMultiplier + " · unscaledDamageGuard=" + s.unscaledDamageGuardEnabled +
|
|||
|
|
" · deathDiag=" + s.deathDiagEnabled);
|
|||
|
|
L(" " + P(b.parkBossOnRespawn && b.bossHoldUntilGateOpen) +
|
|||
|
|
" 813w6 parkBossOnRespawn=" + b.parkBossOnRespawn + " bossHoldUntilGateOpen=" + b.bossHoldUntilGateOpen +
|
|||
|
|
" 유지 = 게이트 전에 보스가 살아 있는 그림(D-23 재발의 전제)");
|
|||
|
|
|
|||
|
|
// 피격 연출 필터(무적 타는 비네트·타격 반응을 스스로 거른다 = 「비네트/플래시 0」의 근거)
|
|||
|
|
var ui = WL.UI.WLSurvivalUiSettings.Instance;
|
|||
|
|
var tier = WL.Combat.Reaction.WLImpactTierSettings.Instance;
|
|||
|
|
L(" " + P(ui != null && ui.ignoreInvincibleHits) + " WLSurvivalUiSettings.ignoreInvincibleHits=" +
|
|||
|
|
(ui != null ? ui.ignoreInvincibleHits.ToString() : "(에셋 없음)") + " (무적 피격 비네트 0)");
|
|||
|
|
L(" " + P(tier != null && tier.pcHitSkipWhenInvincible) + " WLImpactTierSettings.pcHitSkipWhenInvincible=" +
|
|||
|
|
(tier != null ? tier.pcHitSkipWhenInvincible.ToString() : "(에셋 없음)") + " (무적 피격 타격 반응 0)");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
// ② D-46 — 결과창 정지 중 무적
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
static void Sec2_RunEndInvincible()
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("── ② D-46 결과창 정지 중 PC 무적 (Q11: 초당 3.4~4.9 HP · 55~80 s 사망 · 3판 3/3)");
|
|||
|
|
|
|||
|
|
var g = WLRunGuideSettings.Instance;
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
Survival.ResetDiagnostics();
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
s_pc.Set_JoystickStatus(3);
|
|||
|
|
s_pc.isTarget = false;
|
|||
|
|
|
|||
|
|
// ⓐ 정지 시작 = 무적 래치
|
|||
|
|
RunGuide.ProbeRunEnded(RunOutcome.Win);
|
|||
|
|
RunGuide.SetRunEndHoldUntilForProbe(-1f);
|
|||
|
|
L(" " + P(RunGuide.RunEndHeld && RunGuide.RunEndInvincible && Survival.IsHoldInvincible(s_pc)) +
|
|||
|
|
" 🔴 가짜 RunEnded(Win) → 정지(" + RunGuide.RunEndHeld + ") + 무적 래치(" + RunGuide.RunEndInvincible +
|
|||
|
|
" · on " + RunGuide.RunEndInvincibleOn + "/off " + RunGuide.RunEndInvincibleOff + ")");
|
|||
|
|
|
|||
|
|
// ⓑ 잡몹 연타 240대(= 초당 4대 × 60 s) → 피해 0
|
|||
|
|
double hp0 = s_pc.Get_HP();
|
|||
|
|
var dinfo = Dmg(s_boss, MobRaw); // Beater 는 보스 서브롤이지만 아래 대조군과 같은 조건이면 된다
|
|||
|
|
var mobGo = new GameObject("__WL813w8_MOB");
|
|||
|
|
var mob = mobGo.AddComponent<Actor>();
|
|||
|
|
mob.m_Role = eRole.Mob; mob.m_SubRole = eSubRol.None;
|
|||
|
|
typeof(Actor).GetField("m_Enemy", NP).SetValue(mob, true);
|
|||
|
|
var baseCall = ApplyToPc(s_pc);
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
int blocked0 = Survival.BlockedHoldInvincible;
|
|||
|
|
bool flagSeen = true;
|
|||
|
|
for (int i = 0; i < MobHitsPerResultScreen; i++)
|
|||
|
|
{
|
|||
|
|
Survival.Tick(i * 0.25f);
|
|||
|
|
s_flagDuringBase = false;
|
|||
|
|
Survival.TakeDamage(s_pc, Dmg(mob, MobRaw), baseCall);
|
|||
|
|
if (!s_flagDuringBase) flagSeen = false;
|
|||
|
|
}
|
|||
|
|
double hpAfter = s_pc.Get_HP();
|
|||
|
|
L(" " + P(Math.Abs(hpAfter - hp0) < 1e-6) +
|
|||
|
|
" 🔴 잡몹 " + MobHitsPerResultScreen + "대(초당 4대 × 60 s · 원 피해 " + MobRaw + ") → HP " +
|
|||
|
|
hp0.ToString("F0") + " → " + hpAfter.ToString("F0") + " = **피해 0**");
|
|||
|
|
L(" " + P(Survival.BlockedHoldInvincible == blocked0 + MobHitsPerResultScreen) +
|
|||
|
|
" 전부 정지 무적 경로로 흘렸다(BlockedHoldInvincible " + blocked0 + " → " + Survival.BlockedHoldInvincible + ")");
|
|||
|
|
L(" " + P(flagSeen) +
|
|||
|
|
" 🔴 base 호출 동안 PlayerInvincible=true = 원본 무적 경로(RaiseDamaged(invincible=true) → 비네트·타격 반응 0)");
|
|||
|
|
L(" " + P(WL.Settings.WLGameplaySettings.PlayerInvincible) +
|
|||
|
|
" 호출 뒤 플래그 원복(PlayerInvincible=" + WL.Settings.WLGameplaySettings.PlayerInvincible + " · 디스크 값 무변경)");
|
|||
|
|
bool viaSeen = false;
|
|||
|
|
for (int i = 0; i < DeathDiag.Capacity; i++)
|
|||
|
|
if (DeathDiag.Get(i).via == DamageVia.HoldInv) { viaSeen = true; break; }
|
|||
|
|
L(" " + P(viaSeen) + " 사망 진단(813m3)에 via=HoldInv 로 기록된다(사망 파일에서 바로 구분된다)");
|
|||
|
|
|
|||
|
|
// ⓒ 대조군 — 래치를 풀면 그대로 깎인다(= Q11 그림)
|
|||
|
|
RunGuide.ClearRunEndInvincible("대조군");
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
double ctl0 = s_pc.Get_HP();
|
|||
|
|
for (int i = 0; i < MobHitsPerResultScreen; i++)
|
|||
|
|
{
|
|||
|
|
Survival.Tick(1000f + i * 0.25f);
|
|||
|
|
Survival.TakeDamage(s_pc, Dmg(mob, MobRaw), baseCall);
|
|||
|
|
}
|
|||
|
|
double ctl1 = s_pc.Get_HP();
|
|||
|
|
L(" " + P(ctl0 - ctl1 > 1d) +
|
|||
|
|
" 🔴 대조군(무적 없음) — 같은 " + MobHitsPerResultScreen + "대에 HP " + ctl0.ToString("F0") + " → " +
|
|||
|
|
ctl1.ToString("F0") + "(−" + (ctl0 - ctl1).ToString("F0") + ") = D-46 그림 재현");
|
|||
|
|
}
|
|||
|
|
finally { UnityEngine.Object.DestroyImmediate(mobGo); }
|
|||
|
|
|
|||
|
|
// ⓓ 사망 중 진입은 부활 우선
|
|||
|
|
RunGuide.ClearRunEndInvincible("사망 시험 준비");
|
|||
|
|
s_pcGo.SetActive(false); // Actor.IsDead() = DeadStatus || !activeInHierarchy
|
|||
|
|
RunGuide.ApplyRunEndInvincible(g, s_pc, "사망 중");
|
|||
|
|
bool deadNoLatch = !RunGuide.RunEndInvincible;
|
|||
|
|
s_pcGo.SetActive(true);
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
RunGuide.RunEndHoldTick(g, s_pc, 300f); // 부활 뒤 첫 정지 틱
|
|||
|
|
L(" " + P(deadNoLatch && RunGuide.RunEndInvincible) +
|
|||
|
|
" 🔴 사망 중 진입 = 래치 안 검(부활 우선 · " + deadNoLatch + ") → 부활 뒤 첫 정지 틱에서 걸림(" +
|
|||
|
|
RunGuide.RunEndInvincible + ")");
|
|||
|
|
|
|||
|
|
// ⓔ 해제 = 다음 런 시작
|
|||
|
|
int on0 = RunGuide.RunEndInvincibleOn, off0 = RunGuide.RunEndInvincibleOff;
|
|||
|
|
RunGuide.ProbeRunStarted(2, "restart");
|
|||
|
|
L(" " + P(!RunGuide.RunEndHeld && !RunGuide.RunEndInvincible && RunGuide.RunEndInvincibleOff == off0 + 1) +
|
|||
|
|
" 🔴 가짜 RunStarted(\"restart\") → 정지·무적 동시 해제(무적 on " + on0 + " → off " +
|
|||
|
|
RunGuide.RunEndInvincibleOff + " · 사유 '" + Survival.LastHoldInvincibleReason + "')");
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
double h0 = s_pc.Get_HP();
|
|||
|
|
Survival.Tick(2000f);
|
|||
|
|
Survival.TakeDamage(s_pc, Dmg(s_boss, 500d), ApplyToPc(s_pc));
|
|||
|
|
L(" " + P(h0 - s_pc.Get_HP() > 1e-6) +
|
|||
|
|
" 🔴 해제 뒤에는 런 안 피해가 정상 복귀(HP " + h0.ToString("F0") + " → " + s_pc.Get_HP().ToString("F0") + ")");
|
|||
|
|
|
|||
|
|
// ⓕ 상한 만료에서도 함께 해제
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
RunGuide.BeginRunEndHold("상한 시험");
|
|||
|
|
RunGuide.SetRunEndHoldUntilForProbe(200f);
|
|||
|
|
RunGuide.RunEndHoldTick(g, s_pc, 199f);
|
|||
|
|
bool heldAt199 = RunGuide.RunEndHeld && RunGuide.RunEndInvincible;
|
|||
|
|
RunGuide.RunEndHoldTick(g, s_pc, 201f);
|
|||
|
|
L(" " + P(heldAt199 && !RunGuide.RunEndHeld && !RunGuide.RunEndInvincible) +
|
|||
|
|
" 🔴 상한 만료(199 s 유지 → 201 s) → 정지·무적 함께 해제(사유 '" + RunGuide.LastRunEndHoldReason + "')");
|
|||
|
|
L(" " + P(RunGuide.RunEndInvincibleOn == RunGuide.RunEndInvincibleOff) +
|
|||
|
|
" 건 만큼 풀렸다(on " + RunGuide.RunEndInvincibleOn + " = off " + RunGuide.RunEndInvincibleOff + ") — QA 판정 기준");
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
// ③ D-46 보조 — 훅 밖 경로(도트)도 정지 중에는 전액 환급
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
static void Sec3_DamageGuard()
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("── ③ 훅을 안 거친 감소(도트 등)도 정지 중에는 전액 환급 (DamageGuard 배율 0)");
|
|||
|
|
|
|||
|
|
var s = WLSurvivalSettings.Instance;
|
|||
|
|
if (s == null || !s.unscaledDamageGuardEnabled)
|
|||
|
|
{
|
|||
|
|
L(" [SKIP] unscaledDamageGuardEnabled=0 — 이 축은 꺼져 있다");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const double drop = 20d;
|
|||
|
|
// ⓐ 정지 무적 중
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
Survival.SetHoldInvincible(s_pc, true, "프로브 ③");
|
|||
|
|
DamageGuard.ResetDiagnostics();
|
|||
|
|
DamageGuard.ProbeArm(s_pc, s_pc.Get_HP(), Survival.HookHpLoss);
|
|||
|
|
s_pc.Get_StatInfo().Set_Stat(false, eStat.HP, drop); // 훅을 안 거친 감소(도트 재현)
|
|||
|
|
DamageGuard.Poll();
|
|||
|
|
double refundOn = DamageGuard.LastRefund;
|
|||
|
|
double hpOn = s_pc.Get_HP();
|
|||
|
|
L(" " + P(Math.Abs(refundOn - drop) < 0.5d) +
|
|||
|
|
" 🔴 무적 중 " + drop.ToString("F0") + " HP 도트 → 환급 " + refundOn.ToString("F1") +
|
|||
|
|
" (배율 0 = 전액) · HP " + hpOn.ToString("F0") + "/" + PcMaxHp.ToString("F0"));
|
|||
|
|
|
|||
|
|
// ⓑ 대조군 — 래치 없이는 종전대로 잡몹 배율만 환급한다(순 피해 = drop × 배율)
|
|||
|
|
Survival.SetHoldInvincible(null, false, "프로브 ③ 대조군");
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
DamageGuard.ResetDiagnostics();
|
|||
|
|
DamageGuard.ProbeArm(s_pc, s_pc.Get_HP(), Survival.HookHpLoss);
|
|||
|
|
s_pc.Get_StatInfo().Set_Stat(false, eStat.HP, drop);
|
|||
|
|
DamageGuard.Poll();
|
|||
|
|
double refundOff = DamageGuard.LastRefund;
|
|||
|
|
double expect = drop * (1d - DamageGuard.Multiplier);
|
|||
|
|
L(" " + P(Math.Abs(refundOff - expect) < 0.5d) +
|
|||
|
|
" 대조군(래치 없음) 환급 " + refundOff.ToString("F1") + " = " + drop.ToString("F0") + " × (1 − " +
|
|||
|
|
DamageGuard.Multiplier.ToString("F2") + ") — 813m3 동작 그대로(C8)");
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
// ④ D-23 — 게이트 전 보스 UI 숨김 판정
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
static void Sec4_GateDecision()
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("── ④ D-23 재발 — 게이트 전 보스 UI 숨김 판정(ShouldHideBossUi · 순수 함수)");
|
|||
|
|
|
|||
|
|
var b = WLBossArenaSettings.Instance;
|
|||
|
|
L(" " + P(BossArena.ShouldHideBossUi(b, true, false, false)) + " 무장 · 게이트 닫힘 → **숨김**(= 다음 런 존 사냥 구간)");
|
|||
|
|
L(" " + P(!BossArena.ShouldHideBossUi(b, true, true, false)) + " 무장 · BossArena 게이트 열림 → 표시");
|
|||
|
|
L(" " + P(!BossArena.ShouldHideBossUi(b, true, false, true)) + " 무장 · 런 축 게이트 열림(RunDirector) → 표시");
|
|||
|
|
L(" " + P(!BossArena.ShouldHideBossUi(b, false, false, false)) + " 게이트 축 미무장(813d off) → 정책 미적용(원본 그대로)");
|
|||
|
|
|
|||
|
|
var t = TmpB();
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
t.hideBossUiBeforeGate = false;
|
|||
|
|
L(" " + P(!BossArena.ShouldHideBossUi(t, true, false, false)) + " C8: hideBossUiBeforeGate=0 → 숨김 0(813q2/813y 그대로)");
|
|||
|
|
}
|
|||
|
|
finally { UnityEngine.Object.DestroyImmediate(t); }
|
|||
|
|
|
|||
|
|
L(" " + P(BossArena.HideBossUiNow && BossHpBar.SuppressedBeforeGate && BossBanner.SuppressedBeforeGate) +
|
|||
|
|
" 🔴 실제 상태(무장=" + BossArena.GateArmed + " open=" + BossArena.GateOpen + "/" + RunDirector.GateOpened +
|
|||
|
|
") → HideBossUiNow=" + BossArena.HideBossUiNow + " (바·배너가 같은 판정을 읽는다)");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
// ⑤ D-23 — 바·배너 4진입점 + 게이트 개방 표시
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
static void Sec5_BossUi()
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("── ⑤ D-23 재발 — 게이트 전 4진입점 숨김 → 게이트 개방 순간 표시");
|
|||
|
|
|
|||
|
|
BossHpBar.GateSuppressCount = BossHpBar.GateHideCount = BossHpBar.GateOpenBinds = 0;
|
|||
|
|
BossBanner.GateSuppressCount = BossBanner.GateOpenTriggers = 0;
|
|||
|
|
BossArena.SetBossForProbe(s_boss); // 813w6 이 고정해 둔 「살아 있는 만피 보스」
|
|||
|
|
s_bar.Initialize(); s_banner.Initialize(); // 늦은 구독 경로가 여기서 다시 돈다
|
|||
|
|
|
|||
|
|
L(" " + P(!s_bar.Visible) + " ① 늦은 구독(813y · BindExistingBoss) → 바 안 보임(Visible=" + s_bar.Visible + ")");
|
|||
|
|
L(" " + s_bar.BindExistingBoss());
|
|||
|
|
L(" " + P(!s_banner.Visible) + " ① 늦은 구독(배너) → " + s_banner.TriggerExistingBoss());
|
|||
|
|
|
|||
|
|
// ② Spawned(isBoss) — 813w6 재스폰이 내는 그 이벤트(진짜 디스패치)
|
|||
|
|
BossHpBar.DebugFallbackMaxHp = 1800f;
|
|||
|
|
int sup0 = BossHpBar.GateSuppressCount;
|
|||
|
|
BossHpBar.RaiseFakeBossSpawned(1800f);
|
|||
|
|
L(" " + P(!s_bar.Visible && BossHpBar.GateSuppressCount == sup0 + 1) +
|
|||
|
|
" 🔴 ② 게이트 전 Spawned(isBoss) → 바 안 보임(거절 " + BossHpBar.GateSuppressCount + "회) · 배너 " +
|
|||
|
|
(!s_banner.Visible ? "안 보임" : "🔴보임") + "(거절 " + BossBanner.GateSuppressCount + "회)");
|
|||
|
|
if (s_banner.Visible) s_fail++;
|
|||
|
|
|
|||
|
|
// ③ 런 시작 재바인드(813q2) — 813w6 이 BossSpawned=true 를 유지해도 뜨면 안 된다
|
|||
|
|
// 🔴 813w6 은 재스폰 보스에도 이 플래그를 유지한다(BossArena.cs:1532) = 813c2 조건(`bossBarRebindRequiresArenaSpawn`)이
|
|||
|
|
// 통과되는 상태. Q11 「다음 런」 그림을 그대로 만들기 위해 여기서 같은 값으로 세운다.
|
|||
|
|
BossArena.BossSpawned = true;
|
|||
|
|
L(" BossArena.BossSpawned=" + BossArena.BossSpawned + " (813w6 유지값 재현 → 813c2 조건만으로는 못 막는다)");
|
|||
|
|
string reset = s_bar.ResetForNewRun();
|
|||
|
|
string resetB = s_banner.ResetForNewRun();
|
|||
|
|
L(" " + P(!s_bar.Visible) + " 🔴 ③ 런 시작 리셋 → " + reset);
|
|||
|
|
L(" " + P(!s_banner.Visible) + " 배너 런 시작 리셋 → " + resetB);
|
|||
|
|
|
|||
|
|
// ④ 폴링(bossBarPollFollowsArena)
|
|||
|
|
bool polled = s_bar.PollBoss(true);
|
|||
|
|
L(" " + P(!polled && !s_bar.Visible) + " 🔴 ④ 폴링(force) → 재바인드 0(반환 " + polled + " · 내림 " +
|
|||
|
|
BossHpBar.GateHideCount + "회) · 바 안 보임");
|
|||
|
|
|
|||
|
|
// ⑤ 게이트 개방 → 표시 (진짜 RunEvents.BossGateOpened 경로)
|
|||
|
|
RunDirector.StartRun("probe");
|
|||
|
|
try { BossArena.ForceOpenGate(); } catch (Exception ex) { L(" (참고) ForceOpenGate 예외 — " + ex.GetType().Name + ": " + ex.Message); }
|
|||
|
|
RunDirector.OpenBossGate(RunGateReason.TimeLimit); // gateMinSeconds 를 우회하는 사유(BossArena.cs:582)
|
|||
|
|
L(" " + P(RunDirector.GateOpened) + " 게이트 개방(RunDirector.GateOpened=" + RunDirector.GateOpened +
|
|||
|
|
" BossArena.GateOpen=" + BossArena.GateOpen + " · BossGateOpened 발행 " + RunEvents.BossGateOpened.Raised + "회)");
|
|||
|
|
L(" " + P(!BossHpBar.SuppressedBeforeGate) + " 숨김 정책 해제(SuppressedBeforeGate=" + BossHpBar.SuppressedBeforeGate + ")");
|
|||
|
|
L(" " + P(s_bar.Visible && BossHpBar.GateOpenBinds >= 1) +
|
|||
|
|
" 🔴 ⑤ 게이트 개방 순간 바 표시(Visible=" + s_bar.Visible + " · 개방바인드 " + BossHpBar.GateOpenBinds +
|
|||
|
|
"회 · ratio=" + s_bar.LastRatio.ToString("F3") + ") — " + BossHpBar.LastGateOpenBind);
|
|||
|
|
L(" " + P(BossBanner.GateOpenTriggers >= 1) +
|
|||
|
|
" ⑤ 배너도 개방에서 트리거(" + BossBanner.GateOpenTriggers + "회) — " + BossBanner.LastGateOpenTrigger);
|
|||
|
|
|
|||
|
|
// 원상 복구 — 다음 절이 「게이트 전」 상태를 다시 쓴다
|
|||
|
|
RunDirector.ResetAll();
|
|||
|
|
BossArena.ResetAll();
|
|||
|
|
BossArena.EnsureSubscribed();
|
|||
|
|
BossArena.HoldSpawn(s_fbd, NoopResume);
|
|||
|
|
BossArena.SetBossForProbe(s_boss);
|
|||
|
|
s_bar.Initialize(); s_banner.Initialize();
|
|||
|
|
L(" " + P(BossArena.GateArmed && !BossArena.GateOpen && !s_bar.Visible) +
|
|||
|
|
" 복구: 게이트 재무장 · 바 숨김(다음 절 준비)");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
// ⑥ GC 0
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
static void Sec6_GC()
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("── ⑥ GC (각 ×100 · 문자열·박싱 0 이어야 한다)");
|
|||
|
|
|
|||
|
|
var g = WLRunGuideSettings.Instance;
|
|||
|
|
var b = WLBossArenaSettings.Instance;
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
RunGuide.BeginRunEndHold("GC");
|
|||
|
|
RunGuide.SetRunEndHoldUntilForProbe(-1f);
|
|||
|
|
RunGuide.RunEndHoldTick(g, s_pc, 500f); // 워밍업(첫 호출의 지연 초기화 제외)
|
|||
|
|
|
|||
|
|
long a0 = Alloc();
|
|||
|
|
for (int i = 0; i < 100; i++) RunGuide.RunEndHoldTick(g, s_pc, 500f + i);
|
|||
|
|
long a1 = Alloc();
|
|||
|
|
L(" " + P(a1 - a0 <= 0) + " 정지 틱(무적 재확인 포함) ×100 = " + (a1 - a0) + " B");
|
|||
|
|
|
|||
|
|
a0 = Alloc();
|
|||
|
|
bool acc = false;
|
|||
|
|
for (int i = 0; i < 100; i++) acc |= Survival.IsHoldInvincible(s_pc);
|
|||
|
|
a1 = Alloc();
|
|||
|
|
L(" " + P(a1 - a0 <= 0) + " IsHoldInvincible ×100 = " + (a1 - a0) + " B (acc=" + acc + ")");
|
|||
|
|
|
|||
|
|
var dinfo = Dmg(s_boss, MobRaw);
|
|||
|
|
var call = ApplyToPc(s_pc);
|
|||
|
|
Survival.TakeDamage(s_pc, dinfo, call); // 워밍업
|
|||
|
|
a0 = Alloc();
|
|||
|
|
for (int i = 0; i < 100; i++) { dinfo.Damage = MobRaw; Survival.TakeDamage(s_pc, dinfo, call); }
|
|||
|
|
a1 = Alloc();
|
|||
|
|
L(" " + P(a1 - a0 <= 0) + " 무적 경로 피해 차단 ×100 = " + (a1 - a0) + " B");
|
|||
|
|
|
|||
|
|
a0 = Alloc();
|
|||
|
|
for (int i = 0; i < 100; i++) acc |= BossArena.ShouldHideBossUi(b, true, false, false);
|
|||
|
|
a1 = Alloc();
|
|||
|
|
L(" " + P(a1 - a0 <= 0) + " 보스 UI 숨김 판정 ×100 = " + (a1 - a0) + " B");
|
|||
|
|
|
|||
|
|
a0 = Alloc();
|
|||
|
|
for (int i = 0; i < 100; i++) acc |= BossHpBar.SuppressedBeforeGate;
|
|||
|
|
a1 = Alloc();
|
|||
|
|
L(" " + P(a1 - a0 <= 0) + " BossHpBar.SuppressedBeforeGate ×100 = " + (a1 - a0) + " B");
|
|||
|
|
|
|||
|
|
RunGuide.EndRunEndHold("GC 끝");
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
// ⑦ C8 롤백
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────
|
|||
|
|
static void Sec7_C8()
|
|||
|
|
{
|
|||
|
|
L("");
|
|||
|
|
L("── ⑦ C8 롤백 (신규 2필드 0 → 813w7 · 813q2/813y 동작 그대로 = 결함 재현)");
|
|||
|
|
|
|||
|
|
// ⓐ invincibleWhileRunEndHold = 0
|
|||
|
|
var t = TmpG();
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
t.invincibleWhileRunEndHold = false;
|
|||
|
|
UseTmpG(t);
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
RunGuide.BeginRunEndHold("C8");
|
|||
|
|
RunGuide.SetRunEndHoldUntilForProbe(-1f);
|
|||
|
|
RunGuide.RunEndHoldTick(t, s_pc, 600f);
|
|||
|
|
L(" " + P(RunGuide.RunEndHeld && !RunGuide.RunEndInvincible && RunGuide.RunEndInvincibleOn == 0) +
|
|||
|
|
" ⓐ invincibleWhileRunEndHold=0 → 정지는 그대로(" + RunGuide.RunEndHeld + ") · 무적 0(on " +
|
|||
|
|
RunGuide.RunEndInvincibleOn + ") = 813w7 동작");
|
|||
|
|
double c0 = s_pc.Get_HP();
|
|||
|
|
Survival.Tick(3000f);
|
|||
|
|
Survival.TakeDamage(s_pc, Dmg(s_boss, 200d), ApplyToPc(s_pc));
|
|||
|
|
L(" " + P(c0 - s_pc.Get_HP() > 1e-6) + " → 피해가 그대로 들어간다(HP " + c0.ToString("F0") + " → " +
|
|||
|
|
s_pc.Get_HP().ToString("F0") + ") = D-46 재현");
|
|||
|
|
|
|||
|
|
// 정지 중에 스위치를 끄면 즉시 풀린다
|
|||
|
|
t.invincibleWhileRunEndHold = true;
|
|||
|
|
RunGuide.RunEndHoldTick(t, s_pc, 601f);
|
|||
|
|
bool onAgain = RunGuide.RunEndInvincible;
|
|||
|
|
t.invincibleWhileRunEndHold = false;
|
|||
|
|
RunGuide.RunEndHoldTick(t, s_pc, 602f);
|
|||
|
|
L(" " + P(onAgain && !RunGuide.RunEndInvincible) +
|
|||
|
|
" ⓑ 정지 중 스위치 on→off → 다음 틱에서 무적 즉시 해제(" + onAgain + " → " + RunGuide.RunEndInvincible + ")");
|
|||
|
|
RunGuide.EndRunEndHold("C8 끝");
|
|||
|
|
}
|
|||
|
|
finally { UseRealG(); UnityEngine.Object.DestroyImmediate(t); }
|
|||
|
|
|
|||
|
|
// ⓒ hideBossUiBeforeGate = 0 → 게이트 전에도 종전처럼 물린다(= D-23 재현)
|
|||
|
|
var tb = TmpB();
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
tb.hideBossUiBeforeGate = false;
|
|||
|
|
UseTmpB(tb);
|
|||
|
|
s_bar.Initialize();
|
|||
|
|
L(" " + P(!BossHpBar.SuppressedBeforeGate) + " ⓒ hideBossUiBeforeGate=0 → 숨김 정책 off(SuppressedBeforeGate=false)");
|
|||
|
|
L(" " + P(s_bar.Visible) + " → 게이트 전인데도 늦은 구독이 만피 보스를 문다(Visible=" + s_bar.Visible +
|
|||
|
|
" ratio=" + s_bar.LastRatio.ToString("F3") + ") = D-23 재현");
|
|||
|
|
}
|
|||
|
|
finally { UseRealB(); UnityEngine.Object.DestroyImmediate(tb); }
|
|||
|
|
|
|||
|
|
// ⓓ RuntimeDisabled — 가이드 축이 꺼지면 틱이 정지·무적을 원본으로 되돌린다(813w7 계약 그대로)
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
SetHp(s_pc, PcMaxHp);
|
|||
|
|
RunGuide.BeginRunEndHold("RuntimeDisabled 준비");
|
|||
|
|
RunGuide.SetRunEndHoldUntilForProbe(-1f);
|
|||
|
|
RunGuide.RunEndHoldTick(WLRunGuideSettings.Instance, s_pc, 700f);
|
|||
|
|
bool heldBefore = RunGuide.RunEndHeld && RunGuide.RunEndInvincible;
|
|||
|
|
WLRunGuideSettings.RuntimeDisabled = true;
|
|||
|
|
WLBossArenaSettings.RuntimeDisabled = true;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
RunGuide.ForceTick(); // 러너가 매 프레임 부르는 그 경로(!Active → 정지 해제)
|
|||
|
|
L(" " + P(heldBefore && !RunGuide.Active && !RunGuide.RunEndHeld && !RunGuide.RunEndInvincible) +
|
|||
|
|
" ⓓ RuntimeDisabled=1 → 틱이 정지·무적을 함께 푼다(직전 " + heldBefore + " → held " +
|
|||
|
|
RunGuide.RunEndHeld + "/무적 " + RunGuide.RunEndInvincible + " · 예외 0)");
|
|||
|
|
L(" " + P(!BossArena.HideBossUiNow) + " → 보스 UI 숨김 정책도 무동작(HideBossUiNow=" + BossArena.HideBossUiNow + ")");
|
|||
|
|
}
|
|||
|
|
finally
|
|||
|
|
{
|
|||
|
|
WLRunGuideSettings.RuntimeDisabled = false;
|
|||
|
|
WLBossArenaSettings.RuntimeDisabled = false;
|
|||
|
|
s_bar.Initialize();
|
|||
|
|
RunGuide.ResetDiagnostics();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|