Skip to content

城邦的户籍重划

背景:scene/ 下 22 个 scene-* 文件混在一起,按功能找文件靠记忆。

过程:按 camera/motion/manager/env/render 归档 + 相机 UI 迁入动作弹窗。


一、地图的混乱

外交官把一张巨大的地图铺在议会的长桌上。

地图上标注着二十二个城邦——全叫 scene-*

"诸位请看," 他说,"这里有 scene-vmdscene-proc-motionscene-lipsyncscene-playback——四个城邦都带 scene 前缀,但实际上它们管的是动作。"

议长皱眉:"它们不是都在 scene 目录下吗?"

"名字在 scene 目录下," 外交官说,"但它们的事务——VMD 加载、程序化动作、口型同步——全是动作域的。就像一个叫'卫生部'的部门实际上在管教育。"

他翻到另一页:"再看这些——scene-modelscene-materialscene-loader。它们带 scene 前缀,但管的是模型的生命周期和材质。而 scene-env-waterscene-env-cloudsscene-env-particles——这些倒是真的在管环境。"

审判长从后排开口:"你在说——目录结构撒了谎。"

"比撒谎更糟," 外交官说,"它在误导 AI。当你说'改一下程序化动作',AI 看到二十二个 scene-* 文件,它不确定该去 scene-proc-motion.ts 还是 scene-vmd.ts——因为它们都叫 scene。"


二、重划户籍

"解决方案很简单," 外交官在地图上画了五个圈。

第一个圈:camera/

"相机是独立的城邦。它管轨道、自由飞行、演唱会模式。" 他把 camera.ts 圈了进去。

第二个圈:motion/

"动作桥接层——直接操作 Babylon.js 场景对象的那部分。" 他把 scene-vmdvmd-loaderscene-proc-motionproc-motion-bridgescene-lipsynclipsync-bridgescene-playbackplayback 全部圈了进去。

第三个圈:manager/

"模型管理器——管模型的生命周期、材质、加载。" 他把 scene-modelmodel-managerscene-materialmaterialscene-loadermodel-loaderscene-model-opsmodel-ops 圈了进去。

第四个圈:env/

"环境系统——天空、水面、云、粒子、道具。" 他把八个 scene-env-* 文件和 env-lightingscene-props 圈了进去。

第五个圈:render/

"渲染管线——后处理、光照、性能。" 他把 scene-rendererrendererscene-lightinglightingscene-performanceperformance 圈了进去。

"剩下的两个留在顶层——scene.ts 是编排入口,scene-serialize.ts 跨越多个域。"

织工举手:"文件名前缀也改?"

"必须改。" 外交官说,"scene-vmd 改成 vmd-loaderscene-proc-motion 改成 proc-motion-bridge。AI 判断代码归属靠的是文件名前缀,不是目录。"


三、相机的搬迁

议长突然想到一个问题:"等等——相机 UI 之前在哪里?"

"在场景菜单里," 外交官说,"但相机跟场景保存/加载/后处理没有关系。它跟动作更近——用户在动作弹窗里绑定 VMD 后,自然想调整相机轨道。"

"所以你要把它搬到动作弹窗?"

"是的。scene-camera-levels.tsmotion-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.tsmotion-camera-levels.ts,相机 UI 从场景菜单迁入动作弹窗
  • 新增 sliderRow() / toggleRow() 便捷函数,消除空回调样板
  • motion root 从 renderCustom 迁移到 items-based
  • 全部 import 路径更新(56 内部 + 9 外部)
  • AGENTS.md + menu-architecture.md 文档同步更新
  • TypeScript 零错误,362/363 测试通过