- 新增
docs/RELEASE_CHECKLIST.md,七部分:Windows 编译与测试、包内容、真机功能(含 Q68–Q70 改动的逐项验证)、干净机器从零安装与卸载回滚、许可证与合规、版本可复现与文档一致、发布动作。全部条目未完成。 - 条目汇总自 WORK_QUEUE(Q7、Q13、Q43、Q51、Q53、Q68–Q70 的待做)、LICENSE_MATRIX §5/§7、README「对外分发前还缺什么」,并标出需授权和仅对外发布才必需的项。
- 汇总时新发现一处文档缺口:卸载说明没有写怎么移除首次运行导入的自签证书,已列入清单。
- README 文档索引和「对外分发前还缺什么」加了指向清单的链接。
- 许可证:用户要求尽量宽松并交由助手决定,定为 MIT。新增根目录
LICENSE(纯 MIT,版权人暂写「DropBridge contributors」)和NOTICE(适用范围)。MIT 只覆盖自写代码和文档:patches/随所改上游;airbridge_radio.exe因链接 devourer 仍整体按 GPL-2.0 分发;awdld.exe为 GPL-3.0-or-later;固件授权问题不变。 - 打包脚本:
Package-Distributable.ps1、Package-Headless.ps1原来按原文件名复制许可证,WinAWDL 与 devourer 的LICENSE同名互相覆盖,包里缺 WinAWDL 的 MIT 文本。改为各自改名复制,并加入本仓库LICENSE、NOTICE和 WinAWDLairdrop/LICENSE。脚本未在 Windows 上运行。 - 文档对齐:DISTRIBUTION.md(去掉不存在的
Build-Python.ps1、从未生效的 stdin 热更新说法等);LICENSE_MATRIX.md(按当前组件改写,补 lwIP/Python/cryptography/补丁/Zadig 条目和 §7 包内许可证文本;Wintun、.NET 10 标为历史);STATUS.md(更新到 Q70,写明 Q68–Q70 未编译未测试);BUILD_WINDOWS.md(新增 §7:Q50 所用的 lwIP77dcd25、Python 3.12.10、cryptography 50.0.2,标明不是锁定);包内两份 README;Build-Supervisor.ps1与airdrop-beacon.ps1的文件头注释;README 许可证一节、安装步骤补上 UAC 前的确认弹窗。 - 验证:涉及文件的相对链接和脚本路径均存在;
.ps1仍为 CRLF;check_project.py停在预期的「upstream 未恢复」。lwIP、CPython、cryptography 等的许可证在矩阵里标为「未在本仓库内核对」。 - 遗留:包内仍缺 libusb LGPL 文本、lwIP BSD 声明、VC++ 运行库条款;lwIP/Python/cryptography 未锁定(加入 sources.lock.json 需先改 check_project.py);锁文件与实际构建不一致(Q50 完整构建用 devourer
f7e0731,锁为34a9e6f;OWL 锁0ee3263,构建用子模块da255a7);信标在 iPhone 未设为「所有人」时能否唤醒未单独测试;beacon 脚本「上游原样」的来源无法在本机核对,且文件头注释已有改动。
- 按「使用 → 开发 → 状态与限制」重排:新增常见问题、仓库结构、验证层级表;托盘菜单逐项说明保留并按 Q70 源码更新(重启限流、信标 10 分钟重试、接收文件夹回退)。
- 更正:许可证表不再把自有组件写成 MIT(仓库没有 LICENSE 文件,LICENSE_MATRIX 记为未定);硬件收窄到 USB ID
0bda:c820;构建步骤写明 netd 没有构建脚本、lwIP 与 python-embed 需手动准备。 - 状态栏写明 Q68–Q70 未在 Windows 上编译和测试,当前 HEAD 与最后一次测过的包不同。
- 验证:相对链接与脚本名逐个核对存在;
tools/check_project.py通过 README 检查后停在「upstream 未恢复」(本机无 upstream/)。README 描述的 Q70 行为本身未经实机验证。 - 发现但未改的过时文档:DISTRIBUTION.md 提到不存在的
tools\Build-Python.ps1;LICENSE_MATRIX.md 的 devourer 提交与 sources.lock.json 不一致,且仍写 .NET 10 WPF 与 Wintun;STATUS.md 把自有组件写成 MIT;Build-Supervisor.ps1 文件头仍写backend\AirBridge32.exe;beacon 脚本文件头仍写「仅对所有人可见的接收端有反应」;packaging/README.txt 未提 Q70 行为。
- 审查在 Linux 上静态进行,共 44 条代码问题,另有一条流程问题:Q69 未编译即提交,且日志写「都已修复」而队列写 MAC 修正未确认。
- 修复分三组并行完成,逐条先对照源码复核:工具与打包
57098b4、托盘与自解压8be6ebd、C++ 与补丁640e2a4。明细见 docs/WORK_QUEUE.md 的 Q70。 - 结果:40 条修复;2 条部分修复(日志写入开销、链路速率告警);1 条经核对不成立(
ipc_reader帧长上限);1 条未实现(每次启动唯一停止事件名)。补丁那条的「堆溢出」说法不成立,只修了属实的「长度为 0 时永久停滞」。 - 策略性改动待用户确认:信标每 10 分钟重试、重启限流以「稳定运行 10 分钟」为清零条件、接收端单独重启 10 分钟内最多 3 次、轻量流量阈值 100 帧/秒。
- 验证只有三项:补丁在 winawdl
2196fb48新克隆上git apply --check通过;驱动 INF sha1 未变;airdrop_send.py通过 py_compile。C#、C++、PowerShell 均未编译、未运行、未实机测试。 - 下一步:Windows 上按 Q70「待做」逐项编译和验证。
/code-review审查报告了 10 项主要问题和 4 项次要候选,逐条对照源码复核,全部属实,都已修复。明细见 WORK_QUEUE Q69。 涉及src/supervisor/Program.cs、src/awdl_netd/{local_io.cpp,local_io.h,lwip_proxy.cpp,main.cpp}、src/radio_usb/main.cpp。- 影响最大的两项:radio 停止事件停一次后一直处于触发状态(托盘内任何重启都会让新 radio 立刻退出);
receiver 重启后 netd 的 mDNS 转发线程因
WSAECONNRESET永久退出(iPhone 看不到本机)。 - 复核中发现并顺带处理:如果在
StartAll开头就复位停止事件,可能抹掉退出时刚发出的停止信号,所以改为在子进程列表锁内、确认未进入退出后再复位。 - 验证:本机为 Linux,没有 csc/MSVC/MinGW,没有编译;只人工通读了 diff(C# 按 csc 4.x 的 C# 5 语法写,没有使用
?.、$""等新语法)。 没有运行硬件,也没有传输文件。MAC 修正依据 RFC 4291 的 EUI-64 布局,upstream/不在本机,未能对照 OWL 源码。
- 用户确认:经验证,iPhone 端不需要把隔空投送设为「所有人(10 分钟)」。README 使用步骤、 包内 README 的日常使用和故障排查都删掉了这条要求。只改文档。
- 用户确认:Windows → iPhone 发送无法实现。WORK_QUEUE Q48 改为 ⛔ 终止,原记录保留; SEND_PATH 标题和开头加上结论;README、STATUS 和包内 README 改为「只做接收」, README 的「发布前还缺什么」去掉发送一项。
- README:AirBridge32 → DropBridge 的改名说明从开头挪到文末新增的「名称」一节; STATUS 已在本日重写为当前快照,从「历史快照」列表移到正式文档。
- 只改了文档,没有编译,也没有运行硬件。
- WORK_QUEUE 补上 Q68(提交
8c00d85,Supervisor 审查修复)。仓库里找不到它的编译、测试或真机证据, 所以记为 🔜,并写明待做的 Windows 验证项。 README.md:去掉 Q25–Q29 时期的过期横幅和旧证据清单,改写为现状、使用方法、架构、构建步骤和发布前缺口。docs/STATUS.md:整篇重写为 2026-11-04 快照。验证分四层列出(源码 / 本机编译 / 本机硬件 / 真实传输), 补齐 Q31–Q68 的速度里程碑和托盘功能;撤回 10-28「射频退化需冷复位」这一未被证实的结论。packaging/README.txt:改为自动装驱动流程、Q60–Q67 的菜单项、约 1.1 MB/s 的速度和 Q51 已知问题; 更正「不写注册表、不装驱动包」的旧说法,列出 HKCU Run、通知注册、驱动包和证书,并补全卸载步骤。- 验证:文中引用的脚本、证据文件和菜单项都对照源码与
tools/Package-Distributable.ps1核对过。tools/check_project.py在本机(Linux)因缺少upstream/winawdl中止,排在它后面的检查没有跑到。 本条只改了文档,没有编译,也没有运行硬件。
- 用户收到「收到文件:tmpXXXX (0 KB)」通知——那是 receiver 上传过程中在目标目录建的
Python
tempfile临时文件,被文件监视器当成“收到的文件”。 - 改法:监视器忽略无扩展名且(以
tmp开头,或 8–12 位小写随机名且此刻 0 字节)的条目; 300 ms 观察窗内已消失的文件也跳过。普通文件兜底气泡 4000→2000 ms,图片兜底 5000→3000 ms。 - 验证:建
tmpt3mujtn不再有任何提示;建_normal2.txt仍出现received file (balloon): _normal2.txt。 - 说明:系统 toast 的横幅时长由 Windows 控制(short ≈ 5 s),无法再短。
- 证据:
evidence/temp-file-noise-2026-11-02.log。
- 用户反馈改名/改接收文件夹没生效。根因:supervisor 跑的是
receiver.py,而{"do":"set",...}的命令循环只在engine.py(另一个入口)里,receiver.py不读 stdin。 所以 Q60 的改名、Q65 的改文件夹的"热更新"从来没生效,日志却写了成功。 - 证据:12:44:42 改到 Desktop 后,12:44:54 的
IMG_3579-1.PNG仍落在data\received, receiver 日志里没有任何 settings 事件。 - 修法:
RenameReceiver/SetReceivedFolder改为「写设置 + 只重启 receiver」 (射频不动,1–2 秒);改文件夹失败时如实报错,不再假成功。 - 验证:设置指向
build\_folder_live启动,receiver--outdir正确;杀掉 python 让看门狗 重启 receiver,新进程--outdir仍是新目录。已按用户选择把接收目录恢复为C:\Users\ndpyz\Desktop\test并重启(receiver 命令行已确认)。 - 另:同一时段的「发送被拒绝 / 卡等待」是链路问题——
/Ask请求体 30 s 读超时 → 408, 同时low link rate: 2 frames/s、radio 在 149↔6 反复重调;与接收文件夹无关。 - 证据:
evidence/received-folder-2026-11-02.log第 5、6 节。
- 用户要能改「收到的文件存哪」。托盘菜单新增「接收文件夹:<当前路径>…」→ 系统文件夹选择框。
settings.ini新增receivedFolder(空 = 包内data\received,非法路径回退默认);Supervisor.ReceivedDirectory改成可变,SetReceivedFolder()建目录 + 写探针验证可写 + 写设置 + 通过 receiver stdin 发{"do":"set","folder":...}(热更新,不重启射频); 托盘改完重建 FileSystemWatcher,菜单文字与「打开接收文件夹」同步。- 验证:设置指向
build\_custom_received后重启,receiver 命令行--outdir已是新目录; 往该目录丢图,日志received picture (toast);-Test全过(settings 覆盖 receivedFolder 往返、空值=默认、null 被拒)。只重打 portable,dist仍只留 zip + 解压目录两项。 - 未验证:菜单里的文件夹选择与"立即生效"要人工点一次。
- 证据:
evidence/received-folder-2026-11-02.log。
- 需求:收到苹果实况图自动生成一份「单图片副本」,要可选开关。
- 规则:
IMG.HEIC/.JPG/.PNG + IMG.MOV或单独的.HEIC/.HEIF→ 同目录IMG-still.jpg; JPG 静图字节级复制(不重编码),PNG/HEIC 走 WIC(WPF BitmapDecoder)解码后存 JPEG(92)。 普通单张 JPG/PNG 不生成;自己生成的-still.jpg被 watcher 忽略,不递归不重复通知。 - 开关:
settings.ini的stillCopy(默认 true)+ 托盘勾选项「为实况图生成单图副本」, 切换即写盘。生成工作在独立 STA 线程做,不卡托盘。 - 构建:新增 WPF 引用
PresentationCore/WindowsBase/System.Xaml(系统自带,无新依赖)。 - 验证:
_live2.JPG+_live2.MOV→_live2-still.jpg(8230 B,与源一致);_pnglive.PNG+_pnglive.MOV→_pnglive-still.jpg(走 WIC 重编码);单张 PNG 不生成; .MOV 不再单独弹气泡;-Test全过(settings 测试覆盖 stillCopy 默认值与持久化)。 - 未验证:没有真实 HEIC 样本,只验证了 WIC 解码→JPEG 这条链路(用 PNG 走通);
无 HEIF 扩展时会记
still copy failed并跳过。 - 证据:
evidence/still-copy-2026-11-02.log。
- 用户不要 Q61 那个右下角自绘小窗了。删掉
ReceivedPopup与AppIdentity.IconForPopup(约 250 行),ShowReceived的兜底从"弹窗"改成"普通气泡通知"——toast 不可用时仍会 提示收到照片,只是没有复制按钮。 - 验证:放入
_popup_gone_test.png后日志received picture (toast): _popup_gone_test.png, 没有任何自绘窗口;exe 从 107 KB 降到 102 KB。 - 保留:
ClipboardBridge(toast 按钮回调仍在用)、HandleActivation、命名管道转发。 - 证据:
evidence/toast-button-2026-11-01.log第 6 节。
- 用户要「系统通知里带一个确认/复制按钮」,并同意为此写 HKCU 注册(包里仍是一个 zip)。
- 关键事实:未安装的 exe 想有可点的 toast 按钮,必须有 ①带 AUMID 的开始菜单快捷方式
②
dropbridge://协议;否则 toast 被静默丢弃(实测未注册的 AUMID 连通知设置键都不生成)。 - 包内两个脚本:
tools\toast.ps1(PowerShell 5.1 调 WinRT 发带<actions>的 toast, 输入走环境变量避开引号地狱)、tools\register.ps1(写 AUMID、协议、开始菜单快捷方式并 回读字节校验,-Remove可撤销)。 - DropBridge.exe:启动时校验协议是否指向自己(搬家后自动重写);收图片先走 toast
(标题「收到照片」+ 文件名/大小 + 「复制到剪贴板」+ 缩略图),失败才退回自绘弹窗;
带
dropbridge://copy?file=...的第二个实例用命名管道把 URI 转给运行中的实例; 菜单加「清理通知注册信息」。 - 验证:
Start-Process "dropbridge://copy?file=<IMG_3637.PNG>"→ 日志clipboard: copied ... (toast button),STA 读剪贴板image: True 1290x2796+ 文件; 收图日志received picture (toast);通知库 Payload 里确认有<action content="复制到剪贴板">。 - 未验证:toast 横幅此刻不弹(已入库)——本机专注助手/通知设置可能拦了横幅,需用户侧确认。
- 证据:
evidence/toast-button-2026-11-01.log。
- 用户要「收到图片后的通知里加一个复制按钮,点了把图放进剪贴板」。 系统 toast 给绿色 exe 加不了按钮(要注册 COM 激活器),所以改成右下角自绘小窗: 设备名 + 文件名 + 大小 + 缩略图 + 「复制图片」「打开文件夹」「×」,12 s 自动消失、 悬停暂停、最多 3 个、不抢焦点(NOACTIVATE + TOPMOST)。
- 复制内容:位图 + 文件同时进剪贴板(粘到聊天是图、粘到资源管理器是文件); 解码不了(HEIC/WebP)时只放文件。非图片文件仍走系统通知。
- 关键坑:第一版弹窗画在屏幕外——托盘进程 DPI 不感知,
Screen.WorkingArea给的是 物理 2194×1234,而窗口坐标是 96 dpi 虚拟化的,于是定位到 (1834,954) 虚拟 = 物理 (3209,1669)。加TrayIcons.RealScale()(临时线程感知读真实 DPI)把工作区 除以 1.75 后定位,窗口落在物理右下角、可见。试过给进程加 dpiAware 清单, WinForms 把窗体反向缩到 197×123,已回退。 - 验证:屏幕截取确认弹窗压在最上层;
SendMessage(BM_CLICK)点复制后,STA 进程读到image: True 720x540和files: ...\_final_vis.png,位图与文件都在剪贴板里。 - 未验证:多显示器/任务栏不在底部、边打字边收图的主观体验。
- 证据:
evidence/received-copy-popup-2026-11-01.log。
- 用户要两件事:托盘里的选择要本地持久化;再加一个改设备名称的入口 (iPhone 隔空投送里显示的那个名字)。确认后按「只做三项 / 立即生效 / 托盘跟随」实现。
- 新增
Settings:<包目录>\data\settings.ini(纯文本、可手改),键为deviceName与beacon;启动读、改动写,没改过不落盘。旧版data\beacon-off标记作为迁移路径读取。开机自启仍走注册表 Run 项(本来就持久)。 - 托盘菜单新增「设备名称:<当前名字>…」对话框:校验后写 settings.ini、更新托盘提示、
更新通知身份(AUMID DisplayName),并通过 receiver 的 stdin 发
{"do": "set", "name": ...}立即改名(不重启射频);receiver 没跑就下次启动生效。 - 18 处气泡标题改成当前设备名;
--check增加device:行。 - 验证:
-Test新增src\supervisor\settings_test.cs(PASS settings,覆盖默认值、 不落盘、迁移、中文名往返、非法名拒绝);成品文件夹写deviceName=客厅的电脑、beacon=false重启后 receiver 公告receiving as 客厅的电脑、没有 beacon.log、 通知身份 DisplayName 也跟着变;删掉文件重启恢复默认且不再自动创建。 - 未验证:菜单对话框与"立即生效"要人工点一次(无 GUI 环境只能验证到"写文件→下次启动")。
- 证据:
evidence/settings-device-name-2026-11-01.log。
- 用户反馈:应用身份生效后标题变成
DropBridge,但头部图标位置空了。 - 原因:
IconUri指向 exe 时 Windows 解析不出图像,必须指向图像文件。 - 修复:打包时把
assets\dropbridge.ico放进包(文件夹版根目录、单文件 payload 的backend\),AppIdentity优先用 exe 同目录的DropBridge.ico;并把Ensure()从Main开头移到托盘分支——否则--check在临时副本里跑会把 IconUri 指到那个副本 (本轮实测踩到,注册表一度指向build\diagnostics\dist-check-*\...)。 - 验证:注册表 IconUri 指向
dist\DropBridge\DropBridge.ico且文件存在;portable 解压 344 文件含图标、--checkexit 0 无 MISSING;单文件解压后backend\DropBridge.ico在。 - 仍未验证:通知头部是否真的显示图标(全屏下 toast 被抑制,抓不到)。
- 证据:
evidence/icon-refresh-2026-11-01.log第 10 节。
22:11 用户确认:通知标题显示 DropBridge、头部图标正常(不再是橙色通用方块、也不空),
第 10 节的 IconUri 方案在真机生效。托盘 14:57 起连续运行 7 h 14 min,6 进程与三端口正常。
- 用户指出两处残留:资源管理器里的旧蓝色图标、通知左上角的橙色通用图标。
- 蓝色:全仓扫了 25 个
DropBridge.exe的 32 px 图标(ExtractIconEx + 像素哈希)。 旧蓝图标只在旧副本里:dist\app\20261030-135002\backend\DropBridge.exe和build\diagnostics\*的历史解压。删掉dist\app下除最新外的三个旧解压目录 (无 data\,单文件 stub 会重解压);用SHGetFileInfo复核 dist 两个 exe 的 shell 图标已是新的黑线条图。diagnostics 里的历史副本保留不删。 - 橙色:进程没有 AppUserModelID,Windows 只能显示通用橙色方块 + 文件名。
新增
AppIdentity.Ensure()(Main 第一行调用):SetCurrentProcessExplicitAppUserModelIDHKCU\Software\Classes\AppUserModelId\DropBridge.Tray(DisplayName=DropBridge、 IconUri=exe 全路径)。
- 弯路(别重走):先试"开始菜单快捷方式 + IPropertyStore 写 AUMID",PowerShell 对 WScript 创建的 .lnk 能写进去,但应用内创建的 .lnk 写入静默不生效(文件不增长、 字节里搜不到 AUMID)。放弃快捷方式,改用注册表;遗留的快捷方式已删。
- 未验证:通知头部是否真的换了图标。这台机器前台是全屏应用,Windows 在全屏下抑制 toast;两次 14 s 截屏(含触发"收到文件"气泡)都没抓到,通知库里也没有记录。 需要用户不全屏时看一次。
- 重打包:单文件 16,898,560 B(882d290e…)、portable 22,232,102 B(9231202d…);
--check全部 exit 0 无 MISSING。 - 证据:
evidence/icon-refresh-2026-11-01.log第 9 节。
- 用户反馈「还是细条,优先别重绘」。查到真根因:托盘进程是 DPI 不感知的进程,
GetDpiForSystem()被虚拟化成 96、SmallIconSize报 16,而真实系统 DPI 是 168(175 %), 任务栏按GetSystemMetricsForDpi(SM_CXSMICON,168) = 28 px绘制 —— Windows 把 16 px 帧放大到 28 px,所以又糊又细。图没问题,取的帧错了。 - 修复:
TrayIcons.SmallIconPixels()用SetThreadDpiAwarenessContext(PER_MONITOR_AWARE_V2)临时把当前线程切成感知态读真实 DPI 与 28 px,然后立刻还原(进程和不感知的菜单布局都不变)。 - 素材回退到用户原图:
tools/icon_frames.py从 256 px 母图纯 Lanczos 重新采样出 16/20/24/28/32/40/48/64/256 九档,删掉上一轮手绘的tools/hint_icon.py。 - 验证:
-Test打出physical tray size: 28 px、ForTaskbar(light)=28x28;重打包 portable/单文件后--check均 exit 0 无 MISSING;成品文件夹重启后 6 进程、三端口在听。 - 仍存限制:100 % DPI(16 px)下原素材本身偏软,这是素材在该尺寸的固有表现。
- 证据:
evidence/icon-refresh-2026-11-01.log第 8 节。
- 用户反馈托盘图标发糊。量测两份 ICO:16 px 帧里 alpha>=200 的像素是 0(20 px 只有 3 个), 20–199 的半透明像素 103/118 —— 这些尺寸是从 256 px 原图直接下采样来的,细线在 16 px 只剩 30–60 % 覆盖。本机 DPI 96、托盘 16 px,显示的就是这张半透明糊图。
- 修复:新增
tools/hint_icon.py,保留原 48/64/256 帧,把 16/20/24/32 重画成 1 px 笔画的 像素网格图(显示器 + 手机,同母题;16–32 px 不画双向箭头,那个尺寸画了必然糊成一团)。 重建后 16 px 帧 solid=48、semi=0。 - 验证:
Build-Supervisor.ps1 -Test全过;用ExtractIconEx取三个 exe 的 16 px 小图标, 都是新的像素图;重打包 portable/单文件后--checkexit 0、无 MISSING;成品文件夹的data\(28 文件 3,440,599 B)备份后原样放回并重启,托盘换用新图。 - 权衡:16–32 px 去掉了双向箭头,48 px 以上仍是用户原图(保留箭头)。
- 证据:
evidence/icon-refresh-2026-11-01.log第 7 节。
- 用户给了两张同图 ICO(显示器 + 手机 + 双向箭头,16–256 七档):黑色线条版作
assets\dropbridge.ico(exe 图标 + 浅色任务栏托盘),白色线条版作assets\dropbridge-tray-white.ico(深色任务栏托盘)。旧的蓝色方块图连生成脚本tools\make_icon.py一起删掉,免得以后误运行把图标画回去。 - 托盘不再靠
Icon.ExtractAssociatedIcon从 exe 取图:编译时两版都作为清单资源嵌入 (tray-light.ico/tray-dark.ico),运行时读HKCU\...\Themes\Personalize\SystemUsesLightTheme选版本,并订阅SystemEvents.UserPreferenceChanged跟随系统明暗切换;资源缺失才回退 exe 图标。Build-Sfx.ps1同时给单文件 stub 补了/win32icon(之前 stub 根本没有图标)。 - 离线验证:
Build-Supervisor.ps1 -Test全过,新增src\supervisor\tray_icon_test.cs检查两份资源都能按 16×16 读出、明暗两版都取得到;本机SystemUsesLightTheme=0(深色任务栏)→ 实际取白色版。build / 文件夹版 / 单文件三个 exe 提取出的图标 PNG 哈希一致(黑色线条图)。 - 重新打包:headless 16,871,628 B、portable 22,228,055 B(343 文件 41.7 MB)、单文件
16,892,928 B。portable 解压后
--checkexit 0 无 MISSING;单文件--extract后在解压 目录里--check同样通过。 - 现场:打包前 dist\DropBridge 正在运行(用户同意后停掉托盘,6 秒内进程与端口全清),
data\(21 文件 3,369,659 B)备份后原样放回;重打完成于 12:33:53 重新启动, 约 60 秒后 6 个进程齐全、49876/49877/8771 都在听。 - 踩到的坑(既有行为,未修):单文件 stub 只认
--where/--extract,--check会被 忽略并直接拉起托盘;验证单文件包必须先--extract。 - 未验证:真的切换系统明暗设置看托盘换图(会改用户桌面);黑色线条 exe 图标在深色 资源管理器背景上对比度偏低,这是单色素材的固有取舍。
- 证据:
evidence/icon-refresh-2026-11-01.log。
- 三层修复:
- 作业对象:非 radio 子进程全部
AssignProcessToJobObject到KILL_ON_JOB_CLOSE的 job(ChildJob,kernel32 P/Invoke),父进程一死系统连带清理,端口不残留; - radio 不进作业:改为收
AIRBRIDGE_PARENT_PID,用WaitForMultipleObjects同时等 父进程句柄与Local\DropBridgeRadioStop,任一触发就关监听走干净收尾(进作业会被 TerminateProcess,正是楔死芯片的原因); - 启动回收:
ReclaimLeftovers()只在端口被占时回收本包目录下的同类进程。
- 作业对象:非 radio 子进程全部
- 实测:就绪后
Stop-Process -Force强杀托盘 → 8 秒内本包进程与 49876/49877/8771 全消失; 立刻重启(不拔插)40→43 秒到数据面、shutdown: done/ exit 0。 - 顺带修掉一个我自己引入的坑:固件就绪轮询原先按 20000 次计数(每次 USB 读在卡死时要 ~15 ms), 实测挂起 309 秒才超时;改为按墙钟 1 秒。
- 注意:强杀后 radio 的最后几行日志写不出来(它的日志走父进程的管道),所以“干净退出”是靠 行为证明的:不拔插即可再次就绪。
- 根因是我们自己的启动顺序,不是 devourer:RX 线程原先在
accept()之前启动(还先睡 1 s), 而StartRxLoop()进入时clear_stale=true会清掉_rx_stop→ 停止请求在循环还没看它之前就被吞掉, join 永远等不到。这也解释了日志里“finished 之后 1 秒又出现 entering StartRxLoop”的怪现象。 - 修法三条:
- RX 线程改到
accept()成功 + HELLO 发完之后才启动(accept 失败的迭代没有线程,无需等待); - 两条收尾路径统一:
StopRxLoop()→ 有界等 2 s → 仍在则radio->Stop()取消传输 → 再等 2 s → 能回就 join,只有两次都失败才 detach; - accept 失败且本轮没有线程时直接跳过等待(之前这里白等 2 s)。
- RX 线程改到
- 实测:整段收尾 48 ms、
shutdown: done、exit 0、无 detach;停→启循环(不拔插)连续通过。 期间有一次启动命中已知的“init 卡住”(Q51 家族),设备自行恢复,不拔插也能再起。
- Q57(卡死进程被当健康)已修并真机验证:
Child.LastOutputUtc每行输出打点,Supervisor.SilentSeconds("radio")读静默秒数,TrayApp.CheckSilence在 5 s 看门狗里判断 “活着但 >30 s 没输出”→ 记日志radio silent for N s (hit X)并整链重启;3 分钟内第二次触发 弹“网卡没响应,请拔插”。选静默做判据是因为 radio 空闲也每 ~5 s 打stats cb=。 实测:用NtSuspendProcess冻结 radio 模拟假死 → 23:11:19 检测到 32 s 静默并重启 (新 radio 23:11:35 起)。冻结进程无法响应优雅停,必然被强杀,这就是第二次提示拔插的原因。 - Q58 残留量化:收尾已改为“有界等待 + 先
radio->Stop()再等 2 s”,但实测该 RX 线程 既不因StopRxLoop()也不因Stop()返回,最终仍 detach。因此 detach 是 devourer RX 循环 的固有行为,不是我们代码的残留;竞态只发生在进程退出瞬间,多轮未崩。 - 停→启循环(全程不拔插)连续两次通过:23:15:42 ready 40 s → 23:17:34
shutdown: doneexit 0; 23:17:40 ready 43 s → 23:19:45 同样干净退出。 - 结论:两项都收口——Q57 新增能力、Q58 已知残留且有量化说明。后续若要彻底消除 detach, 需要 devourer 侧提供可取消的 RX 传输。
- 定位:加 WARN 打点后看到 join(rx_thread) 卡住——StopRxLoop() 返回了,但 RX 工作线程仍在 StartRxLoop 里不出来。
- 修复:收尾改成有界等待(等 x_ended 最多 2 s,到了就 join,否则 detach,再由 adio->Stop() 取消传输);并把 stats_thread/writer/stats_stop 移到 accept 循环外(首版把它们放在循环内,失败路径留下 joinable std::thread → std::terminate)。
- 实测(三次连续,中间不拔插网卡):ready 02:31:03 → stop 02:32:03 exit 0;ready 02:32:53(43 s);ready 02:34:42(43 s)→ stop 02:35:33 shutdown: done / exit 0。从此停链路不会再楔死网卡。
- 残留风险:RX 线程是 detach 而不是 join,理论上可能在 adio->Stop() 时仍碰 adio/queue;三轮未崩,但更干净的做法是让 RX transfer 可取消(devourer 侧问题),记为后续项。
- 另:本轮用户实测正式发送 29,456,959 B / 26.85 s = 1.10 MB/s,哈希一致。
- 做了两轮真机回归:① --auto-exit 110 让监管自己优雅停 → radio 4 秒内没退完,被强杀(stopped (exit -1));② 立刻再启动(不拔插)→ radio 104 秒未就绪、再等 300 秒仍不 ready,网卡照旧楔死。
- 说明:命名事件确实送到了(radio 会关监听、跑 accept 失败收尾),但收尾里有一步阻塞,超出 4 秒预算 → 最终仍被 TerminateProcess。
- 首版还有真 bug:stats_thread/writer/stats_stop 在 accept 循环内声明,失败路径留下 joinable std::thread → 析构走 std::terminate。已改到循环外,重编通过。
- 下一步不猜:在 StopRxLoop → join(rx) → queue.stop → join(stats) → join(writer) → radio->Stop() → radio.reset() 每步前后加 WARN 日志(WARN 才能落盘),跑一次就能定位。
- 顺带:本轮用户实测的正式发送是 29,456,959 B / 26.85 s = 1.10 MB/s,哈希一致,说明新二进制+自动 beacon 的接收路径是好的。
- 现象:今晚每次停链路后再起,radio 要么
no device 0bda:c820、要么卡在 libusb (0.03 s CPU 持续几分钟、radio.log 一行没有),只能拔插网卡。根因是监管用Process.Kill()(TerminateProcess)停子进程,radio 死在半个 USB 传输里。 - 修法:radio 在
listen()后打开命名事件Local\DropBridgeRadioStop并挂线程等待, 被唤醒就closesocket(listener),让accept()失败、走原有干净收尾(StopRxLoop → 等 RX 线程 → 队列停 → 设备 Stop → libusb release/close/exit)。监管在建链前EnsureRadioStopEvent()创建手动复位事件,Child.Stop()对 radio 先 signal 再等 4 s, 超时才 Kill。没用 Ctrl+C:监管是 winexe 无控制台、子进程 stdio 被重定向。 - 已验证:两处重编通过(supervisor 65 KB / radio 597,504 B),离线测试全过。
- 未验:start → 优雅停 → 再 start(不拔插)。当前网卡是修前楔死的,需要你最后拔插一次。
- 依据 Q54:带 beacon 时 1.09–1.10 MB/s / 867–970 fps / CRC 3–4%,不带时 0.32–0.88 MB/s / 313–788 fps / CRC 13–49%。beacon 决定手机 AWDL 是否醒着,是今晚最大的体验变量。
- 改动:
src\beacon\airdrop-beacon.ps1(上游 MIT 原样)→ 打包进backend\beacon.ps1;TrayApp启动它、看门狗每 5 s 检查并在它退出时重启、退出时杀掉;日志写data\logs\<会话>\beacon.log;托盘加「蓝牙 beacon(帮 iPhone 发现这台电脑)」默认勾选, 关掉写data\beacon-off记住;脚本缺失/无蓝牙只记日志不挡接收。 - 验证到的:01:42:37 起链路,0.4 s 后
beacon.log就有advertising(早于 radio 自己完成初始化)。 - 没验证到的:那两次 radio 在 libusb 里卡死(4 分钟 0.03 s CPU、radio.log 一行都没有), 所以端到端要等拔插网卡后重测。卡死是我今晚反复强杀 radio 造成的已知状态,不是 beacon 引起 (beacon 只碰蓝牙)。
- 由此发现两个真缺陷:Q57 卡死进程仍被当健康(需要静默检测:radio 空闲也每 5 s 打 stats,
30 s 无输出即判死并重启,连续失败就提示拔插);Q55 强杀留子进程占端口。
- 按用户要求:每个提交都立刻推送到云端。
- 起因:另一台设备(
MCxiaotian)的e1ba5b7把 awdld 的-s 2改成-s 1(RTL8821C 是 1T1R), 但他们那轮单流测试peers=0、没有任何传输,等于没验证;而他们记录的慢窗 CRC 67–70% 很像“宣告双流→手机发双流→我们解不出”的表现。所以用真机 A/B 定案。 - 做法:新增
--streams 1|2(Supervisor.AwdldStreams,默认 1;--check的计划里打印streams:一行),同一视频/手机/身份/插口,两次都开蓝牙 beacon。 - 结果:
-s 226.89 s / 1.10 MB/s / 970 fps / CRC 3.4%;-s 127.04 s / 1.09 MB/s / 867 fps / CRC 4.2%;落盘哈希都等于源文件,无 408。差 0.9% 属噪声 → 保留-s 1。 - 真正重要的发现:这两次 1.10 MB/s 都是在 beacon 广播时测到的;今晚此前不带 beacon 的 多次只有 0.32–0.88 MB/s、CRC 13–49%,同样的二进制、同样的文件。beacon 把手机的 AWDL 叫醒并对齐,帧率直接到 970。→ 让托盘在运行时自动开 beacon,是剩下最大的体验收益(待做)。
- 暴露一个真缺陷(未修,记 Q55):强杀
DropBridge.exe时子进程仍占 49876/49877/8771, 下一次启动报「端口已被占用」(01:35 用户亲眼看到)。正常退出不受影响。
- 用户表态“不用管协议,一切服务于最终使用体验”,所以把驱动安装缝进程序本身:
RadioBinding(supervisor 内)用 WMI 读USB\VID_0BDA&PID_C820&MI_02的 Service; 未绑 WinUSB 时用 ShellExecuterunas自提升一次,然后在提权副本里certutil -addstore Root/TrustedPublisher+pnputil /add-driver装我们自己的 WinUSB 包, 复查后继续启动。托盘菜单加「安装驱动(首次使用)」;日志写data\logs\driver-install.txt。 - 驱动包不再依赖 Zadig:
tools\Build-DriverPackage.ps1用 SDK 自带的makecat+signtool现做一个极窄的 INF(只含MI_02)+ 自签证书 + 已签名目录,signtool verify /pa0 错误。 Zadig 留在setup\只作兜底。证书私钥留在构建机上、不进包,指纹记录在packaging\driver\signing-thumbprint.txt。 - 顺手修了两个真实体验问题:
- 未知参数以前会静默进托盘并启动整条链路(我拿旧包实测踩到,进程挂住不退出); 现在 exit 2 并写一行 supervisor.log。
- 首次启动自动设开机自启并提示一次(
data\autostart-offered标记,菜单可关), 避免“装好了但没在跑,所以收不到”。
- 验证:
--install-driver在已绑机器上 exit 0 且不碰 pnputil(幂等);从 zip 解压出来同样 exit 0;--bogusexit 2;zip 启动一次确实把 Run 指向包内 exe 并写标记(测完已清掉 Run);--check仍 exit 0。 - 未验证:真正的未绑→已绑转换。要验证必须先解绑唯一可用网卡,恢复手段明确(Zadig 或本安装器), 但需要用户点头并点一次 UAC。在这一步之前,驱动安装只能算“已构建且签名校验通过”。
- 用户问“需要把 Zadig 带进去吗”。结论:要带,因为每台新机器都必须把
USB\VID_0BDA&PID_C820&MI_02绑到 WinUSB,否则 libusb 打不开网卡;不带的话第一次上手就卡在 “自己去找 Zadig + 选对设备”。 - 做法(
packaging\setup\,进包即setup\):Bind-WinUSB.cmd自提升 → 先跑只读的Check-Binding.ps1(0=已绑 / 1=需绑 / 3=网卡不在)→ 确实需要时才打开zadig-2.9.exe→ 10×3 s 复查 → 写setup.log;脚本里只出现MI_02一个设备 ID,避免误绑 MI_00/复合设备。 配中文README.txt(三个接口的区别、逐步操作、恢复原驱动的方法)与licenses\ZADIG.txt(版本 2.9.788、SHA-2564ecaa95d…、上游地址、GPLv3 源码书面要约)。 - 离线验证:本机已绑 → exit 0;把 VID/PID 换成不存在的 → exit 3;zip 解压后 6 个文件齐全、
从解压树里跑 shipped 检查脚本 exit 0、同树
--checkexit 0 无 MISSING。 - 代价:
dist\DropBridge-portable.zip16,858,026 → 22,196,026 B(+5.3 MB 就是 Zadig)。 许可证义务沿用 owl 的 GPLv3 处理方式(全文 + 源码要约);若不想承担,删掉zadig-2.9.exe改成给下载页即可,脚本路径已按“打不开就人工下载再复查”设计。 - 新的分发形态:解压 →
setup\Bind-WinUSB.cmd(一次性)→DropBridge.exe(托盘)。
- 用户要求“拉取上游最新并说明”,实际只有 devourer 动了:
f7e0731→34a9e6f(7 个提交,全是 jaguar2)。 上游作者是在另一台设备上跑分发版时踩到坑再修的,所以这批的价值是换台/换环境的鲁棒性,不是提速; 但真机 A/B 意外证明它同时把吞吐拉回来了。 - A/B(同一 29,456,959 B 视频、同一手机/插口/
data\身份、分钟级间隔):- 发货版
f7e0731:92.86 s = 0.32 MB/s,293–313 fps,传输窗 CRC 49.3%; - 新版
34a9e6f:36.48 s = 0.81 MB/s,323→788 fps,CRC 12.8%(Q46 基准 1.0975 MB/s、787 fps、15.1%)。 机制对得上:上游88be325/d0a9e10修的是 fast_retune 后0x838DFS CCA 与 notch 的残留——我们每秒在 149↔6 快切,正是这条的作用面。
- 发货版
- 补丁:
patches\devourer-airbridge.patch用新 base 重新生成(git diff从34a9e6f),对 pristine basegit apply --checkexit 0,零冲突;docs/sources.lock.json更新为34a9e6f95c08da6cb7c4e621e070058e2cc5e779。 - 固件启动:
cpu-reset retry新旧版本都有(旧版 00:13/00:24 会话也出现),HalmacJaguar2Fw.cpp两版字节相同, 不是升级引入。超时的代价是整包重下(2 min 01 s vs 43 s)。本 fork 把就绪轮询 250 ms→1 s 并打印真实启动 耗时(实测 8.3–21.9 ms、1–2 次轮询),“只是慢”时不再触发重下;14 次连续启动干净。 - 分发版已重建:
dist\DropBridge-portable.zip(16,858,027 B,7f8f2f5b…),radio20efc55c…(596,480 B)。 解压验证--checkexit 0 无 MISSING;--auto-exit44.2 s 数据面就绪、receiving as DropBridge、退出清干净。 - 仍未做:这份 zip 的真机传输、新 radio 的 3 次中位数、固件超时的根因(今晚 4/10 会话出现,改完轮询后 14 次未复现,尚不确定)。
- 单流会话
20261030-172321现场 peers=0、没有接收请求;用户随后补充先前忘记回复、要求继续。此轮不能作为单流性能失败或改善证据。硬件单流宣告修正仍在源码,不将其称为卡顿修复。 - 恢复原成品监管,17:27:46 会话
20261030-172720就绪;实际-s 2 -j 400、social、149/20,原 data。16 PM 同一张 339331 B JPG 两次均落盘并 HTTP200:17:28:34.189–17:28:39.003 = 4.814 s / 0.0705 MB/s;17:30:24.861–17:30:27.096 = 2.235 s / 0.1518 MB/s。 - 用户第一轮反馈观感流畅,第二轮反馈“卡了一下但还是正常”;第一轮 Ask 收体 1.639 s。主观响应改善不能等同于吞吐提速,也不能归功于未启用的单流候选。
- 保留原配置进程继续运行,不再重启。源码候选不推广到原 ZIP/SFX,Q51 根因/稳定性仍未解决;后续需要有效单流对照与无线回程/ACK/聚合证据。脱敏数据追加到
evidence/one-stream-advertisement-2026-10-30.log,成功会话已冻结,入口见build/original-16pm-two-successes-collect.log。
- 对照用户反馈“15 PM 顺畅、16 PM 卡顿”继续追踪,发现监管与 Start-Chain 都传
-s 2。WinAWDL 明确用它开启第二空间流的接收 MCS;硬件记录为 RTL8821C 1T1R,能力宣告超过硬件实际支持。 - 两处入口改为
-s 1。托盘三项离线检查、手动入口 dry-run、现有 frame_fixup 测试及隔离候选--check均通过。后端二进制哈希未变;没有同时修改 TCP 合并或频道。 - 已用隔离候选监管、原 package root/data 启动真机对照,17:23:47 会话
20261030-172321就绪,实参-c 149 -s 1 -j 400 -v;此轮没有形成有效手机请求,后来恢复原成品并连续两次成功(见上)。配置错误确认,但卡顿根因/改善尚未验证。原 ZIP/SFX 仍为旧版,未替换。 - 证据:
evidence/one-stream-advertisement-2026-10-30.log。Q51 保持进行中,未修改驱动、网络或持久化逻辑。
- 用户确认 17:02/17:03 的两次 JPG 来自 16 Pro Max,15 Pro Max 同时打开隔空投送页。339331 B / 2.891 s = 0.1174 MB/s;450306 B / 5.658 s = 0.0796 MB/s,第二次在上传前另有约 4.39 s 的 Ask 读取等待。
- 两个慢传窗口的网卡 CRC 错误/接收回调增量比例为 69.6% / 67.0%,此前快传窗口约 15.2% / 20.2%;这是整张网卡统计,不能直接视为 16 PM 的丢包率或机型因果证据。netd 软件丢弃/输入等待均为 0;本机仍为同步主控。
- 优先排查无线接收错误与重传,未确定是帧/聚合兼容还是双机调度影响,不归因于 iOS 版本。另见同 SenderID 17:06 上传 219233 B 时只到 16379 B 后 408,该轮条件未确认。
- 用户反馈 15 PM 退出分享页后 16 PM 仍卡等待:17:09:10 的 Ask 请求体到约 14 KB 后停顿,30 秒后 408。冻结后于 17:11:01 同配置重启,17:11:27 新会话就绪;16 PM 同图重试仍不顺,17:12:44.260–17:12:56.918 的 Ask 读取耗时 12.658 秒,含 117891 B 预览,此时还未开始上传文件主体。慢 Ask 时 peers=1,但 17:13:03 再次出现第二 peer,不能把关闭分享页等同于彻底隔离另一设备的 AWDL。
- 未修改源码、驱动、网络或运行参数。脱敏证据:
evidence/two-phones-16pm-2026-10-30.log;三轮现场均冻结于build/diagnostics/,见证据和build/*16pm*collect.log。不能把这些结果归因于 iOS 版本,也未验证关闭聚合等候选修复。
- 用户运行本次成品后,16:53:51 收到 493588 B 照片;随后手机看不到设备。六个进程和端口都存活,16:56 起新连接出现 SYN/SYN-ACK 重传、TLS EOF/握手超时。netd 无软件丢弃;本机 TLSv1.3/HTTP 200 仅用 0.031 秒,不能把此故障简单归因于 receiver 退出。
- 用现有 Collect-Session 冻结故障:
build/diagnostics/20261030-165851-discovery-lost-after-upload。原始日志含个人信息,留本机,不提交。 - 17:00:25 核对 PID/路径后停止原监管及子进程,17:00:28 重新启动同一成品,沿用原配置和 data。用户确认重新出现;17:01:04 又收到 1542983 B PNG,上传返回 200,未取得手机原始哈希,不声称端到端哈希一致。
- 未修改源码、驱动、网络、防火墙或数据。根因仍未确定,重启恢复不能当作修复。证据:
evidence/discovery-recovery-2026-10-30.log;Q51 保持进行中。
- 当前只有源码,恢复锁定 devourer、WinAWDL、OWL/radiotap 及现有补丁;使用本机 VS2026/MSVC、MinGW 和便携 Python 构建完整组件,未修改源码或构建脚本。
- 产物:
dist/DropBridge-portable.zip16,676,195 B、dist/DropBridge.exe16,677,888 B,以及 headless ZIP。portable 干净解压及 SFX 解压后--check均 exit 0。 - 验证:radio 2/2、AWDL 5 个测试程序、托盘 3 项、会话分析 10/10 通过;netd 6/7,IPC 用例 15 秒及独立 90 秒均超时,未修复,不宣称回归全绿。
- 依赖边界:lwIP 使用 2.2.1(历史文档写 2.2.2,但未锁定且对应 release 标签不存在);Python 3.12.10 / cryptography 50.0.2。因此不能以本次构建继承旧包的真机性能结论。未运行无线、未测试手机;未修改持久化功能。
- PR 预检不可用:匿名 API 404、gh 未登录。仅提交构建记录,不推送。完整证据与产物哈希见
evidence/compile-windows-2026-10-30.log,原始构建与失败日志留在build/。
- 起因:用户问“我的设备应该叫 DropBridge,为什么叫 AirBridge32”。查证:
docs/用户任务书.md:5写的是 “项目名称:AirBridge32(暂定)”,程序一直沿用这个暂定名(iPhone 列表里看到的就是--name AirBridge32), 而目录/仓库叫 DropBridge。 - 改动只限用户可见面:receiver
--name、托盘 Text/气泡标题/诊断标题、开机自启项名、单实例互斥名; 产物改成DropBridge.exe、dist\DropBridge\、DropBridge-portable.zip、DropBridge-headless.zip、 图标assets\dropbridge.ico;Start-Chain.ps1的--name也改了。 - 兼容:
Build-Supervisor.ps1仍额外输出一份AirBridge32.exe,因为build\diagnostics\*里冻结的 测试包 launcher 指向该路径;自解压 stub 的 launcher 查找也先DropBridge.exe再回退旧名。 内部组件名(airbridge_radio、radio_pipe_bridge、awdl_netd、awdld)不变。 - 验证:portable zip 解压后
--checkexit 0、无 MISSING;--auto-exit 45数据面就绪且 receiver 打印receiving as DropBridge,退出后无进程/端口、%LOCALAPPDATA%\DropBridge与AirBridge32都不存在; 单文件dist\DropBridge.exe --extract→app\20261030-135002(backend 内有 DropBridge.exe、netd 4e7a65ee), 对该目录--checkexit 0。 - 注意:这份新包里的 netd 是 4e7a65ee(含 mDNS 组播 join、查询路由、opt-in 出站代理),与 Q46 实测 1.0975 MB/s 用的 300aac25 不是同一个二进制;数据面未变,但真机传输还没在这份 zip 上复测。
- 用户要求做“电脑发给手机”,并要求复用仓库已有实现。新增 netd 的 opt-in 出站 TCP 代理(
--allow-send才起,本地 8772,先CONNECT <ipv6> <port>再原样转发,TLS 端到端);tools/airdrop_send.py复用上游sender.py/transfer.py/identity.py(MIT),只替换“发现”(走 netd 的 mDNS 代理)与“连接”(走 8772 代理)两处传输假设,并支持--address直连。 - 修掉一个真 bug:netd 开了
LWIP_IPV6_MLD却从未加入ff02::fb,lwIP 在ip6_input丢弃手机全部 mDNS 组播(表现为rx_seen涨、代理零包)。加入组播组后能收到手机的查询与应答;应答改为优先回给最后一个提问者;新增AWDL_NETD_MDNS_DEBUG(含前 96 字节十六进制)。 - 用上游
airdrop-beacon.ps1(走 Intel 蓝牙,与坏的 Realtek MI_00 无关)把手机叫进 AWDL:收到 180 条 mDNS 应答、解析出地址fe80::64e2:eff:feb5:8e78与 SRV 端口,出站 TCP 与 TLS 握手都到达手机——但 iPhone 以 TLS alert 拒绝本项目自签证书(上游Unwilling)。用户确认 AirDrop 一直是「所有人」,所以不只是“仅联系人”问题。 - 离线回归:
tools/test_netd_tcp.py --case outbound通过(CONNECT 解析 + lwIP 发出 SYN)。真机结论见docs/SEND_PATH.md§7;队列 Q47(基础,完成)/Q48(阻塞)。 - 硬件:用户报告的重启实际未发生(uptime 11 h 36 min),
pnputil /restart-device对 MI_00 无效,仍为CM_PROB_FAILED_ADD;要真正恢复需shutdown /r /t 0或完整重启。接收方向不受影响。
- 同一成品(
dist\AirBridge32\AirBridge32.exe)、默认配置(social + 合并),29,456,959 B 于 01:01:59.934 → 01:02:26.772 = 26.84 s / 1.0975 MB/s;落盘 SHA-2565d057f9c…一致、无 408。 - 数据面干净:
input_fail=0、input_retry=0、tx_fail=0;无线侧手机帧率中位数 787(峰值 891)、CRC 15.1%、RSSI 62–70、MCS 17–19;1,286 帧承载 29.66 MB(约 23 KB/帧,聚合生效)。 - 与历史最好 1.12 MB/s(26.26 s)相差 2% 以内,且这是分发出去的那个文件夹里的二进制。
- 默认配置现有两轮(0.9231 / 1.0975 MB/s),按 FINAL_PLAN 还差一次才能取 3 次中位数;20 次零干预、24 h、干净 VM、签名、固件许可仍未做。
- 收尾:停链路无残留、端口全空;会话冻结在
build/diagnostics/20261029-010238-shipped-folder-video/。
- 用即将发出的
dist\AirBridge32\AirBridge32.exe起链路(托盘模式,--data dist\data保持同一证书/实例 ID),默认配置(social + 合并)。 - 用户发的是 1.21 MB 小图而不是 29 MB 视频:
IMG_0333.PNG1,207,111 B,00:55:35.483 → 00:55:36.714 = 1.23 s / 0.98 MB/s,落盘哈希c9bb9fe1…一致、无 408、input_fail=0/input_retry=0。满足 FINAL_PLAN「<1 MB 小文件端到端 ≤2 s」(此文件 1.21 MB 也在 2 s 内)。 - 29 MB 路径尚未对这份成品跑过;接收侧二进制(netd/radio/awdld)与 Q41 逐字节相同,只换了 supervisor(图标/根解析/自启路径),不在数据路径上。
- 收尾:停链路无残留、端口全空;会话冻结在
build/diagnostics/20261029-005549-shipped-folder-smallfile/。
- 用户要求“完整版、易分发、单文件夹、压缩包、常驻托盘”,按文件夹形态重做入口:根目录
AirBridge32.exe就是托盘程序(winexe、无控制台窗口),backend\放全部后端,data\在同级生成。 - 代码:
ResolveRoot在 exe 目录含backend\时以它为包根(自解压形态逻辑不变);AutostartValue在文件夹形态下注册根目录 exe;托盘图标改为从 exe 内嵌图标提取(assets\airbridge32.ico,由tools/make_icon.py用 Pillow 生成,Build-Supervisor.ps1用/win32icon:嵌入),取不到回退系统图标。 - 新增
tools/Package-Distributable.ps1:可重复生成dist\AirBridge32\与dist\AirBridge32-portable.zip(含中文 README、VERSION、许可证、Python._pth修正),拒绝在dist\之外删除、拒绝覆盖含data\的目录。 - 验证(本机,无手机):干净解压后无参数
--checkexit 0 且无 MISSING;--auto-exit 15043 s 就绪(49876/49877/8771 + UDP 15353),第二实例立即 exit 0(单实例),00:40:49 自动退出且无残留进程/端口;%LOCALAPPDATA%\AirBridge32未创建;data\keys/received/logs全在文件夹内。 - 已知:未签名、Realtek 固件再分发许可未解决(仅供内部测试)、干净 VM/卸载残留/24 h/3 次中位数均未做。文档见
docs/DISTRIBUTION.md。
dist\AirBridge32.exe之前仍含 pre-Q28 的 netd(6c3f4465…),Q28/Q29/Q31/Q38 都没进日常双击路径。现从 build 重新打包:16,824,320 B、bf20e9f2…;payload 内 netd300aac25…、监管3ec4d28b…;旧 exe 备份为dist\AirBridge32-pre-q38.exe。- 验证:
AirBridge32.exe --extract解到dist\app\20261029-002027,其中 netd 哈希一致;对该目录跑--checkexit 0、无 MISSING,计划为 social + 合并(Q41 实测 0.9231 MB/s 的同一配置)。 - 未做:干净 VM、代码签名、固件再分发、真机双击验证(只验了离线解包与计划)。
- 同一 Q38 包,不加任何覆盖参数(
social+ 合并开,即监管默认):00:18:39.146 → 00:19:11.057 = 31.91 s / 0.9231 MB/s,落盘 29,456,959 B、SHA-256 一致、无 408、input_fail=0、input_retry=0。 - 射频:手机 826/749 帧/s(中位数 670)、CRC 15.8%、RSSI 72–75、thermal 28/2;awdld 3,536+ joined(聚合生效)。
- 当晚五轮对照(同视频):fixed+j0+丢帧 0.3836;fixed+j0+等待 0.8518;fixed+j0 对照 0.6917;fixed+合并 0.040(CRC 40.3%);social+合并 0.9231(CRC 15.8%)。
- 结论:默认配置不需要改;合并与信道序列耦合(social 开、fixed 关)。Q38 的 netd 修好之后,默认配置已接近历史最好 1.12 MB/s,余下差距属轮次噪声(同配置两轮 0.85 vs 0.69 差 23%)。
- 验收提醒:单次结果不能当验收,按 FINAL_PLAN 要 3 次取中位数;下一件事是把 Q38 的 netd 打进单文件包并用默认配置做重复测量。
- 收尾:只杀包内子进程、端口全空、未复位射频;会话冻结在
build/diagnostics/20261029-001923-q38-social-merge-phone/。
- Q39:同包、同 fixed 149/20,只把 TCP 合并打开(
join_us=400)。118 s 只推进 4.69 MB(约 40 KB/s),用户肉眼可见很慢,按停止条件中止。 - Q40:紧接着用同包
-j 0做对照,42.60 s / 0.6917 MB/s、500–561 fps、RSSI 77、零丢帧 —— 说明合并轮之后射频没有退化。 - devourer 计数器(日志增量)给出归因:合并关 11.4% / 12.1% CRC、手机帧率中位数 121 / 625;合并开 40.3% CRC、帧率中位数 33,RSSI 区间三者重叠(73–80)、MCS 中位数都是 19。即合并让手机发聚合帧,同速率下大帧的 CRC 错误率是单帧的 3.4 倍。
- 结论:fixed 149/20 下保持
-j 0;历史 1.12 MB/s 的 social+合并 不能用外推到当前条件,需单独对照(下一步)。 - 两轮
-j 0之间 0.85 vs 0.69 MB/s(23%)属手机/链路轮次噪声,不是代码差异。 - 收尾:每轮都只杀包内子进程、确认端口全空、未复位射频;三份会话分别冻结在
build/diagnostics/20261029-000444-*、-000917-*、-001530-*。
- 同一包、同一参数(fixed 149/20、
-j 0、metric 200/counter 100000)、同一视频,只换 netd(9cc25b27…→300aac25…)。会话20261029-000151,00:03:55.399 开始上传,00:04:29.983 落盘。 - 结果:29,456,959 B / 34.58 s = 0.8518 MB/s,SHA-256
5d057f9c…一致,无 408。input_fail=0、input_retry=103(103 次“队列满”改成了等待)。 - 对照:Q37(64 槽仍丢帧)76.79 s / 0.3836 MB/s /
input_fail=76;Q27(老 netd)0.114/0.104 MB/s。本轮对 Q37 是 2.2 倍、对 Q27 是 8.2 倍。 - 含义:此前的低速主要是我们自己丢帧造成的重传与拥塞窗口回退,不是空口能力不足。103 次等待证明队列确实满过。
- 收尾:只杀包内子进程(已确认无残留、端口全空),未复位射频;
Collect-Session.ps1冻结到build/diagnostics/20261029-000444-q38-fixed-j0-phone/。 - 下一变量:仍用
-j 0(joined=0),而历史最好 1.12 MB/s 是合并开启 → 立刻跑 merge 开启对照(会话20261029-000451)。
- Q37 显示 64 槽仍会在手机开局整窗突发时丢帧(76 帧)。根因是
tcpip_inpkt()拿不到struct tcpip_msg时返回ERR_MEM,而 netd 直接丢掉这一帧。 - 改成
g_netif.input()返回ERR_MEM时按 1 ms/次最多等 500 ms 后重投同一 pbuf(失败时 pbuf 仍归调用方),超时才计input_fail丢弃;新增input_retry计数并在ipc stats里输出,分析工具显示为input waits=(旧日志无此字段也能解析,回归 10/10)。 - 本机同一突发 A/B(16 MB、240 KB 在途、batch 128):Q31
input_fail=115/ 4.47 MB/s / 1 次回退;Q38input_retry=21/input_fail=0/ 7.25 MB/s / 0 次回退。batch 16/64 不触发等待,说明只在过驱动时生效。CTest 7/7。 - 测试包
build/diagnostics/input-retry/test-package(334 文件只换 netd300aac25…,两份--checkexit 0,入口Start-Q38-Test.cmd)已备好,尚未上手机。 - 未做:真机验证;没有改池/窗口/合包或驱动。
- 用户手机可用后起链路:23:54:04 起(radio listening 23:54:46),会话
20261028-235404;参数与Start-Q31-Test.cmd一致,只额外加--smoke 1500(限定寿命)。 - 用户在 23:56:31 发同一个 29.46 MB 视频一次:23:57:48.927 完整落盘
7545-7.MP4,76.79 s = 0.3836 MB/s,POST /Upload 200,无 408;SHA-2565d057f9c…与历史副本一致。 - 同一配置对照 Q27(老 netd、8 槽、无 Q29 清理)的 258.1/284.6 s = 0.114/0.104 MB/s:本轮是 3.4–3.7 倍,且从“两次一次 408”变成一次完整完成。
- netd 计数:22,075 帧 / 31.29 MB,
input_fail=76(0.34%),alloc/copy/tx全 0;丢帧集中在开局(654 帧时 45 个),手机overlap涨到 155 在重传,稳态约 560 帧/s。说明 64 槽仍挡不住手机的初始整窗突发。 -j 0下joined恒为 0,单帧约 1.4 KB,本身压低上限;下一轮应单独对照“合并开启”,不同时改队列。- 收尾:只杀包内子进程(已确认无残留、49876/49877/8771/15353 全空),未复位射频、未改信道;用
Collect-Session.ps1冻结到build/diagnostics/20261028-235816-q31-fixed-j0-phone/。 - 未做:合并开启对照、第二轮稳定性、input_fail 归零的验证。
- Q29 修的是异常连接不回收,此前只在 3–4 个连接上验过。用 soak 跑 100 个顺序连接(每个 1 MB、32 KB 在途、接收端每次停读 0.3 s):全部逐字节一致、零丢帧、结束时
active=0。 - netd 句柄 121 → 122 后稳定(前 50 个 121、后 50 个 122),私有字节 2.3 MB、工作集 7.0–7.1 MB 不变;单连接 1.60–1.68 MB/s(含 0.3 s 停读)。清理路径在 100 次反复下不泄漏、不残留。
- 未覆盖:并发连接、异常断开后的重连竞态、真机空闲超时;未改代码,只加证据与文档。
- 原有六个测试都在
AWDL_NETD_MDNS_PROXY未设置时跑,而监管启动 netd 时是开着的(多两条 UDP 路径、两个 netconn、两个线程)。给 soak 加--mdns:按生产环境设置该变量,并断言出现mDNS proxy ... ready。 - 同一命令只差
--mdns,2×16 MB、64 KB 在途:关 4.05/4.11 MB/s、句柄 121、私有 2.3 MB;开 3.65/4.13 MB/s、句柄 126、私有 2.4 MB;两臂都零丢弃、字节逐字节一致,句柄/私有跨连接不增长。 - CTest 增加
netd_proxy_soak_mdns(4 MB × 2,10.3 s),全套 7/7。文档注明该用例占用 UDP 15353 与 TCP 8771,不能与真机链路同时跑。 - 未做:真机、空口、并发多连接;没有改 mDNS 代码或驱动/网络。
- 监管只保留最近会话,真机跑完必须先冻结。新增
tools/Collect-Session.ps1:解析会话(-Session或-Latest)、把六个日志复制到build\diagnostics\<时间>-<tag>\session\、对原始会话跑analyze_session.py生成analysis.txt/analysis.json、写manifest.json(采集时间、源会话、HEAD、未提交文件、日志与收到的文件 SHA-256、当前后端二进制哈希)。 - 只复制日志:不复制
data\keys、不复制data\received文件;不启动 radio/awdld,不碰网络。 - 用
dist\data\logs\20261028-194820实测:冻结出 6 个日志,两份7545-*.MP4的 SHA-256 均为5d057f9c…(与历史记录一致),分析给出 0.11/0.10 MB/s 与 awdl 帧数。 - 交接文档 §8 增加一条命令式收尾流程。
- 把在途窗口推到满窗:64 槽下 80/96/128/192 KB 在途全部零丢帧(16.8–29.8 MB/s),240 KB 满窗也零丢帧。首次 240 KB 挂住是测试台缺陷——数据发完后尾部丢段不再重传,测试端死等 ACK;补 go-back-N 尾部重传后同一配置 0 丢帧跑完,所以不能把那次失败读成队列上限。
- 256 MB 单连接(64 KB 在途、batch 16):11.657 s、23.03 MB/s、199,742 帧、0 丢帧、句柄恒为 121、私有字节 2.3 MB 不变,接收字节一致。真机 930 帧/s 比这里的约 20k 段/s 低 20 倍,正常链路撞不上 64 槽;仍待真机验证的只有“USB/合包是否以 >64 帧突发投递”。
analyze_session.py新增 netd 汇总:解析ipc stats、tcp flow、proxy flow,输出丢帧四计数(alloc/copy/input/tx)、逐 flow 载荷/接受/失败与末尾空闲、代理双向字节与结束阶段;input_fail非零时显式告警并注明“是 lwIP 输入边界、不是空口证据”,--json带netd。新增 3 个回归(正常 delta/总量、netd 中途重启只算最后一段、没有 netd.log 不报错),全套 9/9。- 试过按目标帧率节流(
--rate)来复现“真机速率下 8 槽是否丢帧”,但 Windows sleep 粒度让节流不可信(1000/s 配置超时),已放弃这条轴并在证据里说明,不再据此下结论。 - 边界不变:全是本机回环,没有 radio/AWDL/USB/空口/手机,也没有改驱动或网络。
- 给
tools/test_netd_tcp.py增加--case soak:顺序复用同一 netd 进程的多次连接,双向逐字节校验,统计 goodput/回退重传,8 s 无 ACK 进展即失败,并断言句柄与私有字节不随连接增长;注册 CTestnetd_proxy_soak。测试台首轮还修掉两个自身缺陷:丢帧后死等 ACK(加 go-back-N 重传与零窗口探测),以及只检查seq > acked时漏掉零窗口停等。 - soak 第一次跑就暴露真问题:
input_fail非零后传输停住。定位到tcpip_inpkt()从MEMP_NUM_TCPIP_MSG_INPKT(上游默认 8 槽)取struct tcpip_msg,槽满即ERR_MEM丢帧;8 槽约等于 8 个 1360 B 段(≈11 KB)在途。 - 单变量 A/B(同一测试台、同一冻结基线,只有 netd 不同):槽=8 时 8 KB 在途 2.15 MB/s 正常,11 KB 在途掉到 0.38 MB/s(3 次重传),16 KB 在途 1 MB 用了 85.1 s(0.01 MB/s、28 次重传、142 帧被丢);槽=64 时 16/32/64 KB 分别为 2.58/3.63/16.78 MB/s,零丢帧、零重传。
- 改动:
lwipopts.h把该池设为 64(#ifndef保护,注释写明理由与 win32 100 项环形上限),内存代价约 3 KB,工作集/私有字节实测不变。CTest 6/6。 - 准备只换 netd 的
build/diagnostics/ipc-queue/test-package(334 文件集合与冻结包相同,仅awdl_netd.exe不同,最终哈希9cc25b27…),两次--check(fixed 149/20,-j 400/-j 0)exit 0、无 MISSING;入口Start-Q31-Test.cmd未执行。 - 未做:真机/空口、24 h、多连接并发、窗口/池/合包调参、驱动或网络改动。本机成功不代表 iPhone 断流已修复;0.10–0.32 MB/s 与本机塌速相似,只是需要真机验证的假设。
- 用户要求将目前情况出一版交接文档;新增 docs/HANDOFF_2026-10-28.md,整合至 240f88c,集中列明目标/授权、硬件、版本、真机负结果、Q28/Q25/Q29 本机验证边界、测试目录/哈希、下一次 fixed/-j 0 单组件入口及停止条件。HANDOFF 保留历史时间线,修正过时的“先设计 TCP 观测”指令;README/STATUS/队列同步入口。
- 21:38:15(UTC+8)只读 PnP/进程/监听复查:MI_02 OK/Problem 0/WinUSB;MI_00 Error/Problem 31/BTHUSB。本轮重新确认蓝牙问题码,无恢复尝试。项目链路进程和 49876/49877/8771 监听为空。
- 校验最新源码/证据清单、开发与测试 netd、监管、修复前构建、手动入口和 334 后端文件哈希;链接/项目一致性/diff 检查后本地中文提交。未重跑 CTest、未启动 radio/awdld/手机实验、未改源码/驱动/网络、未覆盖旧包或推送。
- 基于 58ffe00 的真实负对照,给本地 receiver socket 增加 5 s 发送无进展超时;两侧 recv 以 1 s 轮询共享退出标记,健康空闲继续等待、正常 FIN 仍允许响应,线程结束后才关闭 lwIP/Windows socket。未改依赖、合包、窗口/池、无线或驱动/网络。
- 首轮仅 shutdown 未唤醒本地 recv;第二轮本地轮询解决回压/远端 RST,但本地 receiver RST 暴露 lwIP recv 未退出。最终增加 lwIP 接收轮询、join 后再 shutdown,CTest 5/5 通过;两个失败日志保留 build/diagnostics/proxy-cleanup。
- 真实往返 65,536/3,400 B、2.2 s 延迟读/空闲轮询、正常半关闭响应、接收端不读+远端 RST、空闲远端 RST、本地 receiver RST 均通过。send 超时记录 10060;故障连接清理而 netd 存活,不宣称整链恢复时限。
- 独立 build/diagnostics/proxy-cleanup/test-package 从旧包复制,334 后端文件集合/哈希对照仅 netd 不同;监管 fixed 149/20 的 -j 400 和 -j 0 两个 --check 均通过。首次真机手动入口沿用 Q27 -j 0,仅换 netd,本轮未执行;旧包与单文件 exe 保留。PowerShell 对 GUI exe 的退出码检查改为 Python 等待真实退出码,离线检查会生成会话目录但不启动链路。
- 最终日志、故障输出、测试目录计划/哈希落 evidence;未要求手机配合,Q25 真机定位、Q20/Q7 验收仍待。5 s 本地发送停滞可能终止极端回压连接,手机体验未测。
- 自行实现有界 IPv6/TCP 头解析与 32 连接计数,IPC 输入/输出关联 seq/ACK/原始窗口、载荷和重叠/跳跃迹象;代理记录 lwIP 与 Windows socket 双向真实读写累计量及阻塞阶段。5 s 汇总,不记录载荷/设备 IPv6,不复制上游实现、不加依赖。
- CTest 4/4:保留 Q28 两项,增加解析/边界/回绕/容量回归与真实 lwIP 往返;65,536 B 上传和 3,400 B 回包逐字节一致,延迟读和上传半关闭后响应正常。
- 新本机负对照:接收端不读,IPC 仍可排队、ACK 停顿且窗口尚非零;远端 RST 后超过 6 s 的新快照仍 active=1,up 堵在 local_send、down 堵在 local_recv。Q29 单独修复。测试主动清理后的关闭不能算自动恢复。
- 测试端逐包等待、无界突发及错误的零窗口前提先后修正,首次失败日志均保留 build/diagnostics/tcp-diag;最终证据与哈希落 evidence。未改生产窗口/合包/无线参数,未启动 radio/awdld,未替换 dist 原包,未做手机验收。
- 用户明确不想频繁用手机测试,改为本机源码与故障注入。修复 netd 发送失败仍 ERR_OK、IPC 接收退出后进程假活;新增分配/复制/投递/发送计数,5000 ms 本机发送无进展超时、部分流失败后终止,不改合包/无线参数。
- CTest 两项通过:生产函数 + 真实 lwIP 池耗尽/所有权/回环收发/非读取对端/60 KB 精确载荷,以及五种真实进程情形(健康心跳、延迟连接、EOF/半包/非法长度/分片)。旧冻结 netd 在同一 EOF 对照中 >3 s 不退出,按预期失败;新版本机观察约 0–0.1 s 退出 2。
- 测试夹具曾因未启动 lwIP core 的池回收断言、固定睡眠误判心跳失败;均已修正,日志保留 build/diagnostics/netd-offline,最终通过记录和旧版预期失败对照提交 evidence。
- 新二进制仅在 build/awdl_netd/Release,原 dist/app/20261028-143130 和单文件包未替换;没有 radio/awdld、手机发送、设备复位、驱动/网络变更。Q25 完成了粗粒度计数,TCP seq/ACK/窗口仍待做,不声称真机速度或稳定性已改善。
- 会话
20261028-194820,实际 awdld-c 149 -s 2 -j 0 -v;监管提交 3e03fc6,相同原后端包、证书和实例 ID。启动一次固件重试后约 120 s 就绪,未额外复位或改驱动/网络。 - 第一轮同一 29,456,959 B 视频:19:52:09.924–19:56:28.031,258.107 s / 0.11413 MB/s;第二轮:19:57:53.352–20:02:37.914,284.562 s / 0.10352 MB/s;均完整落盘,SHA-256 与旧副本一致,无手机源哈希。用户明确报告体感非常慢、比开启时慢,不再追加第三轮。
- 关闭的回程 AWDL data 每轮约 1.4 万,历史开启成功轮约 2166;TX/RX data 比约 0.63 对 0.10。计数包含所有邻居,未细分 TCP ACK;邻居范围和对端序列变化,不能将差异直接当成开关因果。
- 两轮无 408 不能证明原断流已修复,速度验收明显未达。保留开关但默认仍为 -j 400,下一步仍是 Q25 被动数据面/失败路径观测。
- 原始日志冻结到
build/diagnostics/no-merge-20261028/20261028-194820/,脱敏证据和哈希见 evidence。已停止链路,无残留;MI_02 WinUSB/OK、MI_00 原有 Error 未变。
- 用户明确要求先测关闭合并,新增 opt-in
--no-tcp-merge/-NoTcpMerge,默认仍 -j 400;关闭仅改为 -j 0,不修改此前指出的 netd/队列等其它实现。 - 选用最近能进入传输的 fixed 149/20 主控配置,对照同一后端包
20261028-143130;不重打包、不改驱动/网络、不额外复位。启动前链路未运行,MI_02 WinUSB/OK、MI_00 原有 Error。 - 监管编译/环境继承检查、实际 CLI 两组 --check、脚本 AST 通过;会话日志和计划明确 join_us。真实发送待链路就绪后提示用户,不把离线结果记作传输通过。
- 用户要求将当前全部信息整合,供其它 AI 接手;新增根目录
HANDOFF.md,集中给出目标/授权、架构、配置、历史正常与异常时间线、最新三组结果、源码与证据、构建和后续排查。 - 重新核对蓝牙线索:约 13:47 软件 USB 复位后记录 MI_00 Error,但 01:19/01:44 已有低速,13:43 已有断流;不能把全部问题归因于蓝牙,也没有恢复到正常蓝牙状态后的受控对照。
- 交接时只读复查 MI_02 WinUSB/OK、MI_00 Error,项目链路没有残留进程;未重跑无线、复位、改驱动/网络或重打包。明确区分 TCP 合并与 lwIP 的待验证假设及未实现的 Q25。
- README、STATUS 增加最新交接入口,工作队列追加 Q26。保留历史文档和失败记录,文档链接/路径/证据一致性检查与
check_project.py、git diff --check作为本次验证。
- 用户确认 iPhone 方便配合。A fixed 会话
20261028-150311:radio 固件重试一次后约 120 s 就绪;首个同一视频 29,456,959 B / 91.136 s = 0.32322 MB/s,接收哈希与旧文件一致;第二个 323.813 s 后 HTTP 408,约 15.98 MB 传输体,第三次按停止条件未执行。零换台记录,温度 28/+2。 - B social 会话
20261028-151652:约 43 s 就绪,主控正常且能看到手机邻居;用户两次明确报告接收端不可见(第二次关闭重开分享面板),零 Discover/Upload。不能计作上传失败或完整 A/B。 - 固定 149 仍复现断流,关闭当前跳频不足以解决;但没有相同物理冷启动初态,不能排除历史射频状态。停止把热故障、全卡假死或必须拔插当成既定结论。
- 分析器补齐“接收端已 30 s 无内容、radio 仍偶有单帧”的窗口关联,六项回归通过。对照源码确认 joined 是 TCP 合并追加段数,不能代替载荷进度。
- 原始会话归档到
build/diagnostics/{baseline,fixed,social}-20261028/;脱敏证据和哈希提交到 evidence。实验进程已全部退出,MI_02 WinUSB/OK、MI_00 原有 Error 未变;未额外执行设备复位或驱动/网络变更。Q20 仍未通过,下一项 Q25 被动 TCP 诊断。
- 监管支持
--chanseq fixed|social,手动脚本支持-ChannelSequence,默认维持 social。启动计划与会话记录模式。 - 关键:OWL 用 getenv 存在性判断开关,fixed 模式必须移除
AWDL_CHANSEQ;两种模式均移除旧AWDL_FOLLOW_MASTER_SEQ。未修改上游、无线补丁或驱动。 Build-Supervisor.ps1 -Test编译通过,真实 PowerShell 子进程检查 fixed/social 环境及非法模式拒绝通过;两组--check与非法 CLI 退出 2 通过,脚本语法解析通过。- 原 14:32 链路仍在,尚未启动 A/B,不把离线验证记为 Q20 通过。已询问用户 iPhone 是否方便配合,后续按
docs/STALL_DIAGNOSIS.md操作。
- 用户要求继续排障并本地提交;本次先离线复盘,不追加无线实验。
analyze_session.py --diagnose按失败上传窗口关联 radio/AWDL 计数,支持跨午夜,缺失证据保留为空;五项回归通过。- 第三次上传最大换台 2175 us;上传归零后 action RX 仍增长、主控未变、温度 29/+3;不能认定为全卡假死或热故障,也没有空口 ACK/TCP 序列证据。
- 已对照锁定 devourer 时隙实验与 OWL 调度。下一步固定 149 与 social 单变量对照,详见
docs/STALL_DIAGNOSIS.md。Q20 未通过。
- 用户实际发送三次同一 29,456,959 字节视频:前两次 75.25/77.45 秒(0.39/0.38 MB/s),落盘 SHA-256 一致;第三次约 10.2 MB 后停顿,14:43:49 HTTP 408。未达到稳定、流畅、快,不宣称断流修复成功。
- 修复包实际运行会话
20261028-143206,没有设备复位、驱动/系统网络变更。保留原始本地日志,提交脱敏摘要及docs/RESEARCH_2026-10-28.md。用户要求暂停后只读取日志,不追加实验或无线实现改动。 - 收尾已完成的分析工具改动:识别监管时间前缀,显示失败/未完成请求,落盘字节与 wire 字节分开;三项离线回归通过。修正上一提交对
.gitattributes的覆盖,恢复原有文件规则,仅增加补丁上下文空白豁免。
- 在锁定 devourer
f7e0731的 build 副本应用patches/devourer-airbridge.patch;Prepare-Devourer.ps1创建独立本地副本、校验 SHA、幂等应用。原始 upstream/devourer 无改动。GPL-2.0 库补丁保持在 GPL radio 进程边界;厂商参考仅核对 0x10 C2H 编码,未复制其实现。 - radio 开启 20 ms 有界 C2H 等待;RX 线程通知不拿寄存器锁。收到事件或等待到期后只回读一次 RF18 确认真实信道,避免换台中轮询;无法确认则失败关闭链路,禁止按假定频道继续发送。日志计时现在包含确认及主机侧恢复,旧 0.25 ms 只代表命令提交。
- 快切跨频段完成后重建现有 EFUSE 校准功率表、恢复 Wi-Fi-only 天线配置;温度补偿上下文跟随信道更新,不重置温度平均或把已补偿值当新基线。复用已有能力,不引入新库/驱动。
Build-Radio.ps1编译 radio/桥及两项离线测试;2/2 通过。首次构建的 Windows min 宏冲突已修复,失败日志保留在 build。干净锁定副本应用后 8 个补丁文件 SHA 与构建副本完全一致,重复应用幂等;行尾固定为副本局部 LF。- 手动入口默认从旧 40 MHz 同步为 20 MHz,并与托盘保持主控/信道参数一致;打包增加目标路径和用户 data 保护。真实文件吞吐、射频效果、蓝牙状态不由本项离线结果代替,继续 Q20。
peer frames仅统计发给本机的单播数据载荷,管理帧、Null、固件 C2H 不混入;RSSI 仅以有效 PHY 样本为分母,明确输出rssi_n。窗口使用互斥快照,避免 load/store 清零丢计数;帧率以真实间隔计算。- 修复温度日志 info 分支缺参、采样异常导致线程终止的风险;TX 计数改原子;队列满时真实累加 drop。监管读取新 fps 字段,兼容旧日志。
- MSVC Release 构建通过;离线回归覆盖无 PHY、管理/Null/C2H/其它目标过滤及并发采样 10000 帧守恒。本项未运行射频或声称修好断流。
- 后续 Q19 在 build/devourer-airbridge 的锁定版本副本修改,用主仓库补丁保留可复现性;原始 upstream/devourer 保持干净。用户同意链路就绪后配合连续三次真实传输。
用户要求规划并搭建项目,先做 Phase 0;每次完成改动后本地中文 Git 提交。
完成:任务书完整归档;五个公开上游克隆到 upstream/ 并锁版本;WinAWDL 的专用 devourer 子模块已克隆;审计报告、阶段计划、AGENTS.md、只读 USB 枚举脚本和工程检查脚本已建立。
实测:VSBuildTools 位于 D:/VSBuildTools;CMake 识别 MSVC x64 后缺 PkgConfig,日志 evidence/build-configure.log。没有编译成功或运行无线测试。USB PnP 只读枚举成功,但未找到可确认的 RTL8821CU;本地设备实例结果被 .gitignore 排除。
发现:WinAWDL radio 与 PCIe transport/tx_kick 耦合;运行中换频道被忽略;OpenIPC devourer 有 C8821C USB 真实实现,但不能据此声称用户网卡 Windows AirDrop 通过。SwooshDrop 依赖 WSL2,不适用最终运行路线。
边界:没有运行上游安装器/硬件脚本,没有改网络或驱动,没有加载固件、发射帧或复制第三方实现进入项目。主仓库无远程推送。
接续顺序:README → PHASE0_AUDIT → PLAN → sources.lock.json。先确认接入的目标设备,补齐本地构建依赖,再申请针对具体网卡的绑定实验。Phase 0 尚未全部完成,不能把本轮文档交付当成 AirDrop 可用。
验证命令:python tools/check_project.py;tools/Inspect-Hardware.ps1;git diff --check;git status --short。
用户要求全面扫描项目并接手。按约定先读 README、PHASE0_AUDIT、PLAN、WORK_LOG,再推进不依赖驱动变更的 Phase 0 项。
完成:
- 在
build/deps隔离安装 vcpkg 2026-09-26、libusb 1.0.30、pkgconf 3.0.3;没有vcpkg integrate install,没有改 PATH。 - C8821C-only 配置与编译通过(MSVC 19.44.35228.0、SDK 10.0.26100.0、CMake 3.31.6);产物
devourer.lib、rxdemo.exe、txdemo.exe;ctest71/71 通过,3 项跳过;日志evidence/build-8821c-{configure,build,ctest}.log。 - 关键构建坑:CMake 的 PkgConfig 不会自动加
--msvc-syntax,必须设置PKG_CONFIG_ARGN,否则 MSVC 链接器无法使用-L/-l。 - 新增可复现脚本
tools/Setup-BuildDeps.ps1、tools/Build-Devourer.ps1,已端到端跑通(exit 0)。 - 目标网卡于 12:41 接入并完成只读身份采集:
0bda:c820,复合设备;MI_00 蓝牙(oem154.inf1.5.1012.3),MI_02 Wi-Fi 厂商接口ff/ff/ff(netrtwlanu.inf1030.38.712.2019,RtlWlanu,适配器WLAN 2未连接)。芯片 ID 未读。摘要evidence/hardware-identity.json,完整实例路径只在本地忽略文件。 - 契约对照:
AcceptBlockAck/EndBlockAck是 fork 为 8852C 新增、上游从未包含,且只有 Kestrel 实现;Jaguar2 没有实现 → 8821C 接收方向硬件 BlockAck 不可用;radio 与PcieTransport::tx_kick耦合,USB 必须新增 radio 主程序。fork 基于 OpenIPC5be2b72加 13 个补丁,比上游头落后约两周。见docs/RADIO_CONTRACT.md。 - 许可证矩阵
docs/LICENSE_MATRIX.md:GPL-2.0 radio + GPL-3.0 awdl + MIT 应用层靠进程边界隔离;Realtek 固件再分发未解决。 tools/Inspect-Hardware.ps1增加目标 VID/PID 检测并改为 ASCII 输出(原中文在 PS 5.1 无 BOM 下会乱码);tools/check_project.py扩展到新文档/证据。- 文档同步:README、PHASE0_AUDIT(I 节)、PLAN、BUILD_WINDOWS、HARDWARE_INTAKE、RADIO_CONTRACT、LICENSE_MATRIX。
未做(等待明确授权):
- 未做芯片 ID 读取、WinUSB 绑定、任何固件下载或无线帧发送;没有改驱动、网络或防火墙。
下一步:先确认是否授权对 MI_02 做 WinUSB 绑定并执行一次最小初始化。建议把网卡直连主板 USB 口(当前经 05e3:0608 hub)。授权后按 PLAN Phase 1 顺序:芯片 ID → 固定频道 RX → 有限 TX → 独立见证与 TX report → 换信道 → 拔插恢复。
验证命令:python tools/check_project.py;tools/Inspect-Hardware.ps1;tools/Build-Devourer.ps1;git status --short。
用户确认交付形态:用户看到一个程序(一个安装包/托盘入口)即可,后台允许多进程。已写入 docs/PLAN.md 的架构决定:GPL-2.0 无线进程 + GPL-3.0 AWDL 进程 + MIT 应用层,不承诺单进程单 exe。
完成:
docs/BINDING_RUNBOOK.md:Zadig 绑定 MI_02 到 WinUSB 的逐步操作、只读校验、回滚和失败处理。AI 不执行绑定;需要管理员权限和用户授权。tools/Check-Binding.ps1:只读报告 MI_02 的驱动/服务/问题码,退出码 0=WinUSB、1=仍是厂商驱动、2=设备不在。当前基线为RtlWlanu。tools/usb_probe/+tools/Build-UsbProbe.ps1:libusb 只读探针,只做 open + 打印描述符/端点 + claim/release,不初始化芯片、不写寄存器、不发帧。- 绑定前基线(
evidence/usb-open-baseline.log):设备可枚举(bus 1、address 19、High Speed、bcdUSB 2.00),libusb_open返回LIBUSB_ERROR_NOT_FOUND,与厂商驱动占用 MI_02 的预期一致。 - OpenSSL 3.6.5 装入
build/deps后重建:ctest74/74 通过(3 项跳过),supplicant、station_sm、ccmp_framing三项加密自检通过;sta_client_headless是 Linux-only(TAP),Windows 不构建。产物哈希不变。 - 首次重建遇到
CMake error : Cannot restore timestamp ... generate.stamp(MSB8066),重跑一次即通过;已写入docs/BUILD_WINDOWS.md的“已知构建异常”。 - 更新 README、PLAN、PHASE0_AUDIT、BUILD_WINDOWS、HARDWARE_INTAKE、check_project.py。
未做:
- 没有绑定 WinUSB、没有读芯片 ID、没有 Init/固件下载、没有任何 TX;没有改驱动/网络/防火墙。
下一步:用户以管理员按运行手册绑定 MI_02 → 我用 usb_probe.exe 验证 open/claim → 再单独申请 Init 授权。
用户授权并由用户完成 UAC 与 Zadig 界面操作;AI 负责下载校验、监测与验证。
完成:
- 从官方 release 下载
zadig-2.9.exe(SHA-2564ECAA95D…82039B,Authenticode 签名有效,签名者 Akeo Consulting)。 - 用户以管理员在 Zadig 2.9.788 中选择
802.11ac NIC (Interface 2)(USB ID0BDA C820 02),RtlWlanu→WinUSB,执行 Replace Driver。 - 14 秒后监测到 MI_02 变为
oem155.inf(libwdi 6.1.7600.16385),服务WinUSB。 - 蓝牙 MI_00 仍为 OK;
WLAN 2网络适配器消失(预期)。 tools/Check-Binding.ps1退出码 0;usb_probe.exe输出open OK,成功 claim interface 2;接口 2 端点:bulk IN0x84、bulk OUT0x05/0x06/0x08、interrupt IN0x87。- 证据:
evidence/binding-result.log、evidence/hardware-identity.json的binding段。
未做:没有 Init、固件下载或任何无线帧;回滚步骤尚未演练。
下一步:只读读取 SYS_CFG2 0x00FC 确认芯片 ID,再决定是否申请 Init 授权。
- 扩展
usb_probe支持--read8/--read32:一次 Realtek vendor control read(0xC0、bRequest 5),不写寄存器、不下载固件、不发帧。 - 运行
usb_probe.exe 0bda c820 --read8 00FC,在 claim interface 2 之后读取SYS_CFG2,得到read8 0x00fc = 0x09,随后 release/close。退出码 0。 - 证据:
evidence/chip-id-read.log;evidence/hardware-identity.json的chipIdentity已标为verified、值0x09、含义 RTL8821C(Jaguar2)。 - 结论:Phase 0 的「真实设备身份」完成(VID/PID、接口、驱动、libusb 访问、芯片 ID)。仍未做:WinUSB 回滚演练、
Init/固件下载、任何 RX/TX。 - 下一步:回滚演练(恢复到
netrtwlanu.inf)以关闭 Phase 0;之后申请最小Init授权进入 Phase 1。
用户单独授权后执行 chipstate --init --channel 6 --peek 0x00FC,退出码 0。
- USB:锁定
1-8.3,接口 2,0x84bulk IN、0x05/0x06/0x08bulk OUT、0x87interrupt IN。 - 芯片:
8821C cut=4 (UMC) 1T1R,SYS_CFG1=0x00494537;Boot from EFUSE / Autoload OK。 - 固件:下载 OK,FW boot ready
0xC078。 - 射频:EFUSE
rfe_type=0x22,BB/AGC/RF 表加载,信道 6(20 MHz),IQK 通过,coex WL-only,RX 使能,ready for TX。 - 回读
0x00FC=0x09;零发帧。工具teardown_power_down=false,退出后芯片仍初始化。 - 证据:
evidence/init-first.log;hardware-identity.json增加initialization段。
未做:RX 循环、TX、换信道、拔插恢复、WinUSB 回滚演练。
下一步:写一个有界、可干净停止的 RX 探针(或用 rxdemo 的可控停止方式),在频道 6 做有限时长被动接收;需要单独确认后再跑。
用上游 doctor --listen-secs(限时 RX smoke,干净停止,不发帧):
- 频道 6(2.4 GHz,15 s):750 帧,500 FCS 正常 / 250 CRC 错,判定
HEALTHY。 - 频道 36(5 GHz,12 s):6286 帧,4956 FCS 正常 / 1330 CRC 错,判定
HEALTHY;出现 12668 字节的大完成,说明芯片侧聚合接收在工作。 - 证据:
evidence/rx-first.log、evidence/rx-second-5g.log;hardware-identity.json增加rxSmoke两段。
判断:接收路径真实可用;CRC 比例(2.4 GHz 33%、5 GHz 21%)偏高,但只是单次样本,不能当链路质量结论,需要同信道三次重复和更干净的对照。接收方向 BlockAck 仍未验证。
未做:任何 TX、换信道、拔插恢复、WinUSB 回滚。TX 需要独立见证,不能用 USB 提交成功代替空口结果。
下一步:同信道 RX 三次重复;同时准备 TX 见证方案(第二台设备或抓包手段),否则不进入 TX。
每次 10 秒,仍是 doctor --listen-secs,零发帧:
| run | 总帧 | FCS 正常 | CRC 错 | CRC 比例 |
|---|---|---|---|---|
| 1 | 546 | 485 | 61 | 11% |
| 2 | 1037 | 663 | 374 | 36% |
| 3 | 502 | 425 | 77 | 15% |
结论:CRC 比例随环境流量在 11%–36% 波动,单次样本不能当链路质量;需要受控对照(固定发射源或安静信道)。接收路径本身仍判定 HEALTHY。
证据:evidence/rx-repeat-1..3.log、evidence/rx-repeat-summary.log。
下一步:TX 需要独立见证;在找到第二台可抓包设备前,不把“USB 提交成功”当作空口验证。
用户插入 ESP32-S3-DevKitC-1(CH343,COM17),授权覆盖原固件。使用本机已有 PlatformIO 6.2.0(~/.platformio/penv)与 espressif32@7.0.1,未新装大工具链。
- 写入
tools/esp32_witness/:纯监听固件(频道 6、不扫描/不连接/不发包、匹配 SA57:42:75:05:d6:00)+ 串口抓取脚本。编译、刷写均成功。 - Windows 侧
txdemo每轮发 20 个 probe request(1M,50 ms 间隔)。 - 结果:完整 USB reset 的 3 轮全部收到 20/20 唯一序号 0..19(长度 104,RSSI -11..-9 dBm,seq 0 哈希
9126b0f8跨轮一致)。 - 负面对照:跳过 reset 的一轮,
txdemo报告 20 帧提交、0 失败,但空中 0 帧;因此完整 reset 是重复 TX 的必要条件,也证明“USB 提交成功 ≠ 空口发出”。 - 见证计数有时每轮 +20、有时 +40,疑似抓包/重发重复;唯一序号是可靠指标。
- 证据:
evidence/tx-witness-summary.json、evidence/esp32-witness-tx{2,3,4,5,6}.log、evidence/tx-{second,third,fourth,fifth,sixth}.log、tools/esp32_witness/README.md。
未做:5 GHz、硬件 ACK/BlockAck、反向(ESP32 发、网卡收)、换信道、拔插恢复、WinUSB 回滚。
下一步:反向验证与 ACK 方案;ESP32 只能覆盖 2.4 GHz,5 GHz/AWDL 仍需别的见证。
用户要求用 Dart,接受“Dart 负责应用层与 AWDL/AirDrop、C++ 保留无线后端”的建议。
- 新增
docs/ARCHITECTURE.md:一个用户可见程序;Dart/Flutter 应用通过 MIT 的radio_linkv3 管道驱动 C++radio.exe;radio.exe链接 devourer、独占 USB,不解析 AWDL。 - 明确不采用纯 Dart 无线层:devourer 无 C ABI、固件/PHY/USB 时序留在 C++;FFI shim 作为以后的可选方案,不是第一阶段。
- 许可证边界:radio.exe GPL-2.0,复用的 AWDL 进程 GPL-3.0,Dart 应用只走 MIT 管道;固件再分发仍未解决。
- AWDL 的目标实现是 Dart;Phase 3 可先用 C++ AWDL 作对照以尽快验证 iPhone 发现,迁移范围按实测决定。
- 更新 PLAN/README/check_project。
下一步:Phase 2,写 radio.exe 的 USB 后端(打开/初始化/RX/TX/换信道 + 管道),保持 radio_link v3。
- 新增
src/radio_usb/:C++airbridge_radio.exe,链接 devourer + libusb;打开0bda:c820、claim interface 2、InitWrite、StartRxLoop;实现radio_linkv3 的 HELLO/RX/TX/SET_CHANNEL/SET_BSSID_FILTER/SET_ADDRESS。 - 编译通过;运行后 Python 客户端能收到 HELLO(version 3、channel 6、width 20)。
- 用临时“合成 RX”消息验证了管道方向:主线程写入的 10 字节 RX,客户端能完整收到。
- 未通过:硬件 RX 转发。 RX 回调有计数,但从 RX 线程写管道时客户端在窗口内收不到;客户端关闭后写入返回
ERROR_NO_DATA (232)。问题集中在跨线程写或 RX 启动时序,尚未定位。 - 反复 reset 后出现一次固件启动失败(
FW ready 0x80 != 0xC078),devourer CPU-reset 重试后恢复,doctor判定HEALTHY。 - 发现:Dart 的
dart:io没有 Windows 命名管道接口,IPC 应改为 localhost TCP 或 stdio 长度前缀帧;radio_link帧格式可保留。 - 状态与复现步骤:
docs/RADIO_USB.md;客户端:tools/radio_link_client.py。 - 未做:TX over pipe、SET_CHANNEL、地址/过滤、Dart 客户端。
下一步:先定 IPC(TCP/stdio),再修 RX 转发并重测;不要在未通的情况下接 AWDL。
- 按 Dart 兼容性,把 IPC 从 Windows 命名管道改为 localhost TCP 127.0.0.1:49876,
radio_linkv3 帧格式不变;端口用SO_EXCLUSIVEADDRUSE,避免SO_REUSEADDR被别的本地服务抢连。 - 修了一个协议 bug:TCP 版 HELLO 漏发 8 字节消息头,客户端把 HELLO 当成了 type=3/len=5126。
- RX 回调改为 有界队列 + 独立写线程,慢客户端不再阻塞 USB RX 环。
- 实测 RX:客户端 10 秒收到 311 帧、28957 字节,含真实 beacon,RSSI 约 -114..-121。
- TX:客户端经 TCP 发 20 帧,radio 记录
tx sent 20 failed 0,但 ESP32-S3 同时段空口匹配 0 帧;尚未定位(怀疑 RX 环启动时序或 TX 队列/CCA)。 - 启动耗时在反复 reset 后可达约 2 分钟,并出现过一次固件启动失败后自动重试成功。
- 证据:
evidence/radio-usb-run-rx.log、evidence/radio-usb-run-tx.log、evidence/esp32-witness-radio-tx.log;状态见docs/RADIO_USB.md。
下一步:让 RX 环先于客户端 accept 启动(或 HELLO 后等 RX 就绪)再测 TX;通过后再测换信道与 Dart 客户端。
- 把 RX 环提前到
accept之前启动,TX 帧加 20 ms 间隔,排除“TX 早于 RX 环”的时序问题。 - 复测:TCP TX 提交 20 帧、0 失败,但 ESP32 空口仍 0 帧。
- 对照实验:用已知能上空的
txdemo(DEVOURER_SKIP_RESET=1)再发 20 帧,ESP32 同样 0 帧;RX 仍正常(radio 侧rx cb=100、客户端 47 帧/1.5 s)。 - 结论:当前不能判定后端 TX 有 bug;网卡在多次 reset 后疑似进入 TX 不工作状态,需要拔插/重新上电后再测。
- 证据:
evidence/esp32-witness-radio-tx2.log、evidence/esp32-witness-control-tx.log、evidence/radio-usb-run-tx2.log、evidence/tx-control.log。
下一步:拔插 RTL8821CU,先复跑 txdemo 对照;确认空口恢复后,再复测 TCP TX。
- 拔插后
txdemo对照恢复 20/20,确认之前的 0 帧是网卡 TX 假死,不是后端结论。 - 复测 TCP TX 仍 0;逐步排除 RX 并发(
AIRBRIDGE_NO_RX=1也 0)和 TX 速率(补SetTxMode("1M")仍 0)。 - 根因:
tools/radio_link_client.py解析txdemo的beacon_frame时,把源码注释里的0x40 0x00、0x08 0x01当成帧字节,body 从 100 字节变成 104 字节,帧头错位;芯片收到畸形帧,send_packet仍返回 true。 - 修复:解析前去掉
//与/* */注释。修复后:AIRBRIDGE_NO_RX=1:ESP32 收到 20/20 唯一序号 0..19,RSSI -10,长度 104。- 正常模式(RX 环同时运行):同样 20/20,RSSI -10..-9,长度 104;radio 侧
tx sent 20 failed 0,RX 仍工作。
- 新增
AIRBRIDGE_TX_RATE(默认 1M,SetTxMode)与AIRBRIDGE_NO_RX测试开关。 - 证据:
evidence/esp32-witness-radio-tx6.log、evidence/esp32-witness-radio-tx7-full.log、evidence/radio-usb-run-tx7-full.log。
下一步:TCP SET_CHANNEL 实测、SET_ADDRESS/BSSID 过滤与硬件 ACK,然后 Dart 客户端。
- SET_CHANNEL:客户端从频道 6 发
SET_CHANNEL 36,radio 日志channel -> 36;切换后 2 秒收到 64 帧,说明运行时换信道可用。 - BSSID 过滤:同一会话里 baseline=64 帧;设置假 BSSID
00:11:22:33:44:55后 2 秒 0 帧;发送全零清除后 2 秒恢复 62 帧。全零现在表示清除过滤。 - Dart 客户端:新增
tools/radio_link_client.dart,dart analyze通过;运行后收到HELLO version=3 channel=6 width=20,5 秒收 84 帧 / 14664 字节。DartSocket可用,TCP IPC 选择成立。 - 证据:
evidence/radio-usb-run-channel.log、evidence/radio-usb-run-dart.log。
仍缺:SET_ADDRESS 的硬件 ACK 效果、断连重连(当前 radio 单客户端、断开即退出)、5 GHz/AWDL。
- 第一次用 BSSID
57:42:75:05:d6:00被拒:首字节 0x57 是组播地址;devourer 要求 own 与 BSSID 都是非零单播且不同。 - 换单播 BSSID
56:42:75:05:d6:00后成功:station identity armed ... (net_type=Infra),客户端断开时正常 cleared。 - devourer 警告
tx.retry_limit=0:MAC 不会重传未确认帧;AWDL 需要时应在DeviceConfig::tx.retry_limit设置。 - 仍未验证:空口 ACK(需要 iPhone/第三台观察设备)、断连重连、5 GHz。
- 证据:
evidence/radio-usb-run-address.log、evidence/radio-usb-run-address2.log。
radio.exe改为 accept 循环:radio 保持初始化,客户端断开后回到监听,不再退出进程。- 每个客户端会话重建 RX 队列/过滤状态/写线程,RX 计数器按会话清零。
- 实测:同一 radio 进程连续两个客户端都收到 HELLO 并收包(1 帧 / 5 帧);第二个客户端之后还能发 5 帧 TX(
tx sent 5 failed 0)。 - 证据:
evidence/radio-usb-run-reconnect.log;状态更新docs/RADIO_USB.md。
下一步:tx.retry_limit(当前 0,不重传)、空口 ACK 验证、5 GHz/AWDL。
AIRBRIDGE_RETRY_LIMIT默认 7(0..63);设 7 后SetStationIdentity不再警告retry_limit=0,station identity armed 正常。- 同一 radio 进程连续两个客户端:
- 客户端 A:
SET_CHANNEL 44,频道 44 baseline 47 帧、假 BSSID 过滤 0 帧、清除后 63 帧(5 GHz RX 通过)。 - 客户端 B:HELLO 显示当前频道 44;
SET_CHANNEL 6、设置 own/BSSID、发 20 帧 TX;ESP32-S3 收到 20/20 唯一序号,RSSI -11..-9。
- 客户端 A:
- 证据:
evidence/radio-usb-run-multi.log、evidence/esp32-witness-multi.log;状态更新docs/RADIO_USB.md。
仍缺:空口 ACK 验证、5 GHz TX 见证(ESP32 只能 2.4 GHz)、AWDL 信道调度。
- 取回 OWL 与 radiotap 子模块(
upstream/winawdl/upstream/owl)。 - 用 WinAWDL 的
fetch-wintun.ps1下载 Wintun 0.14.1,SHA-256 与 WireGuard LLC 签名校验通过。 - 用 vcpkg 已下载的便携 MinGW(GCC 13.2)构建
build/awdl/awdld.exe;能打印 usage。 - 新增
src/radio_usb/radio_pipe_bridge.cpp:命名管道 ↔ TCP 字节泵,不改radio_link协议;已用命名管道客户端验证收到HELLO version=3 channel=6 width=20。 - 新增
tools/Build-Awdl.ps1和docs/PHASE3_AWDL.md(运行顺序、授权边界、失败处理)。 - 未运行 awdld:它会创建 Wintun 虚拟网卡并在频道 44 发射 AWDL 帧,需要用户明确授权。
下一步:获得授权后按 PHASE3_AWDL.md 启动 radio + bridge + awdld,用 iPhone 做第一次 AWDL 发现测试。
- 用便携 MinGW 构建
awdld.exe,Wintun 0.14.1 已校验;radio_pipe_bridge验证命名管道 → TCP。 - 实测(iPhone 15 Pro Max / iOS 17.5,隔空投送已打开):
- 频道 44 只有普通 AP;频道 149 有大量 AWDL action 帧。
awdld -c 149提权运行后创建 Wintunawdl0(ifIndex 68),peers 2,同步到 iPhone AWDL 地址62:65:31:c5:29:59,rx action持续增长。rx data 3 / tx data 0;AirDrop 接收服务已广播 AirBridge32,但 iPhone 没看到设备。
- 根因:当前 DSH 会话的 Codex 沙箱离线防火墙规则(
codex_sandbox_offline_block_outbound等)禁止所有非回环 IPv6;ping ::1、链路本地、全局 IPv6 全部General failure,IPv4 回环正常。详见evidence/awdl-sandbox-firewall.txt。 - 未修改任何防火墙规则;测试进程已停止。
- 下一步:在沙箱外的普通管理员终端运行,或明确授权临时停用三条 block 规则并在测试后恢复。
- 关闭 v2rayN/TUN 后 IPv6 恢复(
ping ::1、链路本地均正常),确认首轮失败是 v2ray TUN 的EnableIPv6=false+StrictRoute=true导致,不是沙箱防火墙。 - 按 PHASE3_AWDL.md 启动 radio 149 + 桥 + 提权 awdld +
receiver.py --name AirBridge32。 - iPhone 15 Pro Max(iOS 17.5)在隔空投送中看到 AirBridge32,依次
POST /Discover、POST /Ask、POST /Upload,传输一张 PNG。 - 结果:
IMG_3583.PNG,2,911,531 字节,PNG 签名有效,1290×2796,SHA-256C899358AE8D761C900946DD24F49DBC41F1CE04B05C859B9C071CF8F822C48B1,约 10.1 秒。 - AWDL 计数:
peers 1,rx data 2166 (2025 joined),tx data 191。 - 证据:
evidence/airdrop-first-transfer.txt(去敏);原始接收日志含发送者姓名/IP/令牌,已加入.gitignore,不提交。 - 待办:v2ray TUN 开启时的 IPv6/link-local 排除或系统代理模式;5 GHz 空口 TX/ACK;Dart/Flutter UI 与打包。
- 目标:TUN 常开且不改 v2ray 默认设置时,让 AirDrop 绕过 Windows IPv6 栈。
- 取回 lwIP 2.2.2,新增
src/awdl_netd:IPv6-only / NO_SYS / mDNS / TCP 用户态栈;构建通过,假握手可让 netif 起来并回 mDNS/MLD 包。 - 新增 awdld 的 IPC host 后端(
host_if_ipc.c,-DAWDL_HOST_IPC=ON),awdld 不再创建 Wintun;构建通过。 receiver.py增加--listen 127.0.0.1代理模式,关闭自带 mDNS,TLS/HTTP/Upload 逻辑不变。- 由于
upstream/被忽略,改动以patches/awdl-ipc-host.patch+tools/Apply-AwdlPatches.ps1跟踪。 - 状态:骨架/构建通过;尚未接真实 radio/iPhone 做端到端传输。
- 文档:
docs/USERSPACE_DATAPATH.md。
- 修复
host_if_ipc.c未初始化 Winsock 导致的WSANOTINITIALISED (10093);awdld 现在能连上awdl_netd的 49877。 - 真实链路:radio 149 + 桥 + awdl_netd + awdld(非管理员)启动成功;awdld 日志
host interface awdl0, index 1,tx data从 9 增长到 12,说明 mDNS/MLD 出向包已进入 AWDL。 - 当前
peers 0,iPhone 未处于活跃 AWDL 状态;端到端发现/传输仍待测。 - 更新
patches/awdl-ipc-host.patch与docs/USERSPACE_DATAPATH.md。
- 发现 lwIP mDNS 初始公告后不再频繁广播;改为每 2 秒
mdns_resp_announce,与 WinAWDL 接收端行为一致。 - 服务实例/主机名改为 12 位十六进制 ID(与 iPhone 自己的
_airdrop实例格式一致),TXT 仍为flags=0x3fb。 - 诊断抓到了 iPhone 的 mDNS 响应:
_airdrop._tcp.local、12 位实例、flags=2039;说明 AWDL 双向数据可达。 - 但 iPhone 仍未建立
/Discover,当前peers 0;下一步需要确认 iPhone 的 Bluetooth/AirDrop 活跃状态,或对比 NSEC/记录差异。
- 按“复用成功实现”的要求,mDNS 不再由 lwIP 生成:Python
receiver.py用原mdns.py生成记录,awdl_netd只把 UDP 5353 在本地 15353 与 AWDLff02::fb之间搬运。 receiver.py新增ProxyAnnouncer、--mdns-address-file、--mdns-proxy-port;仍复用原 TLS/HTTP//Discover//Ask//Upload逻辑。- 实测:iPhone 能看到 AirBridge32;
POST /Discover、POST /Ask都到达 receiver 并返回 200。 - 剩余:
POST /Uploadbody 卡住,临时文件 0 字节;下一步修 TCP 代理/窗口,而不是继续改 mDNS。 - 提交:
a3ce8d1之后的这一轮改动。
- 把 TCP 层从 lwIP raw callbacks 改为 lwIP sockets API + 阻塞代理(
local_io.cpp隔离 Winsock,lwip_proxy.cpp只碰 lwIP sockets)。 - mDNS 继续复用
receiver.py的mdns.py记录,经 UDP 5353 ↔ 15353 代理。 - 实测环境:
singbox_tunUp、ping ::1100% loss;没有 Wintun、没有 Windows IPv6 参与。 - 结果:iPhone 15 Pro Max(iOS 17.5)通过 AirBridge32 传回
IMG_3583-1.PNG,2,911,531 字节,PNG 1290×2796,SHA-256C899358A...C48B1,与之前成功文件一致;上传约 36 秒。 - 证据:
evidence/airdrop-userspace-tun-on.txt(去敏);原始接收日志未提交。 - 状态:用户态 AWDL 数据面端到端可用;下一步优化吞吐、清理 PoC 开关、接 Dart UI。
- 对照第一次 Wintun/Windows 栈的 10.1 秒,调优 lwIP:
TCP_WND=240 KiB+ 窗口缩放、TCP_MSS=1360、TCP_SND_BUF=32 KiB、lwIP 和本地代理 socket 都开TCP_NODELAY。 - 实测 TUN 仍为 Up:
IMG_3583-2.PNG2,911,531 字节,上传 11.15 秒(~261 KB/s),SHA-256 与第一次成功文件一致。 - 证据:
evidence/airdrop-userspace-tuned.txt(去敏);原始接收日志未提交。 - 下一步:清理
AWDL_NETD_*PoC 调试开关,接 Dart/Flutter UI 与安装流程。
- 20 MHz 下 2.9 MB/11.15 s(~261 KB/s);80 MHz 设备不可见、发送失败。
- 40 MHz 实测:
IMG_3582.PNG7,710,365 字节,TUN 仍 Up,上传 7.35 秒(~1.05 MB/s),约 4 倍提升。 - 结论:40 MHz 是当前 USB/devourer 路径的推荐宽度;80 MHz 暂不可用。
- 证据:
evidence/airdrop-userspace-width40.txt(去敏)。
SelectedChannel.ChannelOffset对 80 MHz 一直传 0,中心信道被算成 149;改为按 5 GHz 80 MHz block 计算,ch149 传 offset=1(中心 155)。- 日志确认
channel 149 width 80 MHz offset 1,但 iPhone 仍看不到设备、无法发送;80 MHz 在当前 RTL8821CU + devourer USB 路径不可用。 - 保持 40 MHz 为推荐;证据
evidence/airdrop-userspace-width80-attempt.txt。
- 新增
app/Flutter Windows 应用:AirBridge32.exe自动按顺序启动并监管 radio/bridge/awdl_netd/awdld/receiver,显示状态与日志,退出时逆序清理。 - 新增
tools/Package-Portable.ps1:收集后端、PyInstaller 打包 receiver、复制 VC 运行库/许可证,生成dist/AirBridge32-portable.zip(约 19.6 MB)。 - 实测:从解压目录双击
AirBridge32.exe能启动全部后端,iPhone 通过隔空投送传回IMG_3588.PNG(2,578,074 字节)。 - 已知:便携版本次约 216 KB/s,手动同链路 40 MHz 约 1.05 MB/s;下一轮定位 receiver onefile/Flutter 监管层差异。
- 文档:
docs/PORTABLE.md。
AirBridge32.exe的窗口 X 改为最小化,app/radio/后端继续驻留;第二次接收不再重新初始化 radio。真正退出用界面“退出”按钮。- 便携包 receiver 改为 embeddable Python 3.12 + 原始 receiver.py,不再 PyInstaller;ZIP 约 27 MB。
- 对照结论:同一个 Flutter 壳 + 系统 Python 是 2.58 MB / 3.2 s;PyInstaller/embedded 出现 12–55 s 波动,Dart 不是瓶颈。
- 下一步:原生 C++ receiver 直接并入
awdl_netd,去掉 Python 与本地 TCP 跳;并补单实例/托盘。
- 新增
src/awdl_netd/native_mdns.cpp,按mdns.py构造 PTR/SRV/TXT/AAAA/NSEC,非阻塞每 2 秒发ff02::fb:5353; awdld的tx data稳定增长,确认包已发出,但 iPhone 仍看不到设备;- 默认恢复 Python mDNS(
AWDL_NETD_MDNS_PROXY=1),原生实现保留在AWDL_NETD_NATIVE_MDNS=1后面; - 下一步抓原生 DNS 报文与
mdns.py逐字节对比;详见docs/NATIVE_RECEIVER.md。
receiver.py不再每次secrets.token_hex(6);改为从 AWDL IPv6 IID 派生稳定 12 位 ID,重启不变。ANNOUNCE_PERIOD_S2.0 → 0.5 秒;启动连发 3 次;查询双回复;MIN_REPLY_GAP_S0.1 → 0.02。- 实测用户反馈:比之前快,但还不是“一打开就在”;剩余延迟可能在 iPhone 的 AWDL/BLE 扫描。
- 文档:
docs/DISCOVERY.md。
- 发现 0.5 秒公告会挤占 AWDL 时隙:6 MB 文件 45 秒只收到 1.76 MB(~40 KB/s)。
/Upload期间置位TRANSFER_BUSY,mDNS 公告/查询回复暂停;上传结束恢复。- 实测 1.16 MB / 5.4 秒(~215 KB/s),体感明显恢复;仍低于手动 40 MHz 的 1.05 MB/s,后续继续定位。
- 更新
docs/DISCOVERY.md与patches/awdl-ipc-host.patch。
用户要求以“手机端更稳、传输更快、系统更稳、分发更方便”为核心,给一个最终方案。只写决定,未改代码。
- 新增
docs/FINAL_PLAN.md:V1 保留 GPL 边界(radio GPL-2.0 + awdld GPL-3.0 + MIT 其余进程),删除app/(Flutter/Dart)与“embeddable Python 发行版 + Flutter 监管”的启动方式;用户侧改为单文件自解压 exe + 原生托盘监管,Python 接收端保留并由监管进程隐藏拉起。 - 明确不做的项:单进程合并(GPL-2.0-only 与 GPL-3.0 不兼容)、Dart 重写 AWDL、Wintun、80 MHz、receiver 改 C++(均为 M3 后再议)。
- 速度按 E1 时隙/信道序列 → E2 ACK/TX 优先级 → E3 USB 帧率 → E4 mDNS 开销 四条实测线推进;依据是 23:01 日志中 iPhone 成为 sync master 后 149 仅占 16 个时隙中的 4–7 个、joined 帧约 150 帧/s,而 1.05 MB/s 约需 700 帧/s。
- 稳定性设计:看门狗指标、恢复阶梯(重开 RX → 重新 Init → USB reset → 重启 radio → 提示拔插)、单实例、USB 选择性挂起、崩溃隔离与退避重启、日志轮转与脱敏诊断包。
- 分发:单文件解压到
%LOCALAPPDATA%、无需管理员、驱动绑定仍走 Zadig 且回滚演练列为硬门槛;Realtek 固件再分发三选一仍需用户拍板。 - 发现一个真实分发阻塞:
identity.py依赖外部openssl.exe,干净机器(无 Git/OpenSSL)首次启动会失败;已列为 WP1.5,改用包内cryptography进程内生成证书。 - 同步更新
README.md阅读入口与docs/PLAN.md顶部口径。
未做:没有删除 app/、没有改任何后端代码、没有跑硬件实验(本轮只落方案)。下一步按 WP0 用同一张 20 MB 文件重建基线,再决定 WP1 的自解压外壳实现方式。
用户要求把待办固化,避免上下文丢失或换 agent 后忘记;之后开始执行。
- 新增
docs/WORK_QUEUE.md:Q1–Q13 的可交接队列(状态/依赖/完成判据/证据),并把"本轮未授权硬件实验"单列。 AGENTS.md的必读清单加入docs/FINAL_PLAN.md、docs/WORK_QUEUE.md,并规定交接以队列为准。- README 阅读入口加队列链接。
- 下一步:Q1 离线吞吐台(不改射频、不发帧);Q2 身份证书去 openssl;Q3 手动链路脚本(跑之前要授权)。
新增 tools/bench_receiver.py:在 127.0.0.1 起真实 receiver(TLS + chunked + DVZip + cpio + 落盘),服务端计时 /Upload 开始到 stored。
- 20 MB 随机字节、3 次:compressed 中位数 227.7 MB/s(152/228/236),stored 中位数 277.9 MB/s。SHA-256 全部一致。
--trace全开复测:274.2 / 259.8 MB/s,日志 2.6 KB —— 调试日志不是开销。- 结论:Python/TLS/zlib/cpio/磁盘有约 200 倍余量;1.05 MB/s 只用到 0.45%。速度问题锁定在空口调度、ACK 与 USB 帧率,不再考虑“换语言/去 Python”。
- 证据:
evidence/bench-receiver.md、evidence/bench-receiver-trace.md。 - 局限:客户端与服务端同进程,数字是下限;但不影响“远高于需求”的结论。
- 顺带发现:官方
test_receiver.py在本机 9 个里 8 个失败(连::1,本机 IPv6 回环被 TUN 挡,WinError 10013);bench 用127.0.0.1绕开,测试环境需要另行修(未列入队列)。 - 队列:Q1 → ✅;下一项 Q2。
identity.py 改为优先用 cryptography 进程内生成 RSA-2048 自签证书(临时文件 + os.replace 原子落盘),只在模块缺失时回退原来的 openssl.exe 路径。
- A:PATH 去掉 Git/OpenSSL → 进程内生成,证书 1046 B、密钥 1704 B,
ssl.load_cert_chain通过。 - B:屏蔽
cryptography→ 回退 Git 自带 openssl,证书 1084 B,通过。 - C:两者都没有 → 明确抛
IdentityError,不静默降级。 - D:端到端 1 MB 上传(PATH 无 Git/OpenSSL)→ 117.2 MB/s、SHA-256 一致。
- 证据:
evidence/identity-no-openssl.log;改动以patches/airdrop-identity.patch跟踪(upstream 未入库)。
附带修复两个真问题:
- 旧
patches/awdl-ipc-host.patch是 CRLF,对 LF 工作树apply --check必然失败;而Apply-AwdlPatches.ps1在补丁既不能正应用也不能反应用时仍然exit 0,等于把损坏藏起来。已用 LF 重新生成两个补丁(identity 单独一个,互不重叠)。 - 脚本改为逐补丁判定"应用 / 已应用 / 冲突抛错",新增
-Repo;在锁定提交的干净 worktree 上应用后,identity.py、mdns.py、receiver.py、awdl/CMakeLists.txt、host_if_ipc.c 五个文件哈希与活树完全一致,重复执行幂等。证据:evidence/patches-reproduce.log。
队列:Q2 → ✅;下一项 Q3(手动链路脚本)。
- 新增
tools/Start-Chain.ps1:默认 dry-run,只有显式-Run才会初始化网卡并让 awdld 发帧——授权边界写进工具本身。布局自适应(仓库build\/ 打包backend\),启动前查 49876/49877/8771/15353,逐个等 ready 行,Ctrl+C 逆序清理并复查端口,chain.log记录 commit/时间/频道。 - 新增
tools/Package-Headless.ps1:产出无 Flutter、无 Dart 的dist\AirBridge32-headless.zip,内含 backend、python-embed、receiver、Start.cmd、README 与许可证。 - 实测(不含射频):dry-run 两种布局路径正确;
python -m http.server 49876占端口时按预期拒绝启动;打包后Start-Chain.ps1解析到backend\python\python.exe与backend\airdrop\receiver.py。 - 体积:headless ZIP 16.0 MB(解压 36.4 MB),旧便携版 27.0 MB;去掉 Flutter/Dart 省 11 MB。
- 未做:
-Run真机起停三次、与便携包的同文件 A/B、单文件 exe 外壳(Q4)。这三项都要硬件授权或额外外壳工作。 - 证据:
evidence/headless-package.log。 - 队列:Q3 → 🔜(脚本与包完成,实测待授权);Q4 载荷已就绪,外壳未做。
机器上没有 7-Zip/WinRAR,没有装任何打包器;改用 Windows 自带 csc.exe(.NET Framework 4.8)编一个 7 KB 的 C# stub,System.IO.Compression 读内嵌 ZIP。
src/sfx/Program.cs:解压到%LOCALAPPDATA%\AirBridge32\<exe 时间戳>\,extracted.txt作标记;默认解压后启动Start.cmd;--extract/--where便于安全测试;对 zip 条目做目录穿越校验、兼容Compress-Archive写出的反斜杠与空目录条目。tools/Build-Sfx.ps1:dist\AirBridge32-headless→dist\payload.zip→dist\AirBridge32.exe。- 实测:exe 16.0 MB(stub 仅比载荷大 7 KB);首次解压 0.4 s / 333 文件,第二次 0.03 s;包内 Python 导入 ssl/zlib/cryptography 正常;解压目录 dry-run 路径正确。
- 结论:不需要 Flutter/Dart/Python 安装或第三方打包器即可"一个 exe";代价仅是 .NET Framework 4.8(Win10 1903+/Win11 自带)。
- 未做:干净 VM、SmartScreen/AV、真机启动(Q13 / 硬件授权)。
- 决定:托盘监管(Q5)同样用 C# + WinForms(系统自带),不再写 Win32 C++:代码量约为 C++ 的三分之一,且能满足托盘、通知、单实例、开机自启。
- 证据:
evidence/single-exe-prototype.log;队列 Q4 → ✅。
用户授权硬件实验(23:37)。按计划把 Flutter 的职责搬进一个 20 KB 的 winexe。
src/supervisor/Program.cs:单实例(Local\AirBridge32-tray互斥体)、按序拉起 radio → bridge → netd → awdld → receiver、用每个进程的 stdout/stderr ready 行判定就绪、日志按会话落data\logs\<stamp>\<进程>.log并只保留最近 10 个会话、托盘菜单(状态/接收目录/日志/开机自启/重启/退出)、接收目录 FileSystemWatcher 弹气泡、退出逆序 kill 并等待。- 三种模式:
--check(只校验,不启动)、--smoke N(真机起停,写 smoke.txt)、--auto-exit N(托盘测试钩子);--autostart-on/off与菜单共用注册表项。 - 实测(真实 RTL8821CU,未接 iPhone):radio 初始化 42 s 后 listening,链路在 0.5 s 内全部就绪,awdld
tx action增长说明空口在发同步帧;--smoke 20与托盘--auto-exit 75都以退出码 0 结束,进程表无残留;单文件包解压后backend\AirBridge32.exe无参数--check全部路径正确。 - 打包链更新:
Package-Headless把监管放进backend\,src/sfx解压后优先启动它(其次Start.cmd);重建后仍是 16.0 MB。 - 未验证:气泡/菜单外观、开机自启实际写入、真实文件传输与“收到文件”通知(需要 iPhone)、24 h 常驻。
- 语言决定:C# + WinForms 取代 Win32 C++(.NET Framework 4.8 是 Windows 自带,产物 20 KB);已回写 FINAL_PLAN。
- 证据:
evidence/supervisor-tray.log;队列 Q5 → ✅,新增 Q14(Q5 真机传文件通过后再删app/)。
- 日志:单个
.log超 5 MiB 滚动成.log.1,会话目录保留 10 个;托盘新增“复制诊断信息”(root/状态/OS/各日志末 40 行,写剪贴板)。 - 看门狗 5 s 检查存活;只有 receiver 死时单独重启,其它后端死才整链重启(退避 1/5/30 s,10 分钟 5 次后停)。
- 实测(真机):
Stop-Process杀 receiver(pid 48096)→ 5 s 内发现 → 0.2 s 内起新 receiver(32968)并 ready,airbridge_radiopid 前后不变(42468)。 - 对照:早先版本整链重启要 42 s(radio 固件重初始化),所以选择了选择性重启。
- 同日遇到一次已知固件启动失败:
FW ready check failed (0x80 != 0xC078),cpu-reset 重试后恢复,启动被拖到 >60 s;说明固件重试需要可见状态和更长超时(Q7 剩余)。 - 重新打包:headless ZIP 与单文件 exe 均 16.0 MB,监管 24 KB。
- 证据:
evidence/supervisor-recovery.log;队列 Q6 → ✅,Q7 → 🔜(假死检测、24 h 未做)。
- 监管启动前先试绑 49876/49877/8771/15353;占用即失败,不碰射频。实测:
python -m http.server 8771占住时--smoke 53 秒内返回started=False、error=port 8771 is already in use;解除后--smoke 8正常,说明不误报 TIME_WAIT。 - 新增
tools/analyze_session.py:从监管会话目录的receiver.log(POST /Upload头 →stored)算真实 MB/s,从awdld.log算 joined 帧/s 与所需帧率;--json可机读,--latest <包根>取最近会话。 - 监管日志新增
start chain root=... args=...一行,便于定位会话。 - 重新打包:headless ZIP 与单文件 exe 均 16.0 MB,监管 25 KB。
- 下一步:等一次真实 iPhone 传输,用分析器给出“现链路 vs 便携包”的可比数字(Q3/Q8)。
用户在托盘版上实测同一张 2.58 MB 照片五次:
| 配置 | 用时 | 速率 |
|---|---|---|
| 149 / 40(首链) | 69.2 s | 37 KB/s |
| 149 / 40(同一进程再发) | 70.7 s | 36 KB/s |
| 149 / 40(整链重启) | 91.8 s | 28 KB/s |
| 149 / 20 | 12.8 s | 202 KB/s |
| 149 / 40(USB 拔插冷启动后) | 13.6 s | 190 KB/s |
- 慢速时
joined帧 0–47/s,中间有 6–18 s 零帧空窗,但rx action仍在收(手机停发 → TCP 退避),tx data同样归零。 - 快速时稳定 110–220 帧/s、每帧约 1,455 B。40 MHz PHY 单帧 <0.2 ms,实测每帧 6.7 ms → 上限是时隙占用率,不是带宽、不是 Python(离线 228 MB/s)。
- 环境底噪(doctor 被动 RX):149 安静(101/154)、153 与 157 无 AP、36/40 忙但干净 → 邻居干扰解释不了 40 MHz 的 37 KB/s。
- 结论一:40 MHz 会进入退化状态,只有 VBUS 冷启动能恢复(同 20 MHz 下 202 KB/s 正常)。这需要产品化的链路健康检测(Q7),不能靠用户拔插。
- 结论二:冷启动后 40 MHz ≈ 20 MHz ≈ 190–200 KB/s;awdld 整晚只
switch channel to 149一次,不跟随对端 36/6 时隙,常态只用约 2/16 个时隙。最大速度杠杆是让 awdld 跟随/协商信道序列(新增 Q15)。 - 监管新增
--channel/--width参数,用于这种 A/B(本次 20 MHz 对照就是用它跑的)。 - 证据:
evidence/transfer-regression-2026-10-28.log;队列 Q7 补注、新增 Q15。
- 按用户要求把常态带宽改成 149 / 20 MHz:监管默认
width=20,托盘状态栏改为跟随实际配置(BandText),headless 包与单文件 exe 重新打包(16.0 MB),并用单文件 exe 起了一条 149/20 实例验证(00:45:35 就绪)。提交4a4f06a。 - Q15 第一步:
src/radio_usb/main.cpp的SET_CHANNEL同频段改用 devourerFastRetune,跨频段仍走完整SetMonitorChannel,并把耗时写进日志。 - 实测(radio 单独跑 + Python 客户端):
149→361.86 ms、36→14914.75 ms、149→4412.2 ms(原完整路径约 90 ms);换台后 RX 正常(36 上 43 帧/2 s、RSSI -40)。 - 找到 Q15 主体的确切位置:OWL 的
awdl_handle_chanseq_tlv把对端序列存进awdl_peer.sequence后就不管了,state->channel.sequence始终是静态“全 149”,所以daemon/core.c的逐时隙换台永远只看到 149。下一步是把主控序列(含编码)写回 state,再让 core 的时隙循环驱动 SET_CHANNEL;跨频段那个 2.4 GHz 时隙先跳过。 - 重新打包并重启为 149/20(会话 20261028-005000,radio 00:50:43 就绪)。证据
evidence/radio-fast-retune.log;队列 Q15 → 🔜。
owl/src/rx.c:收到信道序列 TLV 且发送者是当前同步主控时,把序列(含编码)写回state->channel.sequence/state->channel.enc,打印adopted channel sequence of sync master <mac>;由AWDL_FOLLOW_MASTER_SEQ环境变量开启,默认关闭。这样daemon/core.c的逐时隙换台才会真正按主控时隙表走。src/radio_usb/main.cpp:跨频段换台新增AIRBRIDGE_HOP_SKIP_CROSSBAND=1跳过(2.4 GHz 那个时隙用完整路径要 ~90 ms,会把超帧拖死);同频段仍是FastRetune。- 补丁体系:新增
patches/owl-follow-chanseq.patch(OWL 子仓库),tools/Apply-AwdlPatches.ps1改为管理三个补丁、支持子仓库、可重复执行;已验证补丁能干净应用到 OWL 锁定提交da255a7的干净 worktree。 - 测试状态:带两个开关跑过一次,但当时
peers 0(iPhone 没开隔空投送),收不到序列 TLV,未验证跟随效果。下次需要用户在 iPhone 上打开隔空投送,再看adopted channel sequence与 radio 的channel x -> y (…)是否出现,并测吞吐。 - 重新打包并恢复默认(开关不设)的 149/20 链路。证据
evidence/q15-follow-sequence.log;队列 Q15 → 🔜(代码完成,待验证)。
- 用户开了隔空投送后先验证跟随模式:
adopted channel sequence of sync master 8e:b4…正常出现,awdld 确实按 36/149/6 逐槽切(109 次),但吞吐 101 KB/s,只有默认的一半。原因:手机只在它的 149 时隙里给我们发数据,跟着跳到 36 只是白等 + 换台开销。跟随方向否定。 - 改试反向:OWL 的选举规则是 metric 更高者当主控(
compare(peer, self) < 0拒绝对端);iPhone 的 metric 是 65,我们设AWDL_ELECTION_METRIC=200后稳定成为sync master(日志确认sync master is now <我们的地址> (met 200)),序列变成全 149。 - 实测(同一张 2,578,074 B 照片):7.68 s = 336 KB/s,传输中 joined 帧 196–266/s(默认模式 ~150/s)。比默认快约 66%。
- 40 MHz + 主控实测更慢:7,368,180 B / 42.7 s = 172 KB/s,与用户“40 MHz 会死”的体感一致,继续用 20 MHz。
- 落地:监管给 awdld 固定
AWDL_ELECTION_METRIC=200(默认即主控模式,无需环境变量);OWL 两处改动(rx.c 跟随开关、election.c metric 覆盖)收进patches/owl-airbridge.patch(重命名),Apply-AwdlPatches.ps1同步更新。打包版启动确认sync master is now 92:06:… (met 200)。 - 风险:强制主控会影响同一空域其他 Apple 设备的 AWDL 选举,属于产品级取舍;336 KB/s 是一次测量,需要重复三次确认。
- 证据:
evidence/q15-master-mode.log;队列 Q15 → ✅。
- 打开 radio 的逐 5 秒链路计数(新增
AIRBRIDGE_RADIO_LOG=info透传 +radio: stats cb/crc/...)后再测 29.46 MB 视频:30.37 s = 970 KB/s,传输中 635 帧/s(546–817)。 - 链路计数:传输期间回调 28,620 次、CRC 增加 4,291(约 15%)、小于 24 字节的帧 2,014;15% 的 CRC 仍能维持慢启动后的窗口增长。
- 同配置 20 分钟前那次是 363 KB/s(81.2 s),两次差 2.7 倍,期间没有任何软件改动(只把 radio 日志级别从 warn 改成 info)。01:34 那次 radio 经历过多次固件启动失败与 full power-cycle 重试才起来,之后跑出 970——与之前 40 MHz“退化态只有冷启动能恢复”的现象同源。
- 结论:1 MB/s 可达(970 KB/s 为实测);但同一配置也会掉到 363 KB/s,必须做链路健康检测(Q7:帧率 < 300 帧/s 持续 10 s 判为低速,进入提示/恢复流程)。
- 顺手修了监管:radio 就绪超时从 180 s 提到 300 s(固件重试会超过 180 s),启动失败后按 1/5/30 s 退避自动重试,而不是等用户点“重新启动”。
- 证据:
evidence/transfer-970kbs.log。
- radio 每 5 秒的
stats/peer frames两行改为可走 warn 级别(AIRBRIDGE_LINK_STATS=1),监管始终打开。这样既保留每 5 秒的链路计数,又不会像 info 级别那样每帧打一条bulk_send。 - 监管新增
OnChildLine解析peer frames=N→Fps = N/5;托盘看门狗每 5 秒采样,连续两次 < 300 帧/s 判为低速:记日志 + 弹气泡“链路速率偏低,可从托盘选重置链路”。 - 托盘新增“重置链路”(重启整链,等效冷复位射频);
_slowSamples/_slowWarned在恢复或重置后清零。 - 阈值依据:昨天四次实测(970/363/251/952 KB/s)对应 750–860 与 190–280 帧/s 两档,丢包率都在 15% 左右。
- 已起链路验证:149/20、主控、warn 级别下只有两条 5 秒统计,没有每帧噪声;
sync master is now是我们自己。 - 下一步:抓一次慢速传输,用新采样确认是“手机降 MCS”还是“手机少发帧”,据此决定 Q7 的恢复动作是重置射频还是别的。
- 慢速根因:托盘报“链路速度偏低”那次,日志显示 12:35:48 手机把同步主控抢走(
new election tree: 66:b9… -> 8e:b4… (met 65, ctr 1514))。OWL 选举先比 counter 再比 metric,只在对方更小时拒绝;OWL 自己从不递增 counter(恒 0),而 iPhone 的 counter 每 3.14 秒 +1,于是开机几分钟后必然反超、序列回到 36/6/149、速度掉回 200 KB/s 档。给 election.c 加AWDL_ELECTION_COUNTER覆盖,监管固定传 100000;重启两分钟后仍是主控(met 200, ctr 100000)。 - 新纪录:12:52 传 29.46 MB 视频 26.26 s = 1121 KB/s,是目前最高;counter 修正后没有再被抢主控。
- AirDrop 列表重复:实例 ID 从 AWDL IPv6 地址派生,而 awdld 每次启动随机 AWDL MAC,ID 每启动变一次(
b011eefffed7→18c2c6fffe5a)。stable_service_id改为先读<keys>/airdrop-service-id;重启验证 ID 不变。 - 全部数据留在程序文件夹内(用户新要求):stub 解压到
exe 目录\app\<构建>(只留最近 2 个),data\放keys\(证书、实例 ID)、received\、logs\、awdl_netd.addr,通过--data传给监管、跨构建共享;开机自启改指向文件夹里的 stub。验证后%LOCALAPPDATA%\AirBridge32下不再生成新目录,并清掉了开发期留下的 13 个旧目录。 - 提交:
1cedcdf(counter)、cc3b026(实例 ID + 1121 KB/s)、本条(便携布局)。 - 证据:
evidence/transfer-970kbs.log、evidence/service-id-persist.log、evidence/data-stays-in-folder.log。
- 按 Q14 删除
app/(Flutter 项目,26 个受版本控制文件、磁盘上 328 MB 含构建产物)与tools/Package-Portable.ps1;现行打包只剩Package-Headless.ps1+Build-Sfx.ps1。 - 文档同步:README 顶部换成当前状态(单文件 exe、托盘、1121 KB/s、20 MHz + 主控、数据留在文件夹内),阅读入口把 PORTABLE/ARCHITECTURE 标为历史;PLAN 在 Dart 决策下加“已被 2026-10-28 决定取代”;ARCHITECTURE、PORTABLE 加历史提示。
- 队列:Q14 完成;Q8–Q11 归档(Q15 的实测已覆盖时隙、帧率、ACK、mDNS 四项);Q12 标为非必需。
- 新增
tools/Monitor-Soak.ps1并已在后台启动(24 h、每 60 s 一次,CSV 落build\soak\soak.csv):记录 supervisor/radio/bridge/netd/awdld/receiver 的工作集、私有内存、句柄、CPU,以及缺失进程与监听端口。首帧:AirBridge32 33 MB 工作集 / 75 MB 私有 / 423 句柄;radio 7 MB;awdld 4 MB;receiver 25 MB;端口 49876/49877/8771 在听。 - 样本里 15353 不在列表是因为它只开 UDP,端口检查只看 TCP——不是缺失。
- 下一步:常驻采样跑满一段后看趋势;速度基线(A)等你方便时发 3 次 29 MB 视频。
- 新增
docs/STATUS.md:给接手人看的一份现状说明——交付形态与目录布局、六个进程与许可证、数据路径、关键配置(149/20、主控 metric/counter、社交时隙、固件快切、实例 ID 持久化、低速检测)、全部速度实测、今天修掉的八个问题(带提交号)、五个未解决项(射频退化、蓝牙 Error、稳定性未验收、固件授权、签名/干净 VM)、1 小时常驻采样、构建与验证命令、下一步建议、证据索引。 - README 阅读入口加入该文档;
tools/check_project.py的必需清单同步。 - 当前实测:最好 1121 KB/s(29.46 MB / 26.26 s);社交时隙 + 快切后起步 930 帧/s,随后因射频退化中断;1 小时常驻采样无泄漏(AirBridge32 33–37 MB 工作集 / ~421 句柄)。
- 蓝牙接口仍是 Error(软件 USB 复位造成),需重启系统恢复,不影响 AirDrop。