Files
STM32F4-Base/docs/UI设计/UI_自动化测试流程.md
T
super_agent_bot be16669564
CI / 静态门禁 (pre-commit) (push) Failing after 4m56s
CI / 主机侧单测 (MSVC) (push) Successful in 47s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 4m4s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 42s
docs(ui/nav): 台架复核落地——收列键语义、回根按 depth、限度取证结论(#69)
台架实机会话 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 执行类拒绝)。
2026-11-02 22:39:48 +08:00

261 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UI 自动化测试流程(模拟器回归)
> **用途**:所有涉及 UI 的改动(菜单结构 / 内容页 / 字体 / 样式 / 几何)改完代码后,按本文跑一遍全部门禁再提交。脚本已入库,直接调用即可,无需临时重写。
> **定位**:`UI_界面制作流程.md` P2/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 |
## 二、流程总览
```powershell
# 仓库根目录执行(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 再取数(接线道,`PAGES` 60 项时实测):正向 **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`):
```powershell
.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)
```
有意改视觉时的流程:
1. **先肉眼确认新效果正确**:窗口模式运行 `simulator\build\Debug\lvgl_sim.exe`,或刷新后跑
`regen_ref_png.py` 看图对照 mockup;
2. 刷新参考:`.venv\Scripts\python.exe simulator/tests/check_content_pages.py --update`(重写 `ref/*.bin`);
3. **重生成目检 PNG**:`.venv\Scripts\python.exe simulator/tests/regen_ref_png.py`。
`.png` 是本地调试产物、**不会**随 `.bin` 自动更新——不重跑则 PNG 仍是旧渲染(缺字/错位,看似"乱码")。
默认 `--scale 6 --invert`(深字白底便于目检);`--no-invert` = 白字黑底(与设备一致);
4. **提交 `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](../测试说明/测试数据/boot_log_data/20261025_uinav_blindwalk_cascade.png) |
| 04_yx / 05_yxp2 / 06_soe | 遥信量两页 / SOE记录 | **密码输入页** | 512 / 515 / 512 | [blindwalk_pwd512](../测试说明/测试数据/boot_log_data/20261025_uinav_blindwalk_pwd512.png) |
| 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-nav` skill** 的「唯一入口」纪律兜住。
### 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 用法与门禁档位
```powershell
# 认屏快检: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 已知限度(别把它当万能闸)
1. **根列选中落在最后两行时,子菜单弹框会被屏幕裁掉**:选带不在可扫窗口里,列数会漏计。
脚本对策是「枚举 + 验终点」——末列先盲按 N 次下移再进(第 N 次尝试),成败仍只由页面
身份裁定,所以它会大声报错而不是静默走错页。实测 `自检记录` / `运行记录` 两页在模拟器
上 5 次枚举内没吃到,现场帧见
[popup_clipped](../测试说明/测试数据/boot_log_data/20261025_uinav_popup_clipped.png);
**真机上弹框位置的取证已完成(2026-11-02 台架,#69)**:菜单步进**依旧**吃不到这两叶
(末列枚举落到同胞页 `alarm` / `selftest`——但落点现在由 STATUS 如实报出,不再谎称到达);
GOTO 直开两叶都到达且完整渲染(自检记录 ink 4073 / 运行记录 ink 3559,证据在 PR #70
台架段)。故 `KNOWN_UNREACHABLE` 对 `--to` 步进**保留**(对称设计的销项条件"真走到"
尚未满足),已验证的绕行路径是 `--goto`;真机(新固件)侧"回根"改认 STATUS `depth`:
`depth==0` 直接返根、`depth>0` 按 s 逐级收(弹框被裁时像素漏计、固件 depth 不漏),
旧的"补按 `l` 兜底"仪式只留给无 STATUS 的老固件像素路径。
2. 同一标题的多个显示态(网口设置的 只读 / 编辑 / 保存)在分类里都是同一个页面 id
`net_cfg`;要区分得看行内文本,写路径判据需要时再加。
3. 认屏复用黄金参考的文案:改了页面文案而没同步 `simulator/tests/` 的文本断言表,本节脚本
会一起红。那是**有意的耦合**(两处必须同时改),不是脚本脆弱。
4. 快检档只证「认屏认得对」,不证「导航走得对」;后者只有完整档的闭环那一段会响。
5. **纯像素路径对"子列标签 ≡ 页标题"的级联会误判**:记录查询子列里的 事故记录 / 操作记录 /
报警记录 / 启动记录 / 遥控记录 / 运行报告 等标签与对应页标题同字,开子列那一屏会被判成
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]}`;GOTO `wave_play` 一次往返到达——#38 那趟"进故障录波看波形"的级联盲走就此降为
一条命令;`selftest` / `runlog` 直开渲染完整(见 §7.4 第 1 条取证结论);PAGE 上按 s 回根
由 STATUS 证实;级联收列按 s 逐级收回(§7.2 纪律据此改写);根列光标可经 STATUS `sel`
对位(记录查询 = 根行 6,对齐一击命中再 ENTER)。换回**产品档**(`fw build: Nov 2 2026
22:29:56 suite=NONE`,同一提交、未加宏)复烧后:STATUS 照常应答(它不设编译期闸),
GOTO 回 **status=4(不支持)**——同一对命令在生产/bench 两侧的行为差异由真机钉死,不止
纸面推理。烧录两轮均过 `/serial-boot-test` 闭环(身份戳匹配 + 回读 `no difference`)。