Restore the nine Chinese notes the append script had silently skipped

A round-by-round comparison shows AGENTS.zh-CN.md missing rounds 18, 19, 22, 23, 44, 49, 51, 52 and 54 while AGENTS.md has them all. The append helper picks its anchor from the fifth non-blank line of a window after a marker and refuses a non-unique anchor; in the Chinese file that anchor is often a code fence or a repeated phrase, so the write was skipped each time and nothing complained. The bilingual pair is a stated requirement, so this drifted for many rounds unnoticed.

The missing notes are restored from the work/rNN-zh.md snippets they were generated from, appended under a heading that says so, rather than being silently re-run.
This commit is contained in:
mckero committed 2026-10-02 13:22:42 +08:00
1 parent 800486b8e0
commit 5674ccb9ad
1 file changed
+117
+117
View File
@@ -1878,3 +1878,120 @@ guest 照样在跑,但事后 `REG_0C` bit 0 仍然是置位的:固件没有
面板设置是第五种、不一样的情况:对比度和反显根本不在帧缓冲里,所以任何渲染 `gFrameBuffer`
的东西都显示不出来。`TYPE_ST7565` 建模控制器自己的寄存器,`tools/uvk5_lcd.py` 把反显应用到
画面上;见 README.md。
---
## 补记:以上缺失的中文笔记
这几轮的英文笔记已写入 `AGENTS.md`,中文侧却被追加脚本静默跳过了(它选的锚点在中文文件里不唯一,而它坚持必须唯一)。
现在按轮次补在这里,内容与英文一致。
**第 18-19 轮:原始键码探针从未跑起来,而对照把「输入」收窄到一个调用。**
做了两个不依赖字库的探针,用来画出它收到的原始键码(第 2 行一条标记横杠、第 20 行五个位),而在七次按键前后
面板都是**逐字节相同**的 —— 第 2 行 26 个亮点、第 20 行一个恒定图案,那正是启动器自己的画面。也就是说两个探针
**都没有画**。
有价值的是这组对照:形状相同、通过 api->fb 填满帧缓冲再 blit 的应用(FillFb,第 17 轮)**是能画的** —— 43 个
非零字节变成 596 —— 而它与这两个探针**唯一的实质差别**就是探针每轮都调用 api->get_key()。再结合第 17 轮的结果
(完整版扫雷画出了完整一帧,然后在**按下键的瞬间**消失,面板回到启动器的标题框 —— 而那只会发生在把某个键当成
EXIT 的路上),图景就是:**应用能跑、能画,是按键这条路结束了它。**
这就是输入目前的状况,而它只有**一个调用宽**:叠加应用里的 get_key()。扇区缓存那个问题也跟着回来了 —— 按键路径
是固件去读外部 flash 的一个可疑位置 —— 但 print_tiny 引发的那些字库读取并不会杀死应用,所以这是**按键路径特有**
的,而不是普遍的 flash 读取问题。
**第 22-23 轮:镜像已恢复且健康;但今天这次启动无法复现。**
一整天的应用安装之后,工作副本已经不是第 17 轮那份能用的镜像了,所以按规矩**先关机**(退出时模型会把内存里的镜像写回
文件)再把原始转储放回去。现在页面能正常上电到自己主画面 —— 485 个非零字节,也就是健康的那种状态 —— 而扫雷已装进
slot 3、2444 字节,正是第 17 轮画出完整一帧的那个产物。
复现不了的是**启动**。F、7、DOWN、MENU —— 再按一次 MENU —— 电台始终停在应用菜单上:它画得完整(标题框、六行、一条
高亮条,ink 1799),却对按键**完全不理**,按下前后读数一模一样。第 17 轮是用**同一个应用、同一个槽、F、7、DOWN×3、
MENU** 量到游戏自己那一帧的,所以问题不在应用;变的是**启动器自己这条路上的按键处理**。
这正是按键那一节已经用另一种说法记过的状态 —— 启动器接管了屏幕、却没有走到应用 —— 而它现在是**唯一的未决项**。
顺便记下与上面那条更正在一起:这一轮早先我怀疑过应用,而「去掉插桩、重建第 17 轮那份源码」的对照并没有把那一帧找回来,
正是这个对照把嫌疑从应用移到了启动器与镜像上。
**第 44–45 轮:逐行读了启动器,证明了覆盖区确实被载入,而调用之前的每一道门都通过。**
三个仪器给出同一幅画面。MENU 之后对 `0x20000280` 做两次 memsave,里面就是应用自己的代码,**2744 字节中 2738 字节完全一致** ——
拷贝完整且一直保留。PC 探针的 **14505 次采样中没有一个落在那 4 KiB 里** —— CPU 从未进入应用 —— 采样停在
`0x08005118`/`0x08005122`。flash 探针最后一条事务就是读应用代码(`addr=105000 len=2744` ✓),其后一无所有。
`0x08005118` 解出来是一个 **PY32 SPI 字节传输例程**:`movs r2,#2; ldr r3,[pc]`(= `0x40013000` ✓);轮询 `[r3+8]` 的 bit1(TXE ✓);
`strb` 到 `[r3+12]`(DR ✓);再轮询 bit0(RXNE ✓);读回 DR。模型自己的注释确认了这个布局(CR1 0x00、SR 0x08、DR 0x0C ✓)
并始终把 TXE 拉高 —— **所以这不是卡在缺失的标志位上,而是固件在不停做传输**,与那 193 万次相吻合。**它在固件里循环,不是挂住。**
随后从固件自己的源码(取回的 `App/apps/app_overlay.c`)读了启动器。`APP_LaunchOverlay` 依次:校验槽位、要求
`h.link_vma` 等于覆盖区缓冲、失效扇区缓存、清零覆盖区、按 `code_size` 读入、校验 CRC,**然后才在第 1162 行调用 `entry(&app_api)`**。
对这个应用而言**每一道门都通过**:magic `FAP1` ✓、hdr 1 ✓、abi 1 ✓、api_min 1 ✓、committed ✓、capabilities 0 ✓、
`code_size 2744` 在 `APP_OVERLAY_MAX` 之内 ✓、`entry_off 0` ✓、`link_vma 0x20000280` ✓ —— 而 `MB_Crc32Bytes` 就是标准 CRC-32
(初值 `0xFFFFFFFF`、反射 `0xEDB88320`、末尾异或 ✓),所以主机工具算出的 `0x3c12630d` **正是固件期望的值** ✓。
**于是:拷贝发生了、纸面上所有检查都过了、调用却仍然没有生效。**在拷贝与调用之间,**唯一碰硬件的语句是第 1159 行的
`RADIO_SetupRegisters(true)`** —— 而 PC 正停在一个 PY32 SPI 例程里。**这就是下一轮的起点,而且它只有一个函数宽。**
**第 49–50 轮:应用跑起来了 —— 而且分出两条彼此独立、现在都已被证实的原因。**
把那个 24 字节的标记应用**装进 slot 1**(而不是 slot 0)之后,一切都变了。按 F、7、DOWN、MENU 之后:那个固定地址读出来是
`efbeadde` —— **应用的第一条指令确实执行了** ✓;覆盖区开头是应用自己的字节 `81b0034803490160` ✓;
而**面板非零 615**,不再是启动器的 43 ✓。**所以 `entry()` 被抵达、应用在跑、而且画上去了。**
显示通路、加载器、头部与 CRC 从来都不是问题 ✓。
**两条彼此独立的原因。**第一,**菜单选中的那一行不是 slot 0** ✗:这一整个会话里我都装进 slot 0,而启动的是另一行 ——
这一点是覆盖区的内容自己招认的:明明装的是 Mark,覆盖区里躺的却是扫雷的代码(`f0b59bb0fa490860c5690024…` ✓)。
第二,**覆盖区尾部在 CRC 校验之前就被改写** ✗:偏移 **2620..2650**(地址 `0x20000CBC` 起)那 6 个字节,
应用代码里是零,覆盖区里却是一个固件指针(`0x0801D640` ✓)。
**第二条正好解释了那个花掉好几轮的「尺寸阶梯」** ✓✓:被改写的偏移是**固定的**,所以**比它短的应用通过校验并运行**,
而**越过它的应用**——扫雷 2744 字节,只比第一个被污染的字节多出约 124 字节——**以 `APP_ERR_CRC` 失败、永不运行** ✓。
小应用能跑、大应用会死,而原因从来不是尺寸本身 ✓。
**两条路都很小。**把应用缩到改写点之下,它就该照原样启动 ✓;或者**找出在 `SectorCache+2620` 写入的那个东西** ✓。
那块区域就是 `PY25Q16_OverlayBuffer()`;而 `py25q16.c` 显示:**任何一次「缓存一个扇区」的读都会**
`ReadBufferRaw(SecAddr, SectorCache, SECTOR_SIZE)` **整块写进它** —— 所以写入者极可能是**固件里某条在加载应用期间缓存扇区的路径** ✓,
而模型可能正在触发它 ✓。
**第 51–52 轮:污染的位置随 `code_size` 移动 —— 模型读 flash 时丢掉了最后约 124 字节。**
同一应用的两个尺寸、两次测量,就把问题钉死了。2744 字节时,那 6 个被污染的字节在偏移 2620、2621、2622、2623、2627、2650;
把它砍掉 176 字节变成 2568 之后,同样的 6 个字节跑到了 2444、2445、2446、2447、2451、2474。**两次都恰好是「从末尾往前数 124 字节」** ✓✓。
**所以这不是某个固定地址被覆盖,而是「这次读的最后 124 字节」是错的** ✗✓。
这与已经成立的事实完全吻合:**24 字节的标记应用通过了校验并跑起来** ✓✓(比损坏的尾巴还短 ✓),
而扫雷在 2568 和 2744 字节时**总是以 `APP_ERR_CRC` 失败、永不运行** ✓。这与本文件里已经记录的那四个 DMA/flash 故障是同一签名 ✓✓,
**并且彻底终结了「尺寸阶梯」之谜**:短应用能跑、长应用会死,原因从来不是尺寸本身 —— **是那次读的尾巴** ✓✓。
**「错在模型、不在镜像」这一点有直接证据** ✓✓:镜像里 slot 1 代码偏移处算出来是头里的 `0x3c12630d`、**零字节差异** ✓;
而客户机覆盖区在同一段上算出来是 `0x1312d98c` ✓ —— **应用在磁盘上是对的,在 RAM 里是错的** ✓。
砍应用这件事仍然值得做、也已经记下:去掉三个临时探针、缩短标题字符串、删掉光标坐标读数之后,
扫雷从 2744 降到 2568 字节、零警告编译通过 ✓ —— **但尾巴 bug 是躲不掉的,因为尾巴会随应用一起缩短** ✓。
下一步:**模型里的 flash 读通路**。固件用的是 `SPI_ReadBuf`,所以这就是 `py32f071.c` 里那条 DMA 驱动的读 ——
也就是本文件那四个已记录 bug 所在的地方 —— **要找的是「最后那一个不完整突发」的计数或寻址** ✓。
**第 54 轮:flash 模型自己的数据是对的,DMA 也是对的 —— 所以那 6 个字节是传输之后被写进去的。**
模型里的读通路只有三行,返回 `s->data[(s->addr++) % PY25Q16_SIZE]`;而模型把那 2 MB 放在 RAM 里、**退出时写回文件** ✓。
所以一次运行留下的那个文件,就是**模型当时认为 flash 里是什么**;拿它与应用比对,flash 这一侧就可以结案:
`flash-r52.img` 与 `flash-r53.img` 在 slot 1 的代码偏移处都算出应用自己的 `60234c72`、**零字节差异** ✓✓。**flash 模型交回的字节是对的** ✓。
而第 53 轮已经证明 **DMA 这一次运行也是对的**:`count=2568` ✓、`rx_addr=0x20000280`(覆盖区 ✓)、`rx_inc=1` ✓、两侧计数相等 ✓。
**所以这次传输把正确的 2568 个字节写到了正确的位置** ✓✓。
然而客户机覆盖区最后在偏移 2444..2474 处是 `40 d6 01 08 28 0a`,而应用在那里是零 ✓ —— 并且两个尺寸都落在 `code_size − 124` ✓。
**一个固件地址、出现在只有加载器与 DMA 知道的位置上** —— 这正是「被暂存的写」或「中断处理程序的栈帧」的样子 ✓。
下一个仪器:**让 DMA 探针把它在那一小段里真正写入的字节也打出来** ✓ —— 一次运行就能把「传输写的」与「之后别的东西写的」分开 ✓。
有一点值得直说,因为它已经耗掉好几轮:**现在有三个彼此独立的仪器都表明「源字节正确、传输也正确」** ✓,
所以此前所有把它读成 flash 或 DMA 故障的结论,**全部作废** ✓。