# 雷达项目交接与运维文档

版本：2026-10-31

本文档用于项目接手、日常运行、版本发布、故障排查和回滚。文档不记录密码、卡密、令牌或私钥。生产主机、账号和真实连接信息统一使用占位符表示。

## 1. 当前结论

项目当前已经从“能跑”推进到“可持续维护”的阶段，但仍需要持续实战验收。

当前已完成：

- 解析器源码统一在 `node/`，不再依赖部署时临时字符串补丁。
- 线路模块由 `lanes/` 负责打包、检查、安装和回滚。
- 本地真实对局回放通过。
- 身份翻转和坐标漂移修复已经同步线上。
- 线上 `radar-node` 重启后恢复 `active`，线路 A/B 已重新连接。
- 线上同步前后文件哈希已核对一致，远端保留了回滚副本。

当前仍需实战确认：

- 多账号同时在线时，一个账号的解析异常是否完全隔离。
- 未知玩家在不同战局、重连、角色重建后的绑定完整性。
- 卡顿、丢包、解析延迟在高并发时是否超过运营阈值。
- 武器、干员、队伍信息在所有角色状态下是否持续稳定。

## 2. 系统结构

### 2.1 数据链路

```text
客户端
  ↓
radar-node / main.py
  ├─ SOCKS5 接入与账号绑定
  ├─ 上下行流量解密
  ├─ 线路 A 转发
  └─ 状态、心跳、容量和连接管理
       ↓ Unix socket
decode_node.py / 线路 B
  ├─ 数据重组
  ├─ 协议块解析
  ├─ PlayerState 解析
  ├─ 移动帧解析
  ├─ 武器、干员、生命状态解析
  └─ 按账号保存捕获和诊断数据
       ↓
node/dfm/
  ├─ liveview.py：实时状态、身份、槽位、轨迹和实体派生
  ├─ repmove4.py：移动帧结构读取
  └─ web/ui/：雷达页面和前端渲染
       ↓
panel/
  ├─ 登录、卡密、账号反馈
  ├─ 管理接口
  └─ 静态页面
```

### 2.2 服务关系

| 服务 | 主要职责 | 依赖 |
|---|---|---|
| `radar.service` | 网关、面板边界和主要业务入口 | `radar-node`、配置文件 |
| `radar-node.service` | SOCKS5、解密、线路 A、线路 B 调度 | `node/.venv`、`node/main.py` |
| `radar-panel.service` | 面板静态资源和管理接口 | `panel/.venv`、`panel/main.py` |

线路 B 默认由 `radar-node.service` 拉起。通常只需要重启 `radar-node`，不要额外创建重复的 decode systemd 服务。

## 3. 目录职责

项目根目录为 `C:\leida\radar-src-lanes`。

| 目录或文件 | 作用 | 修改规则 |
|---|---|---|
| `node/` | 唯一运行源码 | 业务和解析修改的首选位置 |
| `node/dfm/src/core/` | 协议、身份、移动和实体派生 | 高风险区域，必须做真实回放 |
| `node/dfm/web/ui/` | 雷达前端 | 先验证 API 数据，再处理视觉问题 |
| `panel/` | 登录、卡密和管理面板 | 修改后做登录反馈和权限测试 |
| `lanes/` | 发布、测试、设计和部署工具 | 修改模板后重新生成发布脚本 |
| `lanes-deploy.sh` | 线路发布入口 | 生成文件，不直接手改嵌入载荷 |
| `deploy.sh` | 基础安装入口 | 只用于基础环境和首次安装 |
| `node/.env.example` | 节点配置示例 | 不写真实密钥 |
| `panel/.env.example` | 面板配置示例 | 不写真实密钥 |
| `.autotune-limits` | 资源调优限制 | 调整前保存原值并做压测 |

禁止把临时日志、抓包、差异文件、密码、私钥和回滚备份放入发布包。

## 4. 本轮已上线修复

### 4.1 敌我翻转根因

旧逻辑把上行自机坐标交给最近邻槽位匹配。自机槽位短暂停更时，队友槽位可能更接近上行坐标，旧逻辑随后把队友的 PlayerState 当成自机，导致：

1. 自机名称变化；
2. 自机槽位变化；
3. 队伍绑定变化；
4. 同队成员从 `mate` 变成 `player`，或反向变化。

现在的逻辑：

- 本局首次确认的自机 PlayerState 会被锁定；
- 上行位姿只用于查找对应移动槽；
- 位姿不再反向改写已经确认的自机身份；
- 新局由新的 LiveView 实例重新建立身份锁。

### 4.2 干员漂移根因

旧逻辑对移动帧只做世界坐标范围检查。部分 h21 可选字段解析不完整时，位流可能仍然产生看似合法的坐标，最终把错误位置写入轨迹。

现在的逻辑：

- 移动帧必须消费到已知尾部；
- 解析异常或不完整帧不进入轨迹；
- 单帧跨越异常距离时整帧丢弃；
- 长时间静默后的槽位重新建立轨迹，不把旧槽位和新实体硬连；
- `movement_decode` 统计接受、拒绝和异常位移帧数量。

### 4.3 本地回放结果

使用保留的两条真实对局流回放：

| 指标 | 修复前 | 修复后 |
|---|---:|---:|
| flow1 异常跳点 | 4 | 0 |
| flow2 异常跳点 | 21 | 0 |
| flow2 接受移动帧 | - | 14364 |
| flow2 不完整帧拒绝 | - | 1170 |
| flow2 异常位移帧拒绝 | - | 1 |
| 集成测试 | - | 4/4 通过 |

这组结果证明了解析防线有效，但不等同于所有地图、所有角色重建和所有并发规模都已经完成验收。

## 5. 发布流程

### 5.1 修改前

1. 确认工作区没有未识别的用户修改。
2. 保存目标文件 SHA256。
3. 在 `work/` 或 `outputs/` 保存副本、差异和测试记录。
4. 明确本次变更的影响面：解析、网关、面板、前端或部署。
5. 先用离线抓包和单元测试验证，再进入线上维护窗口。

### 5.2 源码和发布检查

在项目根目录执行：

```bash
python -B lanes/tools/make_deploy.py
python -B lanes/tools/check_deploy.py
python -B lanes/tools/test_release.py
python -B lanes/tools/selftest.py
python -B lanes/tools/test_supervisor.py
```

修改 `lanes/` 的部署逻辑后，必须确认：

- `lanes-deploy.sh` 与 `lanes/deploy.sh` 仍由同一模板生成；
- release manifest 中的哈希与源码一致；
- 不存在旧式字符串补丁；
- 所有 bash 文件通过 `bash -n`；
- 回滚包可以恢复原文件；
- 线上 `.env` 没有被发布包覆盖。

### 5.3 线路发布

首次安装保持以下顺序：

```bash
bash deploy.sh
bash lanes-deploy.sh dry --app-dir=TARGET
bash lanes-deploy.sh install --app-dir=TARGET
bash lanes-deploy.sh verify --app-dir=TARGET --watch=120
```

已有线上环境只发布经过审核的版本。不要在生产机上直接编辑 Python 源码后跳过哈希和回滚记录。

## 6. 日常检查

### 6.1 服务状态

```bash
systemctl status radar radar-node radar-panel --no-pager
systemctl is-active radar radar-node radar-panel
ps -ef | grep -E 'main.py|decode_node.py'
```

正常状态：

- 三个服务为 `active`；
- `main.py` 存在；
- 线路 B 的 `decode_node.py` 进程数量与配置一致；
- 日志出现 `decode_node_ready`、`lane_listen`、`lane_forward_connected`。

### 6.2 日志位置

常见日志：

```text
/var/log/radar-node/node.log
/var/log/radar-node/decode/node.log
/var/log/radar-node.out
journalctl -u radar-node
journalctl -u radar
journalctl -u radar-panel
```

重点观察：

- `lane_forward_unreachable`
- `lane_link_down`
- `udp_pending_drop`
- `invalid_packets`
- `pending`
- `dropped`
- `decode_lag_sec`
- `Task exception`
- `Traceback`

单次重启时的 `CancelledError` 可能来自 Uvicorn/Starlette 生命周期收尾。判断标准是：重启后服务是否重新进入 `active`，线路是否出现 `lane_forward_connected`，解析进程是否出现 `decode_node_ready`。

### 6.3 资源和队列

```bash
free -m
uptime
ps -eo pid,rss,pcpu,cmd --sort=-rss | head -n 20
systemctl show radar-node -p MainPID -p MemoryCurrent -p TasksCurrent
```

重点不是单次 CPU 峰值，而是：

- 队列是否持续增长；
- `pending` 是否持续不归零；
- `decode_lag_sec` 是否持续扩大；
- 单账号异常是否造成全局队列堆积；
- 解析进程是否反复重启。

## 7. `lanes-deploy.sh verify` 结果解释

执行：

```bash
bash lanes-deploy.sh verify --app-dir=TARGET --watch=120
```

结果判断：

- `radar-node = active`：只代表进程存在，不代表业务链路完整；
- `lane_forward_connected`：线路 A 到线路 B 已连接；
- `decode_node_ready`：线路 B 已准备接收；
- `0 packets`：当前窗口没有玩家流量，只能说明窗口内无业务输入，不等同于业务通过；
- `pending > 0`：需要结合持续时间判断，短暂重启期间正常；
- `dropped` 持续增加：检查队列容量、解析速度和账号级隔离；
- 有玩家流量时，才检查送出帧、收到帧、解密包和状态更新；
- 观察窗口应覆盖至少一局完整对局或明确的切换战局过程。

## 8. 常见故障排查

### 8.1 雷达闪烁或空白

排查顺序：

1. 看 `radar-node` 是否 active。
2. 看 `lane_forward_connected` 是否存在。
3. 看 decode 进程是否存在。
4. 看账号目录是否产生新的 capture。
5. 看 `pending`、`dropped`、`decode_lag_sec`。
6. 确认前端请求的 API 状态是否持续有新 revision。

没有玩家流量时，不要把空白直接判断为解析失败。

### 8.2 敌我不分

先区分：

- API 的 `kind` 和 `team` 已经错：后端身份绑定问题；
- API 正确但颜色错：前端渲染或缓存问题；
- 只有切换战局后错：旧 LiveView、旧槽位或旧账号状态没有清理；
- 只有单账号长时间运行后错：身份锁、PlayerState 更新和槽位复用问题。

必须保存发生前后两个 API 快照，至少包含：

```text
revision
self_name
self_slot
team
kind
eid
slot
ps_guid
silent_seq
```

### 8.3 干员瞬移或漂移

先检查：

- 是否同一槽位在很短时间内跨越几十米以上；
- `silent_seq` 是否从很大值突然回到 0；
- 移动帧是否被拒绝；
- 是否发生角色重建、死亡、重生或切换战局；
- 是否同一槽位对应了新的 actor 或 PlayerState。

修复后的策略是保留上一可信位置，不把异常坐标写入轨迹。若长时间无更新，允许槽位重新起轨，避免旧位置与新实体连线。

### 8.4 未知玩家

未知玩家通常不是“没有名字”这么简单，常见原因是：

- Character 已出现，但 PlayerState 还未到达；
- PlayerState 已出现，但 Character 通道已经重建；
- actor 与槽位的 `claim` 绑定暂时失败；
- 角色名、队伍、干员和位置来自不同增量时刻；
- 同一槽位被复用，但旧身份缓存未清理。

排查必须同时看：

```text
players
links
claim
mate_slots
entities
life_state
weapon_state
```

不要只靠地图几何位置给未知玩家强行命名。几何匹配只能作为低置信度兜底。

### 8.5 武器不显示

排查顺序：

1. API 实体是否有稳定 `eid` 和 `hero`；
2. weapon decode 是否出现 `layout_short`、`leftover` 或 `error`；
3. 角色是否刚重生或通道刚重建；
4. 前端是否收到新 revision；
5. 是否把未知玩家当成没有武器，而实际是身份绑定尚未完成。

武器显示问题和敌我分类问题要分开记录，避免用前端兜底掩盖后端身份错误。

## 9. 版本回滚

### 9.1 线路版本回滚

```bash
bash lanes-deploy.sh rollback --app-dir=TARGET
systemctl restart radar-node
bash lanes-deploy.sh verify --app-dir=TARGET --watch=60
```

回滚前先确认：

- 没有后续人工修改；
- 当前文件哈希仍等于本次安装后的哈希；
- 回滚点存在；
- 服务维护窗口已建立。

### 9.2 本轮解析修复回滚

本轮同步保留了两个文件的远端备份。实际备份位置以同步记录为准，不要凭记忆拼接路径：

```text
outputs/online-auth-sync-20261030/identity-motion-sync.json
```

回滚时先停止写入或进入维护窗口，再恢复：

```text
TARGET/node/dfm/src/core/liveview.py
TARGET/node/dfm/src/core/repmove4.py
```

恢复后重启 `radar-node`，并重新检查文件哈希、服务状态和线路连接。

## 10. 数据与隐私

- 不在源码、文档、提交记录和日志中写密码、卡密、令牌或私钥。
- 生产抓包只保留必要窗口，并设置容量上限和过期清理。
- 抓包、账号目录和日志不随源码发布包分发。
- 采样时按账号分目录，禁止把多个账号的状态写进同一个无边界缓存。
- 诊断报告中使用账号占位符，不使用真实用户名、公网地址或访问凭据。

## 11. 后续运营级改造清单

优先级从高到低：

1. 建立多账号并发回放，验证账号状态、队列、缓存和轨迹完全隔离。
2. 为身份绑定建立状态机和置信度，而不是依赖单次最近邻结果。
3. 为移动帧建立协议 fixture，覆盖所有 mask、角色重建、复活和切战局场景。
4. 将 `movement_decode`、身份回退、未知玩家数量、武器缺失率纳入监控。
5. 为前端加入“数据过期”和“身份待确认”状态，避免错误信息伪装成确定结果。
6. 修复或收敛正常重启期间的生命周期 Traceback，区分真正异常和主动取消。
7. 增加账号级资源上限、超时、熔断和恢复测试。
8. 建立每次发布的版本号、源码哈希、测试报告和回滚点归档。

## 11.1 本地运营级整改批次（2026-10-31）

本批次只修改本地 `C:\leida` 源码，未修改线上服务器、线上配置、账号数据或日志。

### 已修改的运行边界

1. **抓包按账号维护代际和 run 目录**
   - `node/capture.py` 不再使用一个全局换局代号。
   - 账号 A 换局只丢弃 A 的旧队列和旧目录；账号 B 的待写数据继续写入 B 的当前局。
   - 旧包通过账号代际校验后才落盘，避免旧局数据回流到新局。

2. **密钥候选严格按账号隔离**
   - 已知账号只试该账号的 bank。
   - 未知流只试未归属 bank，不继承任何已知账号的密钥。
   - 冷却表改为 `(account, fingerprint)` 维度，账号 A 的失败不会冻结账号 B 的候选。
   - `reset_account` 只清理该账号的流、bank 和冷却项；仍有其他活跃账号时不清空全节点密钥池。

3. **观察队列不再跨账号淘汰**
   - 单账号达到配额时只丢该账号自己的旧包。
   - 全局队列满且找不到同账号旧包时，拒绝当前账号新包，不拿其他账号的包腾位置。
   - 状态中保留 `dropped_packets`、`dropped_quota` 和 `top_accounts`，便于判断是哪一个账号在耗尽配额。

4. **解析队列增加多进程外层账号闸门**
   - `engine_cluster.py` 增加账号级 inflight 计数和消费确认。
   - 单账号达到上限时只拒绝该账号的新包，其他账号仍可进入同一分片。
   - 子进程退出时清理对应账号的 inflight 计数，避免重启后额度永久占用。

5. **主进程和分流进程的换局处理统一**
   - `main.py` 与 `decode_node.py` 都对重复 idle reset 做账号级去重。
   - 引擎队列拒收时记录 `engine_queue_drop` 并进入账号级实时统计，不再静默吞掉。
   - 新包到达会解除该账号的结束标记，避免下一局被旧的 watchdog 状态拦住。

6. **发布清单同步**
   - 重新生成 `lanes-deploy.sh` 和 `lanes/deploy.sh`。
   - 更新 `release_manifest.json` 的当前源码哈希和可接受版本列表。
   - 发布器仍会拒绝哈希不匹配、语法错误、未知人工修改和不完整回滚。

### 本地验证结果

- 账号隔离回归：6/6 通过（包括跨账号同指纹密钥、解析队列额度 ACK 回收）。
- 抓包发布与换局隔离：5/5 通过。
- 实时交付单元测试：17/17 通过。
- 实时交付集成测试：4/4 通过。
- 前端实时控制器：23/23 通过。
- 登录、注册、续期反馈：20/20 通过。
- 发布与回滚契约：22/22 通过。
- 发布清单和嵌入载荷检查：0 failure。
- 本地 Python 语法扫描：112 files 通过。
- Windows 环境没有 Bash，因此 Linux Bash/systemd 验证保持未执行，不能用本地结果替代线上验收。

### 明早验收顺序

1. 先在本地做两个账号同时输入，其中一个账号持续制造高频数据，确认另一个账号仍有连续快照。
2. 让账号 A 换局，确认账号 B 的 capture、bank、解析快照和前端 revision 不回退。
3. 让账号 A 断流再重连，确认 A 能重新入局，B 不需要重绑、不出现全局空白。
4. 观察状态中的 `pending`、`dropped_quota`、`account_inflight`、`bound_flows` 和 `invalid_packets`，以增量判断，不看累计值冒充本轮故障。
5. 通过后再安排线上维护窗口；本批次没有自动上传、重启或覆盖线上文件。

## 12. 接手原则

- 先读调用关系，再改函数。
- 先保存基线，再改文件。
- 先离线回放，再线上观察。
- 先确认 API 数据，再判断前端问题。
- 任何队列、缓存、身份和轨迹都必须有账号边界。
- 发现异常时保留原始证据和推断结论，二者分开记录。
- 不用临时字符串替换代替正式源码修改。
- 不以“进程还活着”作为业务验收标准。

