台架实机会话 2026-11-02(bench 档 fw 22:25:12 / 产品档 22:29:56,均经 /serial-boot-test 闭环:身份戳匹配 + 回读 no-difference): - 复核② 收列键语义:菜单态按 s 与按 l 同走 menu_back、均收 一级且父列选中留在原地(depth 1→0、sel 不变)——旧断言 「菜单态按 s 不收列」是把待机态 s 无效(task-24 实证)误 读成菜单行为。to_root() 据此在 STATUS depth>0 时按 s 逐级 收(弹框被屏裁时像素 n_cols 漏计、固件 depth 不漏),老固 件像素路径保留 l 兜底仪式。§7.2 纪律与 board-ui-nav skill 恢复配方同步改写。 - 复核① 被裁弹框两叶:菜单步进依旧吃不到(枚举落同胞页, 落点由 STATUS 如实报出、不再谎称到达);GOTO 直开两叶到 达且完整渲染(自检记录 ink 4073 / 运行记录 ink 3559)→ KNOWN_UNREACHABLE 对 --to 保留,绕行路径 --goto。§7.4 改写。 - 产品档(未加宏)实测:STATUS 照常应答(不设闸)、GOTO 回 status=4——生产/bench 行为差异真机钉死;§7.6 增实机验证记 录段。回归:--selftest-quick 66/66+闸 7/7;--selftest 闭环 失败集与 master 基线逐项一致(6 存量像素 + 2 执行类拒绝)。
23 KiB
UI 自动化测试流程(模拟器回归)
用途:所有涉及 UI 的改动(菜单结构 / 内容页 / 字体 / 样式 / 几何)改完代码后,按本文跑一遍全部门禁再提交。脚本已入库,直接调用即可,无需临时重写。 定位:
UI_界面制作流程.mdP2/P3 门禁的可执行版本;设计阶段(P0 规格 / P1 编码)走UI_界面制作流程.md。
一、前置条件
| 项 | 说明 |
|---|---|
| 模拟器已构建 | 仓库根目录:& "simulator\build_sim.bat"(自动探测本机 VS2019/2022/2026 生成器,见 simulator/README.md §3.2) |
| Python | 统一入口 .venv\Scripts\python.exe(AGENTS.md「Python 环境」);两个回归脚本纯标准库,任意 python3 均可,为保持一致一律用 .venv |
二、流程总览
# 仓库根目录执行(python 统一用 .venv,见 AGENTS.md「Python 环境」)
cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警告)
.venv\Scripts\python.exe simulator/tests/check_menu_rows.py # 1) 菜单 8 状态像素回归 + 池基线门禁
.venv\Scripts\python.exe simulator/tests/check_content_pages.py # 2) 内容页黄金参考回归 + 往返池门禁
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 3) Keil 构建(BUILD_OK,0 错 0 警)
.venv\Scripts\python.exe scripts/check_coding_standard.py # 4) 代码规范机械检查(PASS)
| 步 | 脚本/命令 | 门禁内容 | 预期输出 |
|---|---|---|---|
| 1 | simulator/tests/check_menu_rows.py |
菜单 8 状态:选中行白带 / 非选行黑底白字 / 框体 1px 边框 / 往返像素一致(MENU-STATE-03==MENU-STATE-01、MENU-STATE-06==MENU-STATE-01、MENU-STATE-07==MENU-STATE-02)+ MENU-STATE-00 池基线 | PASS: 8 状态全部断言通过(8 图,池基线 5368 B) |
| 2 | simulator/tests/check_content_pages.py |
① 文本级断言(字形模板匹配,正向 444 条 + 反向 45 条,独立于 ref/)→ ② 66 张截图与 ref/ 黄金参考逐字节比对 + 开页-关页往返池水位 |
PASS: 66 内容页 + 往返池水位全部通过(文本断言先于像素比对,失败即中止) |
| 3 | keil-build skill(keil_build.py) |
MCU 端编译(UI 源码双端共用,改一端必须过另一端) | 退出码 0 = BUILD_OK |
| 4 | scripts/check_coding_standard.py |
代码规范 V1.0 机械检查 | PASS |
文本级断言层(2026-09-10 新增):
check_content_pages.py内置字形模板匹配——直接解析ui_font_cn_12.c的 cmap/glyph_dsc/bitmap,把期望字符串(标题/标签/值/页脚,含新增易缺字文案) 渲染成墨点集合,在截图对应 y 带内做精确匹配(LVGL 基线对齐)。价值:缺字显示空白 / 标签写错 这类回归不再只靠像素位图(那需要刷参考,且无视觉后端时难以判读);文本断言不依赖ref/,且 先于像素比对运行,失败即中止(不误刷参考)。文本断言覆盖确定文案,像素参考仍负责几何/样式/ 光标反显等其余视觉。规模与覆盖面(2026-10-26 实跑check_content_pages.py取数):文本断言 310 条、覆盖当时 40 页中的 37 页——pwd/pwe/pwc三张密码态只进像素比对、未登记文本断言,改这几页的文案没有文本层兜底。 2026-10-28 再取数(接线道,PAGES60 项时实测):正向 425 条 / 反向 36 条、覆盖 60 张中的 57 张(仍缺那三张密码态)。更正留痕:上一句登记的是「2026-10-26 实跑取数 310 条」,而当时脚本 的真值是正向 380 / 反向 29——历史行按当时冻结不改数,差值只在这里说明,避免下一个人再抄一遍旧数。
退出码约定:两个脚本均为 0=PASS / 1=断言失败 / 2=环境错误(exe 缺失、参考缺失)。 失败时脚本打印差异明细(文本断言未命中项 / ink runs 对照 / 像素差数),内容页脚本保留现场截图于临时目录并提示路径。
三、内存水位门禁常量
| 常量 | 所在脚本 | 当前值 | 含义 |
|---|---|---|---|
MENU_POOL_BASELINE |
check_menu_rows.py | 5368 B | 菜单根框建完的 LVGL 池 used(x64 模拟器) |
POOL_AFTER_ROUNDTRIP |
check_content_pages.py | 5448 B | 开页-关页往返后水位 = 基线 + 一次性残留 80 B |
水位历史:4784 →(2026-09-14 待机模块常驻 +656)5440 / 5520 →(2026-10-11 裁剪 LVGL 控件类 −72)5368 / 5448。 后一次漂移已核实为有意且更优:像素逐字节未变(裁剪当时黄金参考只有 11 页,在裁剪前生成、裁剪后全部一致;该集合现已扩至 66 张), 且一次性残留仍是 80 B(5448 − 5368)→ 泄漏特征未变,只是
lv_theme_apply不再为已不存在的控件类登记样式条目。
漂移处理:菜单/页面结构或样式变更导致门禁失败时,先确认新值合理(残留分解见
菜单页面设计.md §9.5),再更新脚本常量,并在上面的常量表里把当前值与它的实测来源写清(改了什么是正文说了算,过程查 git log)。禁止为过门禁盲目改常量——
无法解释的漂移 = 内存回归 bug。
四、黄金参考(入库)与目检 PNG(本地产物)
simulator/tests/ref/*.bin(像素黄金参考,66 张 × 3200 B)随仓库入库;派生的 .png
目检图仍是本地产物(.gitignore)。入库的理由:比对是逐字节的,基线不入库时任何新克隆
或新工作树跑 ui-golden 都必然 FAIL(缺基准),这条门禁就只在"有老副本的机器"上真实存在。
可复现性实测(2026-10-22):两份各自独立构建的 lvgl_sim(不同构建目录、相隔 11 天的代码)
对同一批 11 个 .bin SHA-256 全部一致 → 渲染是 CPU 侧确定性输出,基线可以进仓库。
换机后不需要再生成基准;只有有意改视觉时才 --update(改动会进 git diff):
.venv\Scripts\python.exe simulator/tests/check_content_pages.py --update # 生成 ref/*.bin
.venv\Scripts\python.exe simulator/tests/regen_ref_png.py # 生成 ref/*.png(--scale 6 --invert)
有意改视觉时的流程:
- 先肉眼确认新效果正确:窗口模式运行
simulator\build\Debug\lvgl_sim.exe,或刷新后跑regen_ref_png.py看图对照 mockup; - 刷新参考:
.venv\Scripts\python.exe simulator/tests/check_content_pages.py --update(重写ref/*.bin); - 重生成目检 PNG:
.venv\Scripts\python.exe simulator/tests/regen_ref_png.py。.png是本地调试产物、不会随.bin自动更新——不重跑则 PNG 仍是旧渲染(缺字/错位,看似"乱码")。 默认--scale 6 --invert(深字白底便于目检);--no-invert= 白字黑底(与设备一致); - 提交
ref/*.bin的改动(它已入库,等于改基准):git diff --stat simulator/tests/ref必须
误刷保护:--update 会先跑文本级断言,断言失败即中止,避免把缺字/错标签的渲染"刷成"新基准。
何时不该更新:未有意改视觉而某页 FAIL → 是 bug(缺字、几何漂移、样式回归),修代码而不是刷参考。 菜单状态无黄金参考文件——其断言是结构性的(白带占比/边框/往返一致),不依赖入库位图。
五、误报排查
| 现象 | 常见原因与处置 |
|---|---|
| 多页大面积像素差异 | 字体重生成(ui_font_cn_12.c 变化)→ 确认字形正确后 --update 刷参考 |
| 单行/单字差异 | 几何或文案变更 → 先判断是否有意修改;无意 = bug |
| 池水位门禁失败但像素一致 | 样式/结构变更(如动态样式转常量、增删框体)→ 确认新值合理后更新常量并登记 |
只有 netcfg_save 一页失败(保存行值列是 ERR 而非 OK),其余 26 页全过 |
不是代码回归,是工作目录被污染:模拟器按当前工作目录读写 sim_config.ini,而 --cfgtest 超长守卫的场景 2 会伪造一份 4000 B 纯注释"雷"文件、并断言"拒写后原文件不动"——连通过路径也会把它留在原地。之后每次运行都加载它 → 外科手术保存要在 4000 B 注释后补写受管段 → 输出必超 CFG_FILE_MAX_LEN → 引擎按设计拒写 → 保存行显示 ERR,与"工厂默认态"的黄金参考对不上。处置:两套回归已改为在空临时目录里跑(传 cwd=tmp),--cfgtest 已加外壳无论成败一律删除伪造档;手工跑 lvgl_sim 时删掉当前目录的 sim_config.ini 即恢复。禁止为此刷黄金参考(参考是对的) |
找不到 lvgl_sim.exe |
模拟器未构建或 build/ 被删 → 先按 §一 重建 |
| 终端输出中文乱码 | PowerShell 代码页渲染问题,不影响判定(看 PASS/FAIL 与退出码) |
六、与其他文档的关系
UI_界面制作流程.md:P0 需求录入 + 设计规格 → P1 规格→代码 → P2 本文回归 → P3 MCU 上机目检 → P4 归档菜单页面设计.md§9:内容页对象树 / 几何表 / 内存预算(本文数字的来源)simulator/README.md§3.5/§3.6:无头模式(--keys/--screenshot)与回归脚本说明
七、板端有状态导航判据(真机侧,scripts/board_ui_nav.py)
前五节都在模拟器里跑;模拟器与固件同一份 UI 源码,像素判据在 PC 上就成立。本节管另外 一半:真机上把页面走到、并证明走对了——这一步只有真机需要(模拟器侧把键序直接喂给 无头进程,状态是确定的)。
7.1 为什么必须闭环(实测 2026-10-25 大窗)
- RemoDispBus 的 KEY 应答是空帧、不携带任何状态:发了 5 个键不等于到了第 5 层。
- 靠预置键序盲走,9 个「应当到达」的态里只有 3 个真是那个态:
| 留档帧 | 文件名声称 | 实际是 | 墨量 | 证据 |
|---|---|---|---|---|
| 00 / 01 / 02 | 根菜单 / 装置信息 / 交流量 | 名副其实 | 3226 / 3476 / 3346 | 认屏一致 |
| 03_dc | 直流量 | 菜单级联(整屏无页标题) | 4961 | blindwalk_cascade |
| 04_yx / 05_yxp2 / 06_soe | 遥信量两页 / SOE记录 | 密码输入页 | 512 / 515 / 512 | blindwalk_pwd512 |
| 07_vparam | 参数查看页 | 网口设置页 | 3774 | 标题带命中「网口设置」 |
| 08_vset | 定值查看页 | 装置信息页 | 3474 | 标题带命中「装置信息」 |
那三帧的 512 墨量曾被记成「神秘空白态」;与黄金参考 shot_pwd.bin、shot_pwe.bin(墨量
同为 512)逐字对得上之后可以定性:走带 #P 注记的叶子时,第一屏是要密码,不是内容页
——写路径判据(批 5)因此必须把「过密码」当显式状态处理,口径见 §7.5。
- 结论:判据脚本只能信画面。脚本因此每按一键重新认屏再决定下一个键,且「到达」只由 页面身份裁定;中途任何假设都写进轨迹,绝不当成结论。
- 2026-11-02 #69 复犯与收口:修 #38(回放页枚举恒空)的会话里又走了弯路——当时没有走
本脚本,而是临时手搓
board_walk*.py盲按,落错页后反复重来,浪费大量调试时间。四条根因 (协议无身份回读 /sleep(0.30)抓帧拿到 N−1 屏 / 待机·光标·被裁弹框三个物理陷阱 / 技能 路由缺位)与成熟项目对照(accessibility 树优先于截图、auto-wait 断言、可复用工作流)见 issue #69 评论区;前两条已在代码里收口(§7.6 的 STATUS 与排空双帧),后两条靠board-ui-navskill 的「唯一入口」纪律兜住。
7.2 认屏的三条判据(精确字形匹配,复用同一份 ui_font_cn_12)
| 态 | 判据 | 为什么这样判 |
|---|---|---|
| 内容页 | 标题带命中 + 该页黄金行文本至少一条 + 页码行「第 N 页,共 M 页」 | 只认标题会把两屏认成一屏(「参数」既是菜单项也是页名);页码让「遥信量第 2 页 / 共 5 页」成为可比对的事实 |
| 菜单 | 在白底横条(反白选中行)覆盖的行范围内、整幅宽度找反白标签;长标签优先,命中框被包住者丢 | 选中行是黑字白底,只按正常极性永远读不出选中项;「参数定值」那行必然同时落进子串「参数」「定值」,不去重就凭空多两列 |
| 密码页 | 「用户密码」四个字里中三个 | 整串匹配实测差约 1 px 打不中;数字格随输入变,不参与判定 |
另两条实测纪律(理由写死在代码注释里,此处不重述):
- 收级联列的键语义已台架复核定型(2026-11-02,#69):菜单态按
s与l同走menu_back,都收一级且父列选中留在原位(实测 depth 1→0、sel 不变)——旧断言「菜单态按s不收列」是把待机态s无效(task-24 实证)误读成了菜单行为;ssssssss盲按回根不可信这个结论仍然成立,不可信的是"不看回读连按",不是键本身。有 STATUS 时按 depth 收(弹框被屏裁时像素n_cols会漏数、固件 depth 不会),老固件继续按l收 + 验终点; - 菜单树的
#P注记可能写在#page=之前(网口设置 #P #page=net_cfg),解析顺序错了就 剥不掉,那一叶的页面 id 也永远认不出来。
7.3 用法与门禁档位
# 认屏快检:66 张黄金参考(+ 若有台架留档帧),秒级,不需要构建产物
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --selftest-quick
# 完整自检:再加全绑定页闭环导航(需模拟器可执行文件,约 30 s)
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --selftest
# 台架窗口:一条命令走完全部绑定页并逐页存证
# ⚠ 真机动作必须显式确认已持 bench 锁,否则脚本直接拒绝(不碰网络,退出码 3)
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --walk --i-hold-bench-lock
# 只走一页并抓图;--trace 落步迹、--dump-fail 落「认错的那一屏」,--outdir 改存证目录
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --board --to yx --grab D:/tmp/yx \
--i-hold-bench-lock --trace D:/tmp/uinav_trace.json
# 我在哪(固件 STATUS 身份 + 像素双口径)/ bench 固件直开页(#69,见 §7.6)
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --where --i-hold-bench-lock
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --goto wave_play --grab D:/tmp/wx \
--i-hold-bench-lock
- 提交钩子
uinav-selftest跑的是快检档(--selftest-quick)。为什么不挂完整档:闭环 那一段要模拟器可执行文件——它是构建产物,新工作树里没有,正是同一天mem-numbers缺.map那课的形状。脚本对「缺可执行文件」报 FAIL 并给可执行提示,不「跳过当通过」; 合入前 / 上板前请手工跑一次完整档(快检 ≠ 闭环,两者结论不同,别互相顶替)。 - 板端后端只连 7003 取图注键,不含串口、烧录、复位;
--board/--walk必须另加--i-hold-bench-lock才会去连——没给就直接拒绝(退出码 3,不碰网络)。这条不是防御性 设计,是同一天踩出来的:跑--walk想看一眼默认存证目录打印成什么,脚本就照着连上 板子注入了按键,而那时台架属于验收窗的准备方。「必须持锁」写在文档里防不住「我只是跑 一下」,所以把确认做成命令行参数:要动真机,就得在命令行里留下字面证据(脚本不代取锁, 也不替人判断「应该没人在用」)。 - 存证不进工作树:
--walk的帧默认落ci-artifacts/uinav/walk/(版本控制之外),--outdir可改;给的路径若落在版本控制内(且不在ci-artifacts之下),脚本直接拒绝 执行并退出 3。理由与提交门禁同一条:产物一旦写进工作树,git status就不再是判据, 而「记得删」只防得住记得的人。 - 步迹只在
--trace <路径>给出时落盘;认错那一屏只在--dump-fail <目录>给出时留存—— 两者都指到ci-artifacts/下,窗口结束把工作目录交回来时工作树仍是干净的。 - 台架留档帧目录在 gitignore 内、不入库:缺就整段跳过,不算失败——但脚本把「没跑」与 「跑过且通过」分开打印,不把前者写成后者。
7.4 已知限度(别把它当万能闸)
- 根列选中落在最后两行时,子菜单弹框会被屏幕裁掉:选带不在可扫窗口里,列数会漏计。
脚本对策是「枚举 + 验终点」——末列先盲按 N 次下移再进(第 N 次尝试),成败仍只由页面
身份裁定,所以它会大声报错而不是静默走错页。实测
自检记录/运行记录两页在模拟器 上 5 次枚举内没吃到,现场帧见 popup_clipped; 真机上弹框位置的取证已完成(2026-11-02 台架,#69):菜单步进依旧吃不到这两叶 (末列枚举落到同胞页alarm/selftest——但落点现在由 STATUS 如实报出,不再谎称到达); GOTO 直开两叶都到达且完整渲染(自检记录 ink 4073 / 运行记录 ink 3559,证据在 PR #70 台架段)。故KNOWN_UNREACHABLE对--to步进保留(对称设计的销项条件"真走到" 尚未满足),已验证的绕行路径是--goto;真机(新固件)侧"回根"改认 STATUSdepth:depth==0直接返根、depth>0按 s 逐级收(弹框被裁时像素漏计、固件 depth 不漏), 旧的"补按l兜底"仪式只留给无 STATUS 的老固件像素路径。 - 同一标题的多个显示态(网口设置的 只读 / 编辑 / 保存)在分类里都是同一个页面 id
net_cfg;要区分得看行内文本,写路径判据需要时再加。 - 认屏复用黄金参考的文案:改了页面文案而没同步
simulator/tests/的文本断言表,本节脚本 会一起红。那是有意的耦合(两处必须同时改),不是脚本脆弱。 - 快检档只证「认屏认得对」,不证「导航走得对」;后者只有完整档的闭环那一段会响。
- 纯像素路径对"子列标签 ≡ 页标题"的级联会误判:记录查询子列里的 事故记录 / 操作记录 /
报警记录 / 启动记录 / 遥控记录 / 运行报告 等标签与对应页标题同字,开子列那一屏会被判成
PAGE——
--selftest闭环因此对 记录查询 下 6 叶(五本记录 + 录波回放)恒报「进入 记录查询 后不是菜单态而是 PAGE」(2026-11-02 对 master 基线复跑确认为存量,非 #69 引入;clear/reset两页的"失败"是执行类硬闸的刻意拒绝,另计)。真机新固件侧 STATUS 会把它 纠成 MENU(trace 里留"像素与 STATUS 不一致"痕);模拟器侧无 STATUS,这 8 项维持红灯直至 像素判据补上级联上下文——别把它们当 #69 的回归。
7.5 过密码的口径(与黄金回归同源,只有一套走法)
查看密码 = 出厂 000(直接 e 确认);修改密码 = 123,键序 u,r,u,r,u,r,e,与
simulator/tests/check_content_pages.py 的 netcfg_edit 态完全一致——刻意不另发明一套走法
(两套走法迟早分叉,而分叉的那一边往往是真机)。导航器默认只过查看密码:判据需要写
路径权限时必须显式传 modify=True,不因「能进去」就自动升格成可写——只读判据偷偷拿到
修改权限,等于把判据的边界让给了实现。
7.6 身份回读 STATUS 与直开页 GOTO(#69,2026-11-02)
旧协议四个命令(KEEP/INIT/KEY/LCDMEM)里没有一个是"我是谁",闭环只能靠像素反推——本节
判据的全部不确定性都源于此。#69 在 7003(RemoDispBus)上补了两条,两端共享同一份契约头
Drivers/BSP/UI/ui_net_sink.h(布局以该文件注释为唯一权威,此处只记语义):
| 命令 | 语义 | 生产固件 | bench 固件 / 模拟器 |
|---|---|---|---|
| STATUS(4) | 直读固件导航态:kind(MENU/PAGE/STANDBY,待机先判)+ depth/path/sel(ui_menu.c 的 s_* 快照,经 ui_menu_get_nav_state())+ 当前内容页 id(ui_content_page_current_desc()) |
✅ 支持(只读、零画面副作用) | ✅ |
| GOTO(5) | 按页 id 直开页:查 g_ui_page_map,以查看权限 UI_PERM_VIEW 开页并回节点号 |
❌ 编译期闸外,恒回 status=4(不支持) | ✅ 受理(闸 = DTU_BENCH 或 UI_REMO_ALLOW_GOTO) |
- 主机侧优先问、像素兜底:
Navigator每次认屏先 STATUS、后像素,两边打架以 STATUS 为准 并在 trace 留痕;老固件不回 STATUS 时打印一次并永久退回纯像素路径。--where现场打印双 口径;--goto <页 id>直开后仍走与--to相同的终点双验(STATUS + 像素),拒绝码 1 无此 页 / 2 执行类页 / 3 开页失败 / 5 待机态。 - KEY 后稳帧已代码化(规则①不再是纸面纪律):
key()= 读掉 ACK → 排空到帧流静默 ≥900 ms(上限 5 s)→ 连取两帧比对,不等重试至多 8 次并在 trace 标stable=false(时钟页秒针恒在走,属预期)。 - GOTO 的边界:只用于调试取证——它按查看权限直达,不弹密码,因此不是"过密码"的旁路
写路径;
clear/reset主机与固件双侧默认拒绝(直开即到达可执行态,没有第二段确认的 余地)。MCU 侧要拿到 bench 能力须临时给MDK-ARM/STM32F407-Demo.uvprojx加DTU_BENCH宏烧录,测完必须还原宏并重烧产品构建(uvprojx 抖动不入库,同构建节律)。 - 留档帧目录已并一:
WALK_DIR与--walk存证目录同指ci-artifacts/uinav/walk/(#69 前指ci-artifacts/uiwalk/,走的新帧 BOARD_TRUTH 段永远读不到,静默"跳过(无 留档)")。 - 实机验证记录(2026-11-02 台架,两档实烧,输出全文贴在 PR #70 的台架证据评论):
bench 档(
fw build: Nov 2 2026 22:25:12)上 STATUS 首答{kind=MENU depth=0 path=[0] sel=[0]};GOTOwave_play一次往返到达——#38 那趟"进故障录波看波形"的级联盲走就此降为 一条命令;selftest/runlog直开渲染完整(见 §7.4 第 1 条取证结论);PAGE 上按 s 回根 由 STATUS 证实;级联收列按 s 逐级收回(§7.2 纪律据此改写);根列光标可经 STATUSsel对位(记录查询 = 根行 6,对齐一击命中再 ENTER)。换回产品档(fw build: Nov 2 2026 22:29:56 suite=NONE,同一提交、未加宏)复烧后:STATUS 照常应答(它不设编译期闸), GOTO 回 status=4(不支持)——同一对命令在生产/bench 两侧的行为差异由真机钉死,不止 纸面推理。烧录两轮均过/serial-boot-test闭环(身份戳匹配 + 回读no difference)。