Files
dsh_ai1net_server/归档/交接单-20260924-归档/交接单_最小形态真机批次_20260917.md
T
admin 7bd1151c67 chore(工作区): 归档交接单 27 件 + 新增 CODEBUDDY §9 工作区卫生
交接单按归属约定(正文落文档库、工作区只放指针)归档至
归档/交接单-20260924-归档/,含逐件判定 README:
- 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26)
- 4 件主题已被覆盖网络线入口汇总
- 7 件历史接续包/规划件

CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、
工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
2026-09-24 07:58:57 +08:00

435 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 交接单 · 3–5 台最小形态真机批次(覆盖网络线 · 序 ⑥)
> **线**:覆盖网络线 | **序**:⑥(3–5 台最小形态跑通)| **产出**:规划棒 2026-09-17 09:5x
> **唯一来源**:`覆盖网络_应用场景与待完善清单_20260916.md` **§五 第 6 行**("3–5 台最小形态跑通,把 3 个关键估值换成实测,依赖 2/3/4")+ `参数表_覆盖网络_20260917.md` **§7 待测项汇总** + 同表 **§5.2 校验③**(需 ≥2 台中继)
> **本单要做的事**:把参数表 §7 的 **4 个 `待测` 项 + 1 个待校准推导项**设计成**可执行实测步骤**(每项:怎么测 / 样本多大 / 判据 / 写回哪一行),并**合批**落地 **L3 第二中继机**。
> 🔴 **本单不改架构、不新增传输能力**:性质是「**把估值换成实测**」,不是"做打洞"(理由见 §4.1-1,这是本单最重要的一个已定项)。
---
## §1 目标
> **一句话**:让 `参数表_覆盖网络_20260917.md` 里 **5 个空值单元格全部有实测值或"如实标注取不到"的记录**,并让 **relay 从 1 台变 2 台**(跨机真容灾成立),**同时**按回头条件把 `RELAY_MAX_HOSTS` 重算并重下发到两台中继。
**判定"做完了没有"**:
1. `参数表` 中 `HOLE_PUNCH_RATE_LOCAL` / `PER_PLAYER_BW_LOCAL` / `WAN_STEADY_THROUGHPUT` / `JITTER_LINK_MEASURED` / `MEM_PER_HOST_MB` **五行**的"等级"列都不再是 `待测`(改成 `实测`,或写成 `取不到 + 原因`——⛔ 但**不许编数**);
2. 两台中继机各自的 `/status` 都能看到在册会话,且**杀掉任一台后客户端能自动切到另一台**;
3. §5 S7 的回头条件**已执行或已写明"本轮未触发、因为哪个数没换成实测"**。
---
## §2 只读前置(⛔ 只读,不改;P1–P8 逐条核实后才允许进 S 段)
| # | 命令 | 期望输出 / 判据 |
|---|---|---|
| **P1** | `bash "D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh" --claim-exec "<你的会话名>"` | `✓ 已持全局执行锁`。**抢不到 = 有会话在跑 ⇒ 只报告并立刻停** |
| **P2** | `"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "E:/ProgramData/AI技能/aliyun-dsh-server/state.py"` | `[锁] 🔴 被占用`(owner = 你)|HEAD = `640813e`|`[入口] 接续入口_覆盖网络线_20260916.md` |
| **P3** | `cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md \| md5sum` | **`f3e68012698abb352549e2560746d992`**。⚠️ 不等 ⇒ 参数表已被改过,先去 §7 确认 `待测` 计数,再按实际情况调整本单行号 |
| **P4** | `ssh -p 22 bt-server 'systemctl cat dshs-relay \| head -25; echo ---; cat /etc/systemd/system/dshs-relay.service.d/capacity.conf 2>&1'` | 期望看到 **`--max-hosts 225`**(序⑤ 已下发;⚠️ 记忆铁律:主单元 ExecStart **已有显式 `--max-hosts 0`**,**CLI 优先于 `Environment=`** ⇒ 只设 env 会被静默忽略)。⛔ 若未下发 ⇒ **停下报告**,不要边补边测 |
| **P5** | `ssh -p 22 test106 'hostname; nproc; free -m \| head -2; systemctl is-active nginx dshs-relay 2>&1; ss -lntp \| grep -E ":(443\|80\|20080)\b"; ls -d /opt/dsh-relay /opt/dshs-cluster 2>&1'` | 记录:106 有无 nginx / 443 是否被占 / 是否已有 `/opt/dsh-relay`。**这是 S8 的唯一分叉判据**(有 nginx ⇒ 复用 443,零新增口;无 ⇒ 按 §4.2-2 自决) |
| **P6** | `"E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3/node.exe" -v && ls "D:/github/dsh_shenxian/lib/net/relay/" \| head` | Node 22.x + **`lib/net/relay/*.js` 已 build**。缺 ⇒ 先在代码仓 `npm run build`(Node 22)再继续 |
| **P7** | `ssh -p 22 bt-server 'ss -lntp \| wc -l; nft list ruleset \| wc -l; curl -s -o /dev/null -w "%{http_code}\n" --http1.1 -H "Host: alotbuy.com" 127.0.0.1:3080/; curl -s 127.0.0.1:20080/status \| head -c 400'` | **不退化对照基线**:`79` / `72` / `200` / `capacity:{max:225,used:N}`。⛔ 数与本单不一致 ⇒ 先报告差异,别当成 bug 顺手修 |
| **P8** | `cd "E:/ProgramData/AI技能/aliyun-dsh-server" && "E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3/node.exe" "D:/github/dsh_shenxian/scripts/overlay-probe.cjs"` | 12 行 + 退出码 `0/1/2`。**FAIL 清单原样记下**(S9 要与它逐项对照) |
> 🔑 **P1–P8 全部只读**。任何一条需要写动作 ⇒ 说明你走错了,停下回看 §3。
---
## §3 范围
### 3.1 要改的(且只有这些)
| # | 对象 | 性质 |
|---|---|---|
| 1 | 工作区根 `参数表_覆盖网络_20260917.md` | **只改值 + 等级 + 指纹**(§3.2/§3.3/§3.5/§5.1/§5.2/§7/§10),⛔ 不改结构、不新增键 |
| 2 | 代码仓 `scripts/` **新增**一次性脚本(nat-probe / loadgen / bw-probe) | **不进 `src/`、不进产品路径**;判据 = `git status` 里 `src/**` 零改动 |
| 3 | 47 上 `dshs-relay.service.d/capacity.conf` | **仅在 S7 触发时重写**(drop-in 重写 ExecStart) |
| 4 | 106 上新增 `dshs-relay` 单元 + `/opt/dsh-relay/`(build 后 scp) + 节点密钥 | S8(L3 第二中继机) |
| 5 | 47 / 106 的 seeds 广播(`DSHS_OVERLAY_BOOTSTRAP_SEEDS` 或签名目录 `relays[]`) | 加入第二中继项 |
| 6 | 本单自身 §8 执行回报 + 工作区日志 | 收口 |
### 3.2 ⛔ 不动什么(防顺手扩大)
- ⛔ **不做打洞实现**(不在 `src/net/relay/` 加 UDP/dgram/STUN)—— 见 §4.1-1;
- ⛔ **不做 presence / 房间层 / 内容分发 / 游戏服**(`清单 §五` 第 7 步,本单毫不触碰);
- ⛔ **不引入第三方 relay 或把 STUN 库写进产品依赖**(探测脚本可用公网 STUN 作**降级手段**,但不得进 `package.json`);
- ⛔ **不重做序 ②/③/④/⑤ 的任何一步**;
- ⛔ **不改 `dshs` / `dshs-relay` 主单元的既有参数**(只经 drop-in);
- ⛔ **不在 106 上开非 443 的新公网口**(安全组不动);
- ⛔ **不 commit / 不 push**(本线纪律;本单只改工作区与服务器运行态)。
---
## §4 决策点
### 4.1 已定项(规划棒已拍 —— 执行棒**不得自行更改**;要改必须回写 §8 并说明理由)
1. 🔴 **本单只做「把估值换成实测」,不做打洞实现** —— 依据:① `清单 §五 第 6 行`原文是"把 3 个关键估值换成实测",不是"新增传输能力";② **实测确认**:`src/` 全仓**零 UDP/NAT 穿透代码**(`grep -iE "dgram|createSocket|stun|punch|udp" src/` 的命中**全部是 `signature` / `native` 假阳性**),`directory.ts:404-421` 只有 CGNAT 地址**判定**;③ 引入打洞 = **净新增 UDP 入站面**(命中 R5)+需要 STUN/信令设计,属**独立方案**,不是本序的交付物。
⇒ **因此 `HOLE_PUNCH_RATE_LOCAL` 的口径定为:测「这台设备所在网络**能不能**打洞(NAT 映射/过滤行为)」,⛔ 不是"我系统打洞成功率"**(后者需要先有实现)。**测到的数仍然可用**:它决定"打洞实现值不值得做"(若可打洞比例低 ⇒ 这项能力可以直接不做)。
2. **`--max-hosts` 的分母 = 单台中继**(参数表 §5.3 已写死);`FLEET_RELAY_DEMAND`(450)**只作校验上界**。
3. **L3 第二中继机 = 106 升格**(依据:`清单 §3 关键决定`已定"**有公网 IP 的节点自动升格为中继候选**"+参数表 §5.2 校验③"需 ≥2 台")。⛔ 不新建机器、不启用"另开一台云主机"这条路(花钱属边界外,且 §4.3 列着)。
4. **2 台就落地 2 台**:`225 × 2 = 450 = FLEET_RELAY_DEMAND` ⇒ **恰好达标、零余量**。⚠️ **必须把"零余量"这个事实写进参数表**,并登记第三台的**触发条件**(方案里不得出现"余量充足"这类不实表述)。
5. **打洞探测的观察面必须是我们自己的机器**(首选:47 上一次性 UDP 观察器,测完即停),⛔ 不优先依赖公网 STUN(降级手段,且须在 §8 标注"经第三方")。
6. **五项的判定口径一律"实测优先、取不到如实记"**:⛔ 任何一项**都不许用估值顶替**;取不到就写"取不到 + 卡在哪 + 什么条件一出现必须回头"。
### 4.2 交给执行棒自决(⛔ **不上升为提问**)
1. `WAN_STEADY_THROUGHPUT` **怎么造可控载荷**(本单给判定顺序:① 复用既有端点声明机制新增一个临时端口 → ② 复用已声明但空闲的端口 → ③ 降级为"经 relay 的实例面链路吞吐"并如实标注口径);
2. 106 上 relay 的**绑定方式**(P5 探明后按序:**有 nginx ⇒ 复用 443,零新增口** → 无 nginx ⇒ 装 nginx 复用 443(标准组件、不新增监听)→ 都不可行 ⇒ 停下报告,⛔ 不许开新口);
3. 合成节点的**工程实现**(脚本落 `scripts/`,用 Node 22 内置能力,不引依赖);
4. 样本点的具体分布(⚠️ 本单给的是**下限**,允许加密);
5. 所有输出文件落在 `_中间产物_待清理/` 下的临时目录,收口时**只保留正式产物**(任务收尾纪律)。
### 4.3 真需要用户拍板的(**命中才问,且一轮只问这一句**)
**第 4/5 台真机的来源** —— 本单能用现有资源跑满 3 台(47 / 106 / 本机),**打洞分层样本需要"家宽 / CGNAT / 移动 / 企业网"四类环境,云主机给不出**。候选(各有优有劣 ⇒ 才上抛):
**A · 只用现有 3 台先跑满能跑的部分** —— 优点:零成本、零等待、今天就能开工;缺点:打洞率的**样本偏差大**(3 台里 2 台是云),该项大概率只能拿"环境可打洞性"的**定性**结论,写不回一个可信的百分数。
**B · 由用户自备设备(手机热点 / 家里的宽带上的一台机器)跑一次性探测脚本** —— 优点:样本最贴近真实目标场景("同一人的几台设备")、零花费、脚本是一次性的(跑完给 JSON 即可);缺点:需要用户动手,且会占用用户设备一小段时间。
**C · 新开 1–2 台轻量云主机(跨运营商 / 跨地域)** —— 优点:样本可控可复现、随时可扩到 5 台;缺点:**要花钱**,且云主机的 NAT 行为与家宽/CGNAT 仍不同构(花了钱也解决不了分层问题)。
**我的倾向:A 立即开工 + B 并行补样本,C 不动**(理由:C 花了钱还解决不了主要的样本偏差;A+B 组合零成本且能覆盖真实场景)。⇒ **执行棒按 A 开工,不必等这一刻**(B 是"什么时候给什么时候补")。
### 4.4 技术实现裁决顺序(⚠️ 与 `dsh-decision-method §4.4` 一致)
先取"**不改结构的**方案"→ 再取"**改一处、可回滚的**" → 最后才考虑"新增面/新增依赖的"。⛔ 不允许用"新引入一个组件"来绕过取证。
---
## §5 步骤(S0–S9;**每步自带一次可执行的验证**)
### S0 · 只读取证(= §2 P1–P8)
**验证**:P1–P8 全部有原文输出,且 P3 指纹、P7 基线、P8 的 FAIL 清单已落 §8.1。
---
### S1 · 组起 3 台节点(最小形态第一步)
**做**:47(Manager+relay+w-47)/106(Worker w-106)/**本机(第三节点)**。
本机节点 = 一个一次性进程(脚本,只拨出,不声明端口或只声明 1 个 echo 口),用于提供"非云的第 3 个网络视角"。
**验证**:47 上 `curl -s 127.0.0.1:20080/status` 的 `online[]` / `sessions[]` **长度从 2 变 3**(新增本机节点),且本机节点 `via` 非空。
**判据**:3 台全部在册 ⇒ S2 起可测;若本机节点起不来(Windows 侧限制)⇒ **如实记"3 台降为 2 台"**,S2/S3 照常跑(照 2 台口径标注),⛔ 不编第 3 台的数。
---
### S2 · `WAN_STEADY_THROUGHPUT` 实测(写回参数表 **§3.5**)
**为什么这步第一优先**:它是 §5.4 明确"当前取不到、必须回来重算"的那个数,且是 §5.2 的**潜在绑定约束**。
**怎么测**:两端可控载荷。判定顺序(§4.2-1):
1. **首选**:在 106 上把一次性 HTTP 大响应服务绑在 `127.0.0.1:<PORT>`,**经既有端点声明机制**(relay `PORT_ADD`)把它纳入 relay 映射;在 47 上经 relay 拉取。
2. **次选**:复用已声明但空闲的端口(P4 的 `/status.endpoints[]` 里挑一个不在用的)。
3. **兜底**:直接测"经 relay 的实例面链路"的稳态速率,**口径如实写成"经 relay 转发的实例面吞吐"**,⛔ 不得冒充裸链路吞吐。
**样本**:**≥ 5 次**,每次 ≥ 30 s 稳态段(去掉前 3 s 建连/爬升),载荷 ≥ 8 MB;记录 `bytes` / `sec` / 速率。
**判据**:报**中位数**(⛔ 不用峰值、不用单次最好值);同时报 min/max 说明离散度。
**写回**:`参数表 §3.5` → `WAN_STEADY_THROUGHPUT`,**等级改 `实测`**,**来源定位写"本单 §8.x + 完整命令"**,并在**同一行的备注里写清口径**(谁到谁 / 是否经 relay / 稳态段长度)。
---
### S3 · `JITTER_LINK_MEASURED` 实测(写回参数表 **§3.5**)+ **relay RTT 口径校验**
**做两件事**(不要只做第一件):
**(a) 链路 jitter 实测**
- 命令形态:`ssh -p 22 bt-server 'ping -c 300 -i 0.2 -W 1 <106 公网 IP>'`(反向再做一次)。
- 样本:**≥ 200 包**(300 包余量更稳)。
- 取数:`rtt min/avg/max/mdev` + **自算 `p95(|ΔRTT|)`**(相邻包 RTT 差的 95 分位)——⛔ 只用 `mdev` 会低估抖动。
- **判据**:与参数表 `JITTER_LIMIT_MS`(20 ms,**估值口径**)对照给出**达标/不达标**二元结论。⚠️ 预期**不达标**(同链路 relay 路径 RTT 已实测 336 ms)——**如实写"不达标"**,并按 §5(a) 的下一条处理。
**(b) 🔴 relay `rttMs` 口径校验(本单新增的关键验证)**
- 参数表把 `RELAY_RTT_W106 = 336 ms` 标成"实测",但**它是 relay 心跳往返**(`server.ts:810` 注释:①②③ = 测 RTT / 察觉半开 / 保 NAT 表项),**可能含应用层处理与验签耗时**,**不一定等于网络 RTT**。
- ⇒ **三方对比**:`ICMP RTT`(ping)|`TCP 握手 RTT`(`ssh -p 22`或到 443 的 `curl -w %{time_connect}`)|`relay 心跳 rttMs`。
- **判据**:若 relay `rttMs` 与 ICMP RTT **差距 > 2×** ⇒ 在 `参数表 §3.5` 给 `RELAY_RTT_W106` **加一条口径备注**("心跳往返,含应用层,⛔ 不等于网络 RTT"),并在 `OBS` 侧登记"relay rttMs 不得当链路 RTT 用"。
- ⚠️ 这条**不是可选项** —— 336 ms 目前是"跨云链路很差"的唯一证据,若它其实是口径问题,后面所有关于"跨云不可玩"的结论都要重判。
**写回**:`参数表 §3.5` → `JITTER_LINK_MEASURED`(等级改 `实测`;值 = 中位数 + p95 两个数,注明取哪个作为判定值);`RELAY_RTT_W106` 加口径备注。
---
### S4 · `HOLE_PUNCH_RATE_LOCAL` 实测(写回参数表 **§3.2**)
> 🔴 **口径见 §4.1-1**:测的是「**该网络能不能打洞**」,⛔ 不是"本系统打洞成功率"。
**首选路径(不依赖第三方)**:
1. 47 上起**一次性 UDP 观察器**(脚本,绑定一个高位口,**仅测期监听、测完立即停**):收到包即回显 `{from: <对端源 ip:port>, mapping: <看到的源地址>}`。
2. 各节点向它发 5 包 ⇒ 拿到**本节点的 UDP 公网映射**。
3. 节点两两互发:双方各自向对方映射每 200 ms 发 1 包、共 10 包,同时收包。
4. **判定**:任一方收到对方 ≥ 1 包 ⇒ 该对"**可打洞**"。
5. 逐对记录,并**标注两侧网络类型**(家宽 / CGNAT / 移动 / 企业网 / 云)。
**降级路径**:47 的 UDP 入站不可达 ⇒ 用公网 STUN 做映射/过滤行为判定(RFC 3489 简化版:同服务器不同端口 + 不同服务器对比映射是否变化);⛔ **须在 §8 注明"经第三方"**。
**兜底**:两者都取不到 ⇒ **如实写"取不到"** + 卡在哪 + 回头条件(拿到非云环境即可补测)。
**样本**:3 台 ⇒ **3 对**;若第 4/5 台到位 ⇒ **10 对**(每对 10 次尝试)。**打洞率 = 成功对次 / 总尝试次**,并**分层分别给**(⛔ 不给单一的合并百分数就完事)。
**写回**:`参数表 §3.2` → `HOLE_PUNCH_RATE_LOCAL`,**值列写"分层结果"**,等级改 `实测`,注明**样本量**("n=3 对,云节点占 2/3,**不代表家宽场景**"这句话必须写进去)。
**权限附注**:47 上的 UDP 观察口 = **临时入站面** ⇒ 在 §8 **显式列出**(对象 / 端口 / 开放时长 / 关闭证据),并回填 `参数表 §8`(新增一行,结论栏按实际)。
---
### S5 · `PER_PLAYER_BW_LOCAL` 实测(写回参数表 **§3.3**)
**为什么不能"直接测每玩家带宽"**:仓库里**没有游戏/应用层**(`清单 §五` 第 7 步才谈内容分发与游戏)⇒ 没有真实玩家协议可测。**如实处理**:
**测法**:合成玩家载荷 —— 参数扫描(消息率 **5 / 20 / 50 msg/s** × 消息 **200 B** × 玩家数 **10 / 50**),在真机间经 relay 跑 **60 s**。
**取数**:每档的**端到端 p50/p95 单向时延**、**丢包率**、**实际吞吐(KB/s)**。
**判据**:`PER_PLAYER_BW_LOCAL` = "**在 p95 时延 ≤ 2× p50 且丢包 = 0 的前提下,每玩家可达的最大上行速率(KB/s)**"。
**写回**:`参数表 §3.3`,等级改 `实测`,**并在该行备注里明确写出边界**:「本值 = **传输层上限**;**游戏协议的真实需求仍是估值**(`PER_PLAYER_BW_TEXT/BATTLE/SIEGE`)」——⛔ 不得让读者误以为这是"实测出的游戏需求"。
---
### S6 · `MEM_PER_HOST_MB` 校准(写回参数表 **§5.1**)
**为什么必须放大测**:现网 `used = 2`,RSS 只反映 Node 基座(参数表 §5.1 已注明)⇒ **2 MB/台 是推导值,不是实测**。
**怎么测(干净方案,⛔ 不污染生产)**:
1. **本机起一个独立 relay 实例**(高位回环口,**独立于生产的 20080**);
2. 起 **N 个合成 client**(一次性脚本)连它,每个声明 1–2 个端口;
3. 量 relay 进程 RSS。
**样本点**:`N = 2 / 10 / 25 / 50 / 100`(⚠️ 这是下限,允许加密)。
**判据**:① 各点记录 RSS;② **线性回归** `RSS(N) = a + b·N`,`R² ≥ 0.9` 方为有效(否则说明有非线性跳跃,须找出跳点并**在 §8 如实报告**,⛔ 不许硬套斜率);③ **`MEM_PER_HOST_MB` = b(KB/台 → MB/台,向上取整 + 20% 余量)**。
**写回**:`参数表 §6` → `RELAY_RSS_MAX_KB` 哨兵重新核算(若新斜率与 2 MB 差异 > 50% ⇒ 哨兵必须跟着改);`参数表 §5.1` → `MEM_PER_HOST_MB` 等级改 **`实测`**,来源定位写"本单 §8.x(N=2..100 斜率)"。
**权限附注**:纯本机回环、零公网面 ⇒ ⛔ 不动 47 的任何配置。
**顺带(同机合批,不额外开步)**:参数表 **§9 第 4 行**(relay 无 `MemoryMax`)——**只测量、只登记**,⛔ 本单**不改单元语义**(加 cgroup 上限会引入 OOM-kill 新失败模式,属 R11 的"净变差"风险)。
---
### S7 · 🔴 回头条件强制执行(**本单写死的硬门**)
在 S2 / S6 出数之后**立即执行**(⛔ 不允许"下次再说"):
**触发条件**:`MEM_PER_HOST_MB` **或** `WAN_STEADY_THROUGHPUT` 从"推导/待测"换成"实测"。
**必做七件**(逐条落到 §8):
1. 重算 `C_MEM = floor(MEM_BUDGET_MB / MEM_PER_HOST_MB_new)`;
2. 重算 `C_RELAY = min(C_MEM, C_FD)`;**并重新判断带宽是否仍不参与 `min`**(若新实测吞吐使 225 台的控制面+实例面流量逼近实测吞吐 ⇒ **带宽进 `min`**,这是 §5.4 预留的口子);
3. 重算 `RELAY_MAX_HOSTS = floor(C_RELAY × DESIGN_MARGIN)`;
4. **重跑 §5.2 三条校验**(防自锁 `> used×4` / 余量自洽 / 千台需求 `≤ 225 × 2 = 450`);
5. **重下发**:47 上**重写 drop-in `capacity.conf`**(⚠️ **必须重写 ExecStart 而不是只加 `Environment=`** —— 主单元已有显式 `--max-hosts 0`,**CLI 优先于 env**,只设 env 会被静默忽略)→ `daemon-reload` → `restart dshs-relay`;**106 上的第二中继同值同步**;
6. **复验**:`overlay-probe.cjs` 的 `OBS-02`(`max = RELAY_MAX_HOSTS` 且 `free = max - used`)必须 PASS;
7. **回写**:`参数表 §5.2`(含**变更前后对照**:旧值 → 新值 → 为什么变)+ `§10 指纹`+工作区日志记一笔"哪个键从什么换成什么"。
**若未触发**(某项确实没换成实测)⇒ 在 §8 **明写"未触发,因为 X 没换成实测,卡点是 Y,回头条件是 Z"** —— ⛔ 不许默认跳过。
---
### S8 · L3 第二中继机落地(106 升格,**与上面合批**)
**依据**:`清单 §3 关键决定`(有公网 IP 的节点自动升格中继候选)+ 参数表 **§5.2 校验③**(需 ≥2 台)。
**顺序**:
1. **权限影响评估更新(先做,R5)**:把 `参数表 §8` 第 ④ 行从"只评估不实施"改为**实施态**,逐项写明:**新增监听口(几个、哪个、公网还是回环)** / **新增凭据(节点密钥签发)** / **是否改变 106 "入站 = 0" 这条已收窄成果**。⛔ 只能用「收窄 / 维持」二选一作结论;**确有扩大 ⇒ 在 §8 逐条列出**(陈述式,不是征询)。
2. **铺 relay 到 106**:本机 `npm run build`(Node 22)→ scp 到 106 的 `/opt/dsh-relay/`(⚠️ 与本线既定纪律一致:**本机 build 后 scp**;relay 代码真身在 `/opt/dsh-relay/lib/`,⛔ 不是 `/opt/dshs/lib/`)。
3. **签发 106 的 relay 身份**:用既有密钥仪式 CLI `scripts/overlay-keyring.cjs`(序③ 产物);relay 侧 keys 表用**逻辑名 `<net>/<hostId>`** 索引。
4. **绑定方式**:按 §4.2-2 判定顺序(**首选复用既有 443**,零新增口)。
5. **加入 seeds 广播**:把第二中继项写进 `DSHS_OVERLAY_BOOTSTRAP_SEEDS` **或** 签名目录 `relays[]`(⚠️「改一次 seeds 全网刷新」是既定机制;注意 `relays[]` **顺序语义 = 主入口首位**,⛔ 第二中继**不得**插到首位)。
6. **两台各自设 `--max-hosts`**(值 = S7 重算结果;若 S7 未触发则沿用 225)。
7. **端到端验收(见 §6 E9)**。
**硬约束**:⛔ 不新开非 443 公网口;⛔ 不改安全组;⛔ 不动 47 的角色。
---
### S9 · 回填 + 不退化 + 收口
1. **参数表回填**:§3.2 / §3.3 / §3.5 / §5.1 / §5.2 / §7(`待测` 行清空或写明取不到)/ §8 / §10 指纹。
2. **不退化检查**(逐条对照 S0 的 P7/P8):`ss -lntp | wc -l`、`nft list ruleset | wc -l`、门户 `200`、双实例面、`npm test`(**Node 22**)。
3. **临时产物清理**:探测/压测脚本与中间输出按 §4.2-5 处理,**只保留正式产物**。
4. **收口三件(⛔ 缺一即算本条未完成)**:
① `--release-exec` 释放锁;
② **登记下一棒**(序 ⑥ 执行棒的后续 / 或按实际收口点到的一棒)并**用陈述句在回复里告知用户**("已登记自动接续,约 N 分钟后自动开新会话,不用你操作;接续点 = X");
③ 写工作区日志 `.workbuddy/memory/2026-09-17.md`。
---
## §6 验收(判据清单;命令 + 期望输出,可被第三方复现)
| # | 判据 | 期望 |
|---|---|---|
| **E1** | 参数表 `待测` 单元格数量 | **4 → 0**(全部换成实测 **或** "取不到 + 原因 + 回头条件");`MEM_PER_HOST_MB` 等级 `推导 → 实测` |
| **E2** | 每个新填值的"来源等级" | 全部为 `实测`,且每行都有**一条能跑的命令**(复现入口) |
| **E3** | 参数表 §10 指纹 | 已更新,且新值 + 旧值都记在 §8 |
| **E4** | `WAN_STEADY_THROUGHPUT` | ≥ 5 样本 + 中位数 + **口径三要素**(谁到谁 / 是否经 relay / 稳态段长度) |
| **E5** | `JITTER_LINK_MEASURED` | ≥ 200 包 + `p95(|ΔRTT|)` + **ICMP / TCP / relay 三方对比表** |
| **E6** | 打洞探测 | 逐对结果 + **分层标注** + 样本量 + "不代表家宽场景"这句在表内 |
| **E7** | `MEM_PER_HOST_MB` | `N = 2/10/25/50/100` 五点 + 斜率 + `R²`;非线性则如实报告跳点 |
| **E8** | 🔴 **回头条件已执行** | §5.2 三条校验**重跑记录** + 两台 `capacity.conf` 重下发证据 + `OBS-02` PASS(**或**明写"未触发 + 卡点 + 回头条件") |
| **E9** | 第二中继可用 | 两台 relay 各自 `/status` 有在册会话;**杀掉任一台 ⇒ 客户端在 `DIRECTORY_REFRESH_SECONDS`(300 s)内、实测应在 15 s 心跳级切到另一台**(附日志片段) |
| **E10** | 暴露面 | `ss -lntp` / `nft` 行数**与 S0 一致**(或新增项**逐条列出** + 参数表 §8 已回填) |
| **E11** | 不退化 | `npm test` 全绿(Node 22)|双实例面 200/401(∈ `PROBE_CODE_SET`)|门户 `200` |
| **E12** | 收口三件 | 锁已释放 + 下一棒已登记**并已陈述句告知** + 日志已写 |
---
## §7 回滚
| 对象 | 回滚动作 | 粒度 |
|---|---|---|
| 47 的 `--max-hosts` | 删 drop-in `capacity.conf` → `daemon-reload` → `restart dshs-relay`(回 `--max-hosts 0` = 不设限) | 秒级 |
| 106 第二中继 | `systemctl stop/disable dshs-relay`(106)+ 从 seeds / `relays[]` **移除**该项(改一次全网刷新)+ keyring CLI **吊销**该节点密钥 | 分钟级 |
| 打洞/压测脚本 | 全在 `scripts/`,**删文件即可**(`src/**` 零改动 ⇒ 产品路径零回滚需求) | 秒级 |
| 本机独立 relay | 进程退出即消失(无持久化、无开机自启) | 秒级 |
| 参数表 | 保留改动前后的**值对照表**(§8 内),可反向还原;⛔ 不做整文件覆盖式还原(指纹自指,易错) | — |
**备份要求(动手前)**:47 `/opt/dsh-relay/` 铺前打包;`/etc/systemd/system/dshs-relay.service.d/*.bak-<step>-<ts>`;`/etc/dshs/relay-keys.json.bak-<ts>`;106 侧同理(首铺无旧件 ⇒ 记录"无旧件")。
---
## §8 回报格式(执行棒按此格式收口;沿用序⑤ 单的分节)
```
## §8 执行回报(执行棒 · 2026-09-17 10:12 → 11:05)
> **证据等级标记(本单统一口径)**:`【实测】`= 本轮现场跑出来的;`【留档缺口】`= 当时未单独留存、只有结论(**不补造**)。
### 8.1 S0 快照(P1–P8:命令原文 + 原文输出 + 判定)
| # | 命令(原文) | 记录 | 判定 |
|---|---|---|---|
| **P1** | `bash …/handoff-guard.sh --claim-exec "覆盖网络线-序6执行棒"` | `✓ 已持全局执行锁`;收口前复核 OWNER = `覆盖网络线-序6执行棒`,起始 `09-17 10:12` | ✅ |
| **P2** | `python state.py` | 锁被占用(owner = 我)|HEAD = `640813e`|入口 = `接续入口_覆盖网络线_20260916.md` | ✅ |
| **P3** | `sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md \| md5sum` | **`f3e68012698abb352549e2560746d992`** —— 与单内期望**逐字一致** ⇒ 单内行号/§7 计数有效,未偏航 | ✅ |
| **P4** | `ssh bt-server 'systemctl cat dshs-relay …; cat …/capacity.conf'` | 47 relay drop-in 已带 **`--max-hosts 225`**(序⑤ 下发);主单元 ExecStart 确有显式 `--max-hosts 0` | ✅ |
| **P5** | `ssh test106 'hostname; nproc; free; systemctl is-active nginx dshs-relay; ss; ls -d /opt/dsh-relay /opt/dshs-cluster'` | 106 有 nginx —— **但由宝塔托管**(master = `/www/server/nginx/sbin/nginx`,属 `bt.service`)⇒ `systemctl is-active nginx` = `inactive` **是正常态、不是故障**(收口时复核同值);443 已被占 ⇒ **S8 走"复用 443"分支,零新增公网口**;`/opt/dsh-relay` **不存在** ⇒ 首铺 | ✅ |
| **P6** | `node -v && ls lib/net/relay/` | `v22.22.2` + `lib/net/relay/*.js` 已 build(未触发补 build) | ✅ |
| **P7** | `ssh bt-server 'ss -lntp\|wc -l; nft list ruleset\|wc -l; curl …3080; curl …20080/status'` | 基线 **`79` / `72` / `200` / `capacity{max:225,used:2}`** —— 与单内期望**完全一致** | ✅ |
| **P8** | `node scripts/overlay-probe.cjs` | 12 行 + 退出码 `1`(有红项)。⚠️ **【留档缺口】S0 时刻的原始 FAIL 清单未单独留存**(首次运行已发生在 S 段推进中)⇒ 见 §8.8-3 | ⚠️ |
### 8.2 五项的实测值(怎么测 / 样本量 / 值 / 等级 / 写回位置)
| 项 | 怎么测(复现入口) | 样本量 | 值 / 等级 | 写回 |
|---|---|---|---|---|
| `HOLE_PUNCH_RATE_LOCAL` | `overlay-holepunch.cjs --stun`(首选"47 双 UDP 观察器"路径**失败**,按 S4 降级走公网 STUN) | **n = 3 对** | **2/2 可打洞**;映射 `125.83.247.110:33742`(两次 STUN 一致 ⇒ **cone 型**)/云机 `:21200`;⛔ 表内已写"云节点占 2/3,**不代表家宽场景**" + **方向性限制**(本机→云机被安全组拦,真打洞须成功后回摆)。等级 = **实测(分层)** | 参数表 §3.2 |
| `PER_PLAYER_BW_LOCAL` | `overlay-wan.cjs --players`(7 档扫描) | 10 / 50 玩家 × 5/20/50/200 msg/s | **9.8 KB/s(10 玩家)|3.9 KB/s(50 玩家)**;聚合天花板 **200–350 KB/s**;崩坏点:50 玩家 @50 msg/s(丢包 91%)、10 玩家 @200 msg/s(丢包 85.6%)。等级 = **实测**(边界写明"传输层上限,游戏需求仍是估值") | 参数表 §3.3 |
| `JITTER_LINK_MEASURED` | `overlay-jitter.cjs --icmp … --count 300 --interval 0.2` + `--tcp …` | ICMP 各 300 包 × 双向 + TCP 30 次握手 | **`p95(|ΔRTT|)` = 3 ms ⇒ 达标**(对 `JITTER_LIMIT_MS = 20`,且 20 是业界估值口径、已标明)。**三方对比**:ICMP 148.29 / TCP 153 / **relay 344–376** ⇒ relay 是真 RTT 的 **2.3×**(故 `RELAY_RTT_W106` 已加口径备注:**心跳往返,含应用层+验签,≠ 网络 RTT**)。等级 = 实测 | 参数表 §3.5 |
| `WAN_STEADY_THROUGHPUT` | `overlay-wan.cjs --serve/--download/--upload`(**尊重反压**:`write()` 返 false 必等 `drain`) | 每向 5 样本取中位数 | **下行 352 KB/s(106→47)|上行 12213 KB/s(47→106)**;口径三要素齐全。**决定性校验**:relay `/status` 计数 `in=1905328505B` 与 106 侧 `rchar=1905380725` 吻合 ⇒ 12 MB/s 确实跨了 WAN;106 公网出带宽封顶 ≈2.8 Mbps 正是下行 352 的成因。`WAN_UP_BOUND_KBPS` **192 → 作废**(下界偏低 64×) | 参数表 §3.5 |
| `MEM_PER_HOST_MB` | `relay-mem-calibrate.mjs`(本机独立 relay 子进程 + 同进程内 N 个 `RelayClient`;RSS 取 7 次采样中位数) | 6 点(N=2/10/25/50/100/150) | **0.06 MB/台**(斜率 46.7 KB/台,**R² = 0.9424**)⇒ `2 → 实测`;原"2 MB/台"**高估 36×**(把 per-stream 256 KB 当成了 per-host)。**首跑 R²=0.487 已作废**(单次采样噪声),修正过程记在 8.5。等级 = 实测 | 参数表 §5.1 |
### 8.3 参数表 diff 摘要(旧值 → 新值)
| 键 | 旧 | 新 |
|---|---|---|
| `HOLE_PUNCH_RATE_LOCAL` | 待测 | 分层实测(2/2,n=3 对,云节点 2/3) |
| `PER_PLAYER_BW_LOCAL` | 待测 | 9.8 / 3.9 KB/s(10/50 玩家) |
| `JITTER_LINK_MEASURED` | 待测 | `p95(|ΔRTT|)` = 3 ms(达标) |
| `WAN_STEADY_THROUGHPUT` | 待测 | 352(106→47)/ 12213(47→106)KB/s |
| `WAN_UP_BOUND_KBPS` | 192 | **作废**(下界偏低 64×) |
| `MEM_PER_HOST_MB` | 推导 2 | **实测 0.06** |
| `C_MEM` / `C_RELAY` | 501 | **16700** |
| `RELAY_MAX_HOSTS` | **225** | **7515** |
| §5.2 校验③ 结论 | `450 > 225` ⇒ 容量上必须 ≥2 台 | **结论变更**:容量上单台即够;2 台依据改为"公网节点升格"+"跨机真容灾" |
| §5.4 带宽判定 | 旧 | 用实测重判"带宽不进 `min`",登记实例面单次 46.3 MB ≈ **135 s 时延上界** |
| §7 待测项计数 | 4 + 1 待校准 | **0 + 0**(回头条件标记"已执行") |
| §8 权限影响 | ④ 单条 | ④ 改**实施态**(新增监听口 0 / 新增凭据 0 / 106 入站仍为 0,**结论维持**)+ 新增 ⑧(临时 UDP 观察口,已关闭)⑨(106 的 443 `location /dshs-relay`)—— 两条**结论均维持** |
| §9 已知边界 | 4 行 | **8 行**(新增:无失败切流 / per-stream 内存未测 / 106 无 bootstrap / 宝塔管 vhost) |
| §10 指纹 | `f3e68012698abb352549e2560746d992` | **`db1317c2f7aaef7b47785c1f4fc9de03`** |
### 8.4 E1–E12 逐条
| # | 现场证据 | 判定 |
|---|---|---|
| **E1** | `grep -c 待测` = 8,**逐条核对全部落在**:§7 标题/计数口径说明/图例行 `\| **待测** \|` —— **数据单元格 0 个**(4 → 0);`MEM_PER_HOST_MB` 等级 `推导 → 实测` | ✅ |
| **E2** | 五个新值全部标 `实测`/`实测(分层)`,每行带一条可跑命令(见 8.2 第 2 列) | ✅ |
| **E3** | 新指纹 `db1317c2…`、旧指纹 `f3e68012…` 均已记(本节 8.3 + 8.9) | ✅ |
| **E4** | 5 样本中位数 + 口径三要素(谁到谁/是否经 relay/稳态段长度)+ relay 侧与 106 侧字节数交叉校验 | ✅ |
| **E5** | ICMP 300 包 × 双向(≥200)+ `p95(\|ΔRTT\|)` + **ICMP/TCP/relay 三方对比表**(并在表内标明 20 ms 是估值口径) | ✅ |
| **E6** | 逐对结果 + **分层标注** + n=3 对 + 表内明写"不代表家宽场景" + 方向性限制 | ✅ |
| **E7** | 六点 `N = 2/10/25/50/100/150` + 斜率 46.7 KB/台 + `R²=0.9424`(非线性/跳点不适用;首跑 R²=0.487 已作废并记因) | ✅ |
| **E8** | 回头条件**已触发**:§5.2 三条校验**全部重跑**(① 防自锁 `7515 > 4×4` ✅ ② 见参数表 ③ `450 ≤ 7515` 结论变更)+ **两台** `capacity.conf` 重下发(47/106 `"max":7515`)+ **`OBS-02` 复验 PASS**(`max=7515 used=2 free=7513`) | ✅ |
| **E9** | 两台 relay 各自 `/status` 可见在册会话(47:`manager`/`w-106`;106:`max=7515` 就绪,443 入口 **WS 101**)。**前半绿**:杀掉 106 后 w-dev 断连并 **1.2 s 内**自动重连回同一台。**后半红**:**不会切到另一台** —— ⇒ 见 §8.8-1 | ⚠️ **半绿** |
| **E10** | 收口后 `ss -lntp \| wc -l` = **79**(= S0 基线)、`nft` = **72**(= 基线);106 = 14 个监听(基线 14 + relay 的 `127.0.0.1:20080`);参数表 §8 已逐条回填(新增 ⑧⑨) | ✅ |
| **E11** | `npm test`(Node 22)= **138 tests / 137 pass / 0 fail / 1 skipped**;双实例面 `本机:20000=401`/`w-106=401`(∈ `PROBE_CODE_SET`);门户 = **200** | ✅ |
| **E12** | 锁 `--release-exec` 已释放;下一棒已登记 automation **并已陈述句告知**;工作区日志已写 | ✅ |
### 8.5 🔴 S7 回头条件(触发与否 + 重算过程 + 重下发 + `OBS-02` 复验)
1. **触发**:`MEM_PER_HOST_MB` 2 → 0.06、`WAN_STEADY_THROUGHPUT` 待测 → 352/12213 ⇒ 命中"实测值替换后必须重算容量"。
2. **重算**:`C_MEM = floor(1002 / 0.06) = 16700` → `C_RELAY = min(16700, 65536) = 16700` → `RELAY_MAX_HOSTS = floor(16700 × 0.45) = 7515`。三条校验重跑,**结论③ 变更**(单台容量即足够)。
3. **重下发(关键坑位)**:主单元 ExecStart **已有显式 `--max-hosts 0`**,**CLI 优先于 `Environment=`** ⇒ 只设 env 会被**静默忽略** ⇒ 两台一律用 **drop-in 重写 ExecStart**:`47-capacity.conf` / (106 同款) → `daemon-reload` → `restart dshs-relay`。
4. **复验**:47 `/status` `"max":7515,"used":2,"free":7513`;106 `/status` `"max":7515`;**`OBS-02` PASS**。
5. **校准方法自纠**:首跑 `R² = 0.4871` 不合格 ⇒ 定性为**单次 RSS 采样噪声** ⇒ 每点改 **7 次采样取中位数** 并补第 6 点(N=150)⇒ `R² = 0.9424`。**首跑结论已作废、未写进参数表**。
### 8.6 第二中继(106)落地 + 切流验证 + 权限影响评估更新版
| 项 | 结果 |
|---|---|
| 落地 | `/opt/dsh-relay/`(build 后 scp)+ 新单元 `dshs-relay`(**只绑 `127.0.0.1:20080`**)+ 节点密钥 `ops/w-106`;**首铺,无旧件** ⇒ §7"记录无旧件"已满足 |
| 443 暴露 | 走 §4.2-2 自决:**复用既有 443**(`location /dshs-relay` 挂在 `include …/extension/106.54.21.172/*.conf` 里 ⇒ 落在既有 server 块**内部**)。实测 **WS 升级握手 = 101**、`/nope` = **404**。**零新增公网口**(安全组未动) |
| seeds 广播 | 47 的 `…-443fb.conf` 把 `https://106.54.21.172/dshs-relay` 追加到 `relays[]` **末位**(不动首位主入口) |
| 切流验证 | **前半绿 / 后半红** —— 详见 §8.8-1(**单外发现,只报告不动手**) |
| 权限影响(§8④ 更新版) | **新增监听口 = 0**(复用 443)|**新增凭据 = 0**(节点密钥落在既有 `/etc/dshs/relay-keys.json`,按 `<net>/<hostId>` 逻辑名索引)|**106 入站 = 0**(worker 永远只拨出)⇒ **R5 结论维持**:暴露面未扩大 |
| 收口清理(S9-3) | 两个临时节点密钥 **已吊销**(`ops/w-dev`、`ops/w-106p`;两台 keys 表 5 → **3 条**,回到基线 `manager`/`w-106`/`w-47`,先备份后原子写、`loadKeysFile` 自校验);探针目录 `/opt/seq6-probe`(47/106)、`/tmp/seq6-*`、106 的 `node-w-106p.*` **均已删**;复核 `pgrep` 零命中 |
### 8.7 不退化(S0 对照 / 双实例面 / 门户 / `npm test`)
| 项 | S0 基线 | 收口 | 判定 |
|---|---|---|---|
| 47 `ss -lntp \| wc -l` | 79 | **79** | ✅ 完全一致 |
| 47 `nft list ruleset \| wc -l` | 72 | **72** | ✅ |
| 门户 | 200 | **200** | ✅ |
| 双实例面 | 200/401 | `本机:20000=401`、`w-106:41775=401` | ✅ ∈ 码集 |
| `npm test`(Node 22) | — | 138 / 137 pass / 0 fail / 1 skip | ✅ |
| `overlay-probe` | 12 行(见 8.1-P8) | **12/12 PASS**(`OBS-02` `OBS-08` `OBS-11` 三红**全部转绿**) | ✅ |
| 47 relay 会话 | `manager` / `w-106` | 同(重启后自动重连,`identityOk=2`) | ✅ |
> **三红转绿的根因(诚实记录)**:`OBS-11`/`OBS-08` 的红**不是泄漏**,是 S1/S8 期间**临时探针会话在 relay 内存里留下的 2 条离线端点**(relay 每个 endpoint 会占 1 个本地监听 ⇒ 79 → 81)。S9 清理 + relay 重启后端点表回到 2 条、监听口回到 79。`OBS-02` 的红是**探针解析坑**(见 8.8-2),修的是**参数表书写**,不是改脚本。
### 8.8 未过项 / 遗留
1. 🔴 **E9 后半(无"失败切流")—— 本单唯一未过项,根因已定位**:客户端把 relay url **在首次解析后钉死** —— `main.js --client` 无重解析;worker 走 `DSHS_RENDEZVOUS_URL` 同样不吃引导链;只有 Manager 的**拨号通道**有周期重解析(`web/server.ts#refreshOverlay`),而它的换址条件是"**目录里的地址变了**",与"当前 relay 挂了"**无关**。⇒ **已做到哪一步**:第二中继本身可用(101 + 容量就绪 + seeds 已广播)、断连自动重连成立(1.2 s)。**什么条件一出现必须回头解决**:要做**多中继负载分担**或**真容灾切换**时,必须先补"连接失败后重解析 + 排除已失败 relay"这段**新功能**。已登记参数表 §9 第 5 行。
2. ⚠️ **`OBS-02` 假红的解析坑(已修,须防复发)**:探针 `KEY_RE` 取参数表**行内整格**并 `cleanValue`(只剥 `*` / 反引号)⇒ 值格里写 `**7515**(原 225)` 会被当成 `7515(原 225)` ⇒ `NaN` ⇒ 假红。**已把夹注挪出值格**,并在参数表该行写明"值格必须是纯数字"。**回头条件**:以后任何键改值,⛔ 别往值格塞夹注。
3. ⚠️ **【留档缺口】P8 的 S0 原始 FAIL 清单未单独留存**(详见 8.1-P8)。已做到哪一步:收口状态 12/12 PASS 有据可查。**回头条件**:下一棒若仍以探针作对照,**开跑即先存一份原始输出**(`> /tmp/xxx.txt`)。
4. ⚠️ **per-stream 内存开销仍未测**(`MEM_PER_HOST_MB` = 0.06 只是空闲会话斜率):已登记参数表 §9 第 6 行。**回头条件**:`--max-hosts` 若重新收紧,必须先有本数。
5. ⚠️ **106 的 `127.0.0.1:40179` 在收口时已不在监听**(P5 快照里有):非本单所留(本单在 106 只碰 `19777/19778/20080`)⇒ 如实登记,未追查,**亦未顺手修**(R7)。
6. ⚠️ **106 的 `systemctl is-active nginx` = `inactive`**:由宝塔(`bt.service`)托管 nginx,**不是退化**;但**任何"用 systemctl 判 106 nginx 死活"的脚本都会误判** ⇒ 记入运维注意。
### 8.9 指纹(本单收口后的可复现核对口径)
- **参数表**(§10 不计入):
`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum`
⇒ **`db1317c2f7aaef7b47785c1f4fc9de03`**(S0 = `f3e68012698abb352549e2560746d992`)
- **本交接单**(**§8 及其后不计入** —— 本值就在 §8 内,含进去即刻失效):
`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §8 执行回报(执行棒/,$d' 交接单_最小形态真机批次_20260917.md | md5sum`
⇒ **`1edde731eba5034c5f6f3a43864e5a8e`**
- ⚠️ §10 原口径(整文件不计 §10)**无法内嵌数值**(自指),故本单改用上面这条**前缀口径**。
```
---
## 附 A · 规划棒已核实的事实(执行棒**不必重复探索**)
1. 🔴 **`src/` 全仓零 UDP / NAT 穿透代码** —— `grep -iE "dgram|createSocket|stun|punch|udp" src/` 的命中**全部**是 `signature` / `native` / `alternative` 之类**假阳性**;`src/net/relay/directory.ts:404-421` 只有 CGNAT 地址**判定函数**(`100.64.0.0/10`)。⇒ 本单不测"系统打洞成功率"(§4.1-1)。
2. **参数表 §7 计数已核**:`待测` **4 个**(`HOLE_PUNCH_RATE_LOCAL` / `PER_PLAYER_BW_LOCAL` / `WAN_STEADY_THROUGHPUT` / `JITTER_LINK_MEASURED`)+ **待校准推导 1 个**(`MEM_PER_HOST_MB`)。
3. **参数表 §5.2 校验③ 已算出**:`FLEET_RELAY_DEMAND = 450 > RELAY_MAX_HOSTS = 225` ⇒ **≥2 台中继**;`225 × 2 = 450` ⇒ **恰好达标、零余量**(§4.1-4 要求把这个事实写进表)。
4. **`--max-hosts` 的落点陷阱**:主单元 ExecStart **已有显式 `--max-hosts 0`**,**CLI 优先于 `Environment=`** ⇒ 只设 env 会被**静默忽略**,**必须 drop-in 重写 ExecStart**(S7-5 已写死)。
5. **现役只有 1 台中继**(47);`relay` 只绑 `127.0.0.1:20080`,经 nginx 443 暴露(origin + CF 双路 101)。
6. **`relay` 的 `rttMs` 是心跳往返**(`server.ts:810` 注释写明三个作用)⇒ **不一定等于网络 RTT**(S3(b) 要求做三方校验)。
7. **`JITTER_LIMIT_MS = 20 ms` 是估值口径**(业界),不是实测 ⇒ 与实测对比时**必须标明这一点**。
8. **`PER_PLAYER_BW_*` 三行全部是估值**(0.5 / 2–5 / 10–20 KB/s,来源为同一份调研文档)⇒ 本单测的是**传输层上限**,不是游戏协议需求(S5 已写死边界)。
## 附 B · 硬约束复述(防走偏)
- **提问判据**:技术实现(怎么造载荷 / 怎么绑 443 / 脚本怎么写 / 样本怎么分布)**一律自决**;**只有 §4.3 一项**属真取舍,且**执行棒不必等**(按倾向 A 开工)。
- **只做被明确要求的事**:执行中发现的其他缺陷(如既有 502 / 引导链问题)**先报告,不顺手改**。
- **成本纪律**:批量活**先写脚本再让脚本跑**,⛔ 不把"大范围取证"派给无人值守会话。
- **红线**:R5(权限只准收窄;扩大必须出评估)|R7(不做未授权批量写入;本机是生产的前身)|R11(任一维度净变差即停)。
- **收口**:锁必须释放;下一棒必须登记**并用陈述句告知**;日志必须写。
---
## §10 指纹
- **本节口径**(推荐核对用,可复现):**整个 §10 不计入** ⇒
`cd "E:/ProgramData/AI技能/aliyun-dsh-server" && sed '/^## §10 指纹/,$d' 交接单_最小形态真机批次_20260917.md | md5sum` ⇒ 见 §8.9 回填。
- **全文件 md5**:请现取(⛔ 本行故意不内嵌数值 —— 包含本节自身,写进去即刻失效)。
---
## §11 补记(2026-09-17 11:1x,**在指纹口径之外**)
- ✅ **§4.3 的唯一待拍板项已闭环**:用户原话「**1 本机内存大 可以模拟多台**」⇒ 选 **D · 本机模拟多台**,放弃候选 B(自备设备)与 C(新开云主机)。⭐ **本条覆盖 §4.3 里"才上抛 / 倾向 A+B / 执行棒按 A 开工"的表述**。
- 本机实测:总内存 **47.6 GB** / 空闲 27.4 GB / **32 核**;relay 单实例 ≈ **48 MB** ⇒ 可模拟数十台。⚠️ 局限:多实例**共用同一出口 IP** ⇒ 对"切流逻辑"够用,对"家宽 / 运营商 NAT 差异"**无增量**(§4.3 里"云主机给不出分层样本"的判断同样适用于本机多实例)。
- 落到本单:**"第 4/5 台"改由本机多实例承担**(不再等新真机);§5 样本口径里"若第 4/5 台到位 ⇒ 10 对"按此理解执行。
- 🔒 本补记位于 §10 之后 ⇒ **§8.9 回填的前缀指纹与 §10 口径均不变**。