feat(门禁): 编号门禁 id-check 落地(36→37 钩子)+ 批 1 编号单词化改名

新增门禁本体
- scripts/check_ids.py:实现 docs/编号规范.md §8 八项校验;默认 warn-only,
  唯一硬拦项是「可判定比例跌破基线」(防回退的本体)
- scripts/id_registry.json:66 个前缀族,逐项来自 §4 总表 + 全库实测游标,
  偏差一律写进 cursor_note,不凭记忆编造
- scripts/id_aliases.json:旧号 → 新号 / 作废标记(照搬 section_aliases.json 范式)
- 顺带收编 check_test_traceability.py:正则补齐大写后缀与无模块码形态,
  比对范围扩到 test/host 与 7 篇「单元测试说明.md」,另加 --board-only 回退旧口径

批 1 改名(用户授权解禁「不改写存量号」)
- 代码规范条款 S1~S19 → CODE-RULE-nn:78 处 / 2 文件
- UI 像素回归状态 S0~S7 → MENU-STATE-nn:61 处 / 3 文件(冻结在 07)
- 两族属主登记处 = 脚本自身的规则表 / STATES 表,文档只作引用方
- 新增 scripts/rename_ids.py(§7.3 规划脚本):映射表驱动、显式 scope、默认干跑、
  自动写 scoped 别名,跳过 [示例]/[计划] 行与变更记录表,按原字节读写保 BOM 行尾
- 已应用映射表存档 scripts/rename_maps/batch1.json 作审计轨迹

有牙实测(证明门禁不是空转)
- 注入 Z-99 / 裸形 / 悬空 / 越游标 / 未紧邻 / 跨文档重复定义 六例,
  分别被校验 1/2/3/5/6/4 点名,--strict 退出码 1;临时档删除后工作树干净

验证与同步
- 可判定比例基线 82.3% → 82.7%(已 --update-baseline 入档);错误级 1312 → 1291
- ci_local.py 全绿(L0 + L0.5 共 1104 断言);ui-golden Passed;
  check_doc_links.py --all 无法判定 0
- 未改任何 C/H 源文件,故本次不需 @build
- 文档:AGENTS.md 与 pre-commit使用说明.md 计数 36→37 并新增 §2.25 详解;
  编号规范 §4.1 补两行、§8.1 落地记录、§8.2 规范与仓库偏差、§8.3 迁移批次记录
This commit is contained in:
2026-10-21 09:33:54 +08:00
parent efeaccbb18
commit 301c3e5183
16 changed files with 2894 additions and 140 deletions
+39 -39
View File
@@ -8,16 +8,16 @@ check_menu_rows.py — 自绘菜单 8 状态像素回归(160x160 I1,MSB-firs
前置:simulator 已构建(build/Debug/lvgl_sim.exe 存在);纯标准库,任意 python3。
状态(键序注入,u=上 d=下 e=Enter s=Esc):
S0 "" 根框 7 行 sel=0
S1 "d" sel=1
S2 "dd" sel=2
S3 "ddu" 回退 → 须与 S1 全像素一致
S4 "de" 开 L2(实时数据 3 子项),根框不变
S5 "ded" L2 sel=1
S6 "des" 返回 → 须与 S1 全像素一致(销毁/重建一致性)
S7 "ddes" 根框 sel=2、L2 已毁 → 须与 S2 全像素一致
MENU-STATE-00 "" 根框 7 行 sel=0
MENU-STATE-01 "d" sel=1
MENU-STATE-02 "dd" sel=2
MENU-STATE-03 "ddu" 回退 → 须与 MENU-STATE-01 全像素一致
MENU-STATE-04 "de" 开 L2(实时数据 3 子项),根框不变
MENU-STATE-05 "ded" L2 sel=1
MENU-STATE-06 "des" 返回 → 须与 MENU-STATE-01 全像素一致(销毁/重建一致性)
MENU-STATE-07 "ddes" 根框 sel=2、L2 已毁 → 须与 MENU-STATE-02 全像素一致
断言:选中行白带(白占比>0.50)+ 带内黑字;非选行黑底白字(0.03<占比<0.45);
框体 1px 白边框;一致性状态逐字节相同;S0 池水位 = MENU_POOL_BASELINE。
框体 1px 白边框;一致性状态逐字节相同;MENU-STATE-00 池水位 = MENU_POOL_BASELINE。
退出码:0 全 PASS;1 有断言失败;2 运行/环境错误(exe 缺失等)。
"""
@@ -33,14 +33,14 @@ from pathlib import Path
EXE = Path(__file__).resolve().parents[1] / "build" / "Debug" / "lvgl_sim.exe"
STATES = [
("S0", ""),
("S1", "d"),
("S2", "dd"),
("S3", "ddu"),
("S4", "de"),
("S5", "ded"),
("S6", "des"),
("S7", "ddes"),
("MENU-STATE-00", ""),
("MENU-STATE-01", "d"),
("MENU-STATE-02", "dd"),
("MENU-STATE-03", "ddu"),
("MENU-STATE-04", "de"),
("MENU-STATE-05", "ded"),
("MENU-STATE-06", "des"),
("MENU-STATE-07", "ddes"),
]
W = H = 160
@@ -135,17 +135,17 @@ def main():
r.stderr.decode(errors="replace")[:200]))
return 2
paths[name] = out
if name == "S0":
if name == "MENU-STATE-00":
s0_out = r.stdout
# S0(空键序)= 菜单基线:池水位门禁
# MENU-STATE-00(空键序)= 菜单基线:池水位门禁
m = re.search(r"pool after keys: used=(\d+)", s0_out.decode(errors="replace"))
if not m:
print("FAIL: S0 输出未找到 'pool after keys'")
print("FAIL: MENU-STATE-00 输出未找到 'pool after keys'")
return 2
used = int(m.group(1))
check(used == MENU_POOL_BASELINE,
"S0 池水位 %d != 基线 %d(菜单结构/样式变更?确认合理后更新常量)"
"MENU-STATE-00 池水位 %d != 基线 %d(菜单结构/样式变更?确认合理后更新常量)"
% (used, MENU_POOL_BASELINE))
shots = {n: load(p) for n, p in paths.items()}
@@ -169,46 +169,46 @@ def main():
check(px[y0][x] == 1 and px[y0 + h - 1][x] == 1,
"%s 上下边框 x=%d 非白" % (tag, x))
# --- S0:根框 7 行,sel=0 ---
p = S["S0"]
# --- MENU-STATE-00:根框 7 行,sel=0 ---
p = S["MENU-STATE-00"]
border_check(p, ROOT_X0, ROOT_Y0, ROOT_W, ROOT_H, "S0根框")
for i in range(7):
row_check(p, *root_row_strip(i), band=(i == 0), tag="S0行%d" % i)
# --- S1/S2:sel 移位 ---
for name, sel in (("S1", 1), ("S2", 2)):
# --- MENU-STATE-01/MENU-STATE-02:sel 移位 ---
for name, sel in (("MENU-STATE-01", 1), ("MENU-STATE-02", 2)):
p = S[name]
border_check(p, ROOT_X0, ROOT_Y0, ROOT_W, ROOT_H, name + "根框")
for i in range(7):
row_check(p, *root_row_strip(i), band=(i == sel), tag=name + "行%d" % i)
# --- S4:L2 开于 sel=1 行右侧(3 子项、无箭头),根框区与 S1 相同 ---
p = S["S4"]
# --- MENU-STATE-04:L2 开于 sel=1 行右侧(3 子项、无箭头),根框区与 MENU-STATE-01 相同 ---
p = S["MENU-STATE-04"]
lx0, ly0, lw, lh = l2_geom(1, 3)
border_check(p, lx0, ly0, lw, lh, "S4-L2框")
border_check(p, lx0, ly0, lw, lh, "MENU-STATE-04-L2框")
for i in range(3):
row_check(p, *l2_row_strip(lx0, ly0, i, lw), band=(i == 0), tag="S4-L2行%d" % i)
# 根框区(x 0..65)须与 S1 逐像素一致
row_check(p, *l2_row_strip(lx0, ly0, i, lw), band=(i == 0), tag="MENU-STATE-04-L2行%d" % i)
# 根框区(x 0..65)须与 MENU-STATE-01 逐像素一致
for y in range(H):
for x in range(0, ROOT_X0 + ROOT_W):
check(S["S1"][y][x] == p[y][x], "S4 根框区(x=%d,y=%d) 与 S1 不一致" % (x, y))
check(S["MENU-STATE-01"][y][x] == p[y][x], "MENU-STATE-04 根框区(x=%d,y=%d) 与 MENU-STATE-01 不一致" % (x, y))
# L2 框下方区域须全黑(无残留对象)
f = frac(p, lx0 + 1, lx0 + lw - 2, ly0 + lh + 1, min(ly0 + lh + 14, H - 1))
check(f == 0.0, "S4 L2 框下方有像素(占比 %.3f)" % f)
check(f == 0.0, "MENU-STATE-04 L2 框下方有像素(占比 %.3f)" % f)
# --- S5:L2 sel=1,根框区仍与 S1 同 ---
p = S["S5"]
# --- MENU-STATE-05:L2 sel=1,根框区仍与 MENU-STATE-01 同 ---
p = S["MENU-STATE-05"]
lx0, ly0, lw, lh = l2_geom(1, 3)
for i in range(3):
row_check(p, *l2_row_strip(lx0, ly0, i, lw), band=(i == 1), tag="S5-L2行%d" % i)
row_check(p, *l2_row_strip(lx0, ly0, i, lw), band=(i == 1), tag="MENU-STATE-05-L2行%d" % i)
for y in range(H):
for x in range(0, ROOT_X0 + ROOT_W):
check(S["S1"][y][x] == p[y][x], "S5 根框区(x=%d,y=%d) 与 S1 不一致" % (x, y))
check(S["MENU-STATE-01"][y][x] == p[y][x], "MENU-STATE-05 根框区(x=%d,y=%d) 与 MENU-STATE-01 不一致" % (x, y))
# --- 一致性状态:逐字节全像素比对 ---
for a, b, tag in (("S3", "S1", "ddu==d"),
("S6", "S1", "des==d(往返)"),
("S7", "S2", "ddes==dd(往返)")):
for a, b, tag in (("MENU-STATE-03", "MENU-STATE-01", "ddu==d"),
("MENU-STATE-06", "MENU-STATE-01", "des==d(往返)"),
("MENU-STATE-07", "MENU-STATE-02", "ddes==dd(往返)")):
if raw(paths[a]) == raw(paths[b]):
print("[%s] PASS 全像素一致 (%s)" % (a, tag))
else: