# 路线图

按"每个里程碑结束时仓库里都有一件能运行、能验证的东西"来切分。以下状态以随文档一起打包的源码为准。

## M1 · 确定性内核与网络层（完成）

交付：

- 虚拟时间（皮秒整数）、事件队列、事件日志
- 冻结的随机源，带参考向量的回归测试
- 数据包模型、有界队列（drop-tail、按包数与按字节双限制）、链路（带宽、传播延迟、抖动、利用率）
- CLI 骨架：`moonnet version` / `moonnet demo` / `moonnet help`
- 25 个测试，覆盖时间换算、事件排序、堆行为、队列丢弃、链路串行化与抖动的可复现性

验收方式：`moon test` 全绿；同一场景两次运行的到达时刻序列完全一致。

## M2 · TCP 协议栈（完成）

已交付：

- 发送端与接收端状态机（CLOSED / SYN-SENT / ESTABLISHED / FIN-WAIT 等）
- 32 位环绕序号空间、累计确认、接收窗口与流量控制
- 滑动窗口与在途字节记账、乱序缓冲与重组、重复段裁剪
- 主动与被动关闭、TIME-WAIT 计时（用仿真的虚拟时钟，不是真实时钟）
- RTT 估计与 RTO 计算（RFC 6298 的 SRTT/RTTVAR）、Karn 算法、超时重传、指数退避与重传上限
- 链路丢包模型（丢包仍然占用链路时间，与真实网线上的损坏帧一致）
- 场景：单连接批量传输，吞吐与解析计算一致；1% 丢包下仍完整送达

验收方式：`moon test` 中 15 个 TCP 测试全绿，其中包含"窗口上限决定在途字节数""丢失的段被重传且数据完整""连续超时按指数退避并最终放弃""两次运行逐字节一致"几条关键断言。

## M3 · 快速重传与多丢包恢复（完成）

已交付：

- 三次重复 ACK 触发快速重传，不等定时器
- 一次窗口内丢多个包时，用部分确认（NewReno 的思路）逐个补齐：收到部分确认就重传下一个空洞，收到覆盖全部在途数据的确认才退出恢复
- 超时会让连接退出恢复状态，重新回到定时器驱动
- `Sim::step_until`：跑到"关心的那件事发生"为止，而不是把队列排空。协议被取消的定时器事件仍留在队列里，排空会把时钟推到最后一个过期事件上，用它计量传输时间会得到假数字

验收方式：`moon test` 中 3 个新测试覆盖触发阈值、多丢包恢复、以及"两个重复 ACK 不足以触发"。演示场景里，握手阶段的丢包仍然要等满一秒，传输阶段的丢包只需约一个往返——同一个机制在两个阶段的代价差二十倍，这正是这组测试要固定下来的事实。

后续 M4 已实现快速重传时的窗口减半和恢复期的窗口膨胀。

## M4 · 拥塞控制接口与 Reno（完成）

已交付：

- `CongestionControl` 接口：窗口是多少，以及在收到确认、快速重传、重复确认、部分确认、离开恢复、超时这六件事发生时的反应。连接只负责机制，算法只负责决策
- `NoCongestionControl`：唯一限制是接收窗口，作为对照基线
- Reno：RFC 6928 的初始窗口、慢启动、拥塞避免、快速重传时的窗口减半与膨胀、恢复结束时的收缩、超时后塌回单个报文
- 发送速率现在是 `min(接收窗口, 拥塞窗口)`：两个独立的限制，缺一个都不是 TCP
- 演示：同一场景下，无拥塞控制的 200 KB 传输要 4509 秒（队列反复溢出、超时退避到分钟级），Reno 只要 2.678 秒

验收方式：`moon test` 中 6 个新测试覆盖初始窗口公式、拥塞窗口对首批数据的限制、慢启动翻倍、超时后的窗口崩塌、快速重传时的减半与恢复，以及"无拥塞控制时接收窗口是唯一限制"。

## M5 · 更多算法与队列管理（当前范围完成）

已交付 CUBIC（RFC 9438）：

- 三次增长曲线 W(t) = C(t-K)³ + W_max，含凹段与凸段
- 0.7 倍乘性减少（Reno 是一半），以及快速恢复期的窗口膨胀
- Reno 友好区：用 W_est 保证在低带宽延迟积的路径上不比 Reno 慢
- 快速收敛启发式（默认关闭，保留当前实验的明确基线）
- 目标窗口的上下界（不低于当前窗口、不高于 1.5 倍），避免 CUBIC 跑得比慢启动还快

验收方式：`moon test` 中 4 个新测试覆盖丢包响应（同样 7 个报文在途，Reno 保留 3500 字节、CUBIC 保留 4000）、超时后的窗口崩塌与恢复后的曲线回升。早期长肥链路单次对比曾显示 CUBIC 快八倍；受控丢包与多种子汇总后的结论以 M8 为准。

已交付队列管理（drop-tail / RED / CoDel）：

- 场景文件里 `discipline` 字段选择策略，`run --discipline NAME` 做一次性覆盖（只改上行，一次实验只变一个东西）
- RED 的阈值从缓冲区自身大小推出（四分之一开始概率丢弃、四分之三全丢），平均占用按到达更新
- CoDel 按 RFC 8289：目标 5 毫秒、间隔 100 毫秒，间隔按 √丢弃次数收缩；评估点从出队移到入队（理由写在设计文档）
- 报告新增 `queue_discipline`、`uplink_dropped_early`、`mean_queue_packets`、`max_sojourn_ms`

验收方式：`moon test` 中 6 个新测试覆盖 drop-tail 永不提前丢弃、RED 与 CoDel 会在缓冲区未满时丢弃、CoDel 在缓冲区远未满时就动手（而固定速率的信源下延迟不会因此变短——这条限制本身就是被断言下来的），以及端到端对比：同一个场景、同一个算法，drop-tail 下最坏排队 489 毫秒，CoDel 下 226 毫秒且传输时间从 5.5 秒降到 1.8 秒。

已交付队列容量扫描：

- `sweep --field queue_packets` 按正整数容量扫描上下行缓冲区，并保留两个端点；步数过多、非整数或非正容量会给出错误
- 文本和 JSON 汇总每个算法的吞吐、耗时、平均上行队列占用、跨种子平均的“单次运行最大等待时间”以及所有种子中的最坏等待时间
- 平均队列占用使用各次运行传输窗口内的时间加权值，不把结束后的空队列时间算进去

验收方式：回归测试覆盖升序/降序端点、容量唯一性、字段隔离、多种子队列指标的聚合口径与 JSON 可解析性；固定场景重复运行产生相同的 JSON。示例命令扫描 `scenarios/bufferbloat.json` 的 16–600 包容量。

场景数据（`scenarios/bufferbloat.json`，5 Mbps / 10 ms / 600 包缓冲区 / 3 MB，算法固定 CUBIC）：

| 策略 | 传输耗时 | 吞吐 | 最坏排队 | 丢弃 | 超时 |
| --- | --- | --- | --- | --- | --- |
| drop-tail | 16.610s | 1444.9 kbit/s | 978.7 ms | 614（全队尾） | 0 |
| CoDel | 5.044s | 4757.7 kbit/s | 226.4 ms | 34（全提前） | 0 |
| RED | 19273.903s | 1.2 kbit/s | 978.7 ms | 329（192 提前） | 326 |
| drop-tail，缓冲区 64 包 | 7.403s | 3241.7 kbit/s | 104.4 ms | 134（全队尾） | 0 |

RED 那一行不是笔误：当前实现按到达更新平均占用，空闲期间没有衰减。平均值过高后，新包持续被拒绝，只剩定时器重传推进，每 60 秒一次。换成 Reno 同样发生（17128.905s / 294 次超时）。这是当前实现需要修正的限制；修正后应重新运行场景并更新这组数据。

Vegas 属于后续可选研究，不在 `0.1.0` 范围内。

## M6 · 实验与报告（完成）

已交付：

- `src/json`：零依赖的 JSON 读写。解析错误带字节偏移；写出时整数不写成 `9.0`
- `src/lab`：场景与报告。`Scenario` 从文档构造，字段缺失有默认值、写错有带路径的报错；`RunReport` 输出固定字段顺序的 JSON，同一场景两次运行逐字节一致
- `cmd/moonnet run <file>`：跑一个场景文件，`--cc` 换算法，`--json` 出报告；`list` 列出场景
- `scenarios/`：七个可编辑的实验，覆盖小缓冲、长肥链路、干净局域网、丢包、缓冲膨胀和多流公平性

验收方式：`moon test` 中 7 个新测试覆盖场景解析、默认值、错误消息、运行结果、两次运行的逐字节一致，以及报告本身能被 JSON 解析回来。

## M7 · 对比与扫描（完成）

已交付：

- `compare`：同一场景、同一随机源下并排跑多个算法，输出对齐的表格或固定字段顺序的文档
- `sweep`：按值域扫描 `loss`、`delay_ms`、`bandwidth_mbps`、`seed` 四个参数，每个取值一行、每个算法一列
- `render_table`：按列最宽单元格对齐，数值列右对齐，第一个数据行左对齐
- 输出里带一句必要的说明：同一随机源不等于同一串丢包，两张表的差别是行为差别而不是同一丢包下的应对差别

验收方式：`moon test` 中 9 个新测试覆盖对比、渲染、文档、两次运行一致、扫描取值含端点、四个字段各自只改自己、表对齐、定点数格式、算法列表解析。

一个附带发现：在 64 KB 窗口的场景里扫描丢包率，CUBIC 反而比 Reno 慢——两个算法都被接收窗口限制住了，CUBIC 的保守成了负担。它只在窗口足够大、丢包成为主要限制时占优。同一组算法换个场景结论就反过来，这是把参数变成数据的直接收益。

## M8 · 受控丢包与多种子平均（完成）

两个改动，都为了让数字能被相信。

**丢包跟着包的身份走。** 链路原来按每次发送抽签，两个算法发送顺序不同，遇到的就是两串不同的丢包，所谓"对比"里混进了一个不受控变量。现在每个段带一个身份（起始序号 + 这是第几次发送），链路用 `mix64(链路密钥, 身份)` 决定命运：同一个包在同一条链路上永远遇到同样的结果，而重传带着新的身份，所以被丢掉的包仍然能救回来。ACK 没有稳定身份（同一个 ACK 会重复发送），退回按传输顺序抽签——这一条写在输出里，不藏着。

**多种子平均。** 丢包场景下总时间由少数几次超时主导，而超时指数退避封顶到 60 秒，于是同一个场景换一个种子，总时间能从 214 秒跳到 1162 秒。`compare` 和 `sweep` 现在接受 `--seeds N`，报告均值与最快/最慢。

验收方式：`moon test` 中新增/更新的测试覆盖身份的稳定性、多种子汇总的边界（均值落在最快与最慢之间）、以及两个算法在同一串丢包下都完成传输。

这一轮推翻了自己两个结论：

1. 受控之前"CUBIC 快 7.9 倍"不可信——那是两个算法遇到不同丢包造成的。
2. 受控之后单次运行显示"CUBIC 更慢"，多种子平均后反过来：均值 218.6 秒对 706.1 秒，超时 23 次对 112 次。

**待查的问题**：在一次采样中，CUBIC 的平均窗口是 Reno 的七倍（105 个报文对 15 个），耗时却更长。窗口更大而数据没流动，说明连接在停顿。一个可能的方向是：丢失的 ACK 只能靠超时补救，而发得越多、丢的 ACK 也越多。这需要窗口轨迹和停顿时间的统计才能确认，先记在这里而不是写成结论。

## M9 · 多流与公平性（完成）

已交付：

- 场景文件支持 `flows` 分组声明，多条流共用同一条上行链路与同一个队列（一组发送端排队进同一条链路、ACK 走同一条下行链路），这是"公平"一词有意义的最小拓扑
- 每条流有独立身份：段的身份是 `(流号, 序号, 第几次发送)` 的混合，所以两条流不会继承彼此的丢包；流号为 0（单流）时身份与改动前完全一致，因此历史结果没有被扰动
- Jain 公平性指数、每条流的占比/吞吐/重传/超时、聚合吞吐、队列丢弃与线上丢失
- 测量窗口：限时长的长连接，而不是固定大小的传输——固定大小时每一条流最终都能传完，公平性指数恒为 1，指标没有意义
- 饱和标记：若某条流把申报的数据发完并退出竞争，报告会说明份额反映的是申报量而不是路径

验收方式：`moon test` 中 6 个新测试覆盖指数端点（全相等为 1、一条独占为 1/n、空集为 0）、flows 分组展开与三种错误消息、聚合吞吐不超过链路容量、每条流都还有数据要发、两次运行逐字节一致、报告能被 JSON 解析。

两个被这一轮抓出来的错误，都写进了代码注释：

1. **窗口到期后不能排空事件队列。** 第一版在测量后继续 `sim.run()`，迟到的 ACK 释放了更多数据，于是 20 MB "在 5 秒内"通过了 10 Mbps 链路。物理量检查（聚合吞吐不能超过链路容量）现在是一条断言。
2. **固定大小的传输测不出公平性。** 每条流都能传完，指数恒为 1；必须改成限时长的长连接。

跨 9–12 四个种子的测量结果：两条 CUBIC 流合计 7.3–9.2 MB，两条 Reno 流合计 3.9–4.8 MB，即 CUBIC 在混合部署里稳定拿到约两倍份额；但同算法内部谁多谁少随种子变化，Jain 指数 0.77–0.93。聚合吞吐 12.19 MB / 10 秒 ≈ 9.75 Mbps，利用率 97%。

## M10 · 轨迹与尾丢恢复（单流范围完成）

已交付：单流 `run --trace` 保留 TCP 发送、确认、交付、尾部探测、重传、超时和上下行队列轨迹，采样拥塞窗口、阈值、在途字节、SRTT、RTO、队列占用及丢弃计数，并报告最长连续交付间隔；`--trace --json` 输出可归档的 JSON。新增停顿诊断：按 `max(2 × SRTT, 1 ms)` 过滤普通包间隔，汇总停顿次数、累计时长和最长时长；每段附带队列非空、恢复事件、发送窗口阻塞三种可重叠观测信号。信号描述区间内观察到的状态，不声称单一因果。多流轨迹暂未覆盖。

- **已缓解：Reno 队尾丢包造成的长停顿。** 丢包后若 ACK 时钟停止，发送端会在 RTO 前按两倍 SRTT 发一个尾部探测（不超过当前 RTO）；若探测得到的 ACK 表明后续数据已到达，就启动已有的 NewReno 恢复。若探测或 ACK 丢失，原有 RTO 仍是兜底。该实现使用当前累计 ACK 模型，不宣称实现完整的 RACK/SACK。
- **验收基准**：`moon run cmd/moonnet -- run scenarios/bufferbloat-shallow.json --cc reno`。同一份 3 MB、seed 9、无线上随机丢包的场景，从 10047.347s / 192 次超时 / 60.021s 最长交付间隔，降到 20.242s / 4 次超时 / 1.020s 最长交付间隔；全量数据交付。库级回归测试同时检查耗时上限、超时上限、尾探测轨迹和重复运行一致性。
- **验收**：合成轨迹分别覆盖三种信号、阈值过滤与累计时长；浅缓冲 Reno 场景断言有停顿记录、3 MB 全量交付，且两次 `--trace --json` 字节级一致。阈值和信号口径写入 API 文档。
- 后续：再评估是否引入 SACK/RACK，进一步识别队尾以外的多段丢失。
- **已交付：自渲染 SVG 图表。** `plot <trace.json>` 读取归档的单流 `--trace --json` 输出并生成独立 SVG；展示拥塞窗口、在途字节、上下行队列，标出重传、超时、快速重传、尾部探测和停顿区间，不引入绘图库。
- **验收**：测试覆盖图表各序列、事件/停顿标注、XML 文本转义、非法输入和重复渲染一致性；CLI 产物可由 XML 解析器读取。

## 后续工作

- 排查 RED 在长时间空闲后持续拒绝新包的问题，保留原场景的确定性并更新报告数据。
- 在现有单流轨迹基础上支持多流轨迹与图表，展示各流与共享队列的变化。
- 将算法对比、缓冲区与队列策略、多流公平性整理成可由 CI 复算的验收案例。
- SACK 与 RACK 需要新的确认和逐段状态模型，作为后续独立阶段评估，不属于当前 `0.1.0` 的交付范围。

## 明确不做

- 通用离散事件仿真框架（生态已有 moonsim / moondes）
- pcap 抓包解析（已有成熟实现）
- 与真实网络协议栈互操作（本项目的目标是把行为讲清楚，不是替代内核）
