[WL-811a] 전투 액션 코어 프로브 (#811)
WL811a_Trace.cs — 전투 파이프라인 런타임 이벤트 순서·타이밍 트레이스(Play 전용 · 읽기 전용 · 원본/WL 코드 무수정). 설계안 = NerdNavisAi 공유/WL_병렬작업/reports/WL-811a_전투액션코어_설계안.md (A 실측 지도 · B 코어 설계 · C 웨이브 분해 · D PD 결정 표). 컴파일 게이트 oneshot PASS(재시도 1회 · 첫 오픈 재임포트로 1차 900 s 타임아웃). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
abcd038656
commit
8060e1922f
|
|
@ -0,0 +1,178 @@
|
|||
// WL811a_Trace.cs — #811a 전투 파이프라인 런타임 이벤트 순서·타이밍 트레이스 (Play 전용 · 읽기 전용 · 에셋/게임 상태 변경 0)
|
||||
//
|
||||
// unity command run_script --file AgentScripts/WL811a_Trace.cs --entry WL811a_Trace.Run --args '[8]' // 8 s 동안 관찰 (자동 교전이 콤보를 시작하면 기록)
|
||||
// unity command run_script --file AgentScripts/WL811a_Trace.cs --entry WL811a_Trace.RunForce --args '[8]' // 3 s 안에 공격이 없으면 Play_Attack(0) 1회 호출
|
||||
// 결과: Screenshots_WL/WL811a/trace_<class>_<HHmmss>.txt + Console
|
||||
//
|
||||
// 재는 것 (완료 기준 ⓒ · 프레임 폴링이라 해상도 = 1 프레임):
|
||||
// · Animator 상태(클립명 Attack1_S/2_S/3_S) 진입·이탈 시각(unscaled ms)·normalizedTime → 콤보 단계
|
||||
// · WeaponTrailDriver.IsSwinging 상승 에지 → ShowEffect 이벤트(검기 창 시작)
|
||||
// · WeaponTrailDriver.MappedSpawnCount 증가 → 검기 매핑 이펙트 스폰
|
||||
// · 활성 ProjectileBase 수 증가 → Projectile 이벤트 → Shoot_Projectile(히트박스 스폰)
|
||||
// · PC m_DamageRate 변화(리플렉션) → Hit(float) 이벤트
|
||||
// · 몹 HP 감소 / IsDead 전이 → Get_Damage → Cal_Damage / Set_Die
|
||||
// · WLHitFeel.HitCount / FiredCount 증가 · Time.timeScale 전이 → Actor.cs:535 훅 → 히트스톱
|
||||
// · DashDriver.DashCount 증가 → 대시
|
||||
// 원본·WL 코드를 수정하지 않는다. 몹 HP 는 public Get_HP() · 나머지는 public 정적 카운터 · m_DamageRate 만 리플렉션 읽기.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using WL.Combat;
|
||||
|
||||
public static class WL811a_Trace
|
||||
{
|
||||
const string OutDir = "Screenshots_WL/WL811a";
|
||||
|
||||
public static void Run(float seconds) { Launch(seconds, false); }
|
||||
public static void RunForce(float seconds) { Launch(seconds, true); }
|
||||
|
||||
static void Launch(float seconds, bool force)
|
||||
{
|
||||
var old = GameObject.Find("__wl811a_trace");
|
||||
if (old != null) UnityEngine.Object.DestroyImmediate(old);
|
||||
var go = new GameObject("__wl811a_trace");
|
||||
var h = go.AddComponent<Host>();
|
||||
h.StartCoroutine(h.Co_Trace(seconds <= 0f ? 8f : seconds, force));
|
||||
}
|
||||
|
||||
public class Host : MonoBehaviour
|
||||
{
|
||||
static readonly BindingFlags BF = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
static string ClipName(Animator a)
|
||||
{
|
||||
if (a == null) return "";
|
||||
var infos = a.GetCurrentAnimatorClipInfo(0);
|
||||
return infos.Length > 0 && infos[0].clip != null ? infos[0].clip.name : "";
|
||||
}
|
||||
|
||||
public IEnumerator Co_Trace(float seconds, bool force)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("# WL811a 전투 파이프라인 트레이스 " + DateTime.Now.ToString("HH:mm:ss") + " 관찰 " + seconds.ToString("F1") + " s force=" + force);
|
||||
|
||||
var me = MyValue.MyPC;
|
||||
if (me == null) { sb.AppendLine("!! MyValue.MyPC 없음 — Title→인게임 진입 후 실행"); Done(sb); yield break; }
|
||||
|
||||
var anim = me.m_animation;
|
||||
var fiRate = typeof(Actor).GetField("m_DamageRate", BF);
|
||||
var feelSt = WL.Feel.WLHitFeel.Settings;
|
||||
sb.AppendLine("class=" + me.Get_ID() + " feel=" + (feelSt != null ? (feelSt.enabled ? "on" : "off") : "null")
|
||||
+ " hitStop=" + (feelSt != null ? feelSt.hitStopDuration.ToString("F3") + "/" + feelSt.hitStopKillDuration.ToString("F3") : "-")
|
||||
+ " timeScale0=" + Time.timeScale.ToString("F2"));
|
||||
sb.AppendLine("# 열: t(ms unscaled) | clip | nt | 사건");
|
||||
|
||||
float t0 = Time.unscaledTime;
|
||||
string lastClip = ClipName(anim);
|
||||
bool lastSwing = false;
|
||||
int lastSpawn = WeaponTrailDriver.MappedSpawnCount;
|
||||
int lastHit = WL.Feel.WLHitFeel.HitCount, lastFired = WL.Feel.WLHitFeel.FiredCount;
|
||||
int lastDash = DashDriver.DashCount;
|
||||
float lastScale = Time.timeScale;
|
||||
float lastRate = fiRate != null ? (float)fiRate.GetValue(me) : -1f;
|
||||
int lastProj = CountProjectiles();
|
||||
var mobHp = new Dictionary<int, double>();
|
||||
var mobDead = new Dictionary<int, bool>();
|
||||
RefreshMobs(mobHp, mobDead);
|
||||
|
||||
int frame = 0, attackFrames = 0; bool sawAttack = false; float attackEndAt = -1f;
|
||||
while (Time.unscaledTime - t0 < seconds)
|
||||
{
|
||||
yield return null;
|
||||
frame++;
|
||||
float ms = (Time.unscaledTime - t0) * 1000f;
|
||||
string clip = ClipName(anim);
|
||||
float nt = anim != null ? anim.GetCurrentAnimatorStateInfo(0).normalizedTime % 1f : 0f;
|
||||
string pre = string.Format("{0,7:F0} | {1,-22} | {2,5:F3} | ", ms, clip, nt);
|
||||
|
||||
if (clip != lastClip) { sb.AppendLine(pre + "상태 전이 " + lastClip + " → " + clip + (anim != null && anim.IsInTransition(0) ? " (transition)" : "")); lastClip = clip; }
|
||||
bool isAttack = clip.Contains("Attack");
|
||||
if (isAttack) { sawAttack = true; attackFrames++; attackEndAt = -1f; }
|
||||
else if (sawAttack && attackEndAt < 0f) attackEndAt = Time.unscaledTime;
|
||||
|
||||
var drv = me.GetComponent<WeaponTrailDriver>();
|
||||
bool swing = drv != null && drv.IsSwinging;
|
||||
if (swing != lastSwing) { sb.AppendLine(pre + (swing ? "ShowEffect → 검기 창 시작 (IsSwinging=1)" : "검기 창 종료")); lastSwing = swing; }
|
||||
|
||||
int spawn = WeaponTrailDriver.MappedSpawnCount;
|
||||
if (spawn != lastSpawn) { sb.AppendLine(pre + "검기 매핑 이펙트 스폰 +" + (spawn - lastSpawn)); lastSpawn = spawn; }
|
||||
|
||||
if (fiRate != null)
|
||||
{
|
||||
float rate = (float)fiRate.GetValue(me);
|
||||
if (!Mathf.Approximately(rate, lastRate)) { sb.AppendLine(pre + "Hit(float) 이벤트 → m_DamageRate " + lastRate.ToString("F2") + " → " + rate.ToString("F2")); lastRate = rate; }
|
||||
}
|
||||
|
||||
int proj = CountProjectiles();
|
||||
if (proj > lastProj) sb.AppendLine(pre + "Projectile 이벤트 → 활성 ProjectileBase +" + (proj - lastProj) + " (총 " + proj + ")");
|
||||
lastProj = proj;
|
||||
|
||||
if (frame % 30 == 0) RefreshMobs(mobHp, mobDead);
|
||||
foreach (var m in UnityEngine.Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
|
||||
{
|
||||
int id = m.GetInstanceID();
|
||||
double hp; if (!mobHp.TryGetValue(id, out hp)) { mobHp[id] = m.Get_HP(); mobDead[id] = m.IsDead(); continue; }
|
||||
double cur = m.Get_HP();
|
||||
if (cur < hp) { sb.AppendLine(pre + "Get_Damage/Cal_Damage → " + m.name + " HP " + hp.ToString("F0") + " → " + cur.ToString("F0")); }
|
||||
mobHp[id] = cur;
|
||||
bool dead = m.IsDead();
|
||||
if (dead && !mobDead[id]) sb.AppendLine(pre + "Set_Die → " + m.name + " 사망");
|
||||
mobDead[id] = dead;
|
||||
}
|
||||
|
||||
int hit = WL.Feel.WLHitFeel.HitCount, fired = WL.Feel.WLHitFeel.FiredCount;
|
||||
if (hit != lastHit || fired != lastFired) { sb.AppendLine(pre + "WLHitFeel.OnHit (Actor.cs:535) hit+" + (hit - lastHit) + " fired+" + (fired - lastFired)); lastHit = hit; lastFired = fired; }
|
||||
|
||||
if (!Mathf.Approximately(Time.timeScale, lastScale)) { sb.AppendLine(pre + "timeScale " + lastScale.ToString("F2") + " → " + Time.timeScale.ToString("F2") + (Time.timeScale < 0.1f ? " (히트스톱 시작)" : " (복원)")); lastScale = Time.timeScale; }
|
||||
|
||||
int dash = DashDriver.DashCount;
|
||||
if (dash != lastDash) { sb.AppendLine(pre + "DashDriver 대시 +" + (dash - lastDash)); lastDash = dash; }
|
||||
|
||||
if (force && !sawAttack && Time.unscaledTime - t0 > 3f)
|
||||
{
|
||||
force = false;
|
||||
float aspd = (float)me.Get_StatInfo().Get_Stat(eStat.FinalAttackSpeed);
|
||||
sb.AppendLine(pre + "!! 3 s 동안 공격 없음 → Play_Attack(0, " + aspd.ToString("F2") + ") 강제 1회");
|
||||
me.Play_Attack(0, aspd);
|
||||
}
|
||||
// 콤보가 끝난 뒤 0.6 s 여유를 두고 종료 (히트스톱 복원까지 포함)
|
||||
if (sawAttack && attackEndAt > 0f && Time.unscaledTime - attackEndAt > 0.6f) break;
|
||||
}
|
||||
sb.AppendLine("# 프레임 " + frame + " · 공격 상태 프레임 " + attackFrames + " · 공격 관측=" + sawAttack + " · timeScale 종료값 " + Time.timeScale.ToString("F2"));
|
||||
Done(sb);
|
||||
}
|
||||
|
||||
static int CountProjectiles()
|
||||
{
|
||||
return UnityEngine.Object.FindObjectsByType<ProjectileBase>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).Length;
|
||||
}
|
||||
|
||||
static void RefreshMobs(Dictionary<int, double> hp, Dictionary<int, bool> dead)
|
||||
{
|
||||
foreach (var m in UnityEngine.Object.FindObjectsByType<MobActor>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
|
||||
{
|
||||
int id = m.GetInstanceID();
|
||||
if (!hp.ContainsKey(id)) { hp[id] = m.Get_HP(); dead[id] = m.IsDead(); }
|
||||
}
|
||||
}
|
||||
|
||||
void Done(StringBuilder sb)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(OutDir);
|
||||
var me = MyValue.MyPC;
|
||||
string path = System.IO.Path.Combine(OutDir, "trace_" + (me != null ? me.Get_ID().ToString() : "none") + "_" + DateTime.Now.ToString("HHmmss") + ".txt");
|
||||
System.IO.File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false));
|
||||
Debug.Log("[WL811a] 저장 " + path);
|
||||
}
|
||||
catch (Exception e) { Debug.LogWarning("[WL811a] 저장 실패 " + e.Message); }
|
||||
Debug.Log(sb.ToString());
|
||||
UnityEngine.Object.Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue