diff --git a/Assets/Scripts/umamusume/Gallop/Live/StageAnimationPlayer.cs b/Assets/Scripts/umamusume/Gallop/Live/StageAnimationPlayer.cs
deleted file mode 100644
index 47fb9cd..0000000
--- a/Assets/Scripts/umamusume/Gallop/Live/StageAnimationPlayer.cs
+++ /dev/null
@@ -1,211 +0,0 @@
-using System.Collections.Generic;
-using UnityEngine;
-
-namespace Gallop.Live
-{
- ///
- /// 舞台自带 的播放。
- ///
- /// 为什么需要:live10149 的舞台 prefab 引用了 11 个脚本类,UmaViewer 里**只有
- /// StageController 存在**,另外 10 个(AnimationObjectController、MirrorBallProjector、
- /// MirrorReflection、CustomLensFlare、UnityLensFlareController、CustomProjector、
- /// LightProjection、WashLightController、BillboardController、ShaderParamController)
- /// 全部缺失,那些 MonoBehaviour 反序列化后脚本引用为空,永远不执行。
- ///
- /// 其中 AnimationObjectController 的缺席后果最直接:舞台上 283 个 Animation 组件
- /// **全部** `playAutomatically = true`,但**默认 clip(m_Animation)全部为空**。
- /// Unity 的 playAutomatically 播的是默认 clip,默认 clip 为空就什么都不播 ——
- /// 原版是靠那个脚本显式 Play("clip名") 的。于是整个舞台的动画一个都没动:
- /// 迪斯科灯球不转、wash 灯不扫、霓虹灯牌不动。
- ///
- /// **只处理唯一解的情形。** clip 数量分布:
- /// 1 个 clip → 4 个对象(mirrorball_flarelight、wash_ground_b/c/d),全是 `*_loop_000`,
- /// 没有歧义,直接播。
- /// 3 个 clip → `loop_000` / `up_000` / `loop_001`,是「过渡 + 两个循环态」的状态机,
- /// 选哪个、何时切换都没有依据。
- /// 5 个 clip → 272 个对象(占 96%,多半是观众席),同样未知。
- /// 后两类只打一次日志把 clip 名列出来,**不猜**。选择规则逆出来之后再补。
- ///
- /// 播放时机走 OnEnable 而不是初始化时一次性 Play:受时间轴控制的灯(名字含
- /// blinklight/_wash_/laser/spotlight3d)初始是 inactive 的,要等轨道把它们打开,
- /// 在 inactive 对象上调 Play() 不会生效。镜面球容器
- /// pfb_env_live10149_blinklight_mirrorball_flarelight 正好就是这种情况。
- ///
- /// ── 2026-08-07 收窄:wash 灯的扫动 clip 不再 free-run ──
- ///
- /// 「只有一个 clip」说明的是**这个物件只有一个状态**,不等于**这个状态一直在播** ——
- /// 后者正是缺失的 `AnimationObjectController` 负责决定的事,我把两者混为一谈了。
- ///
- /// 实测反例:`wash_ground_b/c/d` 的单 clip 是 `swing_*` 节点 90°→0°→90° 的往返扫动,
- /// 2.0 s 一个来回(12 盏完全同步)。放出来就是一排灯在疯狂摆头,而**原版 MV 里
- /// 地面 wash 灯是不扫的,只闪**。所以这三个不接管。
- ///
- /// 镜面球保留:它的 clip 是绕 Y 单调转到 −359°,是自明的常转 idle,原版里球也确实在转。
- /// (转速是否 1.0 倍仍无依据,见 CLAUDE.md。)
- ///
- /// ⚠ 这条排除是**看参考视频得出的观察**,不是从 bundle 数据推出来的。
- /// 如果哪个舞台的 wash 灯真的该扫,需要新的证据才能放开,别照着这行反推。
- ///
- public static class StageAnimationPlayer
- {
- ///
- /// 扫动类灯具的名字特征。命中的不接管 —— 见类注释。
- /// 和 的 `_wash_` 判据同源,那里也是按名字识别 wash 灯的。
- ///
- private static bool IsSweepRig(string name) => name.Contains("_wash_");
-
- public static void Setup(StageController stage)
- {
- if (stage == null) return;
-
- int single = 0, ambiguous = 0, empty = 0, sweepSkipped = 0;
- var loggedClipSets = new HashSet();
-
- foreach (var anim in stage.GetComponentsInChildren(true))
- {
- if (anim == null || !anim.playAutomatically) continue;
-
- if (IsSweepRig(anim.gameObject.name))
- {
- if (sweepSkipped++ == 0)
- Debug.Log($"[StageAnim] 跳过扫动类灯具(如 '{anim.gameObject.name}'):" +
- "单 clip 只说明它只有一个状态,不说明该一直播;原版地面 wash 灯不扫只闪。");
- continue;
- }
-
- var clips = new List();
- foreach (AnimationState st in anim)
- {
- if (st != null && st.clip != null) clips.Add(st.clip);
- }
-
- if (clips.Count == 0) { empty++; continue; }
-
- if (clips.Count == 1)
- {
- // 唯一解。注意**不要**设 anim.clip、也不要 Play():
- // playAutomatically 是 true,一旦有了默认 clip,Unity 会用自己的时钟
- // 自动播放,于是 live 暂停/拖动进度条时舞台照转。必须由时间轴驱动。
- anim.Stop();
- var driver = anim.gameObject.GetComponent();
- if (driver == null) driver = anim.gameObject.AddComponent();
- driver.Bind(anim, clips[0]);
- single++;
- // 打出时长:镜面球那条 clip 离线量到 2.0s 转满 360°(30 RPM),
- // 如果运行时 length 不是 2.0,说明转速问题出在 clip 解析而不是编排。
- Debug.Log($"[StageAnim] 接管 '{anim.gameObject.name}' ← {clips[0].name} " +
- $"length={clips[0].length:F3}s wrapMode={clips[0].wrapMode}");
- continue;
- }
-
- ambiguous++;
- var names = new List();
- foreach (var c in clips) names.Add(c.name);
- string key = string.Join(",", names);
- if (loggedClipSets.Add(key))
- {
- Debug.Log($"[StageAnim] '{anim.gameObject.name}' 有 {clips.Count} 个 clip," +
- $"选择规则未知,**未播放**:{key}");
- }
- }
-
- Debug.Log($"[StageAnim] 单 clip 已接管 {single} 个;扫动类跳过 {sweepSkipped} 个;" +
- $"多 clip 未处理 {ambiguous} 个;无 clip {empty} 个");
- }
- }
-
- ///
- /// 缺失脚本的运行时普查,当进度表用:每按原签名实现一个原版脚本,
- /// 「脚本丢失」就该下降对应的实例数(已验证 BillboardController 令 892→728,正好 −164)。
- ///
- /// 签名清单、各类实例数、以及「为什么建同名类就能收到字段」见 CLAUDE.md
- /// 「用原签名重建脚本」。一句话:Unity 按**类名 + namespace + 程序集名**解析 MonoScript,
- /// 而 `umamusume.asmdef` 的程序集名与 bundle 里写的完全一致 —— 那 892 个空槽位
- /// 只是没人写,不是读不到。(这条最初记反了,是本普查的结果自己推翻的:
- /// 存活的 `AssetHolder` 和 `StageController` 正是靠这个机制绑上的。)
- ///
- public static class StageMissingScriptCensus
- {
- public static void Dump(StageController stage)
- {
- if (stage == null) return;
-
- int slots = 0, missing = 0, alive = 0;
- var aliveTypes = new Dictionary();
-
- foreach (var tr in stage.GetComponentsInChildren(true))
- {
- var comps = tr.GetComponents();
- foreach (var c in comps)
- {
- // MonoBehaviour 槽位里脚本丢失时,元素为 null。
- if (c == null) { slots++; missing++; continue; }
- if (c is MonoBehaviour)
- {
- slots++; alive++;
- string n = c.GetType().Name;
- aliveTypes.TryGetValue(n, out int k);
- aliveTypes[n] = k + 1;
- }
- }
- }
-
- var parts = new List();
- foreach (var kv in aliveTypes) parts.Add($"{kv.Key}x{kv.Value}");
- Debug.Log($"[StageScripts] MonoBehaviour 槽位 {slots}:脚本丢失 {missing},存活 {alive}" +
- (parts.Count > 0 ? $"({string.Join(", ", parts)})" : "") +
- "。基线 892(live10149);每按签名实现一个类,丢失数应下降对应实例数。" +
- "已验证:BillboardController 令 892→728(-164)。");
- }
- }
-
- ///
- /// 用 live 时间轴的时间手动采样 clip。
- ///
- /// **不能用 Animation.Play()** —— 那跑的是 Unity 自己的时钟,结果是:live 暂停了舞台
- /// 照转、拖动进度条不跟随、播放速度和歌无关。舞台动画属于演出编排的一部分,
- /// 必须和 同源,
- /// 所以这里每帧设 AnimationState.time 再 Sample()。
- ///
- /// 时间取 currentLiveTime,暂停时它不前进,拖动时它跳变,两种情况都自动正确。
- ///
- public class StageAnimationAutoPlay : MonoBehaviour
- {
- private Animation _anim;
- private AnimationClip _clip;
- private string _clipName;
- private float _length;
- private bool _loop;
-
- public void Bind(Animation anim, AnimationClip clip)
- {
- _anim = anim;
- _clip = clip;
- _clipName = clip != null ? clip.name : null;
- _length = clip != null ? clip.length : 0f;
- // clip 自身的 wrapMode 决定循环与否;live10149 的 22 个 clip 里 16 个是 Loop。
- _loop = clip != null && (clip.wrapMode == WrapMode.Loop || clip.wrapMode == WrapMode.PingPong);
- }
-
- private void LateUpdate()
- {
- if (_anim == null || _clip == null || _length <= 0f) return;
-
- var ctrl = Director.instance != null ? Director.instance._liveTimelineControl : null;
- if (ctrl == null) return;
-
- float t = ctrl.currentLiveTime;
- // TODO: PingPong 按 Loop 处理,来回摆的那一半没实现(live10149 上没有 PingPong clip)。
- float sampleTime = _loop ? Mathf.Repeat(t, _length) : Mathf.Clamp(t, 0f, _length);
-
- var state = _anim[_clipName];
- if (state == null) return;
- state.enabled = true;
- state.weight = 1f;
- state.time = sampleTime;
- _anim.Sample();
- // 采样完就关掉,避免 Animation 自己再按 Unity 时钟推进一次。
- state.enabled = false;
- }
- }
-}
diff --git a/Assets/Scripts/umamusume/Gallop/Live/StageController.cs b/Assets/Scripts/umamusume/Gallop/Live/StageController.cs
index 0e06060..4bdfab0 100644
--- a/Assets/Scripts/umamusume/Gallop/Live/StageController.cs
+++ b/Assets/Scripts/umamusume/Gallop/Live/StageController.cs
@@ -110,8 +110,12 @@ namespace Gallop.Live
// 光柱/闪光类 shader 的混合状态被 bundle 材质里的 URP Lit 属性表压成了不透明,先修回来。
RestoreAuthoredBlendState();
- // 舞台自带 Animation 的播放(原版由缺失的 AnimationObjectController 负责)。
- StageAnimationPlayer.Setup(this);
+ // ⚠ 舞台自带的 283 个 Animation **有意不播**,不是漏了。
+ // 它们全部 playAutomatically=true 但默认 clip 为空,原版靠 AnimationObjectController
+ // 显式 Play("clip名");而那个类实测**零序列化字段**,纯行为,方法体拿不到 ——
+ // 「播哪个 / 何时播 / 多快」没有任何数据能还原。曾写过一版替代品,结果是
+ // wash 灯以 2 秒一个来回疯狂摆头、镜面球转速无从校验,遂整体撤除。
+ // 理由与复活条件见 LIVE_TRACKS.md「缺 ground truth,已禁用」。
// 缺失脚本普查:每按签名实现一个原版脚本,「脚本丢失」计数就该下降对应实例数。
StageMissingScriptCensus.Dump(this);
diff --git a/Assets/Scripts/umamusume/Gallop/Live/StageMissingScriptCensus.cs b/Assets/Scripts/umamusume/Gallop/Live/StageMissingScriptCensus.cs
new file mode 100644
index 0000000..b6f9aca
--- /dev/null
+++ b/Assets/Scripts/umamusume/Gallop/Live/StageMissingScriptCensus.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Gallop.Live
+{
+ ///
+ /// 缺失脚本的运行时普查,当进度表用:每按原签名实现一个原版脚本,
+ /// 「脚本丢失」就该下降对应的实例数(已验证 BillboardController 令 892→728,正好 −164)。
+ ///
+ /// 签名清单、各类实例数、以及「为什么建同名类就能收到字段」见 CLAUDE.md
+ /// 「用原签名重建脚本」。一句话:Unity 按**类名 + namespace + 程序集名**解析 MonoScript,
+ /// 而 `umamusume.asmdef` 的程序集名与 bundle 里写的完全一致 —— 那 892 个空槽位
+ /// 只是没人写,不是读不到。(这条最初记反了,是本普查的结果自己推翻的:
+ /// 存活的 `AssetHolder` 和 `StageController` 正是靠这个机制绑上的。)
+ ///
+ public static class StageMissingScriptCensus
+ {
+ public static void Dump(StageController stage)
+ {
+ if (stage == null) return;
+
+ int slots = 0, missing = 0, alive = 0;
+ var aliveTypes = new Dictionary();
+
+ foreach (var tr in stage.GetComponentsInChildren(true))
+ {
+ var comps = tr.GetComponents();
+ foreach (var c in comps)
+ {
+ // MonoBehaviour 槽位里脚本丢失时,元素为 null。
+ if (c == null) { slots++; missing++; continue; }
+ if (c is MonoBehaviour)
+ {
+ slots++; alive++;
+ string n = c.GetType().Name;
+ aliveTypes.TryGetValue(n, out int k);
+ aliveTypes[n] = k + 1;
+ }
+ }
+ }
+
+ var parts = new List();
+ foreach (var kv in aliveTypes) parts.Add($"{kv.Key}x{kv.Value}");
+ Debug.Log($"[StageScripts] MonoBehaviour 槽位 {slots}:脚本丢失 {missing},存活 {alive}" +
+ (parts.Count > 0 ? $"({string.Join(", ", parts)})" : "") +
+ "。基线 892(live10149);每按签名实现一个类,丢失数应下降对应实例数。" +
+ "已验证:BillboardController 令 892→728(-164)。");
+ }
+ }
+}
diff --git a/Assets/Scripts/umamusume/Gallop/Live/StageAnimationPlayer.cs.meta b/Assets/Scripts/umamusume/Gallop/Live/StageMissingScriptCensus.cs.meta
similarity index 100%
rename from Assets/Scripts/umamusume/Gallop/Live/StageAnimationPlayer.cs.meta
rename to Assets/Scripts/umamusume/Gallop/Live/StageMissingScriptCensus.cs.meta
diff --git a/CLAUDE.md b/CLAUDE.md
index 107f13b..45fb103 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -174,7 +174,7 @@ MonoBehaviour 实例脚本为空。**但这些脚本是可以「接管」的**
| namespace | 类 | live10149 实例数 | 状态 / 已 dump 到的字段 |
|---|---|---|---|
-| `Gallop.Live` | `AnimationObjectController` | 282 | ❌ **别建,建了也没用**:实测序列化字段 **一个都没有**(282 个实例全空)。重建只会让「脚本丢失」892→446,拿到 0 bit 新信息 —— 它是纯行为类,播哪个 clip / 何时播 / 多快全在拿不到的方法体里。动画播放已由 `StageAnimationPlayer` 顶上,见下 |
+| `Gallop.Live` | `AnimationObjectController` | 282 | ❌ **别建,建了也没用**:实测序列化字段 **一个都没有**(282 个实例全空)。重建只会让「脚本丢失」892→446,拿到 0 bit 新信息 —— 它是纯行为类,播哪个 clip / 何时播 / 多快全在拿不到的方法体里。顶替版已于 2026-08-07 撤除,舞台 Animation 现在**有意不播**,见下 |
| `Gallop.Live` | `UnityLensFlareController` | 205 | ❌ 只有 1 个字段 `_enableAngleDegree`(0),本体不在这里 |
| `Gallop.Live` | `BillboardController` | 164 | ✅ 已建(字段已读到,朝向行为待定)|
| `Gallop.Live` | `WashLightController` | 27 | ❌ 24 个字段全是具体数值 + 两张投影贴图(`_projectionTexture`/`_cameraProjectionTexture`),缺的是 URP 下的投影实现 |
@@ -219,17 +219,17 @@ MonoBehaviour 实例脚本为空。**但这些脚本是可以「接管」的**
而生效摄像机随 CameraSwitcher 每帧变 —— 切换后面片朝的是观众看不见的那台,
那台还在自己动,看上去就是无缘无故乱转。**凡是引用「当前摄像机」的舞台脚本都要注意这条。**
-**舞台自带 Animation 的播放**(`StageAnimationPlayer`,顶替缺失的 `AnimationObjectController`):
-283 个 `Animation` 组件全部 `playAutomatically = true` 但默认 clip 为空,Unity 因此什么都不播。
-播放不走 `Animation.Play()` —— 那用的是 Unity 自己的时钟,暂停和拖动进度条都会脱节;
-改为每帧按 `LiveTimelineControl.currentLiveTime` 设 `AnimationState.time` 再 `Sample()`,
-与演出时间同源。3 个 / 5 个 clip 的状态机选择规则没有依据,只打日志不猜。
+**舞台自带 Animation 有意不播**(283 个组件,2026-08-07 撤除了替代实现):
+它们全部 `playAutomatically = true` 但默认 clip 为空,原版靠 `AnimationObjectController`
+显式 `Play()`,而那个类**零序列化字段**,纯行为,方法体拿不到 —— 「播哪个 / 何时播 / 多快」
+没有任何数据能还原。写过一版 `StageAnimationPlayer` 顶替,策略是「只有一个 clip 就播」,
+错在把「这物件只有一个**状态**」当成「这个状态**一直在播**」(后者正是那个缺失的类每帧做的
+决定):wash 灯 2 秒一个来回疯狂摆头(原版不扫只闪),镜面球转速无从校验。已整体撤除。
+**完整理由与复活条件见 `LIVE_TRACKS.md` →「舞台自带 Animation 为什么不播」专条。**
-⚠ **「只有一个 clip」≠「这个 clip 一直在播」。** 前者说明这个物件只有一个**状态**,
-后者是 `AnimationObjectController` 每帧做的**决定**。最初的接管规则把两者混为一谈,
-结果 `wash_ground_b/c/d` 的扫动 clip(`swing_*` 节点 90°→0°→90°、2.0 s 一个来回、12 盏同步)
-被 free-run,一排地面 wash 灯疯狂摆头 —— 而原版 MV 里它们不扫、只闪。现已按 `_wash_` 跳过。
-镜面球保留(单调转到 −359°,自明的常转 idle)。
+⚠ 撤除时唯一保留下来的结论:**驱动舞台动画不要用 `Animation.Play()`**,
+那跑 Unity 自己的时钟,暂停/拖动进度条都会脱节;应每帧把 `AnimationState.time` 设成
+`LiveTimelineControl.currentLiveTime`(秒)再 `Sample()`。将来重做直接用这条。
### 🔎 舞台上「行为不对」的第一嫌疑:缺组件,不是解析错
@@ -255,13 +255,14 @@ MonoBehaviour 实例是空的**(基线 892,已补 `BillboardController` −1
推论零:**先 dump 一眼那个缺失组件有没有字段,再决定值不值得重建。**
`AnimationObjectController` 实测**零字段**(282 个实例全空),所以重建它拿不到任何信息,
-`StageAnimationPlayer` 那些时机问题不可能靠「把原类建出来」解决 —— 这个方向是死的,
+舞台动画的那些时机问题不可能靠「把原类建出来」解决 —— 这个方向是死的,
别再试。反过来 `CustomLensFlare` 字段齐全(还牵出 `LensFlareData`),就非常值得建。
判断只要一条命令,不要凭实例数多就以为收益大。
推论二:**顶替缺失组件时,要把「我在替谁做决定」写清楚**,并且区分
「从 bundle 数据推出来的」和「看参考视频观察到的」—— 后者是有效证据,但不是 ground truth,
-换个舞台可能就不成立(`StageAnimationPlayer.IsSweepRig` 的注释就是按这个写的)。
+换个舞台可能就不成立。(反面教材:舞台动画的 `_wash_` 排除规则就是这么来的,
+后来连同整个替代实现一起撤掉了 —— 观察能证伪一个实现,但支撑不起一个实现。)
反例也要记住:**并非所有舞台画面问题都是缺组件。** 光柱渲染成黑墙那次,
根因是材质被盖了 URP Lit 的属性表、`_DstBlend` 从 One 变成 Zero,
@@ -306,9 +307,9 @@ MonoBehaviour 实例是空的**(基线 892,已补 `BillboardController` −1
6 条 quaternion 曲线(`mirrorball_a_000` + `b_000..004`),7 个关键帧,
**恒定 −179.50°/s 绕 Y**(每个关键帧算出来都是这个数),2.0 s 一圈,wrapMode = Loop。
末帧 −359° 而不是 −360°,是为了循环接缝不重复一帧。
-- 这条 clip 正好是 `StageAnimationPlayer` 已接管的单-clip 唯一解之一,采样用的
- `currentLiveTime` 单位是秒(帧号 = ×60),所以**没有单位换算错误**,
- 我们放出来就是 clip 写的 2.0 s 一圈。
+- 曾接管过这条 clip,采样用的 `currentLiveTime` 单位是秒(帧号 = ×60),**没有单位换算错误**,
+ 放出来就是 clip 写的 2.0 s 一圈 —— 但实机仍然偏快,这正是下面第 2 条教训的由来。
+ 该实现已于 2026-08-07 撤除,球现在不转。
两条教训,方向相反,都要记:
diff --git a/LIVE_AUDIT_2026-08-05.md b/LIVE_AUDIT_2026-08-05.md
index b6e3be6..f7c364b 100644
--- a/LIVE_AUDIT_2026-08-05.md
+++ b/LIVE_AUDIT_2026-08-05.md
@@ -541,7 +541,7 @@ A5 一并做掉;剩 6(B 类残余:WashLight/Laser 的 `r.materials`)与
| 事项 | 结果 |
|---|---|
-| 舞台 Animation 全都不播 | 283 个 `Animation` 组件 `playAutomatically = true` 但默认 clip 为空,Unity 什么都不播;原版靠 `AnimationObjectController` 显式 `Play("clip名")`。已由 `StageAnimationPlayer` 顶上,**只接管单 clip 的唯一解情形**(4 个对象),3/5 个 clip 的状态机选择规则没依据,只打日志 |
+| 舞台 Animation 全都不播 | 283 个 `Animation` 组件 `playAutomatically = true` 但默认 clip 为空,Unity 什么都不播;原版靠 `AnimationObjectController` 显式 `Play("clip名")`。**曾写 `StageAnimationPlayer` 顶替,2026-08-07 撤除** —— 那个类实测零序列化字段,「播哪个/何时播/多快」无从还原,顶替版把 wash 灯播成 2 秒一个来回。现状是有意不播,理由见 `LIVE_TRACKS.md` 专条 |
| 按签名重建原版脚本 | 打通了。类名 + namespace + 程序集名对上就能让 Unity 真正反序列化字段 —— `BillboardController` 令 `[StageScripts]` 丢失数 892→728(正好 −164)。**但只拿得到数据,拿不到方法体**:`_rotationType` 的枚举语义随 IL2CPP 元数据一起没有 |
| 地板发白(C4) | 查明是 `mirror_a`,实现 `Gallop.MirrorReflection` 后解决。详见 C4 条目 —— 这条的过程比结论值钱 |
| BlinkLight 调色板槽位 | 槽位号 = `lightNNN_`/`alphaNNN_` 名字前缀,7 组实测无一例外。顺带修掉镜面球球体被 BlinkLight 涂成粉红的问题(`_MulColor0` 是 BgColor1 的通道,BlinkLight 排在它之后,抢了) |
diff --git a/LIVE_DEV_MAP.md b/LIVE_DEV_MAP.md
index 40b84ce..093690f 100644
--- a/LIVE_DEV_MAP.md
+++ b/LIVE_DEV_MAP.md
@@ -108,7 +108,7 @@ Toon shader 已移植,卡住的主要是全屏后处理和几个逆向未完
|------|------|
| `Gallop.MirrorReflection` | ✅ 已建并实现。第二摄像机 + 反射矩阵 + 斜投影 → RT → `_ReflectionTex`。**顺带解决了长期的「舞台地板发白」** —— `mirror_a` 的 `_ReflectionTex` 从没被赋值,Unity 代入白贴图 × 默认 `_ReflectionRate = 1.0` |
| `Gallop.Live.BillboardController` | ⚠️ 已建,字段可读(`[StageScripts]` 丢失数 892→728)。朝向行为写过一版,实机是「一直转来转去而那东西不该转」,已默认关闭(`EnableRotation = false`)——`_rotationType` 语义和 up 轴取法都无依据。同批修掉一个确定 bug:目标摄像机被缓存,而生效摄像机随 CameraSwitcher 每帧变 |
-| `Gallop.Live.AnimationObjectController` | ❌ **不要建**:实测**零序列化字段**(282 个实例全空),纯行为类,重建拿不到任何信息。其最直接的后果(283 个 `Animation` 一个都不播)由 `StageAnimationPlayer` 顶上:按 `currentLiveTime` 采样而不是 `Animation.Play()`,暂停/拖动进度条都跟随。但**「播哪个 clip」和「该不该播」都是它在决定**,我们只能顶前者——wash 灯扫动 clip 被 free-run 就是栽在后者(已按 `_wash_` 跳过) |
+| `Gallop.Live.AnimationObjectController` | ❌ **不要建,也不要顶替**:实测**零序列化字段**(282 个实例全空),纯行为类,重建拿不到任何信息。后果是舞台 283 个 `Animation` 一个都不播(灯球不转/wash 不扫/灯牌不动)——这是**有意接受**的现状。顶替版 `StageAnimationPlayer` 已于 2026-08-07 撤除,理由见 `LIVE_TRACKS.md` 专条 |
| `Gallop.RenderPipeline.CustomLensFlare` (+`LensFlareData`) | ❌ 未建,但**数据齐全、语义不受 IL2CPP 阻塞**——`LensFlareData` 是 Unity legacy Flare 的逐字翻版,URP 侧有 `LensFlareComponentSRP` 对应。199+5 个物件,星芒在原版截图里很显眼。**下一个该做的就是它** |
| `Gallop.RenderPipeline.MirrorBallProjector` | ❌ 字段已 dump,但 4 个 projector 全在原点、共用一个材质、没有任何指向灯球的引用字段——绑定关系只在方法体里。**卡住,不是没做** |
| `Gallop.Live.ShaderParam.ShaderParamController` | ⚠️ 实测是空操作(两个向量都是 (1,1,1,1)),从待办里划掉 |
@@ -117,10 +117,10 @@ Toon shader 已移植,卡住的主要是全屏后处理和几个逆向未完
> **镜面球转速:素材确定,倍率仍未知。** live10149 的 4 个 `MirrorBallProjector` 全部
> `MirrorBallIsLoopRotation = 0` / `LoopRotationSpeed = 0`,shader 的 loop-rotation 通路是
> 关的;真正在转的是 clip(恒定 **−179.50°/s 绕 Y**,2.0 s 一圈,7 帧算出来都是这个数),
-> `StageAnimationPlayer` 按 `currentLiveTime`(秒)忠实地播它。
-> **但实机观察是「所有迪斯科灯球还是太快」** —— 所以原版要么有播放倍率,要么根本不用这条
-> clip 驱动球体。数据确定的是素材,不是原版怎么用素材。倍率属于不可知常量,
-> 要定它只能对着参考视频标定。
+> 曾按 `currentLiveTime`(秒)忠实播过它,**实机观察是「所有迪斯科灯球还是太快」** ——
+> 所以原版要么有播放倍率,要么根本不用这条 clip 驱动球体。数据确定的是素材,
+> 不是原版怎么用素材;倍率属于不可知常量,要定它只能对着参考视频标定。
+> 2026-08-07 起播放实现整体撤除,球现在**不转**,见上一行的专条链接。
### 内部系统
| 轨道 | 状态 |
@@ -188,7 +188,7 @@ Toon shader 已移植,卡住的主要是全屏后处理和几个逆向未完
|------|------|
| `Assets/Scripts/umamusume/Gallop/Live/Director.cs` | 事件订阅 + 所有 handler。写 shader 属性统一走共享的 `PropBlock`,且必须 Get→改→Set |
| `Assets/Scripts/umamusume/Gallop/MirrorReflection.cs` | 平面镜反射(原版脚本按签名重建)|
-| `Assets/Scripts/umamusume/Gallop/Live/StageAnimationPlayer.cs` | 舞台 Animation 播放 + 缺失脚本普查(`[StageAnim]` / `[StageScripts]` 日志)|
+| `Assets/Scripts/umamusume/Gallop/Live/StageMissingScriptCensus.cs` | 缺失脚本普查(`[StageScripts]` 日志),当重建进度表用 |
| `Assets/Scripts/umamusume/Gallop/Live/PostEffect/PostFilmRendererFeature.cs` | PostFilm 的 URP Feature,默认 `_enableRendering = false` |
| `Assets/Scripts/umamusume/Gallop/Live/Cutt/LiveTimelineControl.cs` | 每帧插值 + 事件触发 |
| `Assets/Scripts/umamusume/Gallop/Live/Cutt/LiveTimelineWorkSheet.cs` | 所有轨道字段声明 |
diff --git a/LIVE_TRACKS.md b/LIVE_TRACKS.md
index df3ca89..7d4bebc 100644
--- a/LIVE_TRACKS.md
+++ b/LIVE_TRACKS.md
@@ -208,9 +208,42 @@ originSessionId: 319ae839-0292-4b1e-80de-2f3c91b1f40e
| 问题 | 为什么查不到 |
|---|---|
+| **舞台自带 Animation 的播放**(283 个组件,全曲全场景不播) | 见下方专条 |
| PostFilm 的 `filmOptionParam` 几何语义 | 游戏自己的 PostFilm shader 不在 `shader` bundle(499 个按名字和属性签名都搜过),应在 app 本体 `resources.assets` |
| MonitorControl 的 `dispID` → 资源 | 内容资源族已定位为 `live/uvmovie/gal_uvmovie__`(全库 219 个目录),但 song 1177 只有 `_light` 没有 `_001`,且 `monitorCameraPosKeys` 为 0,两条路都不通 |
+#### 专条:舞台自带 Animation 为什么**不播**(2026-08-07 决定撤除实现)
+
+**现状**:舞台上 283 个 legacy `Animation` 组件一个都不播,这是**有意的**,不是漏了。
+迪斯科灯球不转、wash 灯不扫、霓虹灯牌不动,都属于这一条。
+
+**机制**:这些组件全部 `playAutomatically = true`,但默认 clip(`m_Animation`)**全是空的**。
+Unity 的 playAutomatically 播的是默认 clip,为空就什么都不播 —— 原版靠
+`Gallop.Live.AnimationObjectController` 显式 `Play("clip名")`。
+
+**为什么补不了**:`AnimationObjectController` 实测 **282 个实例、零序列化字段**。
+它是纯行为类,「播哪个 clip / 该不该播 / 播多快」全在方法体里,而方法体随 IL2CPP 元数据
+一起拿不到(见 `CLAUDE.md` → Blocked)。**按原签名重建它拿不到任何信息** ——
+这条路是死的,别被 282 这个实例数勾回去。
+
+**素材本身是全可读的**,这正是容易上当的地方:clip 曲线、时长、wrapMode 全能 dump。
+例如镜面球那条是恒定 −179.50°/s 绕 Y、2.0 s 一圈;wash_ground 那条是 `swing_*` 节点
+90°→0°→90°、2.0 s 一个来回、12 盏同步。**但「素材是什么」推不出「原版何时以何速播它」。**
+
+**试过一版替代品 `StageAnimationPlayer`,已整体撤除。** 它的策略是「只有一个 clip 就播」,
+错在把「这物件只有一个**状态**」当成了「这个状态**一直在播**」—— 后者恰恰是那个缺失的类
+每帧做的决定。实机后果:一排地面 wash 灯 2 秒一个来回疯狂摆头(原版是不扫只闪),
+镜面球虽然该转但转速无从校验。缩到最后它只让一颗球转,且转得对不对说不清 ——
+**一个转错的替代品比不转更糟:既错,又让人以为这块已经做过了。**
+
+**唯一值得留下的结论**(将来任何人重做都适用):
+驱动舞台动画**不要用 `Animation.Play()`** —— 那跑 Unity 自己的时钟,live 暂停时舞台照转、
+拖动进度条不跟随。正确做法是每帧把 `AnimationState.time` 设成
+`LiveTimelineControl.currentLiveTime`(单位是秒)再 `Sample()`,与演出时间同源。
+
+**什么条件下可以重做**:拿到参考视频逐首标定(属于经验值,不是 ground truth,且大概率
+一舞台一套),或者 IL2CPP 元数据解锁(见 `CLAUDE.md` → Blocked)。在那之前**不做**。
+
### 与时间轴无关的渲染问题
| 现象 | 状态 |