Fix/shaderlab#2983
Conversation
…tin functions texture(sampler2D, vec2) returns GVec4 which was incorrectly resolved to the sampler type instead of vec4, causing "No overload function type found" when passing the result to user-defined functions like decode32(vec4). Add resolveGenericReturnType() to correctly map GSampler* → GVec4: sampler2D/sampler3D/samplerCube → vec4 isampler2D/isampler3D/... → ivec4 usampler2D/usampler3D/... → uvec4
…exture2DLod signatures - Simplify resolveGenericReturnType: remove genericParamType param, only check if return type is GVec4 - Fix textureCube/textureCubeLod return type: SAMPLER_CUBE → VEC4 - Add missing texture2DLod builtin function registration - Add texture2DLod test cases to texture-generic.shader
* feat: implement HorizontalBillboard render mode
… type When a builtin generic function (e.g. normalize) receives TypeAny args, resolvedReturnType stays TypeAny. Previously the else branch returned the raw EGenType enum value (200), which is neither a concrete type nor a wildcard, causing downstream user-function overload matching to fail.
…tin functions texture(sampler2D, vec2) returns GVec4 which was incorrectly resolved to the sampler type instead of vec4, causing "No overload function type found" when passing the result to user-defined functions like decode32(vec4). Add resolveGenericReturnType() to correctly map GSampler* → GVec4: sampler2D/sampler3D/samplerCube → vec4 isampler2D/isampler3D/... → ivec4 usampler2D/usampler3D/... → uvec4
…exture2DLod signatures - Simplify resolveGenericReturnType: remove genericParamType param, only check if return type is GVec4 - Fix textureCube/textureCubeLod return type: SAMPLER_CUBE → VEC4 - Add missing texture2DLod builtin function registration - Add texture2DLod test cases to texture-generic.shader
… type When a builtin generic function (e.g. normalize) receives TypeAny args, resolvedReturnType stays TypeAny. Previously the else branch returned the raw EGenType enum value (200), which is neither a concrete type nor a wildcard, causing downstream user-function overload matching to fail.
…ing CodeGen When #define values contain struct member access like `o.v_uv` (where `o` is a Varyings/Attributes/MRT struct variable), the CodeGen now correctly transforms them to just the property name (e.g. `v_uv`), matching the behavior of direct struct member access in regular code. Closes #2944.
…uct-access Verify the actual CodeGen output instead of just checking GLSL compilation: - #define values with struct member access are correctly transformed - varying/attribute declarations are emitted for referenced properties
…ions Add assertions for macro usage in expressions (not just #define transformation): - Macro as RHS in multiplication, as LHS in assignment - Macro as function argument in dot(), texture2D() - Multiple varying properties (v_uv, v_normal) referenced via #define
…ransform Build a combined _globalStructVarMap in visitShaderProgram by scanning both vertex and fragment entry functions, so global #define values like `attr.POSITION` or `o.v_uv` are correctly transformed in all stages. Rewrite define-struct-access tests to use snapshot file comparison against expected/ GLSL outputs for clearer verification.
…cro as builtin arg
…cro scanning - Suppress `uniform` output for global struct-typed variables (e.g. `Varyings o;`) - Register global struct vars in both per-function and cross-stage maps - Unify macro member access scanning into callback-based _forEachMacroMemberAccess - Add registerStructVar() encapsulation in VisitorContext - Add Cocos VSOutput pattern test (global-varying-var)
…version GLES100 visitJumpStatement converted `return expr;` to `gl_FragColor = expr` without a trailing semicolon, causing WebGL compilation errors. Only triggered when fragment entry returns vec4 (Cocos pattern), not void (standard Galacean).
…eprocessor #if !0 and similar expressions now work correctly, matching C/GLSL preprocessor behavior.
…atrix The viewMatrix getter intentionally ignores the camera entity's world scale (using Matrix.rotationTranslation), but _getInvViewProjMat() was using the full entity.transform.worldMatrix which includes inherited scale. This inconsistency causes screenPointToRay to produce incorrect world-space rays when the camera inherits scale from a parent entity (e.g. UICanvas in ScreenSpaceCamera mode with camera as a child of canvas). Closes #2748 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verify that screenPointToRay and viewport-world round-trip produce correct results when the camera inherits non-identity scale from a parent entity. Without the fix, the round-trip deviates by the inherited scale factor (e.g. 105 -> 107.5 at scale 1.5). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ReflectionParser 解析 IComponentRef(entityPath + componentType)时, 目标组件可能在 GLB clone 的子 entity 上而非 clone 根 entity 自身。 getComponents 找不到时 fallback 到 getComponentsIncludeChildren。
CloneManager: 当 source 和 target 属性是同类型 Object 实例(如 Vector4)时, 自动升级为 DeepClone,避免 prefab 模板的引用覆盖克隆体的独立实例。 新增 Map/Set 类型的 deep clone 支持。 ModelMesh: throw string 改为 throw Error 以获得正确堆栈。
AnimatorState.speed is part of the shared AnimatorController asset. Modifying it at runtime pollutes all Animator instances sharing the same controller, causing animation speed corruption after cloning. - Add speed field to AnimatorStatePlayData, initialized from AnimatorState.speed on reset - Add proxy properties (name/clip/wrapMode/transitions/addStateMachineScript) - Change speed calculation to playData.speed * animator.speed - findAnimatorState now returns per-instance AnimatorStatePlayData - Export AnimatorStatePlayData for consumer code
When sizeMode is set to Automatic, the UITransform size is automatically synchronized to the sprite's natural dimensions when the sprite changes. This matches Cocos Creator's Sprite.SizeMode.TRIMMED behavior. - Add SpriteSizeMode enum (Custom / Automatic) - Add sizeMode property to Image with getter/setter - Sync UITransform.size in set sprite and _onSpriteChange - Export SpriteSizeMode from component index
Cherry-picked from #3011 (e2e parts dropped). - Wire up EmissionModule.rateOverDistance: each frame accumulates the delta of the emitter's world position and emits ratePerUnit × distance particles (Unity-aligned). - Sub-interval distance fragment carried across frames; floor-based count instead of subtract-loop to avoid float drift dropping a particle at exact boundaries. - Distribute the N per-frame emissions spatially along [lastPos → currentPos] in World simulation space, and interpolate emit time the same way so age-driven modules (COL/SOL/FOL) render a smooth gradient instead of a uniform block. - Clamp _emit at the maxParticles budget and return the actual count, so a setPosition teleport on a rateOverDistance emitter can't expand into millions of no-op iterations. - Reset baseline + accumulator on stop(StopEmitting*) so a play-after- clear re-syncs from the current position. Includes unit tests covering zero-rate, ratePerUnit × distance, sub-interval accumulation across frames, static emitter, stop+clear reset, spatial distribution, per-particle emit-time spacing, and teleport-induced burst guard.
GuoLei1990
left a comment
There was a problem hiding this comment.
总结
fix/shaderlab 是一个 237 文件的集成分支,大多数变更已被拆成独立 sub-PR(#2987、#2988、#2991、#2992、#2993 等)单独审查。这个 PR 本身不应再作为独立变更合入。
建议
[P1] PR 描述为空,没有摘要、没有勾选 checklist,也没有标注 breaking changes。无法判断哪些变更是该 PR 独有的、哪些已经在 sub-PR 中覆盖。
建议处理方式:等所有 sub-PR 合入 dev/2.0 后,将此 PR 关闭而非合并。唯一独有的内容(e2e vite config 更新、physx WASM 二进制更新)通过干净的 cherry-pick PR 单独提交,避免将 237 文件 omnibus 合入主分支产生无法 bisect 的历史。
[P2] e2e/case/particleRenderer-dream.ts — updateForE2E 和 initScreenshot 被注释掉,该 e2e case 不会执行截图对比,PR 多轮 review 中已提及但未修复。
GuoLei1990
left a comment
There was a problem hiding this comment.
审查(2026-05-26)
已关闭问题清单(来自上轮 bde8c614 审查前的状态)
| 问题 | 状态 |
|---|---|
P1 Collider._onUpdate/_onPhysicsUpdate 循环 |
✅ 已修复 |
P1/P2 UIPointerEventEmitter._bubble currentTarget |
✅ 已修复 |
| cofactor-form 正常矩阵 | ✅ 已修复(_normalMatFromModel 使用 cross product 形式) |
.aac/.flac AudioLoader 扩展名 |
✅ 已合入 |
以下问题仍未修复(历史多轮 CHANGES_REQUESTED 未处理):
问题
[P0] e2e/config.ts 仍不存在,CI Playwright suite 无法启动
本 PR 当前 HEAD bde8c614 上 e2e/config.ts 不存在(或已被删除),而 e2e/tests/index.spec.ts 中硬编码 import { E2E_CONFIG } from "../config"。合并后 Playwright 测试立即报 import 失败。此问题已提 8+ 轮,仍未修复。
[P1] packages/core/src/animation/Animator.ts:237 — findAnimatorState 末尾 fallback 返回错误状态
// State exists in controller but not currently playing — return srcPlayData initialized with the state
return layerData.srcPlayData; // ← state 不匹配,返回正在播放的另一个动画的 playData注释自述"state 未在播放",但实际返回 srcPlayData(其 .state 是正在播放的另一个动画),调用方若据此修改 speed,影响的是错误的动画。应返回 null:
return null;[P1] tests/src/core/Animator.test.ts — crossfade 测试绕开公开 API,戳私有字段
const layerData = animator["_animatorLayersData"][0]; // 私有字段
layerData.srcPlayData.speed = 0.25;
layerData.destPlayData.speed = 0.25;这个测试验证的是 crossfade 期间 src/dest 各自的 per-instance speed,应通过 findAnimatorState() 公开 API 获取,而不是绕开测试该 API 的存在意义。revert findAnimatorState 后测试仍然绿,说明它没有守住被测 bug。
本轮新增内容审查(自上轮 4a568170 以来的 commit)
子 PR 已在各自独立 PR 中审查:
- Clone 修复 → #2992 ✓
- Signal 修复 → #2987 ✓
- Camera 克隆 → #2988 ✓
- Entity clearChildren → #2991 ✓
- SceneLoader cache → #2993 ✓
- cofactor 正常矩阵 → 本 PR 已确认 ✓
P0 + P1 修复后合入。
Animator added a clipChangedListener to each AnimatorState's update flag manager but never removed it. After destroy, the listener kept the state and the destroyed Animator graph reachable, leaking memory across reloads. Track the listener on AnimatorStateData and unsubscribe in _reset; call _reset from _onDestroy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously plain objects took the deep branch and everything else fell to assignment, which mistakenly shared user-defined value classes between source and clone. Switch the rule: assignment only for ReferResource (refCount-managed engine resources like Material/Texture/Mesh/Shader); deep clone for everything else, so nested Entity/Component refs in user data are correctly remapped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Recomputing min/max from UITransform's size and pivot duplicates the bounds derivation done elsewhere. Transform the cached _localBounds by the world matrix instead, matching the pattern used by other UIRenderer subclasses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ease Each camera held its own `_internalColorTarget` for the lifetime of its `BasicRenderPipeline`, so a scene with N on-screen cameras pinned N full-canvas RTs. On a 1078x2249 canvas with MSAA 4x that is 2 * 74 MB = 148 MB just for the scratch buffers (see investigation in galacean/migration-agent#304). Convert `_internalColorTarget` and `_copyBackgroundTexture` to per-frame leases: `BasicRenderPipeline._drawRenderPass` returns both to `RenderTargetPool` at end of every frame, so the next camera in the frame finds a matching free entry and reuses the same underlying RT. Cameras with mismatched format / MSAA / depth still get their own entries -- the pool's existing match key handles that. `RenderTargetPool` gains three bounded-growth strategies so the free list cannot leak across canvas resizes or shape churn: * `tick(currentFrame)` -- destroys entries idle longer than `maxFreeAgeFrames` (default 60). Engine calls this once per `update()`. * `evictBySize(width, height)` -- destroys entries matching the given dimensions. Engine subscribes to canvas size changes and evicts at the previous canvas size, so old full-canvas RTs do not linger. * `maxFreeBytes` -- when a `free*` push would exceed the cap, the oldest entries (by `lastUsedFrame`) are destroyed until the total fits. Scoped to free-list contents only (not total GPU memory), so the cap is device-independent. `RenderTarget._memorySize` becomes `@internal` so the pool can compute per-entry byte size without re-deriving from format/aa. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three review fixes on top of the previous commit: 1. `maxFreeBytes` now applies to the combined free-list total (RT + Texture) instead of each list independently. Previously, with the default 64 MB cap, the pool could actually hold up to 128 MB (64 MB RT + 64 MB Texture) — inconsistent with what `freeListByteSize` reports. The unified `_enforceMemoryCap` picks the older entry across both pools by `lastUsedFrame` and evicts until the combined sum is at or below the cap. 2. `_computeRtBytes` now documents the contract it depends on: that `RenderTarget._memorySize` covers only the RT's own renderbuffers (MSAA + depth RBO) and excludes the attached `colorTexture` / `depthTexture`, whose bytes live on `Texture._memorySize`. So the sum does not double-count. 3. `RenderTargetPool` is no longer re-exported from `RenderPipeline/index.ts` — it stays `@internal`. The test imports it via a relative source path instead, keeping the public surface unchanged. Added a 12th unit test verifying the unified cap actually bounds the combined total. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CR follow-ups: * `tick()` now calls `_enforceMemoryCap()` at the end, so a mid-run reduction of `maxFreeBytes` takes effect within one frame instead of waiting for the next `free*` call. Cost is one extra scan per frame over an already-tiny free list. * Test file adds `afterAll(() => engine.destroy())` to release the WebGL context between test files. * New test locks in the tick-re-enforces-cap behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nough The byte cap was defending against pathological churn within the age window — a scenario covered in practice by canvas-resize eviction (shape coupled to canvas) and frame-age (steady state). With the default 64 MB cap, a single full-canvas MSAA 4x RT (~86 MB on a 1078x2249 RGBA8+D24S8 canvas) was larger than the cap. Every free immediately destroyed the just-pushed RT, defeating the multi-camera sharing this PR exists to enable. The abstraction was unfortunately calibrated against a fictional worst case; the realistic worst cases are already bounded. Dropping it removes a tunable that's hard to set well (device-dependent, no single number works) and a sizable chunk of byte-tracking machinery (`_freeRenderTargetBytes`, `_freeRenderTargetByteTotal`, the combined-pool LRU in `_enforceMemoryCap`, `_computeRtBytes`, `_findOldestIndex`, `freeListByteSize`). `RenderTarget._memorySize` reverts to `private` — pool no longer reads it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GuoLei1990
left a comment
There was a problem hiding this comment.
增量审查(2026-05-28)
已关闭问题清单(来自上轮 bde8c614 审查)
| 问题 | 状态 |
|---|---|
P1 Collider._onUpdate/_onPhysicsUpdate 循环 |
✅ 已修复 |
P1 UIPointerEventEmitter._bubble currentTarget |
✅ 已修复 |
| cofactor-form 正常矩阵 | ✅ 已确认正确 |
以下问题仍未修复:
[P0] e2e/config.ts 缺失 — CI 必然失败(已提 10+ 轮,仍未修复)
当前 HEAD 07abbc4b 上 e2e/config.ts 不存在,而 e2e/tests/index.spec.ts:2 仍为:
import { E2E_CONFIG } from "../config";
Object.entries(E2E_CONFIG).forEach(...)硬编码 import,无 fallback。合并后 Playwright suite 立即报 import 失败,无法启动。
[P1] packages/core/src/animation/Animator.ts:241 — findAnimatorState 末尾 fallback 返回错误数据(已提 10+ 轮,仍未修复)
if (layerData.srcPlayData.state === state) return layerData.srcPlayData;
if (layerData.destPlayData.state === state) return layerData.destPlayData;
// State exists in controller but not currently playing — return srcPlayData initialized with the state
return layerData.srcPlayData; // ← 注释自述"未在播放",但返回的是正在播放的 srcPlayData,其 .state 是另一个动画调用者 findAnimatorState("Idle") 在非 Idle 播放期间,拿到的 playData.state 是正在播放的另一个动画,修改其 .speed 会静默影响错误的动画。应返回 null:
return null;[P1] tests/src/core/Animator.test.ts — crossfade 测试戳私有字段,绕开被测 API(已提 7+ 轮,仍未修复)
const layerData = animator["_animatorLayersData"][0]; // ← 私有字段
layerData.srcPlayData.speed = 0.25;
layerData.destPlayData.speed = 0.25;crossfade 修复的核心路径是 findAnimatorState() 在 crossfade 期间为 src/dest 各返回正确的 per-instance playData。绕开公开 API 直接操作内部结构,导致 revert findAnimatorState 修复后测试仍然绿,无法守住回归。正确写法:
animator.play("Walk");
animator.crossFade("Run", 1.0, 0);
const walkPlayData = animator.findAnimatorState("Walk");
const runPlayData = animator.findAnimatorState("Run");
walkPlayData.speed = 0.25;
runPlayData.speed = 0.25;
sharedWalkState.speed = 10;
sharedRunState.speed = 10;
// 断言 playedTime 按 0.25× 推进而非 10×本轮新增内容审查(bde8c614 → 07abbc4b)
fix(animation) — Animator destroy 时移除 clipChangedListener [正确,无问题]
AnimatorStateData 新增 clipChangedListener 字段,_reset() 调用 removeListener 后置 null,_onDestroy 调用 _reset()。内存泄漏修复方向正确,路径完整。
refactor(clone) — 用 ReferResource 判断 deep vs assignment [正确,无问题]
_inferCloneMode 新逻辑:ReferResource 走 Assignment(引用计数管理的共享资产保持引用共享),其余走 Deep。相比上轮基于"target 已有同类实例"的启发式,这个判断更精确,消除了将构造器创建的 value class 误判为 Assignment 的 bug。方向正确。
refactor(ui) — Text._updateBounds 改用缓存 localBounds [正确,无问题]
_updateBounds 直接 BoundingBox.transform(this._localBounds, worldMatrix, worldBounds),消除 min/max 重复推导,与其他 UIRenderer 子类保持一致。无问题。
P0 + P1 修复后合入。
A multi-pass material whose passes target different queues (e.g. Opaque + Transparent) pushed the same `RenderElement` object into both queues. `RenderElement` carries the batch result (`instancedRenderers`, `_isBatched`), so the queue that batches first stamped those fields on the shared object and the next queue's `BatcherManager.batch` then short-circuited the polluted leader straight into its output — the opaque instance list got drawn under the transparent pass, and the remaining members were re-batched into a second leader and drawn again (producing the "1 olive + 5 greener" signature on N-cube repros). Clone the element once per additional queue: the first target keeps the original (zero overhead), subsequent ones get a pool-allocated copy with its own batch state via `_cloneFrom`. The pool is `ClearableObjectPool` so no GC pressure. Identity fields are shared by copy, batch state stays per-queue.
`_isFrontFaceInvert` previously read `lossyWorldScale`, which lazily triggers `_getScaleMatrix`' polar decomposition — expensive for a function whose answer is just `sign(det(worldMatrix)) < 0`. That sign equals the XOR of every ancestor's local-scale sign bits, so compute it recursively from `_scale` directly and cache the boolean. Invalidate via a new `WorldFrontFaceInvert` bit added to every dirty-flag combo that already carries `WorldScale` (so existing dispatch paths cover it; no new dispatch sites). The bit sits at `0x800` to avoid colliding with `UITransformModifyFlags.Size`/`Pivot` at `0x200`/`0x400`. `_canBatch` and `RenderQueue` call this per element per frame, so hit-rate is effectively 100% in non-mirror scenes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t cleanup Two related leaks the previous `Record<instanceId, ...>` layout caused: 1. `Animator._curveOwnerPool` and `AnimatorLayerData.curveOwnerPool` were keyed by `Component.instanceId`. Destroying the bound component left the entry pinned for the Animator's lifetime, so the curve owners (and the entity-tree defaults they captured) couldn't be collected. `AnimationClipCurveBinding._tempCurveOwner` had the same shape keyed by `Entity.instanceId`. Re-key all three by the live object via `WeakMap<Component | Entity, ...>` so dead keys release the entry. 2. `Animator._reset` reverted default values for every owner *before* removing each state's `clipChangedListener`. If any owner threw on a dead target, the listener-remove block was skipped, leaving the polyfill listener on a shared `AnimatorState`; the listener closure captures `this` (the Animator), which pins the whole entity tree until the AnimatorState itself dies. Move listener cleanup into the inner loop, wrap each `revertDefaultValue` in its own `try/catch`, so listener cleanup always runs. (Root-cause fix would be a destroyed-target guard inside `AnimationCurveOwner.revertDefaultValue` itself — left as a follow-up.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s/arrays differ GLSL forbids samplers and per-instance arrays inside UBOs, so they live in the renderer-group block as plain uniforms bound from the leader. The previous `MeshRenderer._canBatch` only checked macros, so instances disagreeing on which sampler/array was bound were silently batched and the leader's binding was applied to every follower — the wrong texture ended up on every other instance. `ShaderData` now tracks renderer-group sampler/array property ids in a sorted `_instanceBatchFields` (binary insert / linear remove on set/unset) and exposes `_matchesRendererInstanceBatch` for O(K) compare. `MeshRenderer._canBatch` consults it. `RenderQueue`'s instanced path now also re-uploads the renderer block on every draw (leader changes between batches), and clears `_uploadRendererId` so the cache can't shortcut past the new leader. `RenderElement._cloneFrom` drops `subDistancePriority` from the copied fields (the cloned element re-enters sort/scheduling via its own path). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GuoLei1990
left a comment
There was a problem hiding this comment.
增量审查(2026-06-02 @ 8c5a07003)
已关闭问题清单(上轮 7c521e2a 遗留)
| 问题 | 状态 |
|---|---|
P0 e2e/config.ts 缺失 → CI import 失败 |
❌ 仍未修复(HEAD 上仍 404) |
P1 Animator.findAnimatorState 末尾 fallback 返回错误 playData |
❌ 仍未修复(Animator.ts:241 仍 return layerData.srcPlayData) |
P1 crossfade 测试戳私有 _animatorLayersData |
❌ 仍未修复(Animator.test.ts:252-254 仍走私有路径) |
新增 commit(instancing sampler 守卫、animation weakmap、transform _isFrontFaceInvert cache、per-queue batch state 隔离、RT pool)方向独立合理,但都没碰上述 3 条历史遗留。
仍未修复(重申,非重复打补丁——这 3 条是 verified 仍 broken,不是已关闭)
[P0] e2e/config.ts 缺失 — CI 必然报错
e2e/tests/index.spec.ts:2 仍硬 import ../config 且无 fallback,文件在 HEAD 8c5a07003 上仍不存在。合并后 Playwright suite 立即 import 失败、整套 e2e 无法启动。这是合并的硬阻塞。
[P1] Animator.ts:241 — findAnimatorState fallback 语义错误(aspirational comment)
// State exists in controller but not currently playing — return srcPlayData initialized with the state
return layerData.srcPlayData; // ← 注释说"未在播放",实际返回正在播放的 srcPlayData,其 .state 是另一个动画注释承诺的行为与代码相反。调用方在非目标 state 播放期间调用会拿到错误动画的 playData,静默改错动画速度。应 return null。
[P1] Animator.test.ts:252 — crossfade 测试绕开被测 API
测试直接 animator["_animatorLayersData"][0].srcPlayData.speed = ... 并断言私有字段,把 findAnimatorState 这条修复路径整个绕过 —— revert findAnimatorState 修复后测试仍绿,守不住回归。应改为从 animator.findAnimatorState("Walk"/"Run") 公开入口取 per-instance playData,断言 playedTime 按预期 speed 推进。
变更单元粒度(已提过,仅确认趋势)
本 PR 当前 242 文件 / +11.6k−2.5k,横跨 shader-lab / loader / gltf / physics-physx / physics-lite / rhi-webgl / ui / shader / scene-schema 九个域,标题仍是 "Fix/shaderlab"。这个体量无法作为单一变更单元被有效 review 或回滚。团队已在做拆分(#3014 "cherry-pick fixes from fix/shaderlab"、#2999 "从 #2983 抽离动画与 GLTF 加载器修复"、#3015 抽 RT pool)——方向正确,建议把剩余正交域继续抽成独立 PR,让本 branch 收敛或关闭。不再展开重复此点。
结论
P0(e2e/config 缺失)+ 2×P1(findAnimatorState 语义 / 测试绕过)均为 verified 仍未修复,继续 request changes。新增工作建议优先随拆分 PR(#3014/#2999/#3015)合入,而非继续堆进本 monolith。
Please check if the PR fulfills these requirements
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
What is the current behavior? (You can also link to an open issue here)
What is the new behavior (if this is a feature change)?
Does this PR introduce a breaking change? (What changes might users need to make in their application due to this PR?)
Other information: