From 496b4dc5e253f1b4b511e65a14738a46e59f5d87 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Mon, 26 Oct 2026 23:42:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=8F=B0=E6=9E=B6=E8=84=9A=E6=9C=AC):=20bo?= =?UTF-8?q?ard=5Fui=5Fnav=20=E7=9A=84=20UNKNOWN=20=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E9=94=AE=E5=BA=8F=E6=94=B9=E4=B8=BA=20e-s-l=EF=BC=8C=E5=86=B7?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E8=90=BD=E5=9C=A8=E5=BE=85=E6=9C=BA=E6=80=81?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E7=A9=BA=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧逻辑 UNKNOWN 分支里的 l 条件**永不可达**:classify 只在 MENU 态填 cols,UNKNOWN 恒 cols=None ⇒ 回退实际等于静默连按 s。而本机待机态按 s/l 无效、要按 e 才进菜单 ⇒ 冷启动恰好落在待机态时,闭环导航直接开不了工(症状是界面点不动,脚本却报正常)。 现在 BACKOFF_KEYS 默认 esl、--backoff-keys 可覆盖(要旧行为填 sl),回退时**循环消费 键序、绝不连按同一键**,并新增 _dump_unknown 把认不出的帧存证下来,免得下次只能靠 复述。同笔带上 task-24 在 19:41:56 重跑产生的 trace 归档(1 行,不是本次手改)。 --- .../20261026_task24_boardnav_trace.json | 2 +- scripts/board_ui_nav.py | 100 ++++++++++++++++-- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/docs/测试说明/boot_log_data/20261026_task24_boardnav_trace.json b/docs/测试说明/boot_log_data/20261026_task24_boardnav_trace.json index 795367dc..df6325f8 100644 --- a/docs/测试说明/boot_log_data/20261026_task24_boardnav_trace.json +++ b/docs/测试说明/boot_log_data/20261026_task24_boardnav_trace.json @@ -138,4 +138,4 @@ "page": "view_set", "cols": null } -] \ No newline at end of file +] diff --git a/scripts/board_ui_nav.py b/scripts/board_ui_nav.py index 24bcfdd4..7f852386 100644 --- a/scripts/board_ui_nav.py +++ b/scripts/board_ui_nav.py @@ -22,6 +22,8 @@ python scripts/board_ui_nav.py --walk # 台架窗口:走全部绑定页并存帧 python scripts/board_ui_nav.py --board --to yx --grab /tmp/yx 可选:--trace 路径(步迹落盘) --dump-fail 目录(把认错的那一屏存下来) + 可选:--backoff-keys esl(UNKNOWN 屏的回退键序;默认 esl 含 e——本机待机态 + 按 s/l 无效、要按 e 才进菜单,2026-10-26 task-24 台架实测) --board / --walk 属真机动作:必须另加 --i-hold-bench-lock 显式确认已持 bench 锁, 否则直接拒绝(退出码 3,不碰网络)——台架是独占资源,脚本不替人判断有没有在用。 --walk 的存证目录默认在 ci-artifacts/uinav/walk(版本控制之外),--outdir 可改; @@ -57,6 +59,11 @@ WALK_DIR = os.environ.get("DSH_UINAV_WALK") or os.path.join(REPO, "ci-artifacts" # (判据脚本自己在提交门禁里跑,顺手往 scripts/ 写 json 就是把工作树弄脏) TRACE = None FAIL_DIR = None +# UNKNOWN 屏回退键序(task-24,2026-10-26 台架实证):to_root 认不出屏时按此序**循环**消费, +# 绝不连按同一键。默认含 e——本机待机态按 s/l 无效、要按 e 才进菜单;旧逻辑 UNKNOWN 分支 +# 的 l 条件永不可达(classify 只在 MENU 态填 cols,UNKNOWN 恒 cols=None),等于静默连按 s: +# 冷启动恰好落在待机态 = 闭环导航直接开不了工。--backoff-keys 可覆盖(如旧行为 "sl")。 +BACKOFF_KEYS = "esl" W = H = 160 STRIDE = W // 8 @@ -576,6 +583,42 @@ class Navigator(object): "page": st["page"], "cols": st["cols"]}) return fr, st + def _dump_unknown(self, fr, st): + """认不出屏时的现场取证(task-24:不许静默反复按键)。 + + 判定依据 = 现场复算 classify 的三个判据源、与它的判定顺序同口径——打印的是 + "为什么没认出来",不是另立一套标准:密码页投票(4 字中 3 判 PWD)、标题命中 + (page_of 返回的态键)、菜单选带条数(menu_cols)。另打 40x20 墨量预览(每字符 + = 一个 4x4 块的 band_frac),"当前这一屏"在控制台直接可读;给了 --dump-fail 时 + 把原始帧一并落盘——台架窗口短,事后翻帧比现场重走便宜(同 go_page 的 fail_ 帧先例)。 + """ + votes = 0 + for g in self.clf.pwd_chars: + if fr.match(g, (24, 80)): + votes += 1 + _pid, keys, txt = self.clf.page_of(fr) + cols = self.clf.menu_cols(fr) + print(" 依据:ink=%d 密码投票 %d/4 标题=%s%s 选带=%d 页码=%s" + % (fr.ink, votes, txt or "未命中", + "(态键 %s)" % ",".join(keys) if keys else "", + len(cols), str(st.get("no")))) + print(" 当前帧 40x20 预览(# 有墨 / . 空白):") + for y in range(0, H, 4): + row = "".join("#" if fr.band_frac(y, min(y + 4, H), x, min(x + 4, W)) > 0.0 + else "." for x in range(0, W, 4)) + print(" |%s|" % row) + if FAIL_DIR: + try: + if not os.path.isdir(FAIL_DIR): + os.makedirs(FAIL_DIR) + tag = "".join(c if c.isalnum() else "_" for c in (fr.name or "frame"))[:24] + fn = os.path.join(FAIL_DIR, "unknown_%s_%d.bin" % (tag, fr.ink)) + with open(fn, "wb") as fp: + fp.write(fr.data) + print(" 原始帧已存:", fn) + except OSError as exc: + print(" 存原始帧失败(不影响判定):", exc) + def to_root(self, limit=14): """回到"只有一列打开"的根菜单。 @@ -584,6 +627,7 @@ class Navigator(object): · **级联列只能用 l 收**,菜单态按 s 不收列(Lead 大窗实测)——沿用盲走脚本 的 ssssssss 回根法,正是深页错位的来源之一。 """ + unk = 0 for _ in range(limit): fr, st = self.snap() if st["kind"] == "MENU" and self.n_cols(st) == 1: @@ -603,14 +647,24 @@ class Navigator(object): self._step("s", "退出页面 / 密码页 / 空白态回菜单") continue if st["kind"] == "UNKNOWN": - # 认不出的屏:先按 l(收列不会造成破坏),再按 s 兜一次 - if self.n_cols(st) > 1: - self._step("l", "UNKNOWN 且看起来有多列 → 收列") - else: - self._step("s", "UNKNOWN 且无列可收 → 退出") + # 认不出的屏:按可配置键序**循环**退避(默认 esl,--backoff-keys 可改), + # 每轮先打印当前帧与判定依据再按键(task-24 两条台架实证教训都在这): + # · 旧版"看起来多列按 l 否则按 s"——UNKNOWN 态 cols 恒 None(classify 只在 + # MENU 态填 cols),l 分支永不可达,实际是静默连按 s;本机待机态 s 无效、 + # 要按 e 才进菜单,冷启动落在待机态 = 板端闭环回归不可复现; + # · 静默反复按同一键等于什么都没做,现场只剩耗时、不留证据。 + keys = BACKOFF_KEYS or "esl" + ch = keys[unk % len(keys)] + unk += 1 + why = ("UNKNOWN 屏第 %d 次:按 '%s'(退避键序 %r,可 --backoff-keys 覆盖)" + % (unk, ch, keys)) + print("[未知屏] %s" % why) + self._dump_unknown(fr, st) + self._step(ch, why) continue self._step("l", "菜单多列 → 收回一级") - raise RuntimeError("回根失败(14 步内没见到单列根菜单)") + raise RuntimeError("回根失败(%d 步内没见到单列根菜单;UNKNOWN 退避键序 %r," + "可用 --backoff-keys 覆盖)" % (limit, BACKOFF_KEYS)) def seek_in_column(self, col, want, limit=12): """在当前列把选带移到标签 want:先下后上,走到边界就换向(不猜步数)。 @@ -777,7 +831,7 @@ def selftest_board_frames(clf): def selftest_refs(clf): - """仓库黄金参考(18 页)逐帧分类自检:每张图的期望值 = 它的文件名。 + """仓库黄金参考(27 页)逐帧分类自检:每张图的期望值 = 它的文件名。 这一组是**语义正确的基线**(模拟器与板端同一份 UI 源码),用来把"分类器错了"与 "留档帧本来就是走歪的"分开——两类失败含义完全不同,混在一起就只能靠猜。 @@ -887,18 +941,44 @@ def _dump_trace(log): def main(argv): - global TRACE, FAIL_DIR + global TRACE, FAIL_DIR, BACKOFF_KEYS if "--trace" in argv: TRACE = argv[argv.index("--trace") + 1] if "--dump-fail" in argv: FAIL_DIR = argv[argv.index("--dump-fail") + 1] + if "--backoff-keys" in argv: + bk = argv[argv.index("--backoff-keys") + 1] + # 只收导航器真会注的键字符;空串或含别的字符一律当场拒——退避键序打错宁可现在报错, + # 也不要它变成"跑了半天都在按奇怪的东西"(同注释行注记写错必须当场失败的口径) + if not bk or any(c not in "ulrdes" for c in bk): + print("--backoff-keys 不能为空、只允许 u/l/r/d/e/s 的组合(收到 %r)" % bk) + return 3 + BACKOFF_KEYS = bk clf = Classifier() if "--help" in argv: print(__doc__) return 0 if "--list" in argv: - for k in sorted(clf.pages): - print(k, "标题=", [t for t, g in clf.titles.items() if g == k]) + # 只用 Classifier 真实暴露的接口拼清单:tree(菜单树 #page= 叶 = --to 合法页 id + # 与路径的唯一真源)、title2harness(标题→黄金态键)、pwd_pages(#P 叶)、 + # page_path(id→标签路径)。旧版打的 clf.titles 根本不存在(AttributeError, + # 整条子命令不可用),且 clf.pages 的键是**黄金态键**不是页 id——即便 titles 存在 + # 列出来的也不能喂给 --to。**不另造标题表**:手写第二真相下次菜单树改动必然漂移。 + titles_by_page = {} + for (_lvl, label, page, _np) in clf.tree: + if page: + lst = titles_by_page.setdefault(page, []) + if label not in lst: + lst.append(label) + for pid in sorted(titles_by_page): + path = page_path(clf, pid) + states = sorted({k for t in titles_by_page[pid] + for k in clf.title2harness.get(t, [])}) + print("%-12s 标题=%-14s 路径=%s%s 黄金态=%s" + % (pid, "、".join(titles_by_page[pid]), + ">".join(path) if path else "-", + " [#P 需密码]" if pid in clf.pwd_pages else "", + ",".join(states) or "(无文本断言,认屏只认标题)")) return 0 if "--classify" in argv: tgt = argv[argv.index("--classify") + 1]