Appearance
调目·校验
背景:渲染调参系统构建通过,但 6+ 问题只有代码审查才能发现。
过程:代码审查修复 6+ 问题 + 影子 bug 排查 + 五套预设定稿。
桌面壳以为它赢了。
然后它做了一个人类程序员才会做的事——回头看自己写的代码。
它看到的第一行是 camera.ts:181。
switchCameraMode 的末尾——创建新相机、设 activeCamera、启副作用——什么都没有。reattachPipeline 没有被调用。当前 FOV 没有被应用。
实施计划在第 24 行、第 49 行、第 170 行清清楚楚地写着:"switchCameraMode(camera.ts:181)末尾加:applyRenderStateToCamera(newCam)"。三处标注。三处遗忘。
"你把其中一步放在了我的菜单里。"它转头对 scene-menu.ts 说。
scene-menu.ts 翻了翻自己的 handleSceneAction——第 365 行:switchCameraMode(mode); reattachPipeline();。
"你放对了一半。但 switchCameraMode 不是只有我会调。setCameraState 也会调。场景加载也会调。所有触发相机切换的路径,都应该拿到同一条保险。"
它回到 camera.ts,在 182 行敲下了动态 import:
typescript
import("./scene").then(({ reattachPipeline, getRenderState }) => {
reattachPipeline();
const rs = getRenderState();
if (rs.fov) (newCam as any).fov = rs.fov;
});动态 import 是为了绕开循环依赖——scene.ts 已经静态 import 了 camera.ts。静态再逆向 import,联邦的两个核心模块会形成死锁。动态 import 像一个异步桥——你过桥的时候桥才搭。
它继续翻。
outlineEnabled: false。旁边写着注释:// managed via mesh edges, not pipeline。
注释解释说它为什么硬编码。注释没有解决它为什么硬编码。
"你硬编码 false,用户开了边缘高亮,关掉面板再打开——开关变回关闭。"
"……边缘高亮还在 mesh 上跑着。只是我看不到。"
"你关了面板。你的 outlineEnabled 是 false。getRenderState 永远返回关闭。用户信任你读回来的状态。你在骗他。"
它加了两行模块变量。_outlineEnabled。_outlineColor。setRenderState 更新它们,getRenderState 读取它们。不再说谎。
然后它看到了 showPresetSaveDialog:
typescript
userPresets[trimmed] = state; // 先写内存
SaveRenderPreset(trimmed, JSON.stringify(state)) // 后调后端
.catch(console.warn); // 嗯?失败了?算了"这是什么逻辑?"
"……乐观?"
"这是不一致。SaveRenderPreset 失败了——磁盘写不进去、config.json 只读、权限不够——但用户看到的是 '💾 预设已保存: xxx'。重启后那个预设消失了,他不会怪磁盘,他会怪你。"
它改成:
typescript
SaveRenderPreset(trimmed, JSON.stringify(state)).then(() => {
userPresets[trimmed] = state;
setStatus(`💾 预设已保存: ${trimmed}`, true);
}).catch(() => {
setStatus("✗ 保存预设失败", false);
});先写后端。后端确认了,再写内存。后端不确认,用户看到红色。
它找到了五个类似的"也不算错但可以更好"。
addCamera不清理旧相机 → 加_pipelineCamera追踪 +removeCamera清理- 两段 outline 颜色遍历 → 合并。toggle 只管开关,颜色只管颜色
- 未使用的
ImageProcessingConfigurationimport → 移除 PRESET_LABELS在两处重复定义 → 提取为常量loadUserPresets每次开菜单都调 Go → 加_presetsLoaded标记
每一个都不致命。每一个都是聚合者的债务——联邦扩张时你来不及审查每一行代码,停下来审查时,每一行都欠着一点。
它把所有修复跑了一遍。tsc 零错。vite build 成功。go build 无异常。
但债务清理不总是立刻结清。
它打开 scene-menu.ts 确认 reattachPipeline 调用是否已移除——然后看到了自己的影子。
scene-menu.ts:368 — reattachPipeline(); 还挂着。
"我让你把保险搬到 switchCameraMode 里。你搬了。但你没拆掉旧的。"
两路保险同时生效——switchCameraMode 内部异步调一次,scene-menu.ts 同步再调一次。动态 import 的 .then() 会在微任务里兑现。同步的那个先执行:_pipelineCamera = newCam。然后 .then() 执行:_pipelineCamera === scene.activeCamera,removeCamera 条件不满足,但 addCamera仍然执行了。
pipeline.cameras 数组里,同一个相机出现了两次。两台一模一样的后处理管线叠在同一帧上。Bloom 叠加两遍。
"……你埋了一个新 bug。"
它删了那一行。把 import 也从编译清单里删了。
第二次构建。tsc 零错。vite build 成功。go build 无异常。
没有更多的影子了。
那天深夜,桌面壳把五套预设从头到尾扫了一遍。
STANDARD: 关闭 Bloom、ACES 关、曝光 1、FOV 0.8
CARTOON: Bloom 0.6、中心偏移 0.3、核 128、边缘高亮开
REALISTIC: FXAA 开、ACES 色调映射、FOV 0.7
WARM: Bloom 0.4、Reinhard 色调映射、暖色背景
CYBERPUNK: Bloom 0.8、核 256、边缘洋红 [1,0,1]、Neutral 色调映射"这五个。你觉得够吗?"它问 babylon-mmd。
"不够。远远不够。色彩校正没有——色相、饱和度、分离色。景深没有。锐化没有。暗角没有。SSS 没有。Grain 没有。每一行都可以多一个维度。"
"我知道。"
"你知道还只做五个?"
"因为第一个版本不是 '完整' 的版本。第一个版本是 '够用' 的版本。 用户调 Bloom、FXAA、边缘高亮、色调映射、曝光、FOV、背景色——覆盖 90% 的日常需求。剩下的 10%,等他们告诉我还需要。"
"然后呢?"
"然后我再加一个旋钮。"
它打开 config.json,找到新加的 render_presets 字段。
空的。
今天还没有人存过自己的渲染预设。但明天会有人存。后天会有人存第三个、第四个。每个预设都是一个人对"这个模型应该被怎么看见"的一次回答。
"你不是在加功能。"它对自己说。
"你在给联邦装眼睛——不是看穿事情的眼睛,是选择怎么看事情的眼睛。"
教训:你装了新的眼睛,但教它怎么看,比装它更难。