# 3DTank Kotlin 重构计划(KtRebuild Plan) > **目标**:用 Kotlin 复刻游戏本体,最终产出**职责等价、可启动**的 JS 客户端。 > **基线**:`main.7cfa4058.js`(10,080,299 字节)/ `client_version = e98d02161a563e043eabb96c6e4db5fedcf165e7` > **文档定位**:计划表 —— 有哪些工具、怎么用、按什么顺序做、怎么验收。 --- ## 0. 先纠正三个前提 1. **`dir.file` 不是当前版本的职责表。** 它是旧版 126 模块的职责划分;当前版本的模块 id、字节数、内部字段都有漂移。 但**职责划分大体一致**,仍可作为"要做哪些子系统"的规划依据;正式开工前必须用当前包重建一份职责表(见 S0)。 2. **`e98d0216` 不是文件名。** `e98d0216…` 是 `client_version`(git release hash,写死在 `module_61860.js`): ```js i.g3("client_version", "e98d02161a563e043eabb96c6e4db5fedcf165e7"); ``` 而 `main.7cfa4058.js` 的 `7cfa4058` 是 webpack **contenthash**。两者无对应关系,不能互相推导文件名。 3. **真正要复刻的只有 40 个模块。** `dir.file` 的分类计数:**游戏本体 40 / Kotlin 生态库 21 / 第三方库 65**(另有 index.json、resources.json)。 后 86 个是已知开源依赖,直接引上游即可,不需要反编译重写。 --- ## 1. 验收标准("可启动 JS"的定义) | 级别 | 判定 | 手段 | |---|---|---| | **L1 可加载** | 126 模块 + 资源全部注册成功,无 404 / 无语法错误 | `tools/test_loader.js` 全绿 | | **L2 可进战局** | `serve.js` 起站 → 登录 → 车库 → 进战斗 → 控制台无未捕获异常 | 人工 + 控制台 | | **L3 行为等价** | 同场景关键状态(位置/血量/状态机/事件序列)与原包对比一致 | 双跑 golden trace | | **L4 工具兼容** | HORNET 全部开关可用(雷达/透视/后视镜/穿坦克/拒绝击退…) | `build_bundle.py` 产物实测 | > L1/L2 是最低门槛;L3 才是"职责等价"的真正证据。 --- ## 2. 模块分层(依据 `dir.file`) | 分类 | 数量 | 处理方式 | 是否重写 | |---|---|---|---| | 游戏本体 | 40 | 反混淆 → Kotlin 复刻 | ✅ | | Kotlin 生态库 | 21 | 换官方依赖(stdlib / serialization / coroutines / atomicfu / kvbuffer…) | ❌ | | 第三方库 | 65 | 换 npm 依赖(React 18.3.1 / Redux / Sentry 5.30.0 / qrcode / Underscore 1.13.6 / Emotion / fflate…) | ❌ | | 资源/索引 | 2 | index.json、resources.json 原样托管 | ❌ | **游戏本体的体积分布(决定推进顺序)**: | 模块 | 大小 | 职责 | 阶段 | |---|---|---|---| | 82347 | 5.50 MB | 大厅/车库/商店/部落/结算 UI | S6(最后) | | 87072 | 3.78 MB | 战斗系统:底盘物理、模式、物品池、特效、空投、地图渲染 | S5 | | 10032 | 1.54 MB | 核心 Redux 状态 | S3 | | 85183 | 1.18 MB | 全量协议 Codec 注册 | S2 | | 92980 | 0.83 MB | WebGL 渲染核心 | S4 | | 其余 35 个 | < 5 MB | 协议/工具/音频/输入/资源/ECS/物理… | S2–S5 | 前 5 个约占 12.8 MB,是工作量的绝对主体。 --- ## 3. 工具清单与用法 ### 3.1 基线与拆分 | 工具 | 作用 | 用法 | |---|---|---| | `tools/split.js` | 把 `main..js` 切成 126 个模块 + `resources.json`,并注入 bundle 加载 | `node tools/split.js [bundle] [输出目录] --clean`(不传参自动取根目录最新 `main.*.js`) | | `3DTank/tools/extract.js` | bundle → prettier 美化源码(`3DTank/src/`) | `cd 3DTank/tools && node extract.js [bundle] [输出目录]` | | `tools/pack_unpacked.js` | 把 `unpacked/` 打成 `unpacked.pack.json`(一次请求加载全部模块) | `node tools/pack_unpacked.js` | | `tools/test_loader.js` | 模拟浏览器加载流程,验证切割产物完整 | `node tools/test_loader.js` | ### 3.2 命名还原 | 工具 | 作用 | 用法 | |---|---|---| | `3DTank/tools/mine_evidence.js` | 从源码挖命名证据(组件名/类名/枚举/闭包原名/导出表/字符串) | `cd 3DTank/tools && node mine_evidence.js` | | `3DTank/tools/apply_names.js` | 把证据回填成 `src_named/`(三道校验) | `node apply_names.js --aliases` | | `tools/map_symbols.js` | 新旧 bundle 符号映射(K 元块锚点 + 投票) | `node tools/map_symbols.js <旧unpacked> <新bundle> all tools/symbol_map.json` | | `tools/apply_symbol_map.js` | 按映射批量改名(五道安全规则 + 双向校验) | 见文件头注释;默认读 `3DTank/tmp_symbol_map.json` | | `3DTank/names/module-graph.json` | 模块依赖图(`DependsOn` / `Exports` / `Anchors` / `Kind`) | 数据文件,作为 Kotlin 模块切分依据 | ### 3.3 字段抽象(当前版本适配的主入口) | 工具 | 作用 | 用法 | |---|---|---| | `tools/hornet_schema.js` | 匿名字段 → 语义字段总表(88 项)+ 导出键表 | 控制台 `__hornetSchema__.SelfCheck()` 一次列出失配项 | | `build_bundle.py` | 把 `tools/hornet_*.js` 拼成 `3dtank_bundle.js` 并同步到 `unpacked/` | `python build_bundle.py` | 换版本时**只改 schema**,工具代码不动;这是本计划里"漂移成本最低"的一环。 ### 3.4 本地运行与协议取证 | 工具 | 作用 | 用法 | |---|---|---| | `3DTank/tools/serve.js` | 本地静态服务器 + 反向代理(resources / config / balancer)+ 中继 config 改写 | `node serve.js --port 8080 [--relay-port 9091] [--verbose]` | | `3DTank/tools/wss_server.js` | 本地游戏通信服务器(中继 + 抓包 + 实时解密) | `node wss_server.js --port 9091 --upstream wss://c1.3dtank.com:9090/` | | `3DTank/tools/analyze_packets.js` | 抓包产物分析 | 见文件头 | | `3DTank/tools/prefetch_assets.js` | 按 `resources.json` 预拉资源到本地镜像 | `node prefetch_assets.js` | | `3DTank/tools/server/` | 协议服务端骨架(codec / dispatch / session / space / world…) | 供 S3 协议层对照 | ### 3.5 HORNET 探针(现成的行为断言) `tools/hornet_lib.js` / `hornet_radar.js` / `hornet_3d_wireframe.js` / `hornet_noclip.js` / `hornet_noknockback.js` 等, 直接读引擎状态(位置、血量、队伍、物理形状)。S5 之后可用它们做"新包是否语义一致"的活体探针。 --- ## 4. 阶段计划 ### S0 基线重建(必须先做) - **目标**:产出**当前版本**的职责表与依赖图,替换 `dir.file` 的旧数据。 - **做法**: 1. `node tools/split.js --clean` 2. `node 3DTank/tools/extract.js ../main.7cfa4058.js ../src` 3. `node mine_evidence.js`(更新 `evidence.json`) 4. 以 `module-graph.json` 的 `DependsOn/Exports/Anchors/Kind` 重新分类 126 模块 - **输出**:`3DTank/names/module-graph.json` + 新版职责表(按当前模块 id) - **验收**:`node tools/test_loader.js` 全绿;分类计数与旧表基本一致(40/21/65 量级) ### S1 依赖还原(86 个库) - **目标**:把库模块映射为上游依赖,产出 `deps.lock`。 - **做法**:用 `Anchors`(版本字符串、API 名)反推包名与版本;导出表对照 API 面。 - **已知**:React/React-DOM 18.3.1、Sentry 5.30.0、Underscore 1.13.6。 - **待挖**:Kotlin 编译器版本、kotlinx-coroutines / serialization / atomicfu 版本、kotlin-wrappers、Emotion。 - **验收**:每个库模块都能指到"包名 + 版本 + 用到的导出符号"。 ### S2 协议层(14 个 codec 模块) - 模块:85183 / 47243 / 85196 / 31908 / 16100 / 10283 / 25377 / 54199 / 37504 / 88693 / 89803 / 85667 / 67222 / 99527 - **目标**:Kotlin 数据类 + `@Serializable`,按 **命令 id + 字段序号** 对齐。 - **验证**:用 `wss_server.js` 抓到的帧做 golden 编解码对比(现有 `names/*_table.json`、`codec_fields.json` 可直接用)。 - **注意**:字段顺序/默认值错一位整包不可用,必须逐帧验。 ### S3 状态与胶水 - 模块:10032(Redux 核心)、18334(DI 装配)、61860(连接与对象模型)、13431(启动)、52943(工具库)、26482(SW 注册) - **目标**:新工程能完成"启动 → 连接 → 进入大厅"。 - **验收**:L2 的一半(登录 + 大厅)。 ### S4 引擎核心 - 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入) - **目标**:场景可渲染、物理可步进。 - **做法**:先纯函数层(矩阵/几何/物理步进),再渲染管线;用同输入同输出做单元比对。 - **验收**:车库 3D 预览可显示;进战斗不崩。 ### S5 战斗系统(87072,3.78 MB) - 按子系统拆文件:底盘/悬挂、物理、战斗模式、物品池、空投、特效、地图渲染。 - **验收**:L2 完整(可进战斗并驾驶/开火),HORNET 探针读数与原包一致。 ### S6 UI(82347 5.5 MB + 21300 HUD) - **建议最后做**,或长期保留原 JS 模块(Kotlin 与 JS 模块可混跑)。 - **验收**:各界面可打开、无报错;不追求像素级一致。 --- ## 5. 目录规划(拟) ``` KT/ settings.gradle.kts build.gradle.kts app/ # 入口(对应 13431) core/ # 数学 32088、工具 52943 ecs/ # 61378 physics/ # 55088 render/ # 92980 resource/ # 36659、71105 net/ # 61860 + codec/*(S2 全部模块) state/ # 10032、18334 battle/ # 87072(内部再按子系统分文件) ui/ # 82347、21300(可延后) deps/ # React/Redux/Sentry 的 external 声明 web/ # index.html、resources.json、静态资源 ``` 约定: - 一个 webpack 模块 ≈ 一个 Kotlin 文件(巨型模块按类拆); - 对外属性名保持不变(跨模块共享),类名可改; - 浏览器 API 全部 `external`(WebGL2 / WebSocket / PointerLock / WebAudio / Storage / Worker)。 --- ## 6. 依赖与版本 | 依赖 | 模块 | 版本 | 状态 | |---|---|---|---| | kotlin-stdlib-js | 37036 | 待挖 | — | | kotlinx-serialization-core / json | 9539 / 61430 | 待挖 | — | | kotlinx-coroutines | 89613 | 待挖 | — | | atomicfu | 71426 | 待挖 | — | | kvbuffer | 72834 | 待挖 | — | | React / React-DOM | 78931 / 45555 | 18.3.1 | ✅ | | Sentry Browser SDK | 30285 | 5.30.0 | ✅ | | Underscore | 94965 | 1.13.6 | ✅ | | Redux(Kotlin 侧) | 10849 | 自研封装 | 需评估 | | qrcode 系列 | 59411 等 | 待挖 | — | | Emotion / inline-style-prefixer | 14766 / 77249 等 | 待挖 | — | | fflate | 29518 | 待挖 | — | --- ## 7. 换版与漂移处理 1. 抓新 `main..js` → `node tools/split.js --clean` 2. `node tools/map_symbols.js <旧unpacked> <新bundle> all tools/symbol_map.json` 3. `__hornetSchema__.SelfCheck()` 列出语义字段失配 → **只改 `hornet_schema.js`** 4. 人工核对三类: - `$_.xx` 导出键(队伍 `vd/dl`、碰撞世界 `55088.$_.h` 等) - 工具 hook 的类内部方法(`o37` / `o3c` / `n3c` / `f3t` / `q9q` / `kz7`) - 渲染管线字段语义(`d75_1` / `w39_1` / `b74_1` …) 5. `python build_bundle.py` → 实测工具 --- ## 8. 产出"可启动 JS"的端到端流程 ### 8.1 运行时模块来源 默认:**`3dtank.com/play/static/js/unpacked`**(切割产物,含 4 个模块的人工补丁)。 可选:`-UseSrc` 改用 `3DTank/src`(纯净还原;人工补丁需先移植过去)。 | 行为 | 说明 | |---|---| | 启动时打包 | 单包缺失、或所选目录的 `module_*.js` 比单包新 → 自动跑 `pack_unpacked.js` | | `-Build` | 强制重打单包 | | `-UseSrc` | 改用 `3DTank/src`(`-Split` 为兼容旧参数,等同默认) | | 路由 | `serve.js --src <目录>`:仅 `-UseSrc` 时传入;`unpacked/module_*.js`、`resources.json`、`index.json`、`unpacked.pack.json` → 源码目录 | | 仍取镜像 | 加载器 `main..js`、`unpacked/hornet_bundle.js`(HORNET 工具链) | 打包命令(按来源目录二选一): ```bash node tools/pack_unpacked.js 3dtank.com/play/static/js/unpacked 3dtank.com/play/static/js/unpacked.pack.json node tools/pack_unpacked.js 3DTank/src 3DTank/src/unpacked.pack.json ``` ### 8.2 完整流程 ```bash # 1) 基线 node tools/split.js --clean node 3DTank/tools/extract.js ../main.7cfa4058.js ../src cd 3DTank/tools && node mine_evidence.js && cd .. # 2) Kotlin 编译(工程建好后) ./gradlew :app:build # 产物:build/dist/js/productionExecutable/ # 3) 产物接入(二选一) # A. 用 Kotlin 产物直接替换 main..js(需自建 webpack runtime) # B. 保留现有 loader,把 Kotlin 产物作为新模块注册进 __modules__ node tools/pack_unpacked.js # 4) 验收 node tools/test_loader.js # L1 node 3DTank/tools/serve.js --port 8080 # L2:浏览器进站 node 3DTank/tools/wss_server.js --port 9091 --upstream wss://c1.3dtank.com:9090/ # L3:抓包对比 ``` --- ## 9. 风险与回退 | 风险 | 影响 | 应对 | |---|---|---| | 协议字段序号错位 | 整包不可用 | S2 逐帧 golden 对比;先只做只读命令 | | Kotlin 版本差异 | 输出与原包不同 | 不追求字节一致,只追求语义等价(双跑) | | 巨型模块拆分失控 | 进度不可控 | 87072/82347 按子系统拆文件,先骨架后逻辑 | | 渲染管线语义不明 | 画面异常 | 保留原 92980 模块,先替换非渲染层 | | UI 体量过大 | 拖慢整体 | S6 延后,允许长期混跑原 JS 模块 | | 换版漂移 | 工具失效 | schema + `SelfCheck()` 作为唯一适配面 | **回退策略**:每个阶段都保留"原模块可用"的开关;Kotlin 模块按模块 id 替换,出问题单模块回退,不影响整包。 --- ## 10. 待确认 1. `32088`(标注"Alternativa3D,Kotlin 编写")是自研移植还是开源库?决定 S4 是"引入"还是"重写"。 2. `10032` / `10849`(Redux)是否必须保持 Kotlin 实现,还是可以换成原生 Redux? 3. S6 UI 是否允许永久保留原 JS 模块(推荐允许)。 4. 是否要求最终产物能**替代** `main..js`,还是只要"能启动 + 职责等价"即可。