fix(wave): 落盘道改覆盖写,修回放页全板「该段不可读」

段名是 100 个槽位的轮转复用,而写者用 FA_OPEN_APPEND 打开:槽位复用时撞上
同名旧文件,新段接在旧段尾巴上,文件长成 5250 的整数倍。回放页读侧按单段
判 size == total,于是逐件落进 UI_WAVE_REJ_LEN(reject=2),全板没有一段
放得出来。

板级取证(100 件全部归档到 ci-artifacts/wave_append_backup_20261110/):
/wave 共 1868 段、9,807,000 B,尺寸全为 5250 的整数倍,恰好等于 5250 的
文件数为 0;逐段过 wave_read 校验全部合法、零损坏——数据一直是好的,只是
容器尺寸与写者语义相抵。

修法(用户裁定取写者截断,否掉读侧多段感知):打开模式改为
FA_WRITE|FA_CREATE_ALWAYS。本移植的覆盖分支会 remove_chain 并清零目录项
FileSize,是真截断;FA_CREATE_ALWAYS 单独用不含 FA_WRITE,两个位必须同给。
日志七本与 soe.log 仍用追加(靠历史累积,语义正确),读侧口径不动。

板端验收:新段尺寸恰好 5250 B;对已有 28 段的 WAVE00.BIN(147,000 B)录
一次当场变 5250 B,全目录总字节减 141,750 = 147,000 - 5,250。回放页从
bad 态(与黄金参考 shot_wave_play_bad 逐比特相同)转为正常态,页眉 24/24
行、页脚 20/20 行与 shot_wave_play 逐字节一致。

文档:故障录波设计说明 §5.2 陷阱表改打开模式完整口径,§10 新增收口记录;
带日期戳的 11-07 取证归档正文按当时口径冻结,只加一条口径已被推翻的指向。
This commit is contained in:
2026-11-11 01:06:04 +08:00
parent 7b05d7bfd4
commit b772117ed3
4 changed files with 58 additions and 9 deletions
@@ -158,7 +158,7 @@ flowchart TD
| 陷阱 | 事实出处 | 本波处置 |
|---|---|---|
| **取窗竞态** | `App/algo/sample_ring.c` 的 `sample_ring_window()` 没有临界区(读 `s_head` 后逐点拷),现在只有 adcTask 同任务调用才安全 | 录波**不复用它**:adcTask 在自己的节拍里取「本拍帧」压专用环;跨任务只传递「已封存」标志,杜绝跨任务读活缓冲 |
| **FatFs 追加写** | 该移植 `FA_OPEN_APPEND` 不含 `FA_WRITE`,且 `f_write` 簇分配失败时返回 `FR_OK` 加短写、无错误码(`docs/架构设计/STM32F4-Base存储架构说明.md` §10.1) | 打开一律 `FA_WRITE\|FA_OPEN_APPEND`;**判成功必须比对写回字节数**,短写按失败处理(丢段 + 日志),绝不当成功记账 |
| **FatFs 打开模式** | ① 该移植 `FA_OPEN_APPEND` 与 `FA_CREATE_ALWAYS` 都**不含** `FA_WRITE`,缺写位 `f_write` 恒返 `FR_DENIED`;② `f_write` 簇分配失败时返回 `FR_OK` 加短写、无错误码(`docs/架构设计/STM32F4-Base存储架构说明.md` §10.1) | 录波段**一律 `FA_WRITE\|FA_CREATE_ALWAYS`(覆盖,不是追加)**——段名是 100 槽轮转复用,槽位复用时会撞上同名旧文件,追加语义会把新段接在旧段尾巴上,文件长成 5250×N 而读侧只认单段长度,全板判废(2026-11-10 收口,见 §10);日志类(一次 `f_open→写→f_close`、靠历史累积的)才用 `FA_WRITE\|FA_OPEN_APPEND`。**判成功必须比对写回字节数**,短写按失败处理(丢段 + 日志),绝不当成功记账 |
| **只能 8.3 短名** | 出处同一张存储架构说明的短名限制:`FF_USE_LFN=0`,不得以点开头、不得长名 | 文件名 = WAVE 加两位十进制再加 .BIN,共 100 个名字轮转覆盖;目录 /wave(与 /log 平级,FTP 根下可见) |
| **落盘途中环被解冻、甚至已换段**(实现期复核新增第四条,2026-10-29) | adcTask 优先级高于 defaultTask:搬段窗口里 adcTask 可能正好走到 SEALED 滞留 2 s 自弃,甚至已经封出下一段。**数据区 CRC 是对「本道实际读到的字节」算的**,半新半旧的段照样自洽——长度等式照过、两个 CRC 照对,文件骗得过任何读者,也骗过我们自己 | 写完关档后**复检环侧身份**:`wave_ring_sealed()` 仍为 1,且 `wave_ring_get_stat()` 的 `last_t0_ms` 与 `last_seq` 跟本道开始搬时那份段头一致;不符即走既有失败路——unlink 本道开过的文件 + `write_done(-1)`(此时多半回 `E_STATE`,环侧 `err` 已由自弃计过,不重复补账)+ 段号不推进(下一段同号重写覆盖)+ 单条 `[ERR] … seg drop: ring abandoned` 留痕。落点 `App/task/default_task.c`:818 到 823 行(复检与弃段处置),统一留痕打印在 843 行。**2026-11-07 板级已闭环**(#84 台架远程轮:临界占卷命中三判据逐条对上,见 §10「bench 定时自抓与占卷弃段观察闭环记录」小节) |
@@ -573,6 +573,7 @@ ui_sink 17 / wave_ring 113 / wave_store 170 / wave_trig 110 全过;Keil 0 错
`default_task.c` 落盘道;11-06 的 STOR 全删全建掩盖了生长面)——拼接件对回放页单段读侧
(`size == total`)逐件判长度废,1866 段逐段过 `wave_read` 校验**全部合法**(零损坏,纯口径
相抵)。该面是否收口(写者截断 vs 读侧多段感知)是新的产品决定,本轮只如实记账,不并票。
→ **2026-11-10 已裁定并收口为写者截断**,见下「段文件跨开机追加缺陷收口记录」。
板级验收(2026-11-07 台架远程轮,烧录经口头「烧」确认;证据
`docs/测试说明/测试数据/wave_fix_20261107/`):**F1 五条 / F2 五条全过**——F1-1 页关投放
@@ -588,6 +589,42 @@ ui_sink 17 / wave_ring 113 / wave_store 170 / wave_trig 110 全过;Keil 0 错
缺失即其重试成功不打日志的一面;间歇边界(是否断电重连可消)未在本轮验证。详见证据目录 README 的
Before/After 表与串口原文。
### 段文件跨开机追加缺陷收口记录(2026-11-10,写者改截断)
**问题**:手动录波后进回放页,一个波形都看不到,恒显「该段不可读」。根因不在数据,在文件形态:
段名是 100 个槽位轮转复用(`App/algo/wave_store.h` 的「号 0..99 轮转覆盖,写满回绕盖最老的」),
而写者用 `FA_OPEN_APPEND` 打开——槽位被复用时撞上同名旧文件,新段接在旧段尾巴上,文件长成
5250×N。读侧(`App/task/ui_task.c` 的 `ui_task_wave_load_head`)按单段口径判 `size == total`,
于是逐件落进 `UI_WAVE_REJ_LEN`(reject=2),全板没有一段放得出来。
**板级取证**(FTP 直读 `192.168.31.100`,admin/admin;100 件全部归档到
`ci-artifacts/wave_append_backup_20261110/`):`/wave` 共 100 个 `WAVE00.BIN`…`WAVE99.BIN`、
9,807,000 B = 1868 段;尺寸 10,500~147,000 B **全部是 5250 的整数倍**,**恰好等于 5250 的文件数为 0**;
用 `test/host/wave_read.py` 的 `parse_segment()` 逐段校验 **1868 段全过、零损坏**。也就是说数据
一直是好的,只是容器尺寸与写者语义相抵。
**修复(用户裁定取「写者截断」,否掉「读侧多段感知」——后者改动面大,还要同步 PC 侧
`wave_read.py` 与 golden 帧)**:`App/task/default_task.c` 落盘道打开模式
`FA_WRITE|FA_OPEN_APPEND` → **`FA_WRITE|FA_CREATE_ALWAYS`**。本移植 `Lib/FatFs/ff.c` 的 `f_open`
覆盖分支会 `remove_chain` 并把目录项 `DIR_FileSize` 清零,是真截断;⚠ `FA_CREATE_ALWAYS`
单独用**不含 `FA_WRITE`**(旧式标志位),缺写位 `f_write` 恒 `FR_DENIED`,两个位必须同时给。
**验收证据**(本工作树,串口 COM4 + TCP 7003):
- Keil `0 Error(s), 0 Warning(s)`;烧录 `Programming Complete` / `Application started` / `RC: 0` / `FLASH: OK`,
固件戳 `Nov 10 2026 22:03:04` 与本次构建一致;启动锚点全过(`App init OK` / `Network Init` /
`net init: OK` / `protect task start: cycle=20ms elm=9` / 各 `[STACK]`),唯一 `[ERR]` 为既有的
`[ERR][I104] i104: bind port 2404 FAIL, retry in loop`(既有面,非本次回归)。
- **截断面**:清空 `/wave` 后触发一次手动录波,新段 `WAVE01.BIN` 尺寸**恰好 5250 B**;
另在清理前对已有 28 段的 `WAVE00.BIN`(147,000 B)录一次,尺寸当场变成 **5250 B**、
全目录总字节 9,807,000 → 9,665,250(差值 141,750 = 147,000 − 5,250),覆盖写落地。
- **画面面**:回放页从 `wave_play_bad` 态(ink=408,与 `simulator/tests/ref/shot_wave_play_bad.bin`
**逐比特相同**)转为正常态(ink=1087)——页眉 24/24 行、页脚 20/20 行与黄金参考
`shot_wave_play.bin` **逐字节一致**,只有波形主体不同(真机信号 vs 模拟器注入,预期内);
连按 12 次换段,ink 在 1065~1088 间变化,段间波形确实不同。
**未改动**:日志七本与 `soe.log` 仍用 `FA_WRITE|FA_OPEN_APPEND`(它们靠历史累积,语义正确);
读侧逐件判 `size == total` 的口径**保持不变**——现在写者保证单段长度,读侧不需要多段感知。
---
## 11. 与 `docs/算法/采集与算法层设计说明.md §13` 的口径变更(三处)
@@ -11,6 +11,10 @@ PNG 为 4× 放大目检件(非判据)。
`test/host/wave_read.py` 校验**全部合法、零损坏**)——写者 `FA_OPEN_APPEND` 跨开机累积
属既有设计,回放页单段读侧(`size == total`)按口径逐件判「长度废」:全板无可放段。
全量原件 RETR 备份 + sha256 台账在轮末**逐字节还原**(见「收工」节)。
⚠ 上面这段描述的是**当时**(2026-11-07)的盘面;「追加属既有设计」的口径已于 2026-11-10
推翻并收口为写者截断(`App/task/default_task.c` 改 `FA_WRITE|FA_CREATE_ALWAYS`),
收口记录见 `docs/子系统设计/故障录波设计说明.md` §10「段文件跨开机追加缺陷收口记录」。
本目录是带日期的取证归档,正文按当时口径冻结、不回改。
- 串口唯一枚举 CH340 = **COM4**(仓库 `.serialtest/config.json` 钉 COM3 已过时,本仓文件
未动,用 `$env:DSH_SERIAL_CONFIG` 覆盖);板端 FTP `admin/admin`。
- 到场固件 = 修复前构建(当日 01:00 已在跑,串口捕获窗口起点晚于该启动,构建戳不可证;