237 lines
20 KiB
C#
237 lines
20 KiB
C#
|
|
// WL811b_Probe.cs — #811b 전투 액션 코어 골격 검증 프로브 (Play 전용 · 읽기 전용 · 에셋 무수정)
|
||
|
|
//
|
||
|
|
// unity command run_script --file AgentScripts/WL811b_Probe.cs --entry WL811b_Probe.Watch --args '[10]' // ⓒ 이벤트 순서·ms 로그(10 s · 3콤보 1사이클 이상)
|
||
|
|
// unity command run_script --file AgentScripts/WL811b_Probe.cs --entry WL811b_Probe.GcCheck // ⓖ 100회 디스패치 GC 증가(바이트)
|
||
|
|
// unity command run_script --file AgentScripts/WL811b_Probe.cs --entry WL811b_Probe.Arbiter // ⓔ 일시정지 스킵 · 상위 층 스킵 · 스로틀 · 복원
|
||
|
|
// unity command run_script --file AgentScripts/WL811b_Probe.cs --entry WL811b_Probe.Toggle --args '[false, 6]' // ⓕ 코어 off 6 s: 디스패치 0 · 타격감(FiredCount) 계속 → 자동 on 복귀
|
||
|
|
// unity command run_script --file AgentScripts/WL811b_Probe.cs --entry WL811b_Probe.Dump // ⓗ 설정 에셋 값 · 활성 상태
|
||
|
|
// unity command run_script --file AgentScripts/WL811b_Probe.cs --entry WL811b_Probe.Status // 이벤트별 Raised/Dispatched · 중재자 · 상한 카운터
|
||
|
|
// 결과: Screenshots_WL/WL811b/<이름>_<HHmmss>.txt + Console
|
||
|
|
// 코어·원본 코드를 수정하지 않는다. 구독은 프로브 종료 시 해제한다.
|
||
|
|
|
||
|
|
using System;
|
||
|
|
using System.Collections;
|
||
|
|
using System.Text;
|
||
|
|
using UnityEngine;
|
||
|
|
using WL.Combat.Core;
|
||
|
|
|
||
|
|
public static class WL811b_Probe
|
||
|
|
{
|
||
|
|
const string OutDir = "Screenshots_WL/WL811b";
|
||
|
|
|
||
|
|
public static void Watch(float seconds) { Launch(h => h.Co_Watch(seconds <= 0f ? 10f : seconds)); }
|
||
|
|
public static void Arbiter() { Launch(h => h.Co_Arbiter()); }
|
||
|
|
public static void Toggle(bool on, float seconds) { Launch(h => h.Co_Toggle(on, seconds <= 0f ? 6f : seconds)); }
|
||
|
|
|
||
|
|
public static object Dump()
|
||
|
|
{
|
||
|
|
var sb = new StringBuilder();
|
||
|
|
var c = WLCombatCoreSettings.Instance;
|
||
|
|
sb.AppendLine("# WL811b Dump " + DateTime.Now.ToString("HH:mm:ss"));
|
||
|
|
sb.AppendLine("asset=" + (c != null ? "found" : "NULL") + " Enabled=" + WLCombatCoreSettings.Enabled + " RuntimeDisabled=" + WLCombatCoreSettings.RuntimeDisabled
|
||
|
|
+ " arbiter=" + TimeScaleArbiter.Enabled + " feel=" + (WL.Feel.WLHitFeel.Settings != null ? WL.Feel.WLHitFeel.Settings.enabled.ToString() : "null") + " coreSubscribed=" + WL.Feel.WLHitFeel.CoreSubscribed);
|
||
|
|
if (c != null) sb.AppendLine(JsonUtility.ToJson(c, true));
|
||
|
|
Save("dump", sb); return sb.ToString();
|
||
|
|
}
|
||
|
|
|
||
|
|
public static object Status()
|
||
|
|
{
|
||
|
|
var sb = new StringBuilder();
|
||
|
|
sb.AppendLine("# WL811b Status " + DateTime.Now.ToString("HH:mm:ss") + " timeScale=" + Time.timeScale.ToString("F2") + " TotalRaised=" + CombatEvents.TotalRaised + " last=" + CombatEvents.LastEvent);
|
||
|
|
sb.AppendLine(Row("AttackStarted", CombatEvents.AttackStarted.Count, CombatEvents.AttackStarted.Raised, CombatEvents.AttackStarted.Dispatched));
|
||
|
|
sb.AppendLine(Row("ComboStage", CombatEvents.ComboStage.Count, CombatEvents.ComboStage.Raised, CombatEvents.ComboStage.Dispatched));
|
||
|
|
sb.AppendLine(Row("SwingEffect", CombatEvents.SwingEffect.Count, CombatEvents.SwingEffect.Raised, CombatEvents.SwingEffect.Dispatched));
|
||
|
|
sb.AppendLine(Row("HitboxSpawned", CombatEvents.HitboxSpawned.Count, CombatEvents.HitboxSpawned.Raised, CombatEvents.HitboxSpawned.Dispatched));
|
||
|
|
sb.AppendLine(Row("HitboxHit", CombatEvents.HitboxHit.Count, CombatEvents.HitboxHit.Raised, CombatEvents.HitboxHit.Dispatched));
|
||
|
|
sb.AppendLine(Row("HitConfirmed", CombatEvents.HitConfirmed.Count, CombatEvents.HitConfirmed.Raised, CombatEvents.HitConfirmed.Dispatched));
|
||
|
|
sb.AppendLine(Row("Killed", CombatEvents.Killed.Count, CombatEvents.Killed.Raised, CombatEvents.Killed.Dispatched));
|
||
|
|
sb.AppendLine(Row("Dash", CombatEvents.Dash.Count, CombatEvents.Dash.Raised, CombatEvents.Dash.Dispatched));
|
||
|
|
sb.AppendLine(Row("SkillCast", CombatEvents.SkillCast.Count, CombatEvents.SkillCast.Raised, CombatEvents.SkillCast.Dispatched));
|
||
|
|
sb.AppendLine(Row("SkillFired", CombatEvents.SkillFired.Count, CombatEvents.SkillFired.Raised, CombatEvents.SkillFired.Dispatched));
|
||
|
|
sb.AppendLine(Row("Damaged", CombatEvents.Damaged.Count, CombatEvents.Damaged.Raised, CombatEvents.Damaged.Dispatched));
|
||
|
|
sb.AppendLine(Row("Spawned", CombatEvents.Spawned.Count, CombatEvents.Spawned.Raised, CombatEvents.Spawned.Dispatched));
|
||
|
|
sb.AppendLine(Row("LevelUp", CombatEvents.LevelUp.Count, CombatEvents.LevelUp.Raised, CombatEvents.LevelUp.Dispatched));
|
||
|
|
sb.AppendLine("arbiter active=" + TimeScaleArbiter.Active + " applied=" + TimeScaleArbiter.AppliedCount + " ignored=" + TimeScaleArbiter.IgnoredCount + " skipHigher=" + TimeScaleArbiter.SkippedHigherCount
|
||
|
|
+ " skipPause=" + TimeScaleArbiter.SkippedPauseCount + " throttled=" + TimeScaleArbiter.ThrottledCount + " preempted=" + TimeScaleArbiter.PreemptedCount + " lastMeasured=" + TimeScaleArbiter.LastMeasured.ToString("F3") + " last=" + TimeScaleArbiter.LastResult + " " + TimeScaleArbiter.LastInfo);
|
||
|
|
sb.AppendLine("feel hit=" + WL.Feel.WLHitFeel.HitCount + " fired=" + WL.Feel.WLHitFeel.FiredCount + " throttled=" + WL.Feel.WLHitFeel.ThrottledCount + " lastHitStop=" + WL.Feel.WLHitFeel.LastHitStopMeasured.ToString("F3"));
|
||
|
|
sb.AppendLine("budget heavy=" + EffectBudget.Count(EffectBudgetKind.Heavy) + "/" + EffectBudget.Limit(EffectBudgetKind.Heavy) + " afterImage=" + EffectBudget.Count(EffectBudgetKind.AfterImage) + "/" + EffectBudget.Limit(EffectBudgetKind.AfterImage)
|
||
|
|
+ " dissolve=" + EffectBudget.Count(EffectBudgetKind.Dissolve) + "/" + EffectBudget.Limit(EffectBudgetKind.Dissolve) + " burst=" + EffectBudget.Count(EffectBudgetKind.ClusterBurst) + "/" + EffectBudget.Limit(EffectBudgetKind.ClusterBurst) + " combo=" + ComboTracker.LastInfo);
|
||
|
|
Save("status", sb); return sb.ToString();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>ⓖ 100회 디스패치 GC 증가. 구독자 1개(no-op) 등록 후 측정 · Play/Edit 모두 가능(코어 Enabled 필요).</summary>
|
||
|
|
public static object GcCheck()
|
||
|
|
{
|
||
|
|
var sb = new StringBuilder();
|
||
|
|
sb.AppendLine("# WL811b GcCheck " + DateTime.Now.ToString("HH:mm:ss") + " Enabled=" + WLCombatCoreSettings.Enabled);
|
||
|
|
CombatHandler<HitboxSpawnedEvent> h = NoOpHitbox;
|
||
|
|
CombatEvents.HitboxSpawned.Add(h);
|
||
|
|
var pos = Vector3.zero;
|
||
|
|
for (int i = 0; i < 20; i++) CombatEvents.RaiseHitboxSpawned(null, "warm", 1f, 1f, pos); // 워밍업(JIT)
|
||
|
|
long m0 = GC.GetTotalMemory(false);
|
||
|
|
long p0 = UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong();
|
||
|
|
for (int i = 0; i < 100; i++) CombatEvents.RaiseHitboxSpawned(null, "gc", 1f, 1f, pos);
|
||
|
|
long m1 = GC.GetTotalMemory(false);
|
||
|
|
long p1 = UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong();
|
||
|
|
CombatEvents.HitboxSpawned.Remove(h);
|
||
|
|
sb.AppendLine("100 dispatch: GC.GetTotalMemory delta=" + (m1 - m0) + " B · Profiler.MonoUsed delta=" + (p1 - p0) + " B · noop calls=" + s_noop + " (기준 < 1024 B)");
|
||
|
|
Save("gc", sb); return sb.ToString();
|
||
|
|
}
|
||
|
|
static int s_noop;
|
||
|
|
static void NoOpHitbox(in HitboxSpawnedEvent e) { s_noop++; }
|
||
|
|
|
||
|
|
// ── 공용
|
||
|
|
static string Row(string n, int subs, int raised, int dispatched) { return string.Format(" {0,-14} subs={1} raised={2} dispatched={3}", n, subs, raised, dispatched); }
|
||
|
|
|
||
|
|
static void Save(string name, StringBuilder sb)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
System.IO.Directory.CreateDirectory(OutDir);
|
||
|
|
string path = System.IO.Path.Combine(OutDir, name + "_" + DateTime.Now.ToString("HHmmss") + ".txt");
|
||
|
|
System.IO.File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false));
|
||
|
|
Debug.Log("[WL811b] 저장 " + path);
|
||
|
|
}
|
||
|
|
catch (Exception e) { Debug.LogWarning("[WL811b] 저장 실패 " + e.Message); }
|
||
|
|
Debug.Log(sb.ToString());
|
||
|
|
}
|
||
|
|
|
||
|
|
static void Launch(Func<Host, IEnumerator> make)
|
||
|
|
{
|
||
|
|
var old = GameObject.Find("__wl811b_probe");
|
||
|
|
if (old != null) UnityEngine.Object.DestroyImmediate(old);
|
||
|
|
var go = new GameObject("__wl811b_probe");
|
||
|
|
var h = go.AddComponent<Host>();
|
||
|
|
h.StartCoroutine(make(h));
|
||
|
|
}
|
||
|
|
|
||
|
|
public class Host : MonoBehaviour
|
||
|
|
{
|
||
|
|
StringBuilder _sb; float _t0;
|
||
|
|
string Pre() { return string.Format("{0,7:F0} | f{1,-5} | ts={2:F2} | ", (Time.unscaledTime - _t0) * 1000f, Time.frameCount, Time.timeScale); }
|
||
|
|
static string N(UnityEngine.Object o) { return o != null ? o.name : "null"; }
|
||
|
|
|
||
|
|
// ── 구독자(메서드 그룹 · 종료 시 해제)
|
||
|
|
void OnAttack(in AttackStartedEvent e) { _sb.AppendLine(Pre() + "AttackStarted actor=" + N(e.actor) + " idx=" + e.attackIndex + " speed=" + e.animSpeed.ToString("F2") + " target=" + N(e.target)); }
|
||
|
|
void OnCombo(in ComboStageEvent e) { _sb.AppendLine(Pre() + "ComboStage stage=" + e.stage + " clip=" + e.clip + " nt=" + e.normalizedTime.ToString("F3") + " last=" + e.isLast); }
|
||
|
|
void OnSwing(in SwingEffectEvent e) { _sb.AppendLine(Pre() + "SwingEffect effect=" + e.effect + " int=" + e.intParam + " handled=" + e.handled); }
|
||
|
|
void OnHitbox(in HitboxSpawnedEvent e) { _sb.AppendLine(Pre() + "HitboxSpawned prefab=" + e.prefab + " addDmg=" + e.addDmg.ToString("F2") + " life=" + e.lifetime.ToString("F2")); }
|
||
|
|
void OnHitboxHit(in HitboxHitEvent e) { _sb.AppendLine(Pre() + "HitboxHit proj=" + N(e.projectile) + " victim=" + N(e.victim) + " noDmg=" + e.noDmg + " skill=" + e.isSkill); }
|
||
|
|
void OnHitConfirmed(in HitConfirmedEvent e) { _sb.AppendLine(Pre() + "HitConfirmed victim=" + N(e.victim) + " dmg=" + e.damage.ToString("F0") + " cri=" + e.critical + " kill=" + e.isKill + " byPC=" + e.byMainPC + " feelFired=" + WL.Feel.WLHitFeel.FiredCount); }
|
||
|
|
void OnKilled(in KilledEvent e) { _sb.AppendLine(Pre() + "Killed victim=" + N(e.victim) + " killer=" + N(e.killer) + " sub=" + e.subRole + " id=" + e.id + " direct=" + e.byDirectHit); }
|
||
|
|
void OnDash(in DashEvent e) { _sb.AppendLine(Pre() + (e.began ? "DashBegan " : "DashEnded ") + "dist=" + e.distance.ToString("F2") + " attack=" + e.endedInAttack); }
|
||
|
|
void OnSkillCast(in SkillCastEvent e) { _sb.AppendLine(Pre() + "SkillCast skill=" + (e.skill != null ? e.skill.n_SkillID.ToString() : "null") + " slot=" + e.slot); }
|
||
|
|
void OnSkillFired(in SkillFiredEvent e) { _sb.AppendLine(Pre() + "SkillFired param=" + e.eventParam); }
|
||
|
|
void OnDamaged(in DamagedEvent e) { _sb.AppendLine(Pre() + "Damaged(PC) from=" + N(e.attacker) + " dmg=" + e.damage.ToString("F0") + " invincible=" + e.invincible); }
|
||
|
|
void OnSpawned(in SpawnedEvent e) { _sb.AppendLine(Pre() + "Spawned actor=" + N(e.actor) + " boss=" + e.isBoss + " elite=" + e.isElite); }
|
||
|
|
void OnLevelUp(in LevelUpEvent e) { _sb.AppendLine(Pre() + "LevelUp lv=" + e.newLevel); }
|
||
|
|
|
||
|
|
void Subscribe(bool on)
|
||
|
|
{
|
||
|
|
if (on)
|
||
|
|
{
|
||
|
|
CombatEvents.AttackStarted.Add(OnAttack); CombatEvents.ComboStage.Add(OnCombo); CombatEvents.SwingEffect.Add(OnSwing);
|
||
|
|
CombatEvents.HitboxSpawned.Add(OnHitbox); CombatEvents.HitboxHit.Add(OnHitboxHit); CombatEvents.HitConfirmed.Add(OnHitConfirmed);
|
||
|
|
CombatEvents.Killed.Add(OnKilled); CombatEvents.Dash.Add(OnDash); CombatEvents.SkillCast.Add(OnSkillCast);
|
||
|
|
CombatEvents.SkillFired.Add(OnSkillFired); CombatEvents.Damaged.Add(OnDamaged); CombatEvents.Spawned.Add(OnSpawned); CombatEvents.LevelUp.Add(OnLevelUp);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
CombatEvents.AttackStarted.Remove(OnAttack); CombatEvents.ComboStage.Remove(OnCombo); CombatEvents.SwingEffect.Remove(OnSwing);
|
||
|
|
CombatEvents.HitboxSpawned.Remove(OnHitbox); CombatEvents.HitboxHit.Remove(OnHitboxHit); CombatEvents.HitConfirmed.Remove(OnHitConfirmed);
|
||
|
|
CombatEvents.Killed.Remove(OnKilled); CombatEvents.Dash.Remove(OnDash); CombatEvents.SkillCast.Remove(OnSkillCast);
|
||
|
|
CombatEvents.SkillFired.Remove(OnSkillFired); CombatEvents.Damaged.Remove(OnDamaged); CombatEvents.Spawned.Remove(OnSpawned); CombatEvents.LevelUp.Remove(OnLevelUp);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>ⓒ 이벤트 순서·타이밍(자동 교전을 그대로 관찰 · 조작 없음).</summary>
|
||
|
|
public IEnumerator Co_Watch(float seconds)
|
||
|
|
{
|
||
|
|
_sb = new StringBuilder(); _t0 = Time.unscaledTime;
|
||
|
|
_sb.AppendLine("# WL811b Watch " + DateTime.Now.ToString("HH:mm:ss") + " " + seconds.ToString("F1") + " s · Enabled=" + WLCombatCoreSettings.Enabled + " arbiter=" + TimeScaleArbiter.Enabled + " coreSubscribed=" + WL.Feel.WLHitFeel.CoreSubscribed + " pc=" + N(MyValue.MyPC));
|
||
|
|
_sb.AppendLine("# 열: t(ms unscaled) | frame | timeScale | 이벤트 페이로드");
|
||
|
|
int fired0 = WL.Feel.WLHitFeel.FiredCount, hit0 = WL.Feel.WLHitFeel.HitCount, applied0 = TimeScaleArbiter.AppliedCount;
|
||
|
|
float lastScale = Time.timeScale;
|
||
|
|
Subscribe(true);
|
||
|
|
while (Time.unscaledTime - _t0 < seconds)
|
||
|
|
{
|
||
|
|
yield return null;
|
||
|
|
if (!Mathf.Approximately(Time.timeScale, lastScale)) { _sb.AppendLine(Pre() + "timeScale " + lastScale.ToString("F2") + " → " + Time.timeScale.ToString("F2") + " (arbiter " + TimeScaleArbiter.Active + ")"); lastScale = Time.timeScale; }
|
||
|
|
}
|
||
|
|
Subscribe(false);
|
||
|
|
_sb.AppendLine("# 요약: TotalRaised=" + CombatEvents.TotalRaised + " feel hit+" + (WL.Feel.WLHitFeel.HitCount - hit0) + " fired+" + (WL.Feel.WLHitFeel.FiredCount - fired0) + " arbiterApplied+" + (TimeScaleArbiter.AppliedCount - applied0)
|
||
|
|
+ " lastHitStop=" + WL.Feel.WLHitFeel.LastHitStopMeasured.ToString("F3") + " s · timeScale=" + Time.timeScale.ToString("F2"));
|
||
|
|
Save("watch", _sb); Destroy(gameObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>ⓔ 중재자: 외부 일시정지 스킵 · 상위 층 스킵 · 스로틀 · 복원. timeScale 은 끝에 1 로 되돌린다.</summary>
|
||
|
|
public IEnumerator Co_Arbiter()
|
||
|
|
{
|
||
|
|
_sb = new StringBuilder(); _t0 = Time.unscaledTime;
|
||
|
|
_sb.AppendLine("# WL811b Arbiter " + DateTime.Now.ToString("HH:mm:ss") + " Enabled=" + TimeScaleArbiter.Enabled);
|
||
|
|
var feel = WL.Feel.WLHitFeel.Settings;
|
||
|
|
float hs = feel != null ? feel.hitStopDuration : 0.04f;
|
||
|
|
float hsScale = feel != null ? feel.hitStopTimeScale : 0f;
|
||
|
|
TimeScaleArbiter.ResetDiagnostics();
|
||
|
|
float saved = Time.timeScale;
|
||
|
|
|
||
|
|
// ① 외부 일시정지(0) 중 히트스톱 요청 → SkippedPause
|
||
|
|
Time.timeScale = 0f; yield return null;
|
||
|
|
var r1 = TimeScaleArbiter.Request(TimeLayer.HitStop, hsScale, hs);
|
||
|
|
_sb.AppendLine(Pre() + "① pause(0) 중 HitStop 요청 → " + r1 + " (" + TimeScaleArbiter.LastInfo + ") 기대=SkippedPause");
|
||
|
|
Time.timeScale = 1f; yield return null;
|
||
|
|
|
||
|
|
// ② Cinematic 활성 중 HitStop → SkippedHigher · Cinematic 종료 후 1 복원
|
||
|
|
var r2a = TimeScaleArbiter.Request(TimeLayer.Cinematic, WLCombatCoreSettings.Instance.cinematicDefaultScale, 0.3f);
|
||
|
|
yield return null;
|
||
|
|
var r2b = TimeScaleArbiter.Request(TimeLayer.HitStop, hsScale, hs);
|
||
|
|
_sb.AppendLine(Pre() + "② Cinematic → " + r2a + " · 그 중 HitStop → " + r2b + " 기대=SkippedHigher · timeScale=" + Time.timeScale.ToString("F2"));
|
||
|
|
float w0 = Time.unscaledTime; while (Time.unscaledTime - w0 < 0.5f) yield return null;
|
||
|
|
_sb.AppendLine(Pre() + " 0.5 s 후 timeScale=" + Time.timeScale.ToString("F2") + " (기대 1.00) measured=" + TimeScaleArbiter.LastMeasured.ToString("F3") + " active=" + TimeScaleArbiter.Active);
|
||
|
|
|
||
|
|
// ③ HitStop 활성 중 Cinematic → 선점(Preempted) · 종료 후 복원
|
||
|
|
var r3a = TimeScaleArbiter.Request(TimeLayer.HitStop, hsScale, 0.3f);
|
||
|
|
yield return null;
|
||
|
|
var r3b = TimeScaleArbiter.Request(TimeLayer.Cinematic, 0.5f, 0.2f);
|
||
|
|
_sb.AppendLine(Pre() + "③ HitStop → " + r3a + " · 그 중 Cinematic → " + r3b + " 기대=Applied(preempt) preempted=" + TimeScaleArbiter.PreemptedCount + " timeScale=" + Time.timeScale.ToString("F2"));
|
||
|
|
w0 = Time.unscaledTime; while (Time.unscaledTime - w0 < 0.4f) yield return null;
|
||
|
|
_sb.AppendLine(Pre() + " 0.4 s 후 timeScale=" + Time.timeScale.ToString("F2") + " (기대 1.00) active=" + TimeScaleArbiter.Active);
|
||
|
|
|
||
|
|
// ④ 스로틀: 0.5 s 안에 HitStop 5회 요청 → Applied 1 · Throttled/Ignored 나머지 (1.5/s → 최소 간격 0.667 s)
|
||
|
|
int applied0 = TimeScaleArbiter.AppliedCount, thr0 = TimeScaleArbiter.ThrottledCount, ign0 = TimeScaleArbiter.IgnoredCount;
|
||
|
|
w0 = Time.unscaledTime;
|
||
|
|
for (int i = 0; i < 5; i++) { TimeScaleArbiter.Request(TimeLayer.HitStop, hsScale, 0.03f); float t = Time.unscaledTime; while (Time.unscaledTime - t < 0.1f) yield return null; }
|
||
|
|
_sb.AppendLine(Pre() + "④ 0.5 s 안 HitStop 5회 → applied+" + (TimeScaleArbiter.AppliedCount - applied0) + " throttled+" + (TimeScaleArbiter.ThrottledCount - thr0) + " ignored+" + (TimeScaleArbiter.IgnoredCount - ign0) + " 기대 applied 1 · 초당 ≤ 1.5");
|
||
|
|
w0 = Time.unscaledTime; while (Time.unscaledTime - w0 < 0.3f) yield return null;
|
||
|
|
|
||
|
|
// ⑤ 실측 길이: HitStop 1회 → measured ≈ hitStopDuration
|
||
|
|
w0 = Time.unscaledTime; while (Time.unscaledTime - w0 < 0.7f) yield return null; // 스로틀 간격 확보
|
||
|
|
var r5 = TimeScaleArbiter.Request(TimeLayer.HitStop, hsScale, hs);
|
||
|
|
w0 = Time.unscaledTime; while (Time.unscaledTime - w0 < hs + 0.1f) yield return null;
|
||
|
|
_sb.AppendLine(Pre() + "⑤ HitStop " + hs.ToString("F3") + " s → " + r5 + " measured=" + TimeScaleArbiter.LastMeasured.ToString("F3") + " (기대 " + hs.ToString("F3") + " ±1프레임) feel.LastHitStopMeasured=" + WL.Feel.WLHitFeel.LastHitStopMeasured.ToString("F3") + " timeScale=" + Time.timeScale.ToString("F2"));
|
||
|
|
|
||
|
|
TimeScaleArbiter.ReleaseAll();
|
||
|
|
Time.timeScale = saved > 0f ? saved : 1f;
|
||
|
|
_sb.AppendLine("# 종료 timeScale=" + Time.timeScale.ToString("F2") + " active=" + TimeScaleArbiter.Active);
|
||
|
|
Save("arbiter", _sb); Destroy(gameObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>ⓕ 코어 런타임 스위치: off 동안 디스패치 0 · 타격감은 기존 경로로 계속 → 끝에 원복.</summary>
|
||
|
|
public IEnumerator Co_Toggle(bool on, float seconds)
|
||
|
|
{
|
||
|
|
_sb = new StringBuilder(); _t0 = Time.unscaledTime;
|
||
|
|
bool prev = WLCombatCoreSettings.RuntimeDisabled;
|
||
|
|
WLCombatCoreSettings.RuntimeDisabled = !on;
|
||
|
|
int raised0 = CombatEvents.TotalRaised, fired0 = WL.Feel.WLHitFeel.FiredCount, hit0 = WL.Feel.WLHitFeel.HitCount;
|
||
|
|
float minScale = 1f;
|
||
|
|
_sb.AppendLine("# WL811b Toggle on=" + on + " " + seconds.ToString("F1") + " s · Enabled=" + WLCombatCoreSettings.Enabled + " arbiter=" + TimeScaleArbiter.Enabled);
|
||
|
|
while (Time.unscaledTime - _t0 < seconds) { yield return null; minScale = Mathf.Min(minScale, Time.timeScale); }
|
||
|
|
_sb.AppendLine("결과: TotalRaised+" + (CombatEvents.TotalRaised - raised0) + " (기대 " + (on ? ">0" : "0") + ") · feel hit+" + (WL.Feel.WLHitFeel.HitCount - hit0) + " fired+" + (WL.Feel.WLHitFeel.FiredCount - fired0)
|
||
|
|
+ " · 최소 timeScale=" + minScale.ToString("F2") + "(히트스톱 발생 여부) · lastHitStop=" + WL.Feel.WLHitFeel.LastHitStopMeasured.ToString("F3"));
|
||
|
|
WLCombatCoreSettings.RuntimeDisabled = prev;
|
||
|
|
_sb.AppendLine("# 원복 RuntimeDisabled=" + WLCombatCoreSettings.RuntimeDisabled + " Enabled=" + WLCombatCoreSettings.Enabled);
|
||
|
|
Save("toggle", _sb); Destroy(gameObject);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|