diff --git a/AgentScripts/WL813c_Apply.cs b/AgentScripts/WL813c_Apply.cs
new file mode 100644
index 000000000..a2e31d6b7
--- /dev/null
+++ b/AgentScripts/WL813c_Apply.cs
@@ -0,0 +1,226 @@
+// WL813c_Apply.cs — #813 바퀴 2: 전투 패드 4슬롯(우하단 엄지 부채꼴) + 상단 보스 HP 바 (에디트 모드 · Play 불필요)
+// unity command run_script --file AgentScripts/WL813c_Apply.cs --entry WL813c_Apply.CreateSettings
+// unity command run_script --file AgentScripts/WL813c_Apply.cs --entry WL813c_Apply.Resave (무변경 재저장 — 재직렬화 diff 측정 · 802b 교훈)
+// unity command run_script --file AgentScripts/WL813c_Apply.cs --entry WL813c_Apply.Apply
+// unity command run_script --file AgentScripts/WL813c_Apply.cs --entry WL813c_Apply.Revert (추가한 컴포넌트·노드 제거 = 원상)
+//
+// 방식(발주서 ⓓ "NewGameUI.prefab 변경 최소 · 노드 추가만"):
+// ① IngameUIs/BattleUI (BattleUI.prefab 인스턴스) 에 **인스턴스 전용 추가 컴포넌트**로
+// WL.UI.SafeAreaFitter(802b 방식 · 전용 루트 · 스트레치 · offset 0 · 재부모화 0) + WL.UI.WLBattlePadLayout 을 붙인다.
+// → BattleUI.prefab 원본은 손대지 않으므로 SkillUI/SkillEquipUI 의 다른 인스턴스는 회귀 0.
+// ② IngameUIs/WL_HUD (이미 SafeAreaFitter 부착 = Safe Area 레이어) 아래에 WL_BossHpBar 노드 1개를 추가한다.
+// ③ 슬롯 좌표는 프리팹에 굽지 않는다 — 런타임 WLBattlePadLayout 이 에셋 값으로 배치한다(코드 상수 0 · diff 최소).
+// 롤백: Revert() 또는 git checkout -- Assets/Res_Addr/MainUI/NewGameUI.prefab
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using UnityEditor;
+using UnityEngine;
+
+public static class WL813c_Apply
+{
+ public const string NewGameUIPath = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
+ public const string SettingsDir = "Assets/WL/UI/Settings/Resources/WL";
+ public const string SettingsPath = SettingsDir + "/WLHudLayoutSettings.asset";
+ public const string BossBarName = "WL_BossHpBar";
+ public const string PadPath = "IngameUIs/BattleUI";
+ public const string HudPath = "IngameUIs/WL_HUD";
+ const string TmpFontGuid = "a387e325271126742ad15569dba1ac57"; // SkillCard/t_cooltime 이 쓰는 폰트(실측)
+ const int UILayer = 5;
+
+ static Transform FindByPath(Transform root, string path)
+ {
+ var cur = root;
+ foreach (var part in path.Split('/'))
+ {
+ Transform next = null;
+ for (int i = 0; i < cur.childCount; i++)
+ if (cur.GetChild(i).name == part) { next = cur.GetChild(i); break; }
+ if (next == null) return null;
+ cur = next;
+ }
+ return cur;
+ }
+
+ static void EnsureFolder(string dir)
+ {
+ var parts = dir.Split('/');
+ var cur = parts[0];
+ for (int i = 1; i < parts.Length; i++)
+ {
+ var next = cur + "/" + parts[i];
+ if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(cur, parts[i]);
+ cur = next;
+ }
+ }
+
+ /// WLHudLayoutSettings.asset 생성(이미 있으면 값 유지 · 덮어쓰지 않는다).
+ public static object CreateSettings()
+ {
+ var sb = new StringBuilder();
+ var existing = AssetDatabase.LoadAssetAtPath(SettingsPath);
+ if (existing != null) { sb.AppendLine(SettingsPath + " : 이미 있음 — 값 유지"); }
+ else
+ {
+ EnsureFolder(SettingsDir);
+ var so = ScriptableObject.CreateInstance(); // 기본값 = 기준서 §B/§F-1 제안값
+ AssetDatabase.CreateAsset(so, SettingsPath);
+ AssetDatabase.SaveAssets();
+ AssetDatabase.Refresh();
+ sb.AppendLine(SettingsPath + " : 생성");
+ }
+ WL.UI.WLHudLayoutSettings.ClearCache();
+ var s = WL.UI.WLHudLayoutSettings.Instance;
+ if (s == null) { sb.AppendLine("🔴 Resources 로드 실패 — Resources/WL 경로 확인"); return sb.ToString(); }
+
+ sb.AppendLine("Resources 로드 OK · design=" + s.designWidthPx + "x" + s.designHeightPx +
+ " activeSlots=" + s.activeSkillSlots + " slotDia=" + s.slotDiameterPx +
+ " fanR=" + s.fanRadiusPx + " start=" + s.fanStartAngleDeg + " step=" + s.fanStepAngleDeg);
+ // 기하 검증 — 모서리 기준 좌표 · 이웃 여백 · 최대 도달 반경 (기준서 §B 요소3/7)
+ var pts = new List();
+ var dia = new List();
+ for (int i = 0; i < s.activeSkillSlots; i++) { pts.Add(s.FanSlotPx(i)); dia.Add(s.slotDiameterPx); }
+ for (int i = 0; i < 6 - s.activeSkillSlots; i++) { pts.Add(s.ReserveSlotPx(i)); dia.Add(s.reserveDiameterPx); }
+ pts.Add(s.attackCenterPx); dia.Add(s.attackDiameterPx);
+ pts.Add(s.autoButtonCenterPx); dia.Add(s.autoButtonDiameterPx);
+ float minGap = float.MaxValue, maxReach = 0f, minEdgeX = float.MaxValue, minEdgeY = float.MaxValue, maxTopPx = 0f;
+ for (int i = 0; i < pts.Count; i++)
+ {
+ maxReach = Mathf.Max(maxReach, pts[i].magnitude + dia[i] * 0.5f);
+ minEdgeX = Mathf.Min(minEdgeX, pts[i].x - dia[i] * 0.5f);
+ minEdgeY = Mathf.Min(minEdgeY, pts[i].y - dia[i] * 0.5f);
+ maxTopPx = Mathf.Max(maxTopPx, pts[i].y + dia[i] * 0.5f);
+ for (int j = i + 1; j < pts.Count; j++)
+ minGap = Mathf.Min(minGap, Vector2.Distance(pts[i], pts[j]) - (dia[i] + dia[j]) * 0.5f);
+ sb.AppendLine(" pt" + i + " px(dx=" + pts[i].x.ToString("F1") + ", dy=" + pts[i].y.ToString("F1") +
+ ", d=" + dia[i].ToString("F1") + ") reach=" + (pts[i].magnitude + dia[i] * 0.5f).ToString("F1"));
+ }
+ sb.AppendLine(" 검증 minGapPx=" + minGap.ToString("F1") + "(기준 ≥" + s.slotMinGapPx + ")" +
+ " maxReachPx=" + maxReach.ToString("F1") + "(목표 ≤" + s.thumbReachRadiusPx + ")" +
+ " 화면밖여유 dx=" + minEdgeX.ToString("F1") + " dy=" + minEdgeY.ToString("F1"));
+ float bottom40 = s.designHeightPx * 0.4f; // 기준서 §F-1 "하단 40 %" — 설정 값에서 파생(상수 0)
+ sb.AppendLine(" 하단40% 경계=" + bottom40.ToString("F0") + "px · 최상단 버튼 dy+반지름=" +
+ maxTopPx.ToString("F1") + " → 안=" + (maxTopPx <= bottom40));
+ return sb.ToString();
+ }
+
+ /// 무변경 재저장 — LoadPrefabContents→SaveAsPrefabAsset 이 diff 를 만드는지 먼저 잰다(802b 교훈).
+ public static object Resave()
+ {
+ var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
+ try { PrefabUtility.SaveAsPrefabAsset(root, NewGameUIPath); }
+ finally { PrefabUtility.UnloadPrefabContents(root); }
+ AssetDatabase.Refresh();
+ return NewGameUIPath + " : 무변경 재저장 (git diff 로 재직렬화 여부 확인)";
+ }
+
+ public static object Apply()
+ {
+ var sb = new StringBuilder();
+ var settings = AssetDatabase.LoadAssetAtPath(SettingsPath);
+ if (settings == null) return "🔴 " + SettingsPath + " 없음 — CreateSettings 먼저";
+
+ TMPro.TMP_FontAsset font = null;
+ var fontPath = AssetDatabase.GUIDToAssetPath(TmpFontGuid);
+ if (!string.IsNullOrEmpty(fontPath)) font = AssetDatabase.LoadAssetAtPath(fontPath);
+ sb.AppendLine("폰트: " + (font != null ? fontPath : "「미확인」 — 이름 라벨 폰트 미주입"));
+
+ var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
+ try
+ {
+ // ── ① 전투 패드 루트 = IngameUIs/BattleUI (인스턴스 전용 추가 컴포넌트) ──
+ var pad = FindByPath(root.transform, PadPath);
+ if (pad == null) { sb.AppendLine("🔴 " + PadPath + " 없음"); }
+ else
+ {
+ var prt = (RectTransform)pad;
+ if (pad.GetComponent() == null)
+ { pad.gameObject.AddComponent(); sb.AppendLine(PadPath + " : SafeAreaFitter 추가"); }
+ else sb.AppendLine(PadPath + " : SafeAreaFitter 이미 있음");
+
+ var layout = pad.GetComponent();
+ if (layout == null) { layout = pad.gameObject.AddComponent(); sb.AppendLine(PadPath + " : WLBattlePadLayout 추가"); }
+ else sb.AppendLine(PadPath + " : WLBattlePadLayout 이미 있음");
+
+ var so = new SerializedObject(layout);
+ so.FindProperty("target").objectReferenceValue = pad.GetComponent();
+ so.ApplyModifiedPropertiesWithoutUndo();
+
+ // Safe Area 패널 규격(802b 방식): 스트레치 · offset 0 · pivot 중앙 · 재부모화 0
+ sb.AppendLine(" before " + Describe(prt));
+ prt.anchorMin = Vector2.zero; prt.anchorMax = Vector2.one;
+ prt.pivot = new Vector2(0.5f, 0.5f);
+ prt.offsetMin = Vector2.zero; prt.offsetMax = Vector2.zero;
+ prt.localScale = Vector3.one; prt.localRotation = Quaternion.identity;
+ sb.AppendLine(" after " + Describe(prt));
+ }
+
+ // ── ② 상단 보스 HP 바 = IngameUIs/WL_HUD/WL_BossHpBar (기존 Safe Area 레이어 아래) ──
+ var hud = FindByPath(root.transform, HudPath);
+ if (hud == null) { sb.AppendLine("🔴 " + HudPath + " 없음"); }
+ else
+ {
+ var barT = hud.Find(BossBarName);
+ if (barT == null)
+ {
+ var go = new GameObject(BossBarName, typeof(RectTransform));
+ go.layer = UILayer;
+ barT = go.transform;
+ ((RectTransform)barT).SetParent((RectTransform)hud, false);
+ sb.AppendLine(HudPath + "/" + BossBarName + " : 노드 추가");
+ }
+ else sb.AppendLine(HudPath + "/" + BossBarName + " : 이미 있음");
+
+ var bar = barT.GetComponent();
+ if (bar == null) { bar = barT.gameObject.AddComponent(); sb.AppendLine(" BossHpBar 추가"); }
+ if (font != null) bar.SetNameFont(font);
+ bool made = bar.BuildIfNeeded();
+ sb.AppendLine(" BuildIfNeeded made=" + made + " · " + bar.ApplyLayout());
+ // 저장 상태의 기본은 **미표시** — 보스 Spawned 이벤트가 와야 보인다(발주서 §1-2 "이벤트 미도착 = 미표시").
+ var cg = barT.GetComponent();
+ if (cg != null) { cg.alpha = 0f; cg.interactable = false; cg.blocksRaycasts = false; sb.AppendLine(" CanvasGroup alpha=0 (기본 미표시)"); }
+ foreach (Transform c in barT) sb.AppendLine(" child " + c.name + " " + Describe(c as RectTransform));
+ }
+
+ PrefabUtility.SaveAsPrefabAsset(root, NewGameUIPath);
+ }
+ finally { PrefabUtility.UnloadPrefabContents(root); }
+
+ AssetDatabase.SaveAssets();
+ AssetDatabase.Refresh();
+ return sb.ToString();
+ }
+
+ public static object Revert()
+ {
+ var sb = new StringBuilder();
+ var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
+ try
+ {
+ var pad = FindByPath(root.transform, PadPath);
+ if (pad != null)
+ {
+ var l = pad.GetComponent();
+ if (l != null) { Object.DestroyImmediate(l, true); sb.AppendLine(PadPath + " : WLBattlePadLayout 제거"); }
+ var f = pad.GetComponent();
+ if (f != null) { Object.DestroyImmediate(f, true); sb.AppendLine(PadPath + " : SafeAreaFitter 제거"); }
+ sb.AppendLine(" ⚠ 루트 RectTransform 값은 되돌리지 않는다 — git checkout -- " + NewGameUIPath + " 를 쓸 것");
+ }
+ var hud = FindByPath(root.transform, HudPath);
+ var bar = hud != null ? hud.Find(BossBarName) : null;
+ if (bar != null) { Object.DestroyImmediate(bar.gameObject); sb.AppendLine(HudPath + "/" + BossBarName + " : 제거"); }
+ PrefabUtility.SaveAsPrefabAsset(root, NewGameUIPath);
+ }
+ finally { PrefabUtility.UnloadPrefabContents(root); }
+ AssetDatabase.SaveAssets();
+ AssetDatabase.Refresh();
+ return sb.ToString();
+ }
+
+ static string Describe(RectTransform rt)
+ {
+ if (rt == null) return "(rt 없음)";
+ return "aMin=" + rt.anchorMin + " aMax=" + rt.anchorMax + " offMin=" + rt.offsetMin + " offMax=" + rt.offsetMax +
+ " size=" + rt.sizeDelta + " pos=" + rt.anchoredPosition + " pivot=" + rt.pivot + " scale=" + rt.localScale;
+ }
+}
diff --git a/AgentScripts/WL813c_Probe.cs b/AgentScripts/WL813c_Probe.cs
new file mode 100644
index 000000000..a12e25750
--- /dev/null
+++ b/AgentScripts/WL813c_Probe.cs
@@ -0,0 +1,248 @@
+// WL813c_Probe.cs — #813 전투 패드 · 보스 HP 바 실측 덤프 (에디트 모드 · Play 불필요 · 저장하지 않는다)
+// unity command run_script --file AgentScripts/WL813c_Probe.cs --entry WL813c_Probe.DumpPad (발주서 ⓑ · 부채꼴 좌표 + 미러 2회)
+// unity command run_script --file AgentScripts/WL813c_Probe.cs --entry WL813c_Probe.DumpBossEvents (발주서 ⓒ · 가짜 이벤트 → 바 갱신)
+// unity command run_script --file AgentScripts/WL813c_Probe.cs --entry WL813c_Probe.DumpPrefab (발주서 ⓓ · 저장된 프리팹 노드 상태)
+// unity command run_script --file AgentScripts/WL813c_Probe.cs --entry WL813c_Probe.DumpOverride (발주서 ⓓ · WLIngameUiOverride 대상 경로 회귀)
+// 덤프는 Logs/WL813c_probe_*.txt 에도 남긴다(콘솔 잘림 대비).
+using System.IO;
+using System.Text;
+using UnityEditor;
+using UnityEditor.SceneManagement;
+using UnityEngine;
+using UnityEngine.UI;
+
+public static class WL813c_Probe
+{
+ // run_script 는 파일마다 독립 어셈블리로 컴파일된다 — WL813c_Apply 의 상수를 참조할 수 없어 여기 다시 둔다(값 동일).
+ public const string NewGameUIPath = "Assets/Res_Addr/MainUI/NewGameUI.prefab";
+ public const string PadPath = "IngameUIs/BattleUI";
+ public const string HudPath = "IngameUIs/WL_HUD";
+ public const string BossBarName = "WL_BossHpBar";
+ const string LogDir = "Logs";
+
+ static string Save(string name, string body)
+ {
+ Directory.CreateDirectory(LogDir);
+ var path = Path.Combine(LogDir, "WL813c_probe_" + name + ".txt");
+ File.WriteAllText(path, body, new System.Text.UTF8Encoding(false));
+ return body + "\n(저장: " + path + ")";
+ }
+
+ static Transform FindByPath(Transform root, string path)
+ {
+ var cur = root;
+ foreach (var part in path.Split('/'))
+ {
+ Transform next = null;
+ for (int i = 0; i < cur.childCount; i++)
+ if (cur.GetChild(i).name == part) { next = cur.GetChild(i); break; }
+ if (next == null) return null;
+ cur = next;
+ }
+ return cur;
+ }
+
+ /// 발주서 ⓑ — 실제 NewGameUI 프리팹 계층에서 배치를 적용하고 좌표를 덤프한다(저장 안 함 · 미러 2회).
+ public static object DumpPad()
+ {
+ var sb = new StringBuilder();
+ var s = WL.UI.WLHudLayoutSettings.Instance;
+ if (s == null) return "🔴 WLHudLayoutSettings 없음";
+ bool origMirror = s.mirrorLeftHanded;
+
+ var root = PrefabUtility.LoadPrefabContents(NewGameUIPath);
+ try
+ {
+ var pad = FindByPath(root.transform, PadPath);
+ if (pad == null) return "🔴 " + PadPath + " 없음";
+ var layout = pad.GetComponent();
+ if (layout == null) return "🔴 WLBattlePadLayout 없음 — Apply 먼저";
+
+ var canvas = pad.GetComponentInParent