Appearance
城邦的户籍重划
背景:scene/ 下 22 个 scene-* 文件混在一起,按功能找文件靠记忆。
过程:按 camera/motion/manager/env/render 归档 + 相机 UI 迁入动作弹窗。
一、地图的混乱
外交官把一张巨大的地图铺在议会的长桌上。
地图上标注着二十二个城邦——全叫 scene-*。
"诸位请看," 他说,"这里有 scene-vmd、scene-proc-motion、scene-lipsync、scene-playback——四个城邦都带 scene 前缀,但实际上它们管的是动作。"
议长皱眉:"它们不是都在 scene 目录下吗?"
"名字在 scene 目录下," 外交官说,"但它们的事务——VMD 加载、程序化动作、口型同步——全是动作域的。就像一个叫'卫生部'的部门实际上在管教育。"
他翻到另一页:"再看这些——scene-model、scene-material、scene-loader。它们带 scene 前缀,但管的是模型的生命周期和材质。而 scene-env-water、scene-env-clouds、scene-env-particles——这些倒是真的在管环境。"
审判长从后排开口:"你在说——目录结构撒了谎。"
"比撒谎更糟," 外交官说,"它在误导 AI。当你说'改一下程序化动作',AI 看到二十二个 scene-* 文件,它不确定该去 scene-proc-motion.ts 还是 scene-vmd.ts——因为它们都叫 scene。"
二、重划户籍
"解决方案很简单," 外交官在地图上画了五个圈。
第一个圈:camera/
"相机是独立的城邦。它管轨道、自由飞行、演唱会模式。" 他把 camera.ts 圈了进去。
第二个圈:motion/
"动作桥接层——直接操作 Babylon.js 场景对象的那部分。" 他把 scene-vmd → vmd-loader、scene-proc-motion → proc-motion-bridge、scene-lipsync → lipsync-bridge、scene-playback → playback 全部圈了进去。
第三个圈:manager/
"模型管理器——管模型的生命周期、材质、加载。" 他把 scene-model → model-manager、scene-material → material、scene-loader → model-loader、scene-model-ops → model-ops 圈了进去。
第四个圈:env/
"环境系统——天空、水面、云、粒子、道具。" 他把八个 scene-env-* 文件和 env-lighting、scene-props 圈了进去。
第五个圈:render/
"渲染管线——后处理、光照、性能。" 他把 scene-renderer → renderer、scene-lighting → lighting、scene-performance → performance 圈了进去。
"剩下的两个留在顶层——scene.ts 是编排入口,scene-serialize.ts 跨越多个域。"
织工举手:"文件名前缀也改?"
"必须改。" 外交官说,"scene-vmd 改成 vmd-loader,scene-proc-motion 改成 proc-motion-bridge。AI 判断代码归属靠的是文件名前缀,不是目录。"
三、相机的搬迁
议长突然想到一个问题:"等等——相机 UI 之前在哪里?"
"在场景菜单里," 外交官说,"但相机跟场景保存/加载/后处理没有关系。它跟动作更近——用户在动作弹窗里绑定 VMD 后,自然想调整相机轨道。"
"所以你要把它搬到动作弹窗?"
"是的。scene-camera-levels.ts → motion-camera-levels.ts。场景菜单删掉相机入口,动作弹窗在'音乐'上面加一个'相机'。"
审判长沉思:"这不只是搬家,这是重新定义域的边界。"
"没错," 外交官说,"域的边界不是由文件位置决定的,而是由谁在使用这个功能决定的。用户在动作弹窗里操作相机,相机就应该在动作弹窗里。"
四、滑块的简化
搬迁完成后,织工发现了一个恼人的模式。
"每个 slider 都要写 9 个参数," 她抱怨道,"其中第一个回调永远是 () => {}——空的。就像每次寄信都要在信封上写'此信不需要实时预览'。"
外交官笑了:"所以我加了 sliderRow。"
typescript
// 改前:9 个参数,空回调占位
addSliderRow(c, '景深', val, 0, 1, 0.05, () => {}, 'lucide:camera', (v) => {...})
// 改后:8 个参数,无空回调
sliderRow(c, '景深', val, 0, 1, 0.05, 'lucide:camera', (v) => {...})"还有 toggleRow," 他补充道,"把 triggerAutoSave() 内置了。"
织工试了试:"确实短了一行。但你为什么不改 addSliderRow 本身?"
"因为那是加法,不是改法," 外交官说,"旧代码不动,新代码自然会选更短的。这就是——"
他顿了顿。
"让所有 AI 都愿意加的函数,不是逆天的重复函数,而是看到就想用的便利函数。"
五、根级的迁移
最后一步是把动作弹窗的根级从 renderCustom 迁移到 items。
"环境弹窗的根级已经是 items-based 了," 外交官说,"buildEnvRootItems() 返回 PopupRow[],refreshEnvRoot() 重建 items 后调 reRender()。"
"动作弹窗也一样——buildMotionRootItems() 返回 items 数组,refreshMotionRoot() 重建 items 后调 reRender()。"
"这样 reRender() 就能走 patchPanel 增量路径,而不是全量重建。"
议长点头:"从'摧毁并重建'变成'按门牌号送信'。"
"正是。"
六、尾声
会议结束时,地图已经变了样。
二十二个 scene-* 城邦消失了。取而代之的是五个清晰的域——camera、motion、manager、env、render——每个域的名字直接告诉 AI 它管什么。
外交官收起地图,说了最后一句话:
"今天做的事很简单——让名字说出真相。文件叫 motion/vmd-loader,AI 就知道它是动作域的。文件叫 scene/vmd-loader,AI 会犹豫。名字不是标签,名字是承诺。"
对应技术变更:
- 22 个
scene-*文件按业务域拆分为 camera/motion/manager/env/render 子目录 scene-camera-levels.ts→motion-camera-levels.ts,相机 UI 从场景菜单迁入动作弹窗- 新增
sliderRow()/toggleRow()便捷函数,消除空回调样板 - motion root 从
renderCustom迁移到items-based - 全部 import 路径更新(56 内部 + 9 外部)
- AGENTS.md + menu-architecture.md 文档同步更新
- TypeScript 零错误,362/363 测试通过