一、为什么需要调参台

先看没有调参台的世界。E017 的真机失败时间线(原文摘录):

v0  崩溃:跨组件 @BuilderParam 读宿主 this.items
v1  不跟手 / 左卡依次消失:外层 Scroll 抢势 + 离散步进
v2  Scroll 隐形轨道:仍不跟手;按钮无响应
v3  controller 去掉 @Prop + 轮播移出竖向 Scroll + onTouch:逻辑通
v4  setInterval 动画:文案有 anim,UI 无帧;松手误判 tap-snap
v5  ForEach 快照几何:文案对、UI 冻住 → 改为 CardNode @Watch geo*
v6  Slider 无效 → live* + paramEpoch
v7  文字跳动 → 固定 width/height + .scale

注意 v4→v5:「文案有 anim,UI 无帧」——状态全对、日志全对、画面冻住。这种问题靠肉眼和感觉定位,一次就是半天。而 v6「Slider 无效」是另一类:调参控件本身不生效,你以为在调参数,其实什么都没发生。

调参台模式就是对这两个深渊的回答:诊断信息必须长在画面上,参数必须可热改且可验证生效。 它不是 Demo 页——AGENT.md 里写得很清楚:Lab 是「可视化验收与标定,不是业务壳」。

二、诊断优先:文案 × 画面 2×2 矩阵

E017 沉淀的第一件工具:在组件区域盖一层只绑 @State string 的 debug 文案,实时打出状态机关键值:

down → idx=1.23 → snap→2 → anim 1.23→2 → done 2

然后就有了这张定位表:

debug 文案变化

画面动不动

结论

会变

会动

通路正常

会变

不动

渲染绑定问题(E017 主因:ForEach 快照几何,@Watch 派生缺失)

不变

不动

手势 / Controller 根本没进组件

不变

会动

文案绑错了状态(少见,但出现过)

两行就能把「没动画」这个笼统描述切成四个互斥的假设。E017 笔记的原话是:「不要再用『感觉没动画』笼统描述;用 debug 文案把问题切成半。」

这个矩阵后来成为所有 Lab 的标配入口:新组件先加 debugLine,再调观感。先证明通路,再打磨表现——顺序反了就是在冻住的画面上凭空猜几何参数。

三、状态自证:组件每帧报告自己走了哪条路

debugLine 的进化形态是「状态自证」:不只报状态,还报绘制路径。E019 BookFlip 是完整形态,组件每帧拼一行:

this.debugLine =
  `${openTag} · spread ${this.driver.spread + 1}/${Math.max(1, total)} · ` +
    `pages=${this.livePages().length} · ` +
    `p=${this.driver.progress.toFixed(2)} · ` +
    `cp=${this.coverProgress.toFixed(2)} · ` +
    `${this.driver.active ? (this.driver.direction === FlipDirection.Forward ? 'fwd' : 'back') : 'idle'} · ` +
    `${this.scene.lastApi}`;

每个字段都在证伪一类故障:

字段

证伪什么

openTag(closed/opening/open/closing/no-cover)

封面状态机卡死

spread x/y

spread 映射算错(页码跳变、越界)

p / cp

翻页进度与封面进度是否互斥(同时非零 = 双动画互踩)

fwd/back/idle

方向判定与松手后的 settle

lastApi

绘制层走了哪条分支

lastApi 是点睛之笔——它由 Painter 在每帧绘制时写入,枚举了所有可能的分支出口:

'idle_spread'                     静止:平铺双页(并释放 atlas)
'leaf soup n=140 1draw'           翻动中:140 个三角一次 drawVertices
'closed_cover_left'               封面闭合休位
'cover_anim open cp=0.42 …'       封面开合中
'atlas_failed' / 'need_pages'     资源缺失:atlas 建失败 / 页数不足
'cover_leaf_err:…'                封面叶子异常(带序列化错误)

这解决的是一个很阴的问题:绘制代码有多个分支,你不知道真机此刻走的哪条。「画面不对」时看一眼 lastApi,立刻知道是走了降级分支还是主路径、三角数是不是预期的 140。E017 教训「文案动、画面不动」在这里被补全成了「文案必须包含绘制层的自证」——状态对了但 lastApi 是 atlas_failed,你一眼就知道问题在纹理不在几何。

配套原则(E019 P4 验收项原文):诊断文案必须与真实几何一致。不允许出现「诊断说在 spread 3、画面停在 spread 1」——那说明诊断绑的不是驱动画面的那个变量。自证的价值全部来自「它读的就是绘制读的」。

四、参数热更新:live 与重建,写明并防抖

调参台的核心交互是改参数。E017 v6 的「Slider 无效」事故揭示:不是所有参数都该 live 生效。ArkUILab 把参数分成两类,并在 Lab 里显式标注:

类别

例子

生效方式

live

physics(commit 阈值/弹簧)、主题、色板、状态

@Prop @Watch 直接驱动,拖动即时可感

需重建

mesh 分辨率、页数、内容源(Generated/Snapshot)、页宽比

epoch 重绑或 remount,切换期间禁用操作

E017 的修法是 live* 快照 + paramEpoch:Slider 拖动只改 live 值,松手或防抖后才进需要重建的路径。E019 BookFlipLab 换页数/换内容源时的守卫是另一形态——rebuilding 状态直接把所有操作按钮禁掉:

Button(label)
  .enabled(!this.rebuilding)   // 重建期间不允许再触发
  .onClick((): void => { action(); })

调参台的验收项因此有一条硬标准(E019 P4 原文):「参数热更新策略明确(哪些 live、哪些需重建)」。一个参数属哪类不是秘密,要写在 Lab 的说明里——否则下一个调参的人会以为 Slider 坏了。

五、内容源切换:把「输入」也做成变量

调参台不只是参数面板,还要能换喂给组件的东西。E019 的内容源三档:

Generated   程序生成色块页(秒出,验几何/状态机)
Snapshot    @Builder 快照富文本页(createFromBuilder,验真实内容)
调用方自备  PixelMap[](业务接入形态)

先 Generated 再 Snapshot 的顺序有讲究:几何问题和内容问题必须分开验。色块页上穿帮一定是几何错;富文本页上穿帮先怀疑快照/镜像/UV。E019 P4 还规划了网格纹理模式——专门暴露 seam(接缝)和镜像错误,因为纯色/图片内容会把这些缺陷藏住。

同理,ThinkingOrb 的源码注释里有一句:视觉速度「wildly off 时,调 preset speeds 的 divisor,步骤见 Runbook」——标定值和标定方法都固化在文档里,下一个人不用重新发现。

六、异步竞态防护:代际计数、失败回滚、延迟释放

Lab 大量处理异步资源(生成页、快照、解码),三个防护缺一不可,全部来自真实翻车:

代际计数(generation guard):用户连续切换「6 页→12 页→Snapshot」时,最早的异步任务可能最后完成。每个切换请求带上代号,回调时先验码:

private bootGen: number = 0;

// 发起重建
const gen: number = ++this.bootGen;
buildPages(...).then((pages) => {
  if (gen !== this.bootGen) {
    return;               // 过期结果直接丢弃
  }
  ...
});

失败回滚:新页构建失败时不能白屏——旧页列表还在,回滚并保持可用:

private failBoot(gen: number, previous: Array<image.PixelMap>, error: Object): void {
  if (gen !== this.bootGen) { return; }
  this.rebuilding = false;
  this.statusLine = `Page build failed: ${JSON.stringify(error)}`;
  if (previous.length >= 2) {
    this.pagesHolder.items = previous;    // 回滚旧内容
    this.pagesEpoch++;
    this.ready = true;
  }
}

延迟释放:换源成功后旧 PixelMap 延迟 500ms 再 release——最后一帧可能还在引用它,立刻释放就是 use-after-free 式黑块。这与翻书组件内部「idle 才释放 atlas」是同一条纪律在不同层的应用。

七、验收纪律:最低线、确认线、沉淀线

调参台跑出来的结论怎么算数?ArkUILab 的三级验收:

  1. 最低线(机器):每次改完代码跑 HAP 构建,CompileArkTS + PackageHap + BUILD SUCCESSFUL 三项全过才算「构建通过」。不过不许进真机——构建都不过的观感描述没有意义。

  2. 确认线(人):真机操作 Lab 调参台,按 checklist 过交互(拖拽跟手、commit/回弹、边界 peel、换页不闪断),用户确认满意才标记 Verified。E019 的 Verification Log 里「用户确认开合体验满意」就是这么来的。

  3. 沉淀线(文档):真机标定出的数值与故障写进对应 Runbook / Device Lessons(docs/notes/E0XX-*.md),如 E017 的 v0–v9 时间线、E018 的五轮排雷表。没有写下来的真机结论等于没发生——下一个改这个组件的人(多半是几个月后的你自己)只能全部重踩。

八、调参台清单

把全文收口成可抄走的 checklist。新组件验收前逐项打勾:

  • 画面上有 debugLine,绑定驱动绘制的同一个状态变量

  • debugLine 含绘制层自证(lastApi / 分支标记),能区分「状态对但走了降级分支」

  • 参数分 live / 重建两类,Lab 说明里写明归属

  • 需重建的参数切换期间禁用操作(防抖与互斥)

  • 内容源至少两档:程序生成(验几何)+ 真实内容(验集成)

  • 网格/纯色模式暴露 seam 与镜像(如有 UV/纹理)

  • 异步任务带代际计数,过期回调直接丢弃

  • 构建失败可回滚到上一个可用状态

  • 旧资源延迟释放,不在最后一帧引用内回收

  • 构建通过 → 真机 checklist → 结论写进 Runbook,三级全过才叫验收

结语

「Lab 调参台」本质上是把科学的实验方法装进了应用工程:诊断矩阵是控制变量,状态自证是仪器读数,live/重建分类是实验设计,代际计数是样本隔离,Runbook 是实验记录。它看起来让每个组件多花了一天,但 E017 那份 v0–v9 时间线能存在、E018 的五轮排雷能被复述、E019 的封面语义能被固化——全都因为当时画面上有一行会说话的 debugLine。

写得好的组件是资产,验收得好的组件才是可维护的资产

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐