# 技术细节与实现现状 > 面向**开发者与贡献者**。README 只讲使用者需要知道的事,实现细节、内部机制与工程现状放在这里。 > 相关文档:`AGENT.md`(开发总纲与踩坑史)、`docs/superpowers/specs/`(设计文档)。 --- ## 1. 渲染路径 ### 1.1 three.js 播放器(当前主路径) - **背景图层**:每个 `image` 对象 → `Mesh`(`PlaneGeometry` + `MeshBasicMaterial`),按 `we_to_three`(场景中心化、**y 不翻**)定位,`scale` / `alpha` / `brightness` 与 WE 语义一致;`renderOrder=0`、`depthWrite=false`。 - **image 精灵表帧 UV**(v0.8.1):材质 `passes[0].combos.spritesheet` 为真且纹理带 TEXS 帧网格时,按行优先写当前帧 UV(无脚本控制时 1 fps 自动循环;`resizeBackground` 后重写);对象级脚本可经 `thisLayer.getTextureAnimation` 控制 `pause/play/stop/setFrame`(如 `3765967112` 停在脚本帧)。生产 GUI 里背景层 `pointer-events:none` ⇒ 点击切帧不可达,按产品决策不做点击转发(`AGENT.md` §7.42 ②)。 - **粒子系统**:每个 `particle` 对象 → `InstancedBufferGeometry`(每粒子 4 角点 billboard + `ShaderMaterial`)。实例属性 `particlePosition/Size/Uv/Color/Alpha` 按 spec 的 `maxcount` **一次性预分配**(避免 three r170 `_maxInstanceCount` 首帧锁存导致层不绘制),运行期只更新 `instanceCount`。 - **粒子 shader**:billboard(`pos + corner*halfSize`)、`gl_Position.z=0`(2D,避开正交视锥裁剪)、多帧 sprite UV 网格切片(`frameCount` + cols/rows)、纹理 alpha 遮罩(有纹理 `shape=texel.a`、无纹理软圆盘)、`softness`、additive / alpha 混合。 - **渲染循环**:`renderer.setAnimationLoop`(`dt` = `performance.now` 差分,clamp 0.1s)→ 逐 `CpuParticleSim.update(dt)` → 刷新实例缓冲 → `render`;帧体 `try/catch` **异常自愈**(three 的 RAF 一次异常会永久停摆)。 - **相机 / 尺寸**:cover 正交相机(按**窗口宽高比**,非场景比例);`canvas.width/height = 视口逻辑尺寸 × devicePixelRatio`。 - **对象级效果链接线**(2026-09-14 起 P1):`createThreeSceneRenderer()` → `loadSceneToThree()`;隔离对象进 `localScene`、主场景放合成 quad、注入帧钩子;每帧 `renderIsolatedContents()` → `stage.bindOutputs()` → 渲染主场景 → `stage.advance(time)`(串行推进 `EffectRunner`,异步不阻塞本帧)。无带效果对象时 stage 为 null,帧序退化为原路径(零回归)。 - **text 对象 + 脚本运行时**(2026-09-20):与 image 同路径渲染为背景 quad(`CanvasTexture`);**多行文本按 `\n` 分行绘制**(`fillText` 本身不处理换行);驱动按「quickjs 沙箱执行 `text.script` → clock 硬编码兜底 → 跳过」取,文本变化才**就地重绘**同一 canvas;**只对 text 应用 `visible` 过滤**;pkg 内字体经 `FontFace` 加载、失败回退 sans-serif。沙箱实现、实测与边界见 `AGENT.md` §7.42 ②。 - **场景树层级变换**(2026-09-20):`scene-json` 解析 `parent`,`src/client/scene-graph.ts` 的 `resolveWorldTransforms()` 在装载时把父链折叠成世界变换(`scale` 逐分量相乘、`origin` 经父旋转、`angles` 矩阵累积),随 `SceneAssets.worldTransforms` 下发,下游统一读世界值。反算基准与影响面见 `AGENT.md` §7.1 第 9 条。 - **省电与画质档位**(2026-09-21):`paused` / `pauseOnHidden`(停 RAF + 帧内防御 + `elapsedSeconds` 扣暂停时长)、`qualityScale`(渲染像素比 = 设备像素比 × 档位;画布缓冲与对象 RT 的屏幕密度走**同一个** `resolvePixelRatio`);`resize()` 重读 `devicePixelRatio`(跨屏自适应)。见 `AGENT.md` §7.1 第 7 条。 - **壁纸级后处理与清屏色**(v0.8.1):`general.bloom*` → `bloom-stage.ts`(薄封装 GlowStage)→ player 按「壁纸 bloom → 应用级 Glow → 合成」串联;`general.clearcolor` 在 `parseSceneJson` 按 0-1/0-255 双语义归一化到 0-255,player 构造时 `setClearColor`(段渲染同色)。边界见 `AGENT.md` §7.42 ③⑥。 ### 1.2 粒子模拟(Rust/wasm,`wasm/src/particle/`) 经 wasm-bindgen 暴露 `CpuParticleSim`: - 照 linux-wallpaperengine `CParticle` 语义实现:`emitter`(`boxrandom` 逐轴 vec3 / `sphererandom` 球壳 cbrt)、`initializer`(`sizerandom`、lifetime / velocity / color / alpha / rotation / angularVelocity / turbulent)、`operator`(movement 重力/阻力、angularMovement、alphaFade 梯形、size/alpha/colorChange、turbulence、oscillateAlpha/Size/Position、寿命 compaction)。 - **错落稳态(`prewarm`)**:启动时按 `min(maxcount, ceil(rate×平均寿命))` 铺入带**随机出生相位**的粒子,避免「一批同时下落、同时消失」。 - **起播相位 `starttime`**(v0.8.1):`CpuParticleSim::new` 在 `prewarm()` 后按 effect 秒预推进(步长 `1/60/rate`,上限 900 步 = 15 effect 秒),超限静默截断(`AGENT.md` §7.42 ⑤)。 - `build_instance_vertices()` 输出每粒子 `[pos3, size, uv2, color3, alpha]`(10 浮点)喂给 three.js。 ### 1.3 纹理 / 材质解码(`src/client/tex-loader.ts`) - `TEXV0005`:LZ4 解压;支持 **RGBA8888 / DXT1/3/5 / RG88 / R8**(RG88→`vec4(r,r,r,g)`、R8→`vec4(1,1,1,r)`,与 wasm 的 `r8_to_rgba_white_alpha` 对齐)。 - **2 的幂填充裁剪**(`cropToMap`):**原始像素载荷(带 padding)**的 mip 记录尺寸 = `.tex` 头的分配尺寸,按头部记录的逻辑内容宽高裁到内容区(EVA 4096×2048→2400×1555),避免背景只占左上角、右侧露黑;**编码图像类**的 mip 记录 w/h 本就是内容宽高、无 padding 可裁(二分口径见 `AGENT.md` §5.17)。 - **DXT 用 mip0 全分辨率 + `LinearFilter`**(不用 mipmap 过滤),避免大尺寸压缩纹理被三线性 mip 过滤糊化。 - **sprite 精灵表**:解析 `TEXS000x` 段得到帧数与网格(如火把/雾 1024×1024 = 8×8=64 帧),shader 二维网格切片。 - 行序统一(`flipRows` / DXT 块行 `flipCompressedRows`)。 --- ## 2. 与桌面 Wallpaper Engine 的语义对齐 (本轮与桌面 WE 真机逐像素对拍修正) - **混合模式按材质 json 的 `passes[0].blending`**(`add`/`additive` → 加法),不再从材质文件名猜(此前导致 additive 层被当普通混合 → 黑方块 / 全屏泛光)。 - **粒子按对象 scale**:粒子局部坐标与 quad 尺寸乘**本对象真实 scale**(WE `mvp = viewProj×translate×rotate×scale`)。 - **`sizerandom` ÷2**:spec 存的是编辑器值的一半(`p.size` 为整宽),不除会全壁纸粒子 2 倍大;`sizerandom.exponent` 缺省 1.0(黑神话显式 2)。 - **`emitter.directions` 缺省 `(1,1,0)`**(lwe 语义;缺省零向量会让粒子全堆在同一点);`distancemin/max` 逐轴 vec3。 - **y 约定**:`origin.y - sceneH/2` + emitter 局部 `+y` 朝上(与桌面 WE 实测一致)。 - **闪烁**:`oscillatealpha` 等算子接线,星点/火光按 spec 振荡。 - **坐标与变换**(勿再翻转 / 勿漏乘):WE 场景系 = 左下原点、y 向上;three 正交相机 = 中心原点、y 向上。映射 `three = we − viewport/2`,**y 不翻转**。对象 model matrix = `T·R·S`,`angles` 是**弧度**,旋转顺序 `R = Rz·Ry·Rx`。历史事故:曾用 `vh/2 − oy` 导致非居中对象上下镜像。 - **`turbulentvelocityrandom` = `forward` 绕 `normal` 旋转**(不是「沿 normal 偏移」):官方 `TurbulentVelocityRandomProgram` = curl 噪声采样 → 投影到垂直 normal 的平面 → 与 forward 求夹角 → `AngleAxisf(angle*scale + offset, normal) * forward`。方向**恒在垂直 normal 的平面内**。旧实现得到 `(0, cosθ, sinθ)`:x 恒 0、速度全进屏幕上不可见的 z 轴 → 粒子排成一条竖线。影响全库 **9 张壁纸**。 - **对象级 `instanceoverride`**:`alpha/size/lifetime/speed` 是**乘数**,`color/colorn` 是**覆盖**(`UiColorToLinear` = v²),emitter rate **× `count`**;缺省恒等。JS 只解 `{user|script,value}` 编辑器包装,数值语义仍在 Rust(`particle::parse_particle_override`)。 - **`.tex` flags 必须被消费**(权威定义见 `.lwe/.../Texture.h`): - `ClampUVs = 2`(bit1)—— **已修**:WE 默认 **REPEAT**,**仅带此位才 CLAMP**。曾一律落到 three 默认 `ClampToEdgeWrapping` ⇒ `clouds.frag` 有意把第二组 UV 旋转到负象限 ⇒ `cloud1` 全部塌到最左一列 ⇒ CP2077 整屏偏白且随时间越来越白。**全库 477 张 `.tex` 中 105 张因此由 clamp 改 repeat**。 - `NoInterpolation = 1`(bit0)—— **待办**:`applyLinearSampling` 无条件设 `LinearFilter`,带此位的纹理(全库 4 张)本该 `NearestFilter` ⇒ 被插值偏糊。 - ⚠️ **RT 纹理必须保持 CLAMP**(对象合成 quad 依赖它),别在别处一律改成 repeat。 - **纹理 v 轴**:WE 是 `v=0` = 图像顶部;效果链一侧按 WE 约定加载(`tex-loader` 的 `rowOrder` 参数 + `effect-runner` 注入点),并用「隔离局部相机 y 镜像 + 合成 quad `flipGeometryUvY` 反镜像」精确抵消 ⇒ RT 内容与 mask 同约定、画面仍正立。**显示路径一行未改**(避免漏改一处就整图上下颠倒)。 - **效果纹理槽编号 + sampler `combo` 派生**: - **`textures[i] → g_Texture(i)`**(**不是** `g_Texture(i+1)`),写错会让所有效果的纹理槽整体错位一个。 - sampler 声明带 `"combo":"X"` 时,该槽被绑定即置 `X = 1`(必须在 `preprocessWeShader` **之前**);缺失会让 `#if MASK` 的局部作用分支永不启用 ⇒ 效果全图生效。 - **`g_TextureNResolution` 四分量一律同值 = 各分支实际上传尺寸**(视频内在尺寸 / 编码图像解码尺寸 / raw 裁剪尺寸 / DXT 块对齐尺寸,A1 起)⇒ `.z/.x` 与 `.w/.y` 恒为 1;效果 shader 用 `.z/.x` 缩放遮罩 UV(如 `waterripple.vert`),比值必须为 1。插件**不按 `.tex` 头做 padding 分配**,故填头里的分配尺寸会让 NPOT 纹理的遮罩按 分配/内容 挤向画面左上(真机:湖水波动跑到树上)。细节见 `AGENT.md` §5.17。 - **R8 / RG88 有两套通道语义**(`convertUnormToRgba(data, format, alphaPriority)`):`true`(缺省)= 粒子纹理语义(形状写在 R / G);`false` = 效果纹理槽语义(`R8 → (r,r,r,1)`、`RG88 → (r,g,0,1)`,shader 直接读 `.r` / `.rg` 当遮罩数值)。按错语义 ⇒ 遮罩失效 ⇒ 效果覆盖全图。 - ⚠️ **待办**:权威来源应是 `.tex` 头的 `TextureFlags_AlphaChannelPriority`(bit19),现由**调用方路径**推断;对当前库恰好正确,但语义来源错了。 - **`colorBlendMode`(→ combo `BLENDMODE`)不是 alpha 混合**,而是**额外追加一遍「读当前帧缓冲」的混合 pass**(`ApplyBlending(BLENDMODE, A=背景, B=自己, 自己的 alpha)`),alpha 保持背景的。模式表在 WE 明文 `common_blending.h`。three 侧用**预乘片元** + `CustomBlending` 复刻。**未实现的模式回退普通 alpha 混合**(不静默画错)。全库非零只有 3 个对象:`3743126786`=7、`2832263418`=6、`2460786246`=31。关键性质:`BlendScreen(A, 0) = A`。 --- ## 3. 对象级效果链(effects) - **全库覆盖(P1 + P2,2026-09-15)**:线性链与 **24 条具名 RT 图链**(blurprecise×13、blur×3、localcontrast×2、godrays×2、bloom×2、shine×1、bokeh_blur×1)都由同一执行器按计划执行(**模板均已覆盖**;其中 3 条曾因挂在 util 对象上而不挂链 —— v0.8.2 起已随 util 层一并接入,见下条);**原先「整条跳过 + 去重告警」的降级已删除**。 - **新增的效果类别**:`blur` / `blurprecise` / `localcontrast` / `godrays` / `bloom` / `shine` / `bokeh_blur` 属 P2 新增。P1 已有的高频线性效应是 waterwaves 24 / shake 18 / opacity 8 / waterripple 7 / waterflow、pulse、perspective 各 5 / clouds、scroll、foliagesway 各 4 …。 - **计数口径**:`106 线性 + 24 RT`(130 条效果引用)是**声明口径**(按 effect 链数、不过滤 `visible`);其中 1 条线性链的 effect 级 `visible=false`(`2597392171` obj50 的 `effects/shake`)生产侧整条跳过(**`visible` 仅 three 主路径解析**;未接入的 `scene-renderer` / `wasm-renderer` 并源循环未过滤)⇒ three 的**执行口径 = 105 线性 + 24 RT**。 - **部分链在画面上看不到(v0.8.2 已收口)**:24 条 RT 图链里 3 条曾挂在 `util` 对象(当时拿不到隔离条目 ⇒ 不挂链);**v0.8.2 起 util 层已接入渲染**:有 effects 的 util 进隔离路径(无 effects 的 util 层仍不渲染,见下条与 `AGENT.md` §7.42 ⑧)。`text` 对象 v0.8.1 起已挂链(原先 **7 张壁纸共 18 条**、其中 13 条 `blurprecise` 在收链阶段被过滤,边界见 `AGENT.md` §7.42 ①)。 - **验收口径(如实)**:端到端(真实 WebGL 逐像素)跑过的样本是 `2683211654` / `2911105183` / `2011060960`(双链同名 RT)/ `2937346640`(godrays)/ `1968789468`(shine)/ `2597392171`(godrays)/ `3743126786`(16 pass bloom)/ `3765967112`(blurprecise 挂 text,该轮只作「不报错」回归;v0.8.1 起已挂链)/ `1429403119`(性能相对信号);该轮共 **30 PASS / 2 FAIL**,2 条 FAIL **全是 `[5]` 的归因对照**(bloom 不抬全屏 p99),**不是回归**(明细见 `AGENT.md` §7.1 的表)。⚠️ **2026-09-20 起 `[5]` 的全屏 p99 口径失去鉴别力**:Glow 默认开启后开关两侧都钉在 255 ⇒ 由 1 PASS + 2 FAIL 变 3 FAIL;**这不是 bloom 失效**(`[5-归因·主]` 的亮部诊断仍显示 bloom 有效),而是该判据口径不再有鉴别力(见 `AGENT.md` §7.1 的「应用级后处理 ⇒ 已实现」子条)。其余效果类别的「可由现有执行器正确执行」仍是**分类学推断**,**不是逐个实测**。GTR 的 bloom 只测到**局部增亮**(p99 Δ=0),**未与桌面 WE 逐像素对照**。 - **P2 遗留(如实)**:`3789452668` 的 `effects/color_grading`(线性链)有 `varying` 类型不匹配(`vec4` vs `vec2`)⇒ 该链不生效;`2597392171` 的 godrays 引用了全局运行时 RT `_rt_FullFrameBuffer` ⇒ 该槽不绑(保持默认)+ 告警一次,其完整语义属非目标;**未做显存 cap**。 - **对象 RT 的三条不变量**(改这块先读代码注释): 1. **局部正交相机的 left/right/top/bottom 是「世界坐标范围」**,必须覆盖**完整对象世界尺寸**。两个坑:拿 RT 像素当范围(dpr>1 时对象缩小 + 边缘 clamp 拉伸);按 `OBJECT_RT_MAX`(4096) 钳制范围(超限对象只覆盖中央 ⇒ 边缘拉伸带)。超限对象的正确代价是「**分辨率低**」,**不是**几何裁剪。 2. **RT 像素尺寸 = `|world| × 屏幕密度`**(= 对象在画布缓冲上的占位像素),等比收口到 4096。基准必须是**未钳制**的 `world`,**不能**用相机 `range`。旧口径「`min(world × dpr, 视口 × dpr, 4096)`」把预算当基准,与屏上占位差 0.8% ⇒ 合成那一步是双线性缩小 + 亚纹素相位漂移 ⇒ 整层锐度 **−52%**。 3. **`isolate` 的键是 `scene.json` 的对象 id**,不是 player 的图层计数器 id(两者撞键会让隔离 image 与 particle 互相覆盖)。 - **`colorBlendMode ∈ {6,7,31}` 的对象已进入隔离路径、其效果生效**(2026-09-14 `6bb3d71`):修法是把 WE 的混合语义从「内容材质」搬到「合成 quad」——隔离内容材质只做普通 alpha 写入,`createCompositeQuadMaterial` 统一用 `MeshBasicMaterial`(采样非预乘对象 RT)并按 `colorBlendMode` 设 `CustomBlending`。 - **`refraction` 在 GLSL3 下编译失败**(引擎源码自身的 `float(format) == FORMAT_RG88` 缺陷):效果不生效。**已修的是「失败被缓存」**——此前每帧重试 ⇒ 重建材质 + 探针渲染 + 2 条 warning(实测该页 146×2 条刷屏);现在按 pass key 缓存失败,同一 key 只试一次。 - **text 对象的 effects 已挂链(v0.8.1)**:`groupEffectsByObject` 不再跳过 text,隔离 RT / 合成 quad 尺寸在装配期冻结 ⇒ 时钟文本变宽时轻微裁边/拉伸(`AGENT.md` §7.42 ①)。实测样本 `3765967112` 的 4 条 `blurprecise` 挂在 text 对象上。 - **`util` 对象上的 effects 已挂链(v0.8.2)**(全库 **8 对象 / 10 条**;本机 `none` 类型对象挂 effects = **0**,勘误见 `docs/2026-10-20-library-feature-gap-audit.md` 第七节 #11):有 effects 的 util 层进隔离渲染(`util-material.ts` + `setUtilContentTexture`,`AGENT.md` §7.42 ⑧⑨);**无 effects 的 util 层整层不渲染**(记边界)。 - **粒子对象的效果链只有单测覆盖**:全库实测 particle 挂载 effects = 0,管线里的粒子隔离分支**无真实样本可验**。 - ~~**链全为具名 RT 图链的对象不再隔离**(P2 前置优化)~~:**已作废(2026-09-15,P2)** —— `rtGraphOnly` 分支与其告警已删除,isolate 准入回到「至少有一条**可见**链」;原先不隔离的对象重新进入隔离路径,换来效果生效。 --- ## 4. 显存与性能 - **对象 RT 显存(审计脚本 `research/q-rt-vram-sweep.mjs`)**:`1920×1080@dpr1` 合计 **530 MB**(旧口径 386 MB);单壁纸最大 **131.8 MB** / 中位 27.9 MB;`@dpr2` 与 `4K@dpr1` 单壁纸最大 **245.7 MB**(旧 156.1)、中位 95.0。结论:**典型代价 ≈ +18%、最坏 +3.4×**(均为 dpr=1),4096 硬上限把单对象钉在 ~107 MB。 - **具名 RT 显存(P2,Task 9 实测;静态分配估算,非 GPU 实测)**:1080p@dpr1 全库合计 **39.0 MB / 28 张**(@dpr2 **124.0 MB**);单壁纸最大 **11.0 MB**(`2597392171`)、单张最大 **7.91 MB**(`3789452668` 的 `_rt_FullCompoBuffer1`);GTR `3743126786` **5.3 MB / 8 张**(`fbos` scale 2/4/8/16 的降采样金字塔)。占全库显存(3×对象 RT + 具名 RT)约 **6.6%**(两档同比例)⇒ 具名 RT 是「对象 RT + ping-pong」之外的小头,**未做显存 cap**。 - **清晰度归因(`clarity-report.md`)**:隔离路径相对**直渲**锐度 −52%(Laplacian 均方 `713.2 → 341.5`,与 dpr、MSAA 均无关)。逐环节定界:内容→对象 RT 712.9(−0.04%)、效果 pass 711.2(−0.24%)、**合成 quad→屏幕 341.5(−52%)** ⇒ 纯 RT 往返无损,损失 100% 在最后一步重采样。修法与实测(GTR `3743126786`,headless Edge,1280×720):隔离 **711.1** vs 直渲 **713.2(−0.3%)**、逐像素 MAD **0.0414**(修复前 341.5 / MAD 1.804)。 - **性能门槛(代理判据已达成,严格验收未做)**:设计文档 §7.4 自定「`1429403119`(23 对象 / 24 条链,全库最重)1080p **FPS ≥ 30**」为验收门槛;2026-09-20 起 headless Edge 可拿真 GPU,用「每帧 `renderer.render` 提交耗时 ≤ 33.3ms」代理判据在真 RTX 3060 上达成(1080p 中位 2.0ms,见下条订正),**严格 FPS 验收仍缺 GPU 侧计时**。早期 SwiftShader 档只能给**相对信号**(帧间隔与每帧耗时中位数 / p95、隔离对象数、RT 显存估算),**软件光栅化数字不能替代真机 FPS**。2026-10-23 人眼验收无可见卡顿(`TESTING.md` §2.15 V10,量化 FPS 未记录)。 - **2026-09-20 订正(真机 GPU 验收)**:上面的前提**不成立** —— 本机 headless Edge 加 `--use-angle=d3d11 --ignore-gpu-blocklist` 拿到的是**真实 RTX 3060 / ANGLE D3D11**(探针 `research/gpu-probe.mjs`;`verify-object-effects.mjs --gpu` 档内置 renderer 自证)。同时**判据要改**:headless 的 RAF 恒锁 **60Hz 节拍**(真机与软渲染都是 16.7ms)⇒ **帧间隔不可反推 FPS**,改用**代理判据**「每帧 `renderer.render` 提交耗时 ≤ 33.3ms」⇒ 1080p 中位数 **2.0ms**(p95 2.7)、占预算 **6%**,**达成**;严格验收仍缺 GPU 侧耗时(需 timer query 或有头浏览器)。**真机显存(nvidia-smi)**:同一壁纸 1080p **+415 MiB**(对象 RT 静态估算仅 30.5 MB ⇒ 低估 8–14×)。**真机全量回归 37 PASS / 2 FAIL**,FAIL 项与软渲染基线逐项相同。完整数据见 `AGENT.md` §7.1 的 2026-09-20 订正。 - **同轮发现并修复的一处泄漏**:`EffectRunner` 的纹理槽缓存在同一 WebGL 上下文内 resize 重挂链时**每次泄漏 28 张 GPU 纹理**(`renderer.info.memory.textures` 107→303 等差增长);切换壁纸因新建上下文而不显形。修法与证据见 `AGENT.md` §7.5。 - **应用级 Glow 的显存与开销(2026-09-20 实测)**:base RT(画布缓冲尺寸)+ 6 张小 RT(三级各一对 ping-pong,合计约 **0.66×base** 面积);实现用 `HalfFloatType`(RGBA8 口径翻倍)⇒ **720p 静态估算 ≈ 12 MB**(7 张 RT:1280×720 base + 640×360 / 320×180 / 160×90 各两张;逐张相加 = 12,211,200 B = **12.2 MB / 11.6 MiB**;Task 6 记的 **11.9 MB** 已作废,仅作历史记录),`renderer.info.memory.textures` 可精确核对(GTR **28 → 35,+7**);spec 的 @3440×1440@dpr1 RGBA8 口径 ≈ 33 MB ⇒ HalfFloat 约 66 MB。**只统计 `glowEnabled` 时占用**,改阈值 / 强度不重建 RT。**开销**:每帧 CPU 侧 `renderer.render` 提交 **+0.1~+0.4 ms**(16.7ms 帧预算的 0.6~2.4%),**不含 GPU 时间**(真 GPU 光栅化异步,严格验收未做)。**`[6]` 显存泄漏探测仍 Δ 0**,但 5 次观测为 Δ0/Δ1/Δ0/Δ2/Δ0(开关两侧都出现过 Δ>0)⇒ 如实记为**零星残留 1~2 张、与 Glow 无关**(远小于修复前的 Δ196)。**如实**:只测了 GTR `3743126786` 一张壁纸;nvidia-smi 差值噪声 ±400 MiB,**不可用于定量**。完整数据见 `AGENT.md` §7.1 的 2026-09-20「已实现」子条。 - **音频已接入(2026-09-25,A3)**:装配期 `createAudioAnalyzer()` + 对 `desc.sounds` 逐对象选一首 `playWallpaperSound`(`/wallpapers/scene//asset?name=…`,按 `playbackmode` / `volume`;新增设置 `soundEnabled`,默认开启),`onFrame` 每帧 `analyzer.update()` → `ObjectEffectStage.setAudioSpectrum(freqData)` → 各 `EffectRunner` 的音频 uniform,`teardown` 里 `context.close()`。**v0.8.2 进展**:`Simple_Audio_Bars` 竖条已可见(`u_BarBounds` 逗号分隔默认值 + `colorBlendMode` / `BLENDMODE` 对齐,`AGENT.md` §7.42 ⑮⑯);`Simple Visualizer` 由 player 代建 64 条并逐帧更新(§7.42 ⑰);**系统音频(`supportsaudioprocessing`)尚未接入** ⇒ 无音源的可视化壁纸条高恒 0。真机听感已于 2026-10-23 人眼验收通过(`TESTING.md` §2.14 V2)。**v0.8.1**:`volume` 经 GainNode、`playbackmode` 按对象选一首(`single` 播一次、`random` 随机起点);多文件 loop 轮播 / random 播完重随机未实现(`AGENT.md` §7.42 ④)。 - **省电与画质档位**(2026-09-21):`qualityScale` 让渲染像素比 = 设备像素比 × 档位(0.5–1),画布缓冲与对象 RT 的屏幕密度随之等比缩小 ⇒ 显存与填充率同比例下降;暂停停掉整条 RAF(`elapsedSeconds` 扣除暂停时长,恢复不跳帧)。**2026-10-23 人眼验收通过**(`TESTING.md` §2.15 V10,观感无卡顿);暂停的真实省电收益未量化。 --- ## 5. ~~备用 / 未接入路径~~ ⇒ **已于 2026-09-22 删除** ``` scene 壁纸 ──► three.js 播放器(**唯一路径**,v0.3.0 起) │ 渲染失败 / 渲染出 0 个可见对象 ▼ preview 图 + Ken Burns(永不白屏) ``` `wasm-renderer.ts`(`createWasmSceneRenderer` / `createFallbackSceneRenderer`)、`scene-renderer.ts` 的 `renderScene`、 `alignment.ts`、`scene-script.ts`、`shader/glsl-to-naga.ts` **已整体移除**(TS 侧 −4366 行,含 `@webgpu/glslang` 依赖与 `dist/static/glslang.wasm`);活口迁至 `scene-assets.ts` / `wasm-loader.ts` / `wallpaper-controller.ts`。详见 `AGENT.md` §7.14。 原本记录在此的 6 条遗留(wasm 侧 `bind` 索引语义、未消费 `instanceoverride`、对 visualizer/text 不生效、 3 张壁纸判 STATIC、naga `glsl` 前端卡点与 GLSL→SPIR-V 链路、`g_ModelViewProjectionMatrix` / `collect_bindings`) **随代码一起作废** —— 它们只描述那条不再存在的路径。需要时从 git 历史取回(删除前 HEAD = `65f759c`)。 **⚠️ Rust 侧亦已删除(2026-09-22 同日)**:`wasm/src/render/**` + `shaders/*.wgsl` + `WeScene` + `scene.rs`/`tex.rs` 全部移除 (-10344 行),`render` feature 改名 `cpu-sim` 且只留 `js-sys`(`CpuParticleSim::vertices` 仍需要)。**收益**:`we_scene_wasm_bg.wasm` 3108 KB → **161 KB**、`dist/static/` 4594 → **663 KB**。**验证**:native `cargo test` 98 项全绿、`build:wasm` 通过、端到端逐像素 **最大差 0**(dpr=1/2)。详见 `AGENT.md` §7.14。 --- ## 6. 关键约定(踩过的坑) 1. **wasm 产物必须 `--target web`**:曾用 `--target module`(无默认导出 `__wbg_init`)导致 wasm 静默失败、一路回退。 2. **`research/`、`wasm/pkg/`、`dist/static/ptex-*.tex` 是 gitignore**;`lib/`、`dist/` 其余部分**必须提交**。 3. **场景资源禁止浏览器缓存**:`/wallpapers/scene//asset` 返回 `Cache-Control: no-store`。 4. **效果链的一切编译/建管线必须在加载时一次性完成**;`render_frame` / `render_object_effects` / `step` / `EffectChain::render` **不得**做 naga 编译或建管线(每帧只写 uniform + 建 bind group + 提交 pass)。 5. **对象 `angles`(弧度)必须应用**(粒子顶点 shader、背景 `mesh.rotation`)。全库 79 个对象带非零 angles(粒子 75 / image 2 / text 1 / other 1,涉及 15 张壁纸)。 6. **粒子 alpha 属性链**:透明度 = 生命周期衰减 × alpha;JS ShaderMaterial 与 wasm 粒子层双路径同语义,改 alpha 逻辑要双路径验证。 7. **WE 内置粒子/效果纹理走 host 路由,不随包分发**:`/wallpapers/particle-texture?name=<相对 assets/materials 的路径>`(`name` 不能无条件加 `particle/` 前缀 —— 有 `workshop//particle/...` 这类路径)。曾由 `build:client` 复制成 `dist/static/ptex-*.tex`,结果被 `files: ["dist"]` 打进 npm 包(解包 43 MB 里 33 MB 是它)。**2026-09-29 起 client 传可选 `id`**:壁纸自带素材打包在它自己的 `scene.pkg`(`materials/.tex`)里,路由先查 pkg、再回退引擎目录(对齐 WE 挂载顺序);缺这段会让这类粒子纹理 404 → 白图兜底 + `maskMode=0` → 粒子渲染成软圆点(`3793620838` 的小鸟)。**精灵表序列帧(2026-09-29)**:`animationmode` 缺省/`sequence` 时帧号按 `(1 - life/initial_life) × sequencemultiplier × 帧数` 推进(官方 `AnimationLifetime`),只有 `randomframe` 才是 spawn 固定帧 —— 全库仅 2 个多帧精灵表对象(本壁纸的两只鸟)。 8. **音频管线**:`createAudioAnalyzer` 频谱 → EffectRunner 音频 uniform + visualizer 条高;autoplay 被拦时 context suspended、可视化全零,用户手势后恢复。 9. **测试沙箱**:vitest / esbuild 依赖 service 子进程(命名管道),受限沙箱下报 `spawn EPERM` —— 需完整权限运行。 10. **web 壁纸 iframe 的承载 origin 必须「与自身资源同源 + 与宿主跨源」**(`background-layer.ts` 的 `planWebFrameOrigin`):① 桌面壳(`dsh-app://app`,无回环名)用官方接缝 `globalThis.__DSH_TRANSPORT__.streamBaseUrl`(宿主 HTTP origin,权威故不探活);② 回环页(`dsh web`)用另一回环主机名 + 探活;③ 两者都没有才退回 opaque origin 兜底(届时 WebGL 类壁纸空白,见 `AGENT.md` §5.30/§7.13)。桌面版下顺带绕开 `dsh-app://` 代理(它会丢弃 `content-length`,大视频 66 MB 实测 `ERR_FAILED`)。 --- ## 7. 工程现状 / 测试 - **测试基线 = 0 失败 / 919 项全绿**(2026-09-22)。`scripts/known-failures.json` 已清空(机制保留,用于拦新失败)。早先记的「15 项既有失败」已过时,逐条见 `AGENT.md` §7.11: - **I1 是测试过时而非产品缺陷**:用例 `boot()` 不传 ctx 且 stub 了已废弃的 `fetch('/api/settings.describe')`,而设置自 DSH 0.1.2-rc.1 起走 `ctx.remote.settings.describe()` ⇒ 无 ctx 时 `readClientSettings()` 恒返回 `DEFAULTS`,恢复分支永不执行。已按仓库既有范例注入 `settingsCtx(...)`;变异测试(关掉 `index.ts` 的恢复分支)证明用例真的覆盖 I1。 - **3 项全库计数快照按实测刷新**(`object-effects` 142 次/41 种 + 27 次/10 种、`effect-graph` 27 条、`verify-real-library` 169)。**非代码回归**:同一份素材在 `d27975e` / `v0.5.0` / 当前 HEAD 三种代码状态下算出**相同的引用次数**(三方 A/B,见 §7.11)。 - 已消失:`wasm-renderer` 7 项(死路径删除)+ `scene-renderer` 6 项(陈旧 2048 期望改为实际 4096 口径)。 - 另修:`tests/shader/glsl-to-naga.test.ts` 曾让**全量 vitest 永久挂起**,随死路径删除后全量 ~15s 跑完。 - CI 用 `scripts/check-known-failures.mjs` **只对新增失败判红**(空基线下全绿 exit 0;注入失败 → exit 1,已变异验证)。 - **验证手段**: - 单测:`tests/**/*.test.ts`(默认 node),`tests/dom/**` 走 jsdom。 - 全库解析回归:`tests/verify-real-library.test.ts`(全库 scene.pkg 的 scene.json / image 纹理 / particle 规格 / 效果链解析零失败;**依赖本机壁纸库**)。 - **端到端渲染(推荐)**:**`e2e/`(2026-09-22 起入库)** —— 自起 http server + headless Edge(自己 spawn + 手写裸 CDP,不依赖 playwright)+ esbuild 打包 harness,**用生产 `lib/`** 渲染真实素材并逐像素判定,**不依赖 DSH token**。参数走 `e2e/config.mjs`;判据失败**置非零退出码**。`npm run e2e:colorblend` / `e2e:hidpi` / `e2e:compare`。 - **CI**:`.github/workflows/ci.yml`(tsc + `lib/` 产物新鲜度 / vitest 新增失败 / Windows 渲染 e2e)。**未在真实 GitHub runner 上跑过**(本机无推送环境)。 - 全库浏览器回归:`research/verify-wasm-render.mjs` —— **已失效**(硬编码 `?token=` 过期,401),未入库。 - **README 效果素材的录制脚本**:`research/dsh-record/record-showcase.ps1`(真实 DSH 页面 + ffmpeg gdigrab 录屏,裁掉地址栏)。 - ⚠️ **README 里不要用 `