Files
DimensionalRipper/docs/superpowers/plans/2026-10-06-payload-ownership.md
T

134 lines
16 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.
# 公共收包线程归属 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将受支持的服务端游戏包交给真正主线程独占处理,修复把手松开路径,同时保留阶段之外的维度并行。
**Architecture:** 每服务器有界 FIFO 接收显式主线程工作;服务器外层 tick 建立全维度安全阶段,批量调用原处理器。阶段状态阻止递归收口和新增维度写入,旧组装入口复用同一执行权。生命周期清理与通用包失效分开处理。
**Tech Stack:** Java 21、Minecraft 1.21.1、NeoForge、Mixin/MixinExtras、现有 Gradle 隔离服务器回归。
**Spec:** `docs/superpowers/specs/2026-10-06-payload-ownership-design.md`
**执行状态(2026-10-06):** Task 1 已提交 `691e949`;Task 2–5 的公共入口、阶段、适配与诊断代码已实施并有回归证据。Task 4 的完整原生资源异常清理矩阵、Task 5 的双服务器压力测试、Task 6 的完整性能/客户端矩阵未全部验收,因此不勾选混合这些要求的条目。具体通过项和剩余项以 `docs/payload-ownership-1.1.17-validation.md` 为准,本轮只交付候选包。
## Global Constraints
- 第一版新公共入口默认全维度隔离,不凭“玩家当前维度”或包内坐标猜测整个处理器的访问范围。
- 初始批次上限为 64 项或 2 ms 的处理时间,达到任一条件后不启动下一项;不强行中断已开始的处理器。
- 服务器队列上限 4096 项、单连接上限 256 项。
- 记录上限 128 个 ID,超出归入 other,避免恶意 ID 撑大统计。
- 登录、握手、配置阶段、客户端接收和纯网络线程处理不被无条件改派。
- 超时用于诊断,不是允许并发执行的理由。
- 不自动上传远端或部署正式服务器。测试仅操作仓库隔离世界或显式创建的存档副本。
- 不改变原处理器权限、距离、强加载规则;不放松 Level 的线程守卫。
- ServerWarashi 是后续独立修复,需要单独的采样设计及失败测试;本计划不宣称解决该崩溃。
- C2ME、后台光照查询及既有停服超时不纳入修复声明。
## Review Focus
- 主线程正在服务等待中的 Sable 回调时再次 enqueue:不得等待该回调所属 worker,任务 2、3 验证。
- 前一个包传送/替换玩家后,同连接后续旧包不得操作新世界:任务 3、4 验证。
- 嵌套调用导致新世界/循环出现:不得在独占期间启动另一个写入者,任务 2 验证。
- 队列满载时恰好收到松手:拒绝要显式完成 future,断线清理不能遗留约束,任务 1、4 验证。
- 停服与阶段 finally、开关/菜单暂停同时发生:不得恢复已停止或仍应暂停的循环,任务 2、5 验证。
## 执行与证据约定
当前工作树含尚未提交的 1.1.16 归属修复,不能用新 worktree 的裸 HEAD 替代这些依赖。执行开始按 worktree 技能检查隔离方式;保留并核对所有现有变更,禁止 reset 或覆盖。不把原有未验证内容顺手算作本次通过。
以下路径相对仓库根。每个任务先写断言、记录失败,再改生产代码;每任务结束显式暂存该任务文件并提交(git 使用本仓库 safe.directory)。混合文件先核对 diff,不能误纳入用户后续改动。
命令统一使用 Java 21、`--no-daemon --no-configuration-cache --console=plain`。新的 `-PpayloadRegression` 夹具与 `ownershipRegression`、`interactionRegression` 互斥;运行结束必须有全部指定 PASS 标记且没有 unexpected exception,不能仅依赖进程退出码。证据放 `build/reports/payload/`。
## 文件职责
- `core/compat/PayloadWorkQueue.java`:不依赖 Minecraft 的 FIFO、容量、取消及批次预算。
- `core/compat/MainThreadPhaseCoordinator.java`:服务器阶段状态与独占租约。
- `core/compat/PayloadMainThreadDispatch.java`:实际玩家上下文、future、生命周期与 drain。
- `core/compat/PayloadDiagnostics.java`:有界计数及耗时快照。
- `mixin/ServerPayloadContextMixin.java`:两个 enqueueWork 重载,保留原有分支及 guard。
- `mixin/MinecraftServerPayloadMixin.java`:外层 tick 的 drain,不扩展到通用 pollTask。
- 已有 ServerManager、LevelTickLoop、Sable 桥、组装兼容、停止入口:接入阶段生命周期。
- `tests/PayloadWorkQueueRegression.java`:纯 Java 队列测试。
- `tests/payload/PayloadRegression.java`:实际 NeoForge 上下文与多维度集成。
- `tests/payload/SimulatedHandleRegression.java`:可选真实模组处理器及约束清理测试。
## Task 1: 可测试的有界队列
**Files:** 创建 `src/main/java/dev/modzuozhi/core/compat/PayloadWorkQueue.java`、`tests/PayloadWorkQueueRegression.java`;修改 `build.gradle`。
**Interfaces:** `PayloadWorkQueue<K>(LongSupplier nanoTime)`;`<T> CompletableFuture<T> submit(K connection, Supplier<T> action)`;`int drainBatch()`;`void close(Throwable reason)`;`int size()`。拒绝返回异常 future;执行前已取消的任务不产生副作用。锁只保护队列及计数,绝不包住 action 或 completion 回调。
- [ ] 写 `fifoAndExactlyOnce`、`capacityAndCancellation`、`batchCountAndTime`、`closeAndReentrantCompletion` 断言:同连接顺序 0..255;第 257 项拒绝;16 个连接各 256 项填满后第 4097 项拒绝;取消使容量回收;批次最多 64;假时钟到 2_000_000 ns 后不再启动下一项;慢 action 不被中断;close 后旧 future 全结束且不能再接收。
- [ ] 添加 `runPayloadWorkQueueRegression` 到 check;先运行并保存失败证据,不能以无测试发现作为 RED。
- [ ] 实现上述接口;future 完成回调可重新 submit/close 而无锁死;Supplier 异常只影响本项。
- [ ] 运行 `./gradlew.bat runPayloadWorkQueueRegression`,输出 `PAYLOAD QUEUE PASS`;并行提交/取消压力测试核对计数为零。
- [ ] 审阅并提交本任务文件。
## Task 2: 安全阶段与循环生命周期
**Files:** 创建 `src/main/java/dev/modzuozhi/core/compat/MainThreadPhaseCoordinator.java`、`tests/payload/PayloadRegression.java`;修改 `core/dimthread/ServerManager.java`、`core/dimthread/LevelTickLoop.java`、`core/compat/SableMainThreadBridge.java`(均位于 `src/main/java/dev/modzuozhi/`),修改 `build.gradle`。
**Interfaces:** coordinator 的 `void run(MinecraftServer server, String reason, Runnable action)`、`boolean owns(MinecraftServer server)`、`boolean blocksWorkerStart(MinecraftServer server)`、`void stopping(MinecraftServer server)`、`void clear(MinecraftServer server)`;Sable 桥新增 `boolean inHandoff(MinecraftServer server)`。ServerManager.runMainThreadPhase 委托 coordinator,保持现有 CC 调用不变。
- [ ] 写 `exclusivePhase`、`nestedPhase`、`handoffReentryRejected`、`newLoopDuringPhase`、`stopDuringPhase`:处理期间全部世界 owner 为真主线程、worker 写入计数不增加;等待可完成 Sable 回调;独占内嵌套只建一次屏障;handoff 中建全局屏障立即明确失败;新 loop 在阶段退出前不能开跑;预先暂停、菜单暂停、停止状态不被 finally 恢复。
- [ ] `./gradlew.bat runServer -PpayloadRegression` 得到预期断言失败,保存日志;夹具另用超时终止测试而非让生产代码越过屏障。
- [ ] 实现 IDLE/DRAINING/EXCLUSIVE/RESTORING/STOPPING 状态、租约与入口检查。循环注册、scheduleIfNeeded、resume 共享阶段启动门禁;新 loop 参与退出处理。等待不持锁,继续 pump 必要回调;嵌套 drain 不递归。循环已经在途时等待其子任务及线程字段全部归还。
- [ ] 运行夹具输出 `PAYLOAD PHASE PASS`;复跑 ownershipRegression,确认 CC 预置暂停及 Sable 回调仍通过。
- [ ] 审阅并提交本任务文件。
## Task 3: NeoForge 公共入口与 future 语义
**Files:** 创建 `src/main/java/dev/modzuozhi/core/compat/PayloadMainThreadDispatch.java`、`src/main/java/dev/modzuozhi/mixin/ServerPayloadContextMixin.java`、`src/main/java/dev/modzuozhi/mixin/MinecraftServerPayloadMixin.java`;修改 `tests/payload/PayloadRegression.java`、`src/main/resources/dimensionalripper.mixins.json`。
**Interfaces:** dispatch 的 `<T> CompletableFuture<T> submit(ServerPlayer player, ResourceLocation payloadId, Supplier<T> action)`、`boolean owns(MinecraftServer server)`、`void drain(MinecraftServer server)`、`void stop(MinecraftServer server)`。Runnable 适配 Supplier<Void>。任务保存实际 connection、player、接收时 ServerLevel 身份及时间;各服务器实例独立。
- [ ] 用真实 ServerPayloadContext 的 Runnable/Supplier 路径写 `mainPayloadSeesBlockEntity`、`futureContract`、`phaseEscapeAndStalePlayer`、`configurationPassthrough`。断言并发维度下能取得原 BE、true main、至多一次;Supplier 返回 42;异常、取消和 guard 的对外结果与未修改 NeoForge 一致,内部真实失败仍可见;权限拒绝未发生世界变更;配置阶段不调用 player();网络计算原线程不变。
- [ ] 运行 payloadRegression 记录原入口 wrong-owner/null 的失败;记录尚未覆盖的真实 Veil 路径,不冒充实测。
- [ ] 两个 enqueueWork 仅拦截游戏玩家的显式主线程工作。网络/worker 投递不等待;独占内嵌套立即完成;普通主线程外层进入安全阶段时先遵守同连接已排队顺序;DRAINING 中普通投递只入队;同步 handoff 重入按任务 2 契约明确失败。原 handler、上下文与 NetworkRegistry.guard 保留,不双重执行。
- [ ] 在 MinecraftServer.tickServer HEAD 的外层主线程 drain 一批;不在 pollTask 或 worker 回调内 drain。执行前验证连接、player 身份及世界;失效 future 明确结束。无队列不建屏障。
- [ ] 加 `workerWaitCycleDetected` 测试:worker 等待队列 future 时测试必须识别无法收口且取消夹具,不误报自动兼容。运行集成输出 `PAYLOAD ENTRY PASS`,覆盖没有并行 loop 的原版情况。
- [ ] 审阅并提交本任务文件。
## Task 4: 原模组交互与清理路径
**Files:** 修改 `src/main/java/dev/modzuozhi/core/compat/SableAssemblyDispatch.java`、`src/main/java/dev/modzuozhi/mixin/CreateConfigurationPacketMixin.java`、`src/main/java/dev/modzuozhi/mixin/SimulatedAssemblePacketMixin.java`;创建 `tests/payload/SimulatedHandleRegression.java`;修改 `build.gradle`。若真实路径证明需要专用适配,仅新增 `src/main/java/dev/modzuozhi/mixin/JadeRequestDispatchMixin.java` 与 `src/main/java/dev/modzuozhi/mixin/SimulatedHandleLifecycleMixin.java`,并登记可选 mixin。
**Interfaces:** 保持 `SableAssemblyDispatch.dispatch(ServerPlayer,Runnable)`,改为复用公共阶段/投递,禁止再送进已暂停的维度队列。具体可选模组方法描述符从本地实际 jar 审计后锁定,不能猜注入目标。
- [ ] 先记录真实 `UpdatePlayerUsingHandlePacket.handle` 的抓取、距离、松手入口、BE 身份、HandleConstraint 移除结果,建立 RED。核对 HandleBlockEntity.checkPlayers 及断线/死亡/卸载清理,不仅测试模拟 Runnable。
- [ ] 审计 Veil 注册和 Jade 二次 server.execute;建立逃逸测试。只有已观察的逃逸点使用定点包装,不改写全部 MinecraftServer.execute。旧 Create/组装入口在公共独占内直接调用原 handler;阶段外走相同队列。
- [ ] 测试顺序与副作用:抓取一次、更新距离一次、松手一次,最后 constraint 不存在;两个玩家不会互相释放;转速配置、组装拆解保留原权限和距离行为,错误坐标不强加载。
- [ ] 覆盖断线、换维度、死亡、把手拆除、sublevel 卸载和单连接 256 满载后松手被拒绝;若原清理不足,可信生命周期适配在旧世界安全阶段释放资源,不重放过期客户端包。过载断开连接调用原 disconnect/cleanup,日志限频。
- [ ] 真实运行输出 `SIMULATED HANDLE PASS`、`CREATE CONFIG PASS`、`JADE REQUEST PASS`。原模组依赖或原生物理无法启动时,记录未通过项并暂停“把手已修复”的交付结论,不能用假夹具替代。
- [ ] 审阅并提交已验证的适配文件;无必要不创建专用 mixin。
## Task 5: 停止、可观测性与限频
**Files:** 创建 `src/main/java/dev/modzuozhi/core/compat/PayloadDiagnostics.java`;修改 `src/main/java/dev/modzuozhi/core/compat/PayloadMainThreadDispatch.java`、`src/main/java/dev/modzuozhi/mixin/MinecraftServerDimMixin.java`、`src/main/java/dev/modzuozhi/core/compat/ThreadOwnershipDiagnostics.java`、`src/main/java/dev/modzuozhi/command/MasterCommand.java`、`tests/payload/PayloadRegression.java`。
**Interfaces:** `PayloadDiagnostics.snapshot(MinecraftServer server): String`,只读且不新建循环;dispatch.stop 拒绝接收、终结待执行 future,阶段退出不恢复停止中的 loop;clear 在清理结束移除服务器引用。
- [ ] 写 `boundedDiagnostics`、`stopRejectsAndReleases`、`twoServersIsolated`:1000 个不同 ID 最多 128 个具名项加 other;1000 次失败不输出1000份堆栈;停止后无遗留 future/连接/世界引用;某一服务器 stop 不影响另一服务器。
- [ ] 运行得到 RED。实现每服务器计数、队列最老等待、最近 32 事件、等待/执行耗时、批次数;异常全局 30 秒限频,快照无包正文。正常/异常停止入口在既有循环停止与保存之前关闭新队列;不要把旧 30 秒停服风险说成已解决。
- [ ] status 和 crash report 附快照,区分等待 worker、handler 慢和 Sable handoff;测试所有失败 future 最终完成且 handler 从不重复执行。
- [ ] 运行 payloadRegression 输出 `PAYLOAD LIFECYCLE PASS`、`PAYLOAD DIAGNOSTICS PASS`,审阅并提交。
## Task 6: 兼容验收与候选包
**Files:** 修改 `README.md`、`gradle.properties`(候选版本 1.1.17);证据 `build/reports/payload/`。
**Interfaces:** 交付明确列出“源码/字节码审计”“夹具通过”“真实模组通过”的覆盖表,不将它们合并为兼容保证。
- [ ] 分别运行 `./gradlew.bat check`、`runServer -PpayloadRegression`、`runServer -PownershipRegression -PccTestJar=<已验证本地CC路径>`、`runServer -PinteractionRegression`。逐项确认纯 Java、CC、区块、水流、实体路由和新 PASS 标记,不把预期故障夹具当成功事故。
- [ ] 无 Create/Veil/Jade/Simulated 可选模组启动通过;真实模组组合另跑,不自动复制到用户 mods 目录。
- [ ] 对比无交互、普通交互、密集抓握、多人查询及慢维度:主线程 TPS、维度完成 TPS、队列延迟、阶段耗时。无交互新增屏障数必须为零;报告慢维度代价,不以 Carpet MSPT 独立判定。
- [ ] 正常构建 `./gradlew.bat clean build`,不带任何夹具属性。检查 jar 无 tests/Regression/测试模组类、元数据 1.1.17,记录 SHA256。
- [ ] 按 requesting-code-review 技能审查完整差异,修复阻塞问题并重跑受影响测试。README 清楚标注 ServerWarashi 仍未由本计划修复以及真实航空学验收状态;提交本任务。
- [ ] 只交付本地候选 jar 和覆盖/风险说明;如真实物理未通过,交付测试候选而非宣称全部修好。不推远端、不部署。
## 自审结果
设计第 4 节对应任务 1–3;旧适配、Veil/Jade 与生命周期对应任务 4–5;有界诊断与性能/打包对应任务 5–6。所有五个 Review Focus 已映射到断言。ServerWarashi 明确作为独立后续工作,不以当前方案替代采样栈修复。