[WL-813w8] 결과창 정지 중 무적 · 게이트 전 보스 바 숨김 (#813)
This commit is contained in:
parent
5d9e509ac6
commit
716cda7261
|
|
@ -0,0 +1,82 @@
|
|||
# WL813w8 Probe 2026-09-09 23:07:26 (edit mode · Play 0 · 로그인 0)
|
||||
playMode=False
|
||||
|
||||
── ⓪ 준비
|
||||
[PASS] WLRunGuideSettings 로드 · Enabled=True
|
||||
[PASS] WLBossArenaSettings 로드 · Active=True
|
||||
[PASS] WLSurvivalSettings 로드 · Enabled=True (이게 켜져 있어야 D-46 이 성립한다)
|
||||
[PASS] RunGuide.Subscribed(RunEvents)
|
||||
[PASS] HoldSpawn(FieldBossData) → 게이트 무장(armed=True open=False)
|
||||
PC hp=310/310 · 보스 hp=1800/1800
|
||||
바 초기화: initialized subscribed=True · layout size=(880.00, 66.00) unitsPerPx=1.0000 ticks=2 · fill type=Filled method=Horizontal origin=0 sprite=WL_BossBarFill_White amount=1.000 · 중복차단: 1벌 — 조치 없음 · 늦은구독: 게이트 전 — 생략(813w8)
|
||||
배너 초기화: initialized subscribed=True · layout banner pos(0.00, -300.00) size(860.00, 150.00) unitsPerPx=1.0000 warnThickness=180.0 · 늦은구독: 게이트 전 — 생략(813w8)
|
||||
|
||||
── ① 에셋 (813w8 신규 2필드 + D-46/D-23 이 성립하던 조건 값)
|
||||
[PASS] 🔴 신규 invincibleWhileRunEndHold=True (발주서 §1-1 기본 1)
|
||||
[PASS] 🔴 신규 hideBossUiBeforeGate=True (발주서 §1-2)
|
||||
[PASS] 813w7 holdPcOnRunEnd=True 유지(무적은 이 정지와 같은 구간)
|
||||
[PASS] runEndHoldMaxSeconds=120s — Q11 사망은 55~80 s 라 상한이 더 늦다(그래서 무적이 필요했다)
|
||||
[PASS] 813i2 mobHitInvincible=False (잡몹 연타를 끊는 창이 없다 = D-46 의 조건 · 이 발주는 이 값을 바꾸지 않는다)
|
||||
mob 배율=0.1 · unscaledDamageGuard=True · deathDiag=True
|
||||
[PASS] 813w6 parkBossOnRespawn=True bossHoldUntilGateOpen=True 유지 = 게이트 전에 보스가 살아 있는 그림(D-23 재발의 전제)
|
||||
[PASS] WLSurvivalUiSettings.ignoreInvincibleHits=True (무적 피격 비네트 0)
|
||||
[PASS] WLImpactTierSettings.pcHitSkipWhenInvincible=True (무적 피격 타격 반응 0)
|
||||
|
||||
── ② D-46 결과창 정지 중 PC 무적 (Q11: 초당 3.4~4.9 HP · 55~80 s 사망 · 3판 3/3)
|
||||
[PASS] 🔴 가짜 RunEnded(Win) → 정지(True) + 무적 래치(True · on 1/off 0)
|
||||
[PASS] 🔴 잡몹 240대(초당 4대 × 60 s · 원 피해 6) → HP 310 → 310 = **피해 0**
|
||||
[PASS] 전부 정지 무적 경로로 흘렸다(BlockedHoldInvincible 0 → 240)
|
||||
[PASS] 🔴 base 호출 동안 PlayerInvincible=true = 원본 무적 경로(RaiseDamaged(invincible=true) → 비네트·타격 반응 0)
|
||||
[PASS] 호출 뒤 플래그 원복(PlayerInvincible=True · 디스크 값 무변경)
|
||||
[PASS] 사망 진단(813m3)에 via=HoldInv 로 기록된다(사망 파일에서 바로 구분된다)
|
||||
[PASS] 🔴 대조군(무적 없음) — 같은 240대에 HP 310 → 166(−144) = D-46 그림 재현
|
||||
[PASS] 🔴 사망 중 진입 = 래치 안 검(부활 우선 · True) → 부활 뒤 첫 정지 틱에서 걸림(True)
|
||||
[PASS] 🔴 가짜 RunStarted("restart") → 정지·무적 동시 해제(무적 on 2 → off 2 · 사유 '런 시작')
|
||||
[PASS] 🔴 해제 뒤에는 런 안 피해가 정상 복귀(HP 310 → 260)
|
||||
[PASS] 🔴 상한 만료(199 s 유지 → 201 s) → 정지·무적 함께 해제(사유 '상한 만료')
|
||||
[PASS] 건 만큼 풀렸다(on 1 = off 1) — QA 판정 기준
|
||||
|
||||
── ③ 훅을 안 거친 감소(도트 등)도 정지 중에는 전액 환급 (DamageGuard 배율 0)
|
||||
[PASS] 🔴 무적 중 20 HP 도트 → 환급 20.0 (배율 0 = 전액) · HP 310/310
|
||||
[PASS] 대조군(래치 없음) 환급 18.0 = 20 × (1 − 0.10) — 813m3 동작 그대로(C8)
|
||||
|
||||
── ④ D-23 재발 — 게이트 전 보스 UI 숨김 판정(ShouldHideBossUi · 순수 함수)
|
||||
[PASS] 무장 · 게이트 닫힘 → **숨김**(= 다음 런 존 사냥 구간)
|
||||
[PASS] 무장 · BossArena 게이트 열림 → 표시
|
||||
[PASS] 무장 · 런 축 게이트 열림(RunDirector) → 표시
|
||||
[PASS] 게이트 축 미무장(813d off) → 정책 미적용(원본 그대로)
|
||||
[PASS] C8: hideBossUiBeforeGate=0 → 숨김 0(813q2/813y 그대로)
|
||||
[PASS] 🔴 실제 상태(무장=True open=False/False) → HideBossUiNow=True (바·배너가 같은 판정을 읽는다)
|
||||
|
||||
── ⑤ D-23 재발 — 게이트 전 4진입점 숨김 → 게이트 개방 순간 표시
|
||||
[PASS] ① 늦은 구독(813y · BindExistingBoss) → 바 안 보임(Visible=False)
|
||||
늦은구독: 게이트 전 — 생략(813w8)
|
||||
[PASS] ① 늦은 구독(배너) → 늦은구독: 게이트 전 — 생략(813w8)
|
||||
[PASS] 🔴 ② 게이트 전 Spawned(isBoss) → 바 안 보임(거절 3회) · 배너 안 보임(거절 3회)
|
||||
BossArena.BossSpawned=True (813w6 유지값 재현 → 813c2 조건만으로는 못 막는다)
|
||||
[PASS] 🔴 ③ 런 시작 리셋 → 런시작리셋: 숨김만(게이트 전 · 813w8)
|
||||
[PASS] 배너 런 시작 리셋 → 런시작리셋: 숨김·가드 해제만
|
||||
[PASS] 🔴 ④ 폴링(force) → 재바인드 0(반환 False · 내림 0회) · 바 안 보임
|
||||
[PASS] 게이트 개방(RunDirector.GateOpened=True BossArena.GateOpen=True · BossGateOpened 발행 1회)
|
||||
[PASS] 숨김 정책 해제(SuppressedBeforeGate=False)
|
||||
[PASS] 🔴 ⑤ 게이트 개방 순간 바 표시(Visible=True · 개방바인드 1회 · ratio=1.000) — 늦은구독 BossArena.Boss → bound name=__WL813w8_BOSS maxHp=1800 ratio=1.000
|
||||
[PASS] ⑤ 배너도 개방에서 트리거(1회) — 늦은구독 BossArena.Boss → shown name=__WL813w8_BOSS banner=1.20s warn=0.80s
|
||||
[PASS] 복구: 게이트 재무장 · 바 숨김(다음 절 준비)
|
||||
|
||||
── ⑥ GC (각 ×100 · 문자열·박싱 0 이어야 한다)
|
||||
[PASS] 정지 틱(무적 재확인 포함) ×100 = 0 B
|
||||
[PASS] IsHoldInvincible ×100 = 0 B (acc=True)
|
||||
[PASS] 무적 경로 피해 차단 ×100 = 0 B
|
||||
[PASS] 보스 UI 숨김 판정 ×100 = 0 B
|
||||
[PASS] BossHpBar.SuppressedBeforeGate ×100 = 0 B
|
||||
|
||||
── ⑦ C8 롤백 (신규 2필드 0 → 813w7 · 813q2/813y 동작 그대로 = 결함 재현)
|
||||
[PASS] ⓐ invincibleWhileRunEndHold=0 → 정지는 그대로(True) · 무적 0(on 0) = 813w7 동작
|
||||
[PASS] → 피해가 그대로 들어간다(HP 310 → 290) = D-46 재현
|
||||
[PASS] ⓑ 정지 중 스위치 on→off → 다음 틱에서 무적 즉시 해제(True → False)
|
||||
[PASS] ⓒ hideBossUiBeforeGate=0 → 숨김 정책 off(SuppressedBeforeGate=false)
|
||||
[PASS] → 게이트 전인데도 늦은 구독이 만피 보스를 문다(Visible=True ratio=1.000) = D-23 재현
|
||||
[PASS] ⓓ RuntimeDisabled=1 → 틱이 정지·무적을 함께 푼다(직전 True → held False/무적 False · 예외 0)
|
||||
[PASS] → 보스 UI 숨김 정책도 무동작(HideBossUiNow=False)
|
||||
|
||||
RESULT PASS
|
||||
|
|
@ -0,0 +1,645 @@
|
|||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1079,6 +1079,37 @@ namespace WL.Combat.Boss
|
|||
return true;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 813w8 §1-2 — 게이트 개방 전에는 보스 UI(HP 바·배너)를 띄우지 않는다 (Q11 D-23 재발)
|
||||
//
|
||||
// 🔴 근거 = qa/W2-11_재채점_v9.md D-23 — 「다음 런」의 **존 사냥 내내 상단에 만피 빨간 보스 바**가 떠 있다
|
||||
// (캡처 2장 · 계기 `bhp=1800/1800 · balive=True`). 바는 **정상 추종**이고, 문제는 813w6 이
|
||||
// 재스폰 보스를 게이트 개방까지 **살려서 고정**해 두는데 UI 가 그 보스를 물어 버리는 **표시 정책**이다.
|
||||
// ■ 첫 런은 영향 0 — 첫 런의 보스는 813d 게이트가 스폰 자체를 붙잡았다가 **게이트 개방(ForceOpenGate → Release)
|
||||
// 안에서** 등장시킨다. 그 시점에는 이미 `s_gateOpen=true` 라 아래 판정이 false 다.
|
||||
// ■ 게이트 축이 무장돼 있지 않으면(= 813d 가 안 도는 판) 정책을 걸지 않는다 — 원본/타 흐름의 보스를 가리면 안 된다.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 지금 보스 UI 를 숨겨야 하는가(BossHpBar·BossBanner 가 읽는다).
|
||||
/// 🔴 아레나 축 자체가 꺼져 있으면(SO off · RuntimeDisabled) 정책을 걸지 않는다 — 그때 게이트 상태는 의미가 없다(C8).
|
||||
/// </summary>
|
||||
public static bool HideBossUiNow
|
||||
{
|
||||
get { return WLBossArenaSettings.Active && ShouldHideBossUi(Cfg, s_armed, s_gateOpen, WL.Combat.Run.RunDirector.GateOpened); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 위 판정의 순수 함수(정적 상태 접근 0 · 프로브가 가짜 값으로 그대로 부른다 · C8 증거).
|
||||
/// 게이트 축이 무장돼 있고(gateArmed) 아직 어느 축으로도 열리지 않았을 때만 true.
|
||||
/// </summary>
|
||||
public static bool ShouldHideBossUi(WLBossArenaSettings cfg, bool gateArmed, bool gateOpen, bool runGateOpened)
|
||||
{
|
||||
if (cfg == null || !cfg.hideBossUiBeforeGate) return false;
|
||||
if (!gateArmed) return false;
|
||||
return !gateOpen && !runGateOpened;
|
||||
}
|
||||
|
||||
/// <summary>이 스포너가 런 축(WLRunSettings.zoneSpawnerIds)의 사냥 존인가(순수 · 프로브가 그대로 부른다).</summary>
|
||||
public static bool IsRunZoneSpawner(int spawnerId)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -349,5 +349,15 @@ namespace WL.Combat.Boss
|
|||
[Tooltip("🔴 [다음 런](RunDirector.Restart)에서 대기 없이 즉시 1회 재무장한다 — 앞 런에서 이미 굳은 스포너를 "
|
||||
+ "다음 런 시작 시점에 되살린다(D-45 의 직접 처방). 끄면 위의 빈 존 감시로만 되살아난다.")]
|
||||
public bool rearmZoneSpawnsOnRestart = true;
|
||||
|
||||
// ─────────────────────────────────────────── 813w8 §1-2 게이트 전 보스 UI 숨김(D-23 재발)
|
||||
[Header("게이트 전 보스 UI 숨김 (813w8 · D-23 다음 런 존 사냥 중 만피 보스 바)")]
|
||||
[Tooltip("🔴 게이트가 열리기 전에는 보스 HP 바·등장 배너를 띄우지 않는다. "
|
||||
+ "813w6 이 재스폰 보스를 게이트 개방까지 살려서 고정해 두는데, UI(813y 늦은 구독·813q2 런 시작 재바인드)가 "
|
||||
+ "그 보스를 물어 Q11 에서 「다음 런의 존 사냥 내내 만피 빨간 보스 바」가 떴다(D-23 재발). "
|
||||
+ "게이트가 열리는 순간(RunEvents.BossGateOpened) 바·배너가 정상적으로 뜬다. "
|
||||
+ "🔴 첫 런은 영향 0 — 첫 런의 보스는 게이트 개방 안에서 등장한다. "
|
||||
+ "게이트 축이 무장되지 않았으면(813d off) 이 정책도 걸지 않는다. 끄면 813q2/813y 동작 그대로(= D-23 재현).")]
|
||||
public bool hideBossUiBeforeGate = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,6 +122,22 @@
|
|||
//
|
||||
// ■ C8 롤백 — `holdPcOnRunEnd=0` 이면 이 경로가 통째로 죽고 813w6 동작 그대로다.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WL-813w8 §1-1 (2026-09-09 · PD 지시 #813) — 근거 = qa/W2-11_재채점_v9.md **D-46**
|
||||
//
|
||||
// 🔴 실측 결함 — 위 정지는 **PC 의 공격만** 막는다. 몹은 그대로 때려 결과창을 켜 둔 채
|
||||
// **초당 3.4~4.9 HP** 가 깎이고 **55~80 초에 PC 가 죽는다**(런 3회 3/3 · 사망 파일 3건).
|
||||
// 813w7 §6-7 이 「무적 유지 중이라 실질 피해 0」이라고 남긴 「추정」이 뒤집힌 자리다.
|
||||
// `mobHitInvincible=0`(813i2 설계값)이라 잡몹 연타를 끊는 창이 없고,
|
||||
// 상한 `runEndHoldMaxSeconds 120 s` 는 사망(55~80 s)보다 **늦다**.
|
||||
//
|
||||
// ■ 이 파일이 맡은 몫 = 정지 구간과 **정확히 같은 구간**에 813i 무적 API 래치를 건다
|
||||
// `Survival.SetHoldInvincible(pc, true)` — 시간 창이 아니라 래치라서 결과창을 얼마나 오래 켜도 유지된다.
|
||||
// 걸기 = `BeginRunEndHold` + 매 정지 틱(재확인) · 풀기 = `EndRunEndHold`(런 시작·상한 만료·스위치 off·가이드 off).
|
||||
// 🔴 사망 중에는 걸지 않는다(부활 우선) — 부활 뒤 첫 정지 틱에서 다시 걸린다.
|
||||
// 피해 0 은 813i2 피격 창과 같은 원본 경로(플래그를 호출 동안만 올린다)라 **비네트·타격 반응도 0**이다.
|
||||
//
|
||||
// ■ C8 롤백 — `invincibleWhileRunEndHold=0` 이면 래치를 한 번도 걸지 않는다(= 813w7 동작 · D-46 재현).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WL-813i2 §1-2 (2026-09-09 · PD 지시 #813) — 보스 예고 중 자동 회피 스텝
|
||||
//
|
||||
// 🔴 근거 = qa/W2-10_재채점_v8.md §6 — 사망 **14/14 가 보스 연타**(0.52 s 간격 5타 · 한 타에 HP 6~26 %).
|
||||
|
|
@ -260,6 +276,12 @@ namespace WL.Combat.Run
|
|||
/// <summary>마지막으로 정지를 푼 사유(진단).</summary>
|
||||
public static string LastRunEndHoldReason = "";
|
||||
|
||||
// ───────────────────────────────────────── 813w8 진단 (D-46 · §1-1)
|
||||
/// <summary>813w8: 정지 무적 래치를 건 횟수 · 푼 횟수(QA 는 N=M 을 본다).</summary>
|
||||
public static int RunEndInvincibleOn, RunEndInvincibleOff;
|
||||
/// <summary>지금 정지 무적이 걸려 있는가(Survival 래치 실측 · 프로브·QA 가 읽는다).</summary>
|
||||
public static bool RunEndInvincible { get { return WL.Combat.Survival.Survival.HoldInvincibleActive; } }
|
||||
|
||||
/// <summary>진단 링버퍼 칸 수. 게임 수치가 아니라 버퍼 크기다(발주서 §1-1 「링버퍼 32」).</summary>
|
||||
const int kExitLogCapacity = 32;
|
||||
static readonly ArenaExitRecord[] s_exitLog = new ArenaExitRecord[kExitLogCapacity];
|
||||
|
|
@ -522,6 +544,9 @@ namespace WL.Combat.Run
|
|||
// 813w7 §1-2 ①: 결과창 중 PC 정지(D-45) — QA 가 이 값으로 「빈 사냥터」 수리를 판정한다
|
||||
" · 결과정지 " + RunEndHolds + "→" + RunEndHoldReleases +
|
||||
"(틱 " + RunEndHoldTicks + " 타깃 " + RunEndTargetClears + ")" +
|
||||
// 813w8 §1-1: 정지 중 무적(D-46) — QA 는 「무적 N→M · 막은타 B」로 판정한다
|
||||
" 무적 " + RunEndInvincibleOn + "→" + RunEndInvincibleOff +
|
||||
"(막은타 " + WL.Combat.Survival.Survival.BlockedHoldInvincible + ")" +
|
||||
" · 엘리트 " + EliteCount + "(고정 " + (DSUtil.CheckNull(s_lockedElite) ? "-" : LastEliteName) +
|
||||
" 락 " + EliteLocks + " 재타깃 " + EliteRetargets + " 해제 " + EliteReleases + ")" +
|
||||
// 813i2: 예고 감지 · 회피 스텝(QA 가 「예고 → 스텝 1회」를 이 줄로 센다)
|
||||
|
|
@ -638,7 +663,13 @@ namespace WL.Combat.Run
|
|||
static void Poll(WLRunGuideSettings cfg, float now)
|
||||
{
|
||||
var pc = MyValue.MyPC;
|
||||
if (DSUtil.CheckNull(pc) || pc.IsDead()) { ReleaseSteer(); s_lastPosTime = -1f; return; }
|
||||
if (DSUtil.CheckNull(pc) || pc.IsDead())
|
||||
{
|
||||
// 🔴 813w8 §1-1(D-46) — 사망하면 정지 무적을 즉시 푼다(부활 우선 · 813i DeathFlow 가 먼저).
|
||||
// 정지 자체는 유지되고, 부활 뒤 첫 정지 틱에서 무적이 다시 걸린다.
|
||||
ClearRunEndInvincible("사망");
|
||||
ReleaseSteer(); s_lastPosTime = -1f; return;
|
||||
}
|
||||
|
||||
// 🔴 813w7 §1-2 ①(D-45) — 결과창 동안 PC 정지. 목적지 판정보다 **앞**에 둔다
|
||||
// (이 구간에는 목적지가 없고, 원본 자동전투가 존 몹을 소진하는 것을 막는 것이 전부다).
|
||||
|
|
@ -755,8 +786,11 @@ namespace WL.Combat.Run
|
|||
EndDodgeStep("결과창 정지");
|
||||
s_runEndHoldUntil = cfg.runEndHoldMaxSeconds > 0f ? Time.unscaledTime + cfg.runEndHoldMaxSeconds : -1f;
|
||||
RunEndHolds++;
|
||||
// 🔴 813w8 §1-1(D-46) — 정지와 **같은 순간**에 무적 래치를 건다(사망 중이면 부활 우선 · 틱이 다시 건다).
|
||||
ApplyRunEndInvincible(cfg, MyValue.MyPC, "결과창 정지");
|
||||
Log("결과창 중 PC 정지 시작(" + reason + ") — 상한 " +
|
||||
(s_runEndHoldUntil > 0f ? cfg.runEndHoldMaxSeconds.ToString("F0") + "s" : "무제한"));
|
||||
(s_runEndHoldUntil > 0f ? cfg.runEndHoldMaxSeconds.ToString("F0") + "s" : "무제한") +
|
||||
" 무적 " + (cfg.invincibleWhileRunEndHold ? (RunEndInvincible ? "on" : "대기(사망 중)") : "off"));
|
||||
}
|
||||
|
||||
/// <summary>런 종료 정지를 푼다(조종 원복 포함). 프로브가 직접 부를 수 있다.</summary>
|
||||
|
|
@ -768,6 +802,9 @@ namespace WL.Combat.Run
|
|||
LastRunEndHoldReason = reason;
|
||||
RunEndHoldReleases++;
|
||||
|
||||
// 🔴 813w8 §1-1(D-46) — 정지가 풀리면 무적도 **반드시** 같이 풀린다(런 안에서 무적이 남으면 안 된다).
|
||||
ClearRunEndInvincible(reason);
|
||||
|
||||
// 🔴 정지 중 세워 둔 에이전트를 반드시 다시 풀어 준다 — 안 그러면 다음 런에서 PC 가 못 걷는다.
|
||||
var pc0 = MyValue.MyPC;
|
||||
if (!DSUtil.CheckNull(pc0))
|
||||
|
|
@ -792,6 +829,11 @@ namespace WL.Combat.Run
|
|||
|
||||
RunEndHoldTicks++;
|
||||
|
||||
// 🔴 813w8 §1-1(D-46) — 무적 래치 재확인. 이미 걸려 있으면 무동작이고, 사망 중 진입·PC 교체·부활 뒤에는
|
||||
// 여기서 (다시) 걸린다. 스위치가 꺼져 있으면 아무 것도 하지 않는다(C8 = 813w7 그대로).
|
||||
if (!cfg.invincibleWhileRunEndHold) ClearRunEndInvincible("스위치 off");
|
||||
else ApplyRunEndInvincible(cfg, pc, "정지 틱");
|
||||
|
||||
// ⓐ 자동 교전 접근 루프 차단(PCActor.cs:139) — 우리가 0 → 1 로 만든 경우에만.
|
||||
AcquireSteer(pc);
|
||||
|
||||
|
|
@ -812,6 +854,33 @@ namespace WL.Combat.Run
|
|||
agent.isStopped = true;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 813w8 §1-1 — 결과창 정지 중 PC 무적 (D-46)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 정지 무적 래치를 건다(813i 무적 API · `Survival.SetHoldInvincible`).
|
||||
/// 🔴 사망 중에는 걸지 않는다 — 부활 흐름(813i `DeathFlow`)이 먼저다(발주서 §1-1 「부활 우선」).
|
||||
/// 이미 같은 PC 에 걸려 있으면 무동작 = 매 틱 불러도 카운터는 한 번만 오른다(GC 0).
|
||||
/// 프로브가 직접 부를 수 있다.
|
||||
/// </summary>
|
||||
public static void ApplyRunEndInvincible(WLRunGuideSettings cfg, PCActor pc, string reason)
|
||||
{
|
||||
if (cfg == null || !cfg.invincibleWhileRunEndHold) return;
|
||||
if (DSUtil.CheckNull(pc) || pc.IsDead()) return; // 부활 우선
|
||||
if (WL.Combat.Survival.Survival.IsHoldInvincible(pc)) return; // 이미 걸려 있다
|
||||
WL.Combat.Survival.Survival.SetHoldInvincible(pc, true, reason);
|
||||
RunEndInvincibleOn++;
|
||||
}
|
||||
|
||||
/// <summary>정지 무적 래치를 푼다(정지 해제 · 사망 진입 · 스위치 off). 안 걸려 있으면 무동작.</summary>
|
||||
public static void ClearRunEndInvincible(string reason)
|
||||
{
|
||||
if (!WL.Combat.Survival.Survival.HoldInvincibleActive) return;
|
||||
WL.Combat.Survival.Survival.SetHoldInvincible(null, false, reason);
|
||||
RunEndInvincibleOff++;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 813w4 §1-1 — 정지 감시(D-34 안전망)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -1675,6 +1744,9 @@ namespace WL.Combat.Run
|
|||
EndRunEndHold("진단 초기화");
|
||||
RunEndHolds = RunEndHoldReleases = RunEndHoldTicks = RunEndTargetClears = 0;
|
||||
LastRunEndHoldReason = "";
|
||||
// 813w8 — 래치는 EndRunEndHold 가 이미 풀었다(안 걸려 있으면 무동작). 카운터만 0 으로.
|
||||
ClearRunEndInvincible("진단 초기화");
|
||||
RunEndInvincibleOn = RunEndInvincibleOff = 0;
|
||||
// 813i2
|
||||
DodgeSteps = DodgeTelegraphsSeen = 0;
|
||||
DodgeSkippedCooldown = DodgeSkippedPcBusy = DodgeFailedNavMesh = 0;
|
||||
|
|
|
|||
|
|
@ -297,6 +297,16 @@ namespace WL.Combat.Run
|
|||
+ "PC 가 계속 걸어가는 것을 막는다. 끄면 좌표만 그대로 두고 조종만 잡는다.")]
|
||||
public bool runEndHoldStopsAgent = true;
|
||||
|
||||
// ─────────────────────────────────────────── 813w8 §1-1 결과창 정지 중 PC 무적(D-46)
|
||||
[Tooltip("🔴 결과창 정지 동안 PC 를 무적으로 만든다(813i 무적 API 래치 Survival.SetHoldInvincible). "
|
||||
+ "Q11 D-46 실측: 정지는 PC 의 공격만 막고 피격은 그대로여서 결과창을 켜 둔 채 "
|
||||
+ "초당 3.4~4.9 HP 가 깎이고 55~80 초에 사망한다(런 3회 3/3). "
|
||||
+ "래치는 시간으로 만료되지 않고 정지가 풀릴 때(다음 런 시작 · 상한 만료 · 가이드 off) 함께 풀린다. "
|
||||
+ "🔴 사망 중에는 걸지 않는다(부활 우선) — 부활 뒤 첫 정지 틱에서 다시 걸린다. "
|
||||
+ "피해 0 은 813i2 피격 창과 같은 원본 무적 경로라 비네트·타격 반응도 0 이다. "
|
||||
+ "끄면 813w7 그대로(= D-46 재현).")]
|
||||
public bool invincibleWhileRunEndHold = true;
|
||||
|
||||
// ─────────────────────────────────────────── 813i2 §1-2 보스 예고 중 자동 회피 스텝
|
||||
// 🔴 실측 근거 = qa/W2-10_재채점_v8.md §6 — 사망 14/14 가 보스 연타. 813i2 무적 창이 본 방어선이고
|
||||
// 이 스텝은 그 앞의 「덜 맞기」다(813r 회피의 축소판 · 실패해도 무적이 받쳐 준다).
|
||||
|
|
|
|||
|
|
@ -80,3 +80,4 @@ MonoBehaviour:
|
|||
rearmZoneRegenMax: 15
|
||||
rearmZoneRunZonesOnly: 1
|
||||
rearmZoneSpawnsOnRestart: 1
|
||||
hideBossUiBeforeGate: 1
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ MonoBehaviour:
|
|||
runEndHoldMaxSeconds: 120
|
||||
runEndHoldClearsTarget: 1
|
||||
runEndHoldStopsAgent: 1
|
||||
invincibleWhileRunEndHold: 1
|
||||
dodgeStepEnabled: 1
|
||||
dodgeTelegraphStates:
|
||||
- boss_skill1charging
|
||||
|
|
|
|||
|
|
@ -134,7 +134,11 @@ namespace WL.Combat.Survival
|
|||
DetectedCount++; DetectedHp += unscaled;
|
||||
LastDetected = unscaled; LastDetectTime = Application.isPlaying ? Time.time : Survival.Now;
|
||||
|
||||
double mult = Multiplier;
|
||||
// 🔴 WL-813w8 (Q11 D-46) — 결과창 정지 무적 중에는 **전액 환급**한다(배율 0).
|
||||
// 훅 경로는 Survival 이 피해 0 으로 만들지만, 이 폴링이 잡는 경로(도트 · Cal_Damage 직접 호출)는
|
||||
// 원본 무적 플래그조차 거치지 않는다 — 정지 중 「피해 0」을 이 축에서도 지킨다.
|
||||
// 래치가 없으면(기본 · 런 중) 종전 그대로 잡몹 배율을 쓴다(C8).
|
||||
double mult = Survival.IsHoldInvincible(pc) ? 0d : Multiplier;
|
||||
double refund = unscaled * (1d - mult);
|
||||
double maxHp = pc.Get_MaxHP();
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ namespace WL.Combat.Survival
|
|||
SwitchOff = 2, // takeMobDamage/takeBossDamage off → 원본 무적 그대로
|
||||
Passthru = 3, // SO off · 메인 PC 아님 → 원본 100 %
|
||||
Unscaled = 4, // 🔴 훅을 거치지 않은 HP 감소(DoT · Cal_Damage 직접 호출 등)
|
||||
HitInv = 5 // 🔴 WL-813i2 피격 후 무적 창 → 원본 무적 경로(피해 0 · 연타에서 흘린 타)
|
||||
HitInv = 5, // 🔴 WL-813i2 피격 후 무적 창 → 원본 무적 경로(피해 0 · 연타에서 흘린 타)
|
||||
HoldInv = 6 // 🔴 WL-813w8 결과창 정지 무적 래치 → 원본 무적 경로(피해 0 · 런 밖 D-46)
|
||||
}
|
||||
|
||||
/// <summary>사망 직전 피해 로그. 정적 · 링버퍼 16 · 기록 경로 GC 0.</summary>
|
||||
|
|
@ -215,6 +216,7 @@ namespace WL.Combat.Survival
|
|||
case DamageVia.SwitchOff: return "SwitchOff";
|
||||
case DamageVia.Passthru: return "Passthru ";
|
||||
case DamageVia.HitInv: return "HitInv "; // 813i2 피격 후 무적 창(피해 0)
|
||||
case DamageVia.HoldInv: return "HoldInv "; // 813w8 결과창 정지 무적(피해 0)
|
||||
default: return "UNSCALED ";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,14 @@ namespace WL.Combat.Survival
|
|||
/// <summary>마지막으로 연 창의 길이(초).</summary>
|
||||
public static float LastHitInvincibleSeconds;
|
||||
|
||||
// ── WL-813w8 진단(프로브가 읽는다) — 결과창(런 종료) 정지 중 무적 래치
|
||||
/// <summary>정지 무적이 막은 피격 수(D-46 = 결과창 중 초당 3~5대).</summary>
|
||||
public static int BlockedHoldInvincible;
|
||||
/// <summary>래치를 건 횟수 · 푼 횟수.</summary>
|
||||
public static int HoldInvincibleOn, HoldInvincibleOff;
|
||||
/// <summary>마지막으로 걸거나 푼 사유(진단).</summary>
|
||||
public static string LastHoldInvincibleReason = "";
|
||||
|
||||
/// <summary>
|
||||
/// WL-813m3 — 이 훅을 거쳐 실제로 깎인 PC HP 누계(중첩 Cal_Damage 포함 · base 호출 전후 실측).
|
||||
/// DamageGuard 가 "총 감소량 − 이 누계" 로 **훅을 안 거친 피해**(배율 미적용 경로)를 뽑는 기준선이다.
|
||||
|
|
@ -87,6 +95,10 @@ namespace WL.Combat.Survival
|
|||
static float s_hitInvincibleUntil = -1f;
|
||||
static Actor s_hitInvinciblePc;
|
||||
|
||||
// WL-813w8 — 결과창 정지 무적 「래치」(창이 아니다 = 시간으로 만료되지 않는다).
|
||||
// 거는 쪽(RunGuide.BeginRunEndHold)이 풀 때까지 유지된다 — 결과창을 얼마나 오래 띄워도 피해 0.
|
||||
static Actor s_holdInvinciblePc;
|
||||
|
||||
/// <summary>마지막 틱의 게임 시간(Time.time · 프로브 클록).</summary>
|
||||
public static float Now { get { return s_now; } }
|
||||
public static float ReviveInvincibleUntil { get { return s_reviveInvincibleUntil; } }
|
||||
|
|
@ -146,6 +158,49 @@ namespace WL.Combat.Survival
|
|||
return Mathf.Max(0f, st.mobHitInvincibleSeconds);
|
||||
}
|
||||
|
||||
// ─────────────────────────── WL-813w8 결과창 정지 중 무적 래치 (Q11 D-46)
|
||||
//
|
||||
// 🔴 근거 = qa/W2-11_재채점_v9.md D-46 — 813w7 정지(RunGuide.BeginRunEndHold)는 **PC 의 공격만** 막는다.
|
||||
// 몹은 그대로 때려 **초당 3.4~4.9 HP** 가 깎이고 **55~80 초에 사망**(3판 3/3 · 사망 파일 3건).
|
||||
// `mobHitInvincible=0`(813i2 설계값)이라 잡몹 연타를 끊는 창이 없다.
|
||||
//
|
||||
// ■ 왜 「창」이 아니라 「래치」인가 — 결과창을 얼마나 오래 켜 두는지는 PD 손에 달렸다(Q11: 82 s · 94 s).
|
||||
// 시간 창을 갱신하는 방식은 갱신이 한 번만 끊겨도 죽는다. 거는 쪽이 풀 때까지 유지되는 래치가 맞다.
|
||||
// ■ 피해를 어떻게 0 으로 만드나 = 813i2 피격 창과 **같은 원본 경로**다 — `playerInvincible` 을 이 호출
|
||||
// 동안만 올려 `MyActor.Get_Damage`(:308)가 base 를 건너뛰고 `RaiseDamaged(invincible=true)` 만 내게 한다.
|
||||
// → 피해 0 · 비네트(`WLSurvivalUiSettings.ignoreInvincibleHits=1`)·타격 반응
|
||||
// (`WLImpactTierSettings.pcHitSkipWhenInvincible=1`)이 스스로 걸러 준다(UI 무수정).
|
||||
// ■ 값·스위치는 거는 쪽 SO(`WLRunGuideSettings.invincibleWhileRunEndHold`) — 여기는 API 만 둔다(C8).
|
||||
|
||||
/// <summary>결과창 정지 무적이 걸려 있는가(대상 무관 · DamageGuard·프로브가 읽는다).</summary>
|
||||
public static bool HoldInvincibleActive { get { return s_holdInvinciblePc != null; } }
|
||||
|
||||
/// <summary>이 PC 에 결과창 정지 무적이 걸려 있는가.</summary>
|
||||
public static bool IsHoldInvincible(Actor pc)
|
||||
{
|
||||
return pc != null && ReferenceEquals(pc, s_holdInvinciblePc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 결과창 정지 무적을 걸거나 푼다(RunGuide 가 부른다 · 프로브도 직접 부른다).
|
||||
/// on=false 이거나 pc 가 null 이면 해제. 같은 상태로 다시 불러도 카운터는 한 번만 움직인다.
|
||||
/// </summary>
|
||||
public static void SetHoldInvincible(Actor pc, bool on, string reason)
|
||||
{
|
||||
if (!on || pc == null)
|
||||
{
|
||||
if (s_holdInvinciblePc == null) return;
|
||||
s_holdInvinciblePc = null;
|
||||
HoldInvincibleOff++;
|
||||
LastHoldInvincibleReason = reason;
|
||||
return;
|
||||
}
|
||||
if (ReferenceEquals(pc, s_holdInvinciblePc)) return;
|
||||
s_holdInvinciblePc = pc;
|
||||
HoldInvincibleOn++;
|
||||
LastHoldInvincibleReason = reason;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PCActor.Get_Damage 훅(1줄). baseCall = base.Get_Damage(MyActor · 비가상). 비활성이면 baseCall 만 = 원본 100%.
|
||||
/// </summary>
|
||||
|
|
@ -158,6 +213,20 @@ namespace WL.Combat.Survival
|
|||
|
||||
if (IsReviveInvincible(pc)) { BlockedReviveInvincible++; CallBase(pc, dinfo, baseCall, DamageVia.ReviveInv, dinfo.Damage, 0d); return; } // 원본 무적 경로(피해 0 · Damaged(invincible=true))
|
||||
|
||||
// 🔴 WL-813w8 (Q11 D-46) — 결과창(런 종료) 정지 중에는 피해 0. 813i2 피격 창과 **같은 원본 경로**
|
||||
// (플래그를 이 호출 동안만 올린다 → base 가 건너뛰고 Damaged(invincible=true) 만 나간다).
|
||||
// 부활 무적 창보다 **뒤**에 둔다 — 사망/부활 중이면 그쪽이 우선이다(발주서 §1-1 「부활 우선」).
|
||||
if (IsHoldInvincible(pc))
|
||||
{
|
||||
BlockedHoldInvincible++;
|
||||
var gsr = WL.Settings.WLGameplaySettings.Instance;
|
||||
bool raiseHold = gsr != null && !gsr.playerInvincible;
|
||||
if (raiseHold) gsr.playerInvincible = true;
|
||||
try { CallBase(pc, dinfo, baseCall, DamageVia.HoldInv, dinfo.Damage, 0d); }
|
||||
finally { if (raiseHold) gsr.playerInvincible = false; }
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔴 WL-813i2 (Q10 D-21) — 피격 후 무적 창. 부활 창과 **같은 원본 경로**로 흘려보낸다:
|
||||
// playerInvincible 을 내리지 않으므로 MyActor.Get_Damage 가 base 를 건너뛰고
|
||||
// RaiseDamaged(invincible=true) 만 낸다 → 피해 0 · 비네트/타격반응은 각 SO 의
|
||||
|
|
@ -296,6 +365,7 @@ namespace WL.Combat.Survival
|
|||
DeathFlow.Abort(reason);
|
||||
PotionUse.ResetRun(reason);
|
||||
SetReviveInvincible(null, 0f);
|
||||
SetHoldInvincible(null, false, "런 경계(" + reason + ")"); // 813w8 — 맵/PC 가 바뀌면 정지 무적도 반드시 푼다(RunGuide 해제와 이중 안전)
|
||||
if (Verbose) Debug.Log("[Survival] run start (" + reason + ")");
|
||||
}
|
||||
|
||||
|
|
@ -322,6 +392,10 @@ namespace WL.Combat.Survival
|
|||
BlockedHitInvincible = HitInvincibleOpened = HitInvincibleOpenedByBoss = 0;
|
||||
LastHitInvincibleSeconds = 0f;
|
||||
s_hitInvincibleUntil = -1f; s_hitInvinciblePc = null;
|
||||
// 813w8
|
||||
BlockedHoldInvincible = HoldInvincibleOn = HoldInvincibleOff = 0;
|
||||
LastHoldInvincibleReason = "";
|
||||
s_holdInvinciblePc = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
// 뒤늦게 이름이 오면 표시 중인 배너의 이름 줄만 갱신한다(중복 표시 없음).
|
||||
// 이벤트가 없으면 아무 것도 뜨지 않는다 — 폴링 폴백을 두지 않는다(813c 규칙).
|
||||
//
|
||||
// ③ WL-813w8 §1-2 — WL.Combat.Run.RunEvents.BossGateOpened. **게이트 개방 전에는 배너를 띄우지 않는다**
|
||||
// (Q11 D-23 재발: 813w6 이 「다음 런」의 재스폰 보스를 게이트 개방까지 살려서 고정해 두는데,
|
||||
// ①·늦은 구독·런 시작 재트리거가 그 보스로 배너·바를 띄운다). 개방 순간 이 이벤트가 늦은 구독 경로를 태운다.
|
||||
// 판정·값은 Gameplay 소유(`BossArena.HideBossUiNow` · `WLBossArenaSettings.hideBossUiBeforeGate`).
|
||||
//
|
||||
// ■ 값 = WLBossUiSettings.asset (C45) · px→유닛 = 813c WLHudLayoutSettings.UnitsPerPx 하나만.
|
||||
// ■ 숨김 = CanvasGroup alpha (GameObject 를 끄면 OnEnable 이 안 돌아 구독이 끊긴다 · 813c 규칙)
|
||||
// ■ 시간 = Time.unscaledTime (811b 히트스톱·킬캠 슬로모 중에도 배너 길이가 흔들리지 않게)
|
||||
|
|
@ -94,6 +99,8 @@ namespace WL.UI
|
|||
var s = WLBossUiSettings.Instance;
|
||||
if (s != null && !s.bannerEnabled) return "늦은구독: bannerEnabled=false — 생략";
|
||||
if (s != null && !s.bannerTriggerExisting) return "늦은구독: bannerTriggerExisting=false — 생략";
|
||||
// 🔴 WL-813w8(D-23 재발) — 게이트 개방 전(813w6 고정 보스)에는 배너를 띄우지 않는다(바와 같은 판정).
|
||||
if (SuppressedBeforeGate) { GateSuppressCount++; return "늦은구독: 게이트 전 — 생략(813w8)"; }
|
||||
|
||||
Actor found = null;
|
||||
string how = "";
|
||||
|
|
@ -139,12 +146,14 @@ namespace WL.UI
|
|||
CombatEvents.Spawned.Add(OnSpawned);
|
||||
BossEvents.BossPhase.Add(OnBossPhase);
|
||||
RunEvents.RunStarted.Add(OnRunStarted); // WL-813q2(D-23) — 다음 런 리셋
|
||||
RunEvents.BossGateOpened.Add(OnBossGateOpened); // WL-813w8(D-23 재발) — 게이트 개방 = 표시 시점
|
||||
}
|
||||
else
|
||||
{
|
||||
CombatEvents.Spawned.Remove(OnSpawned);
|
||||
BossEvents.BossPhase.Remove(OnBossPhase);
|
||||
RunEvents.RunStarted.Remove(OnRunStarted);
|
||||
RunEvents.BossGateOpened.Remove(OnBossGateOpened);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,6 +175,10 @@ namespace WL.UI
|
|||
|
||||
// WL-813c2 (Q6 D-23 잔여) — 813c 바와 같은 조건. `BossArena.ResetGate()`(`BossArena.cs:630`)는
|
||||
// `s_boss` 를 비우지 않아, 조건이 없으면 **이전 런의 살아 있는 보스**로 배너가 다시 뜬다.
|
||||
// 🔴 WL-813w8(D-23 재발) — 813w6 은 재스폰 보스에도 `BossSpawned=true` 를 유지하므로 아래 조건만으로는
|
||||
// 「다음 런」 시작에 고정 보스로 배너가 다시 뜬다. 게이트 개방 전에는 띄우지 않는다.
|
||||
if (SuppressedBeforeGate) { GateSuppressCount++; return "런시작리셋: 숨김·가드 해제만(게이트 전 · 813w8)"; }
|
||||
|
||||
var hud = WLHudLayoutSettings.Instance;
|
||||
if (hud != null && hud.bossBarRebindRequiresArenaSpawn && !WL.Combat.Boss.BossArena.BossSpawned)
|
||||
return "런시작리셋: 숨김만(이번 런 보스 미등장 · BossArena.BossSpawned=false)";
|
||||
|
|
@ -176,12 +189,32 @@ namespace WL.UI
|
|||
/// <summary>런 시작 리셋 횟수(진단 · D-23).</summary>
|
||||
public static int RunResetCount;
|
||||
|
||||
// ── WL-813w8(D-23 재발) — 게이트 전 보스 UI 숨김 ──────────────────────
|
||||
/// <summary>게이트 전이라 배너를 거절한 횟수(진단).</summary>
|
||||
public static int GateSuppressCount;
|
||||
/// <summary>게이트 개방 이벤트로 배너를 띄운 횟수(진단).</summary>
|
||||
public static int GateOpenTriggers;
|
||||
/// <summary>마지막 게이트 개방 트리거 결과(진단).</summary>
|
||||
public static string LastGateOpenTrigger = "";
|
||||
|
||||
/// <summary>지금 게이트 전이라 보스 UI 를 숨겨야 하는가(Gameplay 판정 · 값은 WLBossArenaSettings).</summary>
|
||||
public static bool SuppressedBeforeGate { get { return WL.Combat.Boss.BossArena.HideBossUiNow; } }
|
||||
|
||||
/// <summary>게이트가 열리는 순간 늦은 구독 경로로 배너를 띄운다(813w6 BossGateOpened 이후 표시).</summary>
|
||||
private void OnBossGateOpened(in BossGateOpenedEvent e)
|
||||
{
|
||||
GateOpenTriggers++;
|
||||
LastGateOpenTrigger = TriggerExistingBoss(); // 같은 프레임의 Spawned 로 이미 떴으면 재트리거 가드가 막는다
|
||||
}
|
||||
|
||||
// ── 구독 ──────────────────────────────────────────────────────────────
|
||||
private void OnSpawned(in SpawnedEvent e)
|
||||
{
|
||||
SpawnedSeen++;
|
||||
if (!e.isBoss) return;
|
||||
BossSpawnSeen++;
|
||||
// 🔴 WL-813w8(D-23 재발) — 게이트 전 보스 등장(= 813w6 재스폰 고정)은 배너를 띄우지 않는다.
|
||||
if (SuppressedBeforeGate) { GateSuppressCount++; return; }
|
||||
string n = e.actor != null ? e.actor.name : null;
|
||||
Trigger(n, false);
|
||||
}
|
||||
|
|
@ -325,6 +358,7 @@ namespace WL.UI
|
|||
public string ResetDiagnostics()
|
||||
{
|
||||
SpawnedSeen = BossSpawnSeen = PhaseSeen = ShowCount = 0;
|
||||
GateSuppressCount = GateOpenTriggers = 0; LastGateOpenTrigger = ""; // 813w8
|
||||
_lastTrigger = -999f; _shownName = ""; _nameKnown = false;
|
||||
HideNow();
|
||||
return "진단 초기화(가드 해제)";
|
||||
|
|
|
|||
|
|
@ -17,6 +17,16 @@
|
|||
// GameObject 를 끄지 않는다(꺼지면 OnEnable 이 안 돌아 구독이 끊긴다).
|
||||
// CanvasGroup alpha/blocksRaycasts 로만 숨긴다.
|
||||
//
|
||||
// ■ WL-813w8 §1-2 (2026-09-09) — 게이트 개방 전에는 바를 띄우지 않는다 (Q11 D-23 **재발**)
|
||||
// 실측: 「다음 런」의 **존 사냥 내내 상단에 만피 빨간 보스 바**(캡처 2장 · `bhp=1800/1800 · balive=True`).
|
||||
// 바 자체는 정상 추종이다 — 813w6 이 재스폰 보스를 **게이트 개방까지 살려서 고정**해 두는데,
|
||||
// 813y 늦은 구독(`BindExistingBoss`)·813q2 런 시작 재바인드(`ResetForNewRun`)·`Spawned` 구독이
|
||||
// 그 고정 보스를 물어 버리는 **표시 정책**이 문제였다.
|
||||
// → 네 진입점 전부에 `BossArena.HideBossUiNow`(게이트 축 무장 + 미개방) 조건을 걸고,
|
||||
// `RunEvents.BossGateOpened` 를 새로 구독해 **게이트가 열리는 순간** 늦은 구독 경로로 물린다.
|
||||
// 🔴 첫 런 영향 0 — 첫 런 보스는 게이트 개방(`ForceOpenGate → Release`) **안에서** 등장한다(그때는 이미 개방).
|
||||
// C8 롤백 = `WLBossArenaSettings.hideBossUiBeforeGate = 0` → 813q2/813y 동작 그대로.
|
||||
//
|
||||
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -64,6 +74,28 @@ namespace WL.UI
|
|||
/// <summary>런 시작 리셋 횟수(진단 · D-23).</summary>
|
||||
public static int RunResetCount;
|
||||
|
||||
// ── WL-813w8(D-23 재발) — 게이트 전 보스 UI 숨김 진단 ──
|
||||
/// <summary>게이트 전이라 바인드를 거절한 횟수(늦은 구독 · 런 시작 재바인드 · Spawned · 폴링).</summary>
|
||||
public static int GateSuppressCount;
|
||||
/// <summary>게이트 전이라 이미 물고 있던 바를 내린 횟수.</summary>
|
||||
public static int GateHideCount;
|
||||
/// <summary>게이트 개방 이벤트로 다시 물린 횟수.</summary>
|
||||
public static int GateOpenBinds;
|
||||
|
||||
/// <summary>지금 게이트 전이라 보스 UI 를 숨겨야 하는가(Gameplay 판정을 그대로 읽는다 · 값은 WLBossArenaSettings).</summary>
|
||||
public static bool SuppressedBeforeGate { get { return WL.Combat.Boss.BossArena.HideBossUiNow; } }
|
||||
|
||||
// ── WL-813w8 — 게이트가 열리는 순간 늦은 구독 경로로 물린다(813w6 BossGateOpened 이후 표시) ──
|
||||
private void OnBossGateOpened(in BossGateOpenedEvent e)
|
||||
{
|
||||
if (_bound) return;
|
||||
GateOpenBinds++;
|
||||
LastGateOpenBind = BindExistingBoss();
|
||||
}
|
||||
|
||||
/// <summary>마지막 게이트 개방 바인드 결과(진단).</summary>
|
||||
public static string LastGateOpenBind = "";
|
||||
|
||||
/// <summary>마지막 갱신 결과 — 프로브(에디터)가 읽는 진단 값.</summary>
|
||||
public float LastRatio { get; private set; }
|
||||
public int SpawnedSeen { get; private set; }
|
||||
|
|
@ -107,6 +139,8 @@ namespace WL.UI
|
|||
if (s != null && !s.bossBarEnabled) return "늦은구독: bossBarEnabled=false — 생략";
|
||||
if (s != null && !s.bossBarBindExisting) return "늦은구독: bossBarBindExisting=false — 생략";
|
||||
if (_bound) return "늦은구독: 이미 물림";
|
||||
// 🔴 WL-813w8(D-23 재발) — 게이트 개방 전(813w6 고정 보스)에는 물지 않는다.
|
||||
if (SuppressedBeforeGate) { GateSuppressCount++; return "늦은구독: 게이트 전 — 생략(813w8)"; }
|
||||
|
||||
Actor found = null;
|
||||
string how = "";
|
||||
|
|
@ -157,6 +191,7 @@ namespace WL.UI
|
|||
CombatEvents.HitConfirmed.Add(OnHitConfirmed);
|
||||
CombatEvents.Killed.Add(OnKilled);
|
||||
RunEvents.RunStarted.Add(OnRunStarted); // WL-813q2(D-23) — 다음 런 리셋
|
||||
RunEvents.BossGateOpened.Add(OnBossGateOpened); // WL-813w8(D-23 재발) — 게이트 개방 = 표시 시점
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -164,6 +199,7 @@ namespace WL.UI
|
|||
CombatEvents.HitConfirmed.Remove(OnHitConfirmed);
|
||||
CombatEvents.Killed.Remove(OnKilled);
|
||||
RunEvents.RunStarted.Remove(OnRunStarted);
|
||||
RunEvents.BossGateOpened.Remove(OnBossGateOpened);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,6 +231,11 @@ namespace WL.UI
|
|||
// `ResetGate()`(`BossArena.cs:630~638`)는 `s_boss` 를 **비우지 않는다**. 그래서 `BindExistingBoss()` 의
|
||||
// ① `BossArena.Boss` 가 **이전 런의 살아 있는 보스**를 그대로 돌려주고 바가 즉시 다시 뜬다.
|
||||
// → 이번 런의 보스가 실제로 등장(`BossArena.BossSpawned` · ResetGate 가 false 로 되돌린다)해야만 다시 문다.
|
||||
// 🔴 WL-813w8(D-23 재발) — 813w6 은 재스폰 보스에도 `BossSpawned=true` 를 **유지**한다(813p 폴백을 위해).
|
||||
// 그래서 아래 813c2 조건만으로는 「다음 런」의 고정 보스가 그대로 다시 물린다 = Q11 재발 그림이다.
|
||||
// 게이트가 열리기 전에는 숨김만 하고, 개방 시점에 `OnBossGateOpened` 가 물린다.
|
||||
if (SuppressedBeforeGate) { GateSuppressCount++; return "런시작리셋: 숨김만(게이트 전 · 813w8)"; }
|
||||
|
||||
var hud = WLHudLayoutSettings.Instance;
|
||||
if (hud != null && hud.bossBarRebindRequiresArenaSpawn && !WL.Combat.Boss.BossArena.BossSpawned)
|
||||
return "런시작리셋: 숨김만(이번 런 보스 미등장 · BossArena.BossSpawned=false)";
|
||||
|
|
@ -253,6 +294,14 @@ namespace WL.UI
|
|||
_nextPoll = now + 1f / s.bossBarPollHz;
|
||||
}
|
||||
|
||||
// 🔴 WL-813w8(D-23 재발) — 게이트 전에는 새로 물지도, 물고 있던 것을 그리지도 않는다.
|
||||
// (`bossBarPollFollowsArena` 는 813w6 이 유지하는 `BossSpawned=true` 때문에 그냥 두면 다시 문다.)
|
||||
if (SuppressedBeforeGate)
|
||||
{
|
||||
if (_bound) { GateHideCount++; HideNow(); }
|
||||
return false;
|
||||
}
|
||||
|
||||
// ③ 아레나가 잡은 보스와 물고 있는 보스가 다르면 그쪽으로 갈아탄다.
|
||||
// 🔴 **물고 있지 않을 때**(= 런 시작 리셋으로 숨긴 상태)는 D-23 을 되살리지 않게
|
||||
// 이번 런의 보스가 실제로 등장했을 때만 새로 문다(ResetForNewRun 과 같은 조건).
|
||||
|
|
@ -331,6 +380,8 @@ namespace WL.UI
|
|||
{
|
||||
SpawnedSeen++;
|
||||
if (!e.isBoss) return;
|
||||
// 🔴 WL-813w8(D-23 재발) — 게이트 전 보스 등장(= 813w6 이 그 자리에서 고정하는 재스폰)은 띄우지 않는다.
|
||||
if (SuppressedBeforeGate) { GateSuppressCount++; return; }
|
||||
Bind(e.actor, null, DebugFallbackMaxHp);
|
||||
}
|
||||
|
||||
|
|
@ -824,6 +875,12 @@ namespace WL.UI
|
|||
sb.AppendLine(" [813q2] 런 리셋 " + RunResetCount + "회 · 런 보스 HP=" +
|
||||
(RunBossHpRatio >= 0f ? (RunBossHpRatio * 100f).ToString("F1") + "%" : "없음") +
|
||||
" (본적=" + RunBossSeen + ") · RunStarted 구독=" + RunEvents.RunStarted.Count);
|
||||
sb.AppendLine(" [813w8] 게이트전숨김=" + SuppressedBeforeGate +
|
||||
"(무장=" + WL.Combat.Boss.BossArena.GateArmed +
|
||||
" 개방=" + WL.Combat.Boss.BossArena.GateOpen + "/" + RunDirector.GateOpened + ")" +
|
||||
" · 거절 " + GateSuppressCount + "회 · 내림 " + GateHideCount + "회 · 개방바인드 " +
|
||||
GateOpenBinds + "회(" + (string.IsNullOrEmpty(LastGateOpenBind) ? "-" : LastGateOpenBind) + ")" +
|
||||
" · BossGateOpened 구독=" + RunEvents.BossGateOpened.Count);
|
||||
sb.AppendLine(" CombatEvents subscribers Spawned=" + CombatEvents.Spawned.Count +
|
||||
" HitConfirmed=" + CombatEvents.HitConfirmed.Count +
|
||||
" Killed=" + CombatEvents.Killed.Count + " Enabled=" + CombatEvents.Enabled);
|
||||
|
|
|
|||
Loading…
Reference in New Issue