diff --git a/CLAUDE.md b/CLAUDE.md index 9d90e69..3641a3f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,6 +111,26 @@ pf = obj.read_typetree()["m_ParsedForm"] # m_Name, m_PropInfo.m_Props ⚠️ A material's `m_SavedProperties` in the bundle lists properties it no longer has — it retains entries from whatever shader it was authored against. `Material.HasProperty()` asks the *current shader*. Always enumerate the shader, never trust the bundle's saved-property table. +**渲染状态也从这张过期表里读,后果最严重。** Gallop 的发光类 shader 把混合写成 +`Blend [_SrcBlend] [_DstBlend]`,也就是**从材质属性取渲染状态**。live10149 的 +**26/26 个材质**存的都是 `_SrcBlend=One, _DstBlend=Zero`(不透明覆盖),且每一个的 float 表里 +都带着 `_WorkflowMode`/`_Surface`/`_ClearCoatMask`/`_Smoothness`/`_Blend` —— 那是 **URP Lit +的属性表**,整批盖上去的,One/Zero 正是 URP Lit 的不透明预设。 + +作者意图可查:全库 16 个声明这两个属性的 shader 里,**14 个默认 `One/One`(加法混合)** —— +`LightBlinkBlend`、`LightBlend`、`StageBeamLight{,Cutoff,Fadeout}`、 +`StageLightBlink{Cutoff,Fadeout}`、`StageProjectorBlend{,VertexAlpha,AnimVertexAlpha}`、 +`StageMonitorBlend*`、`MirrorBallProjector`、`BgUnlitStencilAlpha`,全是发光/投影那一批。 + +被压成不透明后,加法光柱「颜色为黑 = 什么都不加 = 不可见」变成了**一堵实心黑墙**。 +`StageController.RestoreAuthoredBlendState()` 在舞台初始化时把这两个属性写回 **shader 声明的 +默认值**(`Shader.GetPropertyDefaultFloatValue`),对本来就该不透明的 shader 是空操作,自限。 +改了哪些材质会逐条打进 `[StageBlend]` 日志。 + +> 教训比这个 bug 本身通用:**过期存档表不只影响颜色,还能改渲染状态**。 +> 看到「本该发光的东西是黑的/挡住后面的东西」,先查 `_SrcBlend`/`_DstBlend`, +> 再查颜色写对没有。 + Worked example — `mtl_env_live10149_projector001` (shader `Gallop/3D/MirrorAndShadow/MirrorBallProjector`, 22 real properties) saves 40+, including Standard-shader leftovers (`_Glossiness`, `_Metallic`, `_Parallax`, `_Mode`, `_UVSec`) **and** four plausible-looking ghosts the current shader never declares: `_IsLoopYRotation`, `_LoopYRotationSpeed`, `_MirrorBallProjectionIntensity`, `_MirrorBallRotateWS`. Two of those are older spellings of properties that *do* exist (`_MirrorBallIsLoopRotation`, `_MirrorBallLoopRotationSpeed`) — exactly the kind of near-miss that reads as a discovery. Note also that where a component field and a saved property disagree (`MirrorBallFallOffPower` = 1.0 on the component vs `_MirrorBallFalloffPower` = 2.0 in the material), the component wins at runtime: these controllers push their own fields into the material. **Track group names are not always GameObject names.** Four separate silent failures were traced to this in one session, so check the mapping before assuming a track is unimplemented: @@ -201,10 +221,46 @@ MonoBehaviour 实例脚本为空。**但这些脚本是可以「接管」的** **舞台自带 Animation 的播放**(`StageAnimationPlayer`,顶替缺失的 `AnimationObjectController`): 283 个 `Animation` 组件全部 `playAutomatically = true` 但默认 clip 为空,Unity 因此什么都不播。 -只接管**唯一解**的情形(1 个 clip,live10149 上 4 个对象);3 个 / 5 个 clip 的状态机选择规则 -没有依据,只打日志不猜。播放不走 `Animation.Play()` —— 那用的是 Unity 自己的时钟,暂停和拖动 -进度条都会脱节;改为每帧按 `LiveTimelineControl.currentLiveTime` 设 `AnimationState.time` 再 -`Sample()`,与演出时间同源。 +播放不走 `Animation.Play()` —— 那用的是 Unity 自己的时钟,暂停和拖动进度条都会脱节; +改为每帧按 `LiveTimelineControl.currentLiveTime` 设 `AnimationState.time` 再 `Sample()`, +与演出时间同源。3 个 / 5 个 clip 的状态机选择规则没有依据,只打日志不猜。 + +⚠ **「只有一个 clip」≠「这个 clip 一直在播」。** 前者说明这个物件只有一个**状态**, +后者是 `AnimationObjectController` 每帧做的**决定**。最初的接管规则把两者混为一谈, +结果 `wash_ground_b/c/d` 的扫动 clip(`swing_*` 节点 90°→0°→90°、2.0 s 一个来回、12 盏同步) +被 free-run,一排地面 wash 灯疯狂摆头 —— 而原版 MV 里它们不扫、只闪。现已按 `_wash_` 跳过。 +镜面球保留(单调转到 −359°,自明的常转 idle)。 + +### 🔎 舞台上「行为不对」的第一嫌疑:缺组件,不是解析错 + +**这是分诊规则,不是某一次的结论。** 舞台 prefab 引用 11 个脚本类,至今仍有 **728 个 +MonoBehaviour 实例是空的**(基线 892,已补 `BillboardController` −164)。其中 +`AnimationObjectController` 一家就占 282 个实例 —— 舞台上所有「什么时候动、动多快、 +动哪一个」的决定原本都在它手里,而它整个不存在。 + +所以当某个舞台元素表现不对(该动的不动、不该动的在动、动的节奏不对), +**默认假设应该是「原版那个组件缺失,现在由我们猜的行为顶着」,而不是「数据解析错了」**。 +理由是可检验的:字段值、clip 曲线、shader 属性表这些**数据**层面的东西全都能 dump 出来核对, +而且一路核对下来**基本都是对的**;反复出错的是**行为**层面 —— 那正好是拿不到方法体的部分。 + +已经有两个同形状的例子,都是「物件选对了、行为猜错了」: + +| 现象 | 数据对不对 | 真实原因 | +|---|---|---| +| wash 灯疯狂摆头 | clip 曲线完全正确(实测 90°→0°→90°) | 缺 `AnimationObjectController`,我们替它决定了「一直播」 | +| 面片一直乱转 | 字段全部正确读到 | 缺原方法体,`_rotationType==0` 的语义是我们猜的 | + +推论一:动手前先查一眼**这个物件挂过什么脚本、那个脚本我们有没有** +(`[StageScripts]` 日志 + `CLAUDE.md` 的签名清单),比直接怀疑解析快得多。 + +推论二:**顶替缺失组件时,要把「我在替谁做决定」写清楚**,并且区分 +「从 bundle 数据推出来的」和「看参考视频观察到的」—— 后者是有效证据,但不是 ground truth, +换个舞台可能就不成立(`StageAnimationPlayer.IsSweepRig` 的注释就是按这个写的)。 + +反例也要记住:**并非所有舞台画面问题都是缺组件。** 光柱渲染成黑墙那次, +根因是材质被盖了 URP Lit 的属性表、`_DstBlend` 从 One 变成 Zero, +把加法混合压成了不透明 —— 那是**数据**问题,而且是 dump 得出来的(见下)。 +分诊规则的意思是「先查缺不缺组件」,不是「一律归咎于缺组件」。 ### Blocked diff --git a/LIVE_DEV_MAP.md b/LIVE_DEV_MAP.md index 243bfa2..39805a2 100644 --- a/LIVE_DEV_MAP.md +++ b/LIVE_DEV_MAP.md @@ -92,6 +92,14 @@ Toon shader 已移植,卡住的主要是全屏后处理和几个逆向未完 ### 舞台自带脚本(原版 MonoBehaviour,按签名重建) +> **分诊规则:舞台元素「行为不对」时,第一嫌疑是缺组件,不是解析错。** +> 至今仍有 **728 个 MonoBehaviour 实例是空的**(基线 892),光 `AnimationObjectController` +> 就占 282 个 —— 舞台上「什么时候动、动多快」的决定原本都在这些缺失的脚本里。 +> 而数据层(字段值 / clip 曲线 / shader 属性表)是能 dump 出来核对的,一路核对下来基本都对。 +> 所以先查「这个物件挂过什么脚本、我们有没有」,再怀疑解析。详见 `CLAUDE.md` 同名小节。 +> ⚠ 但别一律归咎于缺组件:光柱变黑墙那次是**材质数据**问题(URP Lit 属性表把 `_DstBlend` +> 从 One 压成 Zero,加法混合变不透明),照样是 dump 出来的。 + `Assets/Scripts/umamusume.asmdef` 的程序集名与 bundle 里写的 `umamusume` 一致,所以按 **类名 + namespace + 程序集名** 建类就能让 Unity 把原版的序列化字段真正填进来。 清单和实例数见 `CLAUDE.md`「用原签名重建脚本」。 @@ -100,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` | ⚠️ 未建;其最直接的后果(283 个 `Animation` 一个都不播)已由 `StageAnimationPlayer` 顶上:只接管单 clip 的唯一解情形,按 `currentLiveTime` 采样而不是 `Animation.Play()`,暂停/拖动进度条都跟随 | +| `Gallop.Live.AnimationObjectController` | ⚠️ **未建,282 个实例,是缺口最大的一个**。其最直接的后果(283 个 `Animation` 一个都不播)由 `StageAnimationPlayer` 顶上:按 `currentLiveTime` 采样而不是 `Animation.Play()`,暂停/拖动进度条都跟随。但**「播哪个 clip」和「该不该播」都是它在决定**,我们只能顶前者——wash 灯扫动 clip 被 free-run 就是栽在后者(已按 `_wash_` 跳过) | | `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)),从待办里划掉 | diff --git a/LIVE_TRACKS.md b/LIVE_TRACKS.md index 0273200..df3ca89 100644 --- a/LIVE_TRACKS.md +++ b/LIVE_TRACKS.md @@ -216,7 +216,7 @@ originSessionId: 319ae839-0292-4b1e-80de-2f3c91b1f40e | 现象 | 状态 | |---|---| | 舞台地板长期偏亮发白 | ✅ 2026-08-06 解决。**白的不是文档里记的那三个物件** —— 是与 `plane_000` 同位同尺度、叠在一起的 `mirror_a`(同在 `pfb_env_live10149_main000` 下,局部变换全为单位)。它用 `Cygames/MirrorAndShadow/ReceiveMirror`,`_ReflectionRate` 默认 1.0 而 `_ReflectionTex` 从没赋值,Unity 对未绑定采样器代入白贴图 = 满强度白反射。实现 `Gallop.MirrorReflection` 接上反射 RT 后恢复正常。**教训见 `CLAUDE.md`「现象描述不是 ground truth」**:整轮排查都围着文档里那三个名字转,那个「`_EnvRate` 归零无变化」的实验切的是另一批物件 | -| 小灯泡渲染成黑块 | 用 `StageTransmittedLightMask` / `StageMirrorBallShine`,这两个 shader **没有任何颜色属性**,BlinkLight 点不亮。疑为 URP 移植问题,对应未实现的 95/96 TransmittedLight 轨道 | +| 小灯泡渲染成黑块 / 光柱变实心黑墙 | 🔧 2026-08-07 **找到根因并已修**(待实机确认):Gallop 发光类 shader 的混合状态写作 `Blend [_SrcBlend] [_DstBlend]`,从材质属性读;而 live10149 **26/26 个材质**都被盖上了 URP Lit 的属性表,`_DstBlend` 是 URP Lit 不透明预设的 Zero。全库 16 个声明该属性的 shader 里 **14 个作者默认是 One/One(加法)**,于是所有光柱/闪光/投影都被按不透明画 —— 加法下「颜色为黑=不可见」变成了「实心黑墙」。`StageController.RestoreAuthoredBlendState()` 已把这两个属性恢复成 shader 声明的默认值。**注**:`StageTransmittedLightMask`/`StageMirrorBallShine` 无颜色属性是另一回事,仍未解 | | LED 大屏黑屏 | MonitorControl (10) 未实现,见上 | ---