docs(3dtank): 架构逆向:模块依赖图、6 条运行链路、TOState↔reducer 映射

names/module-graph.json:126 模块的 Kind/职责/依赖/导出/锚点,533 条依赖边,入口 13431(出度 46,依赖链深 17 层)。docs/ArchitectureModules.md:依赖图摘要 + 40 个游戏模块逐条 + 依赖分层(由数据生成)。docs/ArchitectureRuntime.md:启动引导/网络层/Redux/UI 系统/资源与渲染/空间与对象模型 6 条链路,64 条关键函数([证据] 77 / [推断] 15)。names/tostate-map.json + docs/StateMap.md:TOState 实为 89 字段(非估计的 60),89/89 由「toString 字段→槽位」与「根 reducer v8 的实参顺序→ntb 形参→w8 字段顺序」两条独立链路交叉验证,唯一未映射的是只读的 launchParams。
This commit is contained in:
TermiNexus committed 2026-09-18 09:30:30 +08:00
1 parent 4eff6bb315
commit 61b57a3d11
4 files changed
+2784

No files matched your search

+472
View File
@@ -0,0 +1,472 @@
# 3DTank 架构模块调研
> 本文件内容全部由 `3DTank/names/module-graph.json` 的数据生成(一次性内联 node 命令,仓库不保留生成脚本);计数、依赖、分层均非手工绘制。
## 1. 总览
- 模块总数:**126**(均为 webpack 代码模块;index.json 中其余条目是资源模块,不在本调研范围)
- 按 Kind:游戏本体 **40** / Kotlin生态库 **22** / 第三方库 **64**
- 依赖边总数:**533**;孤立模块(无出度且无入度):**3** 个
- 依赖层次最深(最长依赖链):**Level 17**
### 1.1 依赖与结构的抽取方法(覆盖率)
- **依赖(DependsOn)**:取模块工厂第 3 个参数作为 webpack require(形如 `(t, n, i) => {` 里的 `i`),再扫描 `i(<id>)` 静态调用,且 id 必须命中已知代码模块 id 集合。**126/126 模块都执行了抽取**,其中 **97 个模块抽到 ≥1 条边(77%)**;**29 个模块 DependsOn 为空**,原因是:24 个模块工厂只有 1~2 个参数(编译产物里根本没有 require 参数),5 个模块有 require 参数但源码无静态 `i(<id>)` 调用(7793、14766、17323、55920、94965;其中 55920 用 `t.require(n)` 动态取模块,无法静态解析)。`index.json` 只含 id/type/path,**不含依赖信息**,故依赖全部来自源码扫描。
- **Kind**:按 dir.file 职责描述判定。职责点名可识别外部包/产品(React、ReactDOM、Scheduler、Sentry、qrcode、fflate、Emotion、underscore、inline-style-prefixer、prop-types、react-is、react-list、hoist-non-react-statics、@preact/signals-react、WebViewJavascriptBridge、Google Play Billing、Yandex Metrica、Dijkstra 库等)→ 第三方库;Kotlin/JS 编译出的通用基础设施(kotlin-stdlib、kotlinx.serialization、kotlinx.coroutines、atomicfu、ByteBuffer、DOM/CSS 构建器、RBuilder、Kotlin<->React 桥、kotlin-redux 框架核心、通用工具模块)→ Kotlin生态库;其余游戏业务逻辑(客户端、协议编解码、模型注册、UI/HUD、战斗、资源、音频、埋点、游戏引擎、启动入口)→ 游戏本体。规则表已写死为显式 id 列表,126 个模块全部命中、无默认兜底。
- **Anchors**:优先取字符串字面量(长度 3~90,滤掉 HTML/CSS 噪声词与纯符号/数字串,CSS 函数名如 `image-set(` 保留),按 `跨模块出现次数`(仅 1 个模块出现 +4、≤3 个 +2、>40 个 -4)+ `错误信息关键词`(not found / exception / invalid / failed / unknown 等 +5)+ `camelCase 标识符`(+4)打分取前 5;**字面量不足 3 个时**用该模块内出现次数少的高辨识度标识符(导出名 / 属性名,如 `getPositions`、`createRoot`)补齐,纯转发模块补转发目标 `i(<id>)`。仍有 6 个模块(17323、19074、29545、31584、58309、99295)没有任何可用锚点,其 `Anchors` 为空数组(不编造)。
- **顶层类/函数**:统计最浅缩进层的 `function NAME(` 声明(Kotlin/JS 产物会把函数声明提升到同一层);名称首字母大写计入 TopLevelClasses(Kotlin 类名 PascalCase,压缩后仍有大写开头,如 71105 中可见 `ResourceBatchLoadException`),其余计入 TopLevelFunctions。这是启发式口径,非精确语义。
### 1.2 代码量分布(按字节 Top 20)
| # | 模块 | 字节 | KB | Kind |
|---|---|---|---|---|
| 1 | `module_82347` | 5498676 | 5370 | 游戏本体 |
| 2 | `module_87072` | 3775340 | 3687 | 游戏本体 |
| 3 | `module_10032` | 1536764 | 1501 | 游戏本体 |
| 4 | `module_85183` | 1176860 | 1149 | 游戏本体 |
| 5 | `module_92980` | 822309 | 803.0 | 游戏本体 |
| 6 | `module_47243` | 534291 | 521.8 | 游戏本体 |
| 7 | `module_71105` | 372886 | 364.1 | 游戏本体 |
| 8 | `module_21300` | 355930 | 347.6 | 游戏本体 |
| 9 | `module_37036` | 347491 | 339.3 | Kotlin生态库 |
| 10 | `module_18334` | 303005 | 295.9 | 游戏本体 |
| 11 | `module_70130` | 293925 | 287.0 | 游戏本体 |
| 12 | `module_85196` | 235678 | 230.2 | 游戏本体 |
| 13 | `module_45555` | 224856 | 219.6 | 第三方库 |
| 14 | `module_36659` | 182712 | 178.4 | 游戏本体 |
| 15 | `module_63383` | 177741 | 173.6 | Kotlin生态库 |
| 16 | `module_55088` | 148469 | 145.0 | 游戏本体 |
| 17 | `module_52943` | 145059 | 141.7 | Kotlin生态库 |
| 18 | `module_3569` | 141181 | 137.9 | 游戏本体 |
| 19 | `module_9539` | 131066 | 128.0 | Kotlin生态库 |
| 20 | `module_30285` | 123696 | 120.8 | 第三方库 |
## 2. 依赖图摘要
### 2.1 入度 Top 15(被最多模块依赖)
| 模块 | 入度 | Kind | 职责 |
|---|---|---|---|
| `module_37036` | 59 | Kotlin生态库 | Kotlin/JS 标准库(kotlin-stdlib-js)的运行时核心模块 |
| `module_52943` | 39 | Kotlin生态库 | 网页游戏/应用底层工具库,认设备,管键盘,翻译,报错,调用浏览器本地存储,服务管理 |
| `module_61860` | 24 | 游戏本体 | 网络连接与游戏对象模型的核心模块,Kotlin/JS 编译产物。它把客户端和服务端连接起来,并管理游戏对象的生命周期。 |
| `module_24635` | 24 | Kotlin生态库 | Kotlin/JS 二进制序列化/反序列化(Protocol Buffer 风格)编解码框架 |
| `module_32088` | 18 | 游戏本体 | Alternativa3D,3D 数学库,用 Kotlin 编写 |
| `module_71105` | 15 | 游戏本体 | 游戏资源加载系统(Resource Loader) |
| `module_89613` | 15 | Kotlin生态库 | Kotlin Coroutines (kotlinx.coroutines) 核心基础设施的 JS 移植版 |
| `module_31908` | 12 | 游戏本体 | 游戏战斗(Battle)域的协议编解码模块 |
| `module_92980` | 11 | 游戏本体 | 使用 WebGL 技术开发的3D 游戏引擎或大型 3D 交互应用的渲染核心模块 |
| `module_7552` | 10 | 第三方库 | 转发实现 |
| `module_85196` | 8 | 游戏本体 | 游戏车库(Garage)系统的协议编解码模块,属于同一个坦克对战类游戏。它负责定义和注册与“车库/装备/商店”相关的网络协议消息的二进制编解码器。 |
| `module_3569` | 8 | 游戏本体 | 游戏客户端的输入与坦克预览模块 |
| `module_85667` | 8 | 游戏本体 | 客户端协议编解码模块 |
| `module_88693` | 8 | 游戏本体 | 网络协议与数据模型层的实现,主要负责处理客户端与服务器之间关于社交、好友、举报、通知权限等非战斗数据的序列化与反序列化,并管理相关的网络路由和回调。 |
| `module_10849` | 8 | Kotlin生态库 | Kotlin/JS 编译后的 Redux 状态管理框架核心模块 |
### 2.2 出度 Top 15(依赖最多模块)
| 模块 | 出度 | Kind | 职责 |
|---|---|---|---|
| `module_82347` | 46 | 游戏本体 | 负责注册大厅、车库、商店、部落、战斗结算等几乎所有核心 UI 界面的组件样式与逻辑。 |
| `module_13431` | 46 | 游戏本体 | Kotlin/JS 编译后的游戏客户端启动入口模块,负责整个游戏的初始化、渲染和启动流程。 |
| `module_18334` | 28 | 游戏本体 | 游戏客户端的总装配/依赖注入模块 |
| `module_10032` | 25 | 游戏本体 | 游戏客户端核心 Redux 状态管理模块。Redux 架构 |
| `module_87072` | 24 | 游戏本体 | 战斗系统,底盘与物理系统,战斗模式,物品池和特效系统,空投与补给系统,地图与场景渲染 |
| `module_21300` | 19 | 游戏本体 | Kotlin/JS 游戏战斗内 HUD(战斗界面)与用户交互系统 |
| `module_70130` | 19 | 游戏本体 | 游戏战斗场景(Battle)的模型注册与初始化模块 |
| `module_71105` | 16 | 游戏本体 | 游戏资源加载系统(Resource Loader) |
| `module_84441` | 13 | 第三方库 | 负责把文本数据编码成 QR 码的模块矩阵。qrcode 库 |
| `module_47243` | 11 | 游戏本体 | 游戏客户端"面板(Panel)系统"的协议编解码模块,用 Kotlin 编写。它是整个项目中最大的协议模块之一,定义了面板 UI 相关的所有枚举、数据类、编解码器。 |
| `module_52943` | 9 | Kotlin生态库 | 网页游戏/应用底层工具库,认设备,管键盘,翻译,报错,调用浏览器本地存储,服务管理 |
| `module_24923` | 9 | Kotlin生态库 | CSS-in-JS 样式引擎(类似 styled-components / Emotion / Stitches 的实现),用 Kotlin 编写并编译成 JS。它负责在运行时动态生成、管理和注入 CSS,支持 React 组件样式、主题、媒体查询、动画等。 |
| `module_57128` | 9 | 游戏本体 | UI 交互与触控/鼠标手势处理模块 |
| `module_85183` | 8 | 游戏本体 | 定义并注册游戏中各种网络协议消息的编解码器(Codec),用于在客户端与服务器之间序列化/反序列化数据。 |
| `module_3569` | 8 | 游戏本体 | 游戏客户端的输入与坦克预览模块 |
### 2.3 叶子模块(无出度,共 29 个)
其中属于游戏本体的(1 个):`module_26482`
其余叶子模块:`module_905`(第三方库)、`module_7793`(第三方库)、`module_13532`(第三方库)、`module_14766`(第三方库)、`module_15330`(第三方库)、`module_17259`(第三方库)、`module_17323`(第三方库)、`module_19074`(第三方库)、`module_21973`(第三方库)、`module_27736`(第三方库)、`module_29545`(第三方库)、`module_31509`(第三方库)、`module_31584`(Kotlin生态库)、`module_37036`(Kotlin生态库)、`module_38335`(第三方库)、`module_43319`(第三方库)、`module_44794`(第三方库)、`module_55920`(第三方库)、`module_57328`(第三方库)、`module_64280`(第三方库)、`module_66610`(第三方库)、`module_76074`(第三方库)、`module_77672`(第三方库)、`module_78231`(第三方库)、`module_78931`(第三方库)、`module_79763`(第三方库)、`module_94965`(Kotlin生态库)、`module_99295`(第三方库)
### 2.4 孤立模块(既不被依赖也不依赖别人,共 3 个)
- `module_26482`(0.7 KB,游戏本体)Service Worker 注册模块
- `module_76074`(1.4 KB,第三方库)Yandex Metrica(俄版 Google Analytics)的集成模块,用于加载分析脚本并暴露事件上报接口。
- `module_78231`(5.2 KB,第三方库)WebView JavaScript Bridge 的加载与实现模块,用于原生 App(iOS/Android)与网页之间的双向通信。
## 3. 游戏本体模块逐条(40 个)
### `module_82347`(5370 KB)
- 职责:负责注册大厅、车库、商店、部落、战斗结算等几乎所有核心 UI 界面的组件样式与逻辑。
- 依赖的模块:`59411`, `7552`, `40293`, `28485`, `1901`, `14766`, `37036`, `52943`, `59131`, `90875`, `24923`, `61860`, `10032`, `57128`, `18334`, …(共 46 个)
- 顶层类 / 其它顶层函数:5256 / 5656
- 锚点:`imageBackgroundMessageError`、`getDerivedStateFromError`、`commonBlockMessageError`
### `module_87072`(3687 KB)
- 职责:战斗系统,底盘与物理系统,战斗模式,物品池和特效系统,空投与补给系统,地图与场景渲染
- 依赖的模块:`37036`, `52943`, `61860`, `24635`, `85183`, `61378`, `10032`, `10849`, `32088`, `31908`, `72834`, `55088`, `3569`, `92980`, `97556`, …(共 24 个)
- 顶层类 / 其它顶层函数:3094 / 3327
- 锚点:`Invalid weapon effect timings`、`Mesh texture not found`、`Invalid supply type `
### `module_10032`(1501 KB)
- 职责:游戏客户端核心 Redux 状态管理模块。Redux 架构
- 依赖的模块:`37036`, `10849`, `9539`, `52943`, `61430`, `31908`, `89803`, `47243`, `85667`, `88693`, `54199`, `41164`, `99527`, `85196`, `31584`, …(共 25 个)
- 顶层类 / 其它顶层函数:1100 / 1196
- 锚点:`FPS seems awful but cannot decrease the settings`、`Not found params for property `、`Unexpected response status `
### `module_85183`(1149 KB)
- 职责:定义并注册游戏中各种网络协议消息的编解码器(Codec),用于在客户端与服务器之间序列化/反序列化数据。
- 依赖的模块:`37036`, `24635`, `71105`, `61860`, `31908`, `32088`, `85196`, `52943`
- 顶层类 / 其它顶层函数:1164 / 1241
- 锚点:`pointCaptureStartNegativeSound`、`pointCaptureStartPositiveSound`、`pointCaptureStopNegativeSound`
### `module_92980`(803.0 KB)
- 职责:使用 WebGL 技术开发的3D 游戏引擎或大型 3D 交互应用的渲染核心模块
- 依赖的模块:`52943`, `37036`, `32088`, `97556`, `72834`, `9345`
- 顶层类 / 其它顶层函数:494 / 534
- 锚点:`Cannot use default vertex array object`、`Cannot create empty LodGroup`、`Cannot remove last layout`
### `module_47243`(521.8 KB)
- 职责:游戏客户端"面板(Panel)系统"的协议编解码模块,用 Kotlin 编写。它是整个项目中最大的协议模块之一,定义了面板 UI 相关的所有枚举、数据类、编解码器。
- 依赖的模块:`37036`, `24635`, `71105`, `31908`, `52943`, `25377`, `85196`, `85667`, `61860`, `88693`, `99527`
- 顶层类 / 其它顶层函数:605 / 643
- 锚点:`disabledNotificationTypes`、`expireActiveTimeRewards`、`expireRewardTimeRewards`
### `module_71105`(364.1 KB)
- 职责:游戏资源加载系统(Resource Loader)
- 依赖的模块:`37036`, `9539`, `89613`, `52943`, `61860`, `24635`, `9345`, `92980`, `32088`, `72834`, `16100`, `61430`, `36734`, `97556`, `7426`, …(共 16 个)
- 顶层类 / 其它顶层函数:226 / 250
- 锚点:`Unexpected 3D file format for `、`Invalid file extension `、`Unknown resource type `
### `module_21300`(347.6 KB)
- 职责:Kotlin/JS 游戏战斗内 HUD(战斗界面)与用户交互系统
- 依赖的模块:`7552`, `36659`, `82347`, `37036`, `24923`, `3569`, `10032`, `59131`, `52943`, `63383`, `10849`, `7426`, `90875`, `41164`, `31908`, …(共 19 个)
- 顶层类 / 其它顶层函数:348 / 385
- 锚点:`containerUserNickNameClanTag`、`commonContainerIconOptions`、`inputContainerSpectators`
### `module_18334`(295.9 KB)
- 职责:游戏客户端的总装配/依赖注入模块
- 依赖的模块:`37036`, `37504`, `10849`, `52943`, `10032`, `61860`, `47243`, `85667`, `88693`, `10283`, `85196`, `32088`, `3569`, `71105`, `99527`, …(共 28 个)
- 顶层类 / 其它顶层函数:237 / 266
- 锚点:`vkInitFailed`、`initError`、`Cannot find SteamAPI for SteamPaymentModel`
### `module_70130`(287.0 KB)
- 职责:游戏战斗场景(Battle)的模型注册与初始化模块
- 依赖的模块:`37036`, `52943`, `61860`, `87072`, `10032`, `92980`, `61378`, `31908`, `85183`, `10849`, `3569`, `36734`, `97556`, `32088`, `71105`, …(共 19 个)
- 顶层类 / 其它顶层函数:260 / 280
- 锚点:`onGrahpicsSettingsChange`、`onBattleSettingsChange`、`lightingEffectService`
### `module_85196`(230.2 KB)
- 职责:游戏车库(Garage)系统的协议编解码模块,属于同一个坦克对战类游戏。它负责定义和注册与“车库/装备/商店”相关的网络协议消息的二进制编解码器。
- 依赖的模块:`37036`, `24635`, `71105`, `85667`, `32088`, `61860`, `52943`
- 顶层类 / 其它顶层函数:312 / 342
- 锚点:`staticCollisionBoxes`、`mainCollisionBox`、`skyboxFrontSide`
### `module_36659`(178.4 KB)
- 职责:游戏客户端的纹理/资源注册与加载系统
- 依赖的模块:`37036`, `89613`, `52943`, `92980`, `71105`
- 顶层类 / 其它顶层函数:292 / 319
- 锚点:`desktopHudNuclearRechargeFlashBackground`、`deviceFasterHorizontalTrackingRailgun`、`deviceFasterHorizontalTrackingVulcan`
### `module_55088`(145.0 KB)
- 职责:物理引擎与碰撞检测系统,同样是 Kotlin/JS 编译产物。
- 依赖的模块:`32088`, `37036`, `52943`
- 顶层类 / 其它顶层函数:105 / 112
- 锚点:`Bound box size is zero`、`Negative mass`、`Level `
### `module_3569`(137.9 KB)
- 职责:游戏客户端的输入与坦克预览模块
- 依赖的模块:`37036`, `32088`, `52943`, `92980`, `9345`, `55088`, `97556`, `7426`
- 顶层类 / 其它顶层函数:130 / 133
- 锚点:`onPointerLockError`、`Cannot get free screen pointer!`、`Cannot find parent`
### `module_37504`(104.5 KB)
- 职责:游戏客户端"氏族(Clan)系统"的协议编解码模块,用 Kotlin 编写
- 依赖的模块:`37036`, `24635`, `52943`, `61860`
- 顶层类 / 其它顶层函数:103 / 110
- 锚点:`Enum item not found for code `、`modelRegistry`、`modelService`
### `module_61378`(92.7 KB)
- 职责:游戏世界(World)与实体-组件系统(ECS)的核心模块
- 依赖的模块:`37036`, `52943`, `92980`, `3569`, `32088`, `55088`, `89613`, `97556`
- 顶层类 / 其它顶层函数:66 / 73
- 锚点:`World.addGameEffect cannot add effect during update.`、`Cannot remove all while dispatching`、`Cannot dispose while dispatching`
### `module_10283`(91.4 KB)
- 职责:Kotlin/JS 编译后的协议模型注册模块
- 依赖的模块:`37036`, `24635`, `71105`, `52943`, `61860`, `67222`
- 顶层类 / 其它顶层函数:77 / 82
- 锚点:`proBattlesLimitSound`、`socialNetworkParams`、`entranceBackgrounds`
### `module_97675`(90.0 KB)
- 职责:WebView JS‑Native 双向通信桥(Bridge)
- 依赖的模块:`37036`, `52943`, `61430`, `9539`, `89613`
- 顶层类 / 其它顶层函数:30 / 53
- 锚点:`Invalid result structure`、`checkUnfinishedPurchases`、`finishVerifiedPurchase`
### `module_89803`(85.1 KB)
- 职责:游戏/应用中用于处理“匹配系统”和“对战大厅”网络通信和数据序列化的核心代码库。
- 依赖的模块:`37036`, `24635`, `31908`, `61860`, `71105`, `85667`, `52943`
- 顶层类 / 其它顶层函数:78 / 91
- 锚点:`matchmakingModeRanks`、`optionsDefaults`、`supportedModes`
### `module_85667`(80.6 KB)
- 职责:客户端协议编解码模块
- 依赖的模块:`37036`, `24635`, `71105`, `52943`, `61860`
- 顶层类 / 其它顶层函数:143 / 141
- 锚点:`browserExtensionsToDetect`、`stateWithCaptcha`、`clientKey`
### `module_32088`(79.7 KB)
- 职责:Alternativa3D,3D 数学库,用 Kotlin 编写
- 依赖的模块:`37036`, `9539`
- 顶层类 / 其它顶层函数:26 / 35
- 锚点:`Failed requirement.`、` +
this.z1z_1 +
`、` +
this.a20_1 +
`
### `module_31908`(77.2 KB)
- 职责:游戏战斗(Battle)域的协议编解码模块
- 依赖的模块:`37036`, `24635`, `88693`, `52943`, `61860`
- 顶层类 / 其它顶层函数:104 / 110
- 锚点:`experiencePremiumBonusByActions`、`suspiciousByIpUserIds`、`suspiciousUserIds`
### `module_61860`(74.9 KB)
- 职责:网络连接与游戏对象模型的核心模块,Kotlin/JS 编译产物。它把客户端和服务端连接起来,并管理游戏对象的生命周期。
- 依赖的模块:`24635`, `37036`, `52943`, `32088`, `72834`, `89613`, `61430`, `9539`
- 顶层类 / 其它顶层函数:69 / 72
- 锚点:`Constructor codec not found for model `、`Param config-template not found in url`、`Config load exception with status `
### `module_9345`(69.5 KB)
- 职责:A3D 二进制 3D 模型格式 的完整解析器
- 依赖的模块:`37036`, `72834`, `32088`, `97556`
- 顶层类 / 其它顶层函数:52 / 68
- 锚点:`Unknown attribute type id `、`Unknown attribute type `、` not found `
### `module_89679`(58.4 KB)
- 职责:集成 Google Play 计费 API
- 依赖的模块:`89613`, `10032`, `37036`, `52943`, `97675`, `10849`, `56077`, `47243`
- 顶层类 / 其它顶层函数:32 / 32
- 锚点:`Failed to complete payment response with fail`、`API not supported in this browser context`、`Unknown BridgeBillingService shop type`
### `module_16100`(57.0 KB)
- 职责:Kotlin/JS 编译后的战斗地图(BattleMap)协议模型注册模块
- 依赖的模块:`37036`, `24635`, `52943`
- 顶层类 / 其它顶层函数:74 / 82
- 锚点:`collisionGeometryOutsideGamingZone`、`collisionGeometry`、`textureParameter`
### `module_25377`(52.7 KB)
- 职责:游戏客户端与服务器之间的"通知/事件"协议编解码层,用 Kotlin 编写。它基于模块 24635 的二进制编解码框架,定义了所有服务器→客户端的通知消息类型及其序列化格式。
- 依赖的模块:`37036`, `24635`, `31908`, `52943`, `61860`, `85667`, `99527`
- 顶层类 / 其它顶层函数:61 / 69
- 锚点:`Enum item not found for code `、`wsProto`、`modelRegistry`
### `module_13431`(46.6 KB)
- 职责:Kotlin/JS 编译后的游戏客户端启动入口模块,负责整个游戏的初始化、渲染和启动流程。
- 依赖的模块:`30285`, `81886`, `37036`, `82347`, `21300`, `10032`, `89613`, `41164`, `52943`, `7426`, `3569`, `92980`, `10849`, `97675`, `61860`, …(共 46 个)
- 顶层类 / 其它顶层函数:44 / 38
- 锚点:`onVisibilityChanged`、`lockScreenLandscape`、`requestFullscreen`
### `module_36734`(40.5 KB)
- 职责:游戏客户端的音频系统,用 Kotlin 编写。它基于 Web Audio API 实现了完整的音效播放引擎,包括:音频解码、声道管理、优先级调度、3D 空间音效、音量/静音控制、分组增益
- 依赖的模块:`37036`, `32088`, `52943`, `89613`, `7426`
- 顶层类 / 其它顶层函数:35 / 26
- 锚点:`audioContextService`、`onChannelRecycled`、`effectiveGain`
### `module_54199`(39.8 KB)
- 职责:游戏聊天系统的网络协议编解码模块
- 依赖的模块:`37036`, `24635`, `88693`, `52943`, `61860`
- 顶层类 / 其它顶层函数:37 / 54
- 锚点:`linksWhiteList`、`battleIdHex`、`battleLinks`
### `module_88693`(34.3 KB)
- 职责:网络协议与数据模型层的实现,主要负责处理客户端与服务器之间关于社交、好友、举报、通知权限等非战斗数据的序列化与反序列化,并管理相关的网络路由和回调。
- 依赖的模块:`37036`, `24635`, `52943`, `61860`
- 顶层类 / 其它顶层函数:27 / 49
- 锚点:`Enum item not found for code `、`No enum constant projects.tanks.multiplatform.users.model.keys.KeyTypeEnum.`、`modelRegistry`
### `module_1510`(30.9 KB)
- 职责:库Kotlin/JS 编译的支付/交易协议处理器模块
- 依赖的模块:`37036`, `24635`, `52943`, `61860`
- 顶层类 / 其它顶层函数:31 / 47
- 锚点:`questToAchievementMap`、`itemIdMap`、`Enum item not found for code `
### `module_57128`(29.9 KB)
- 职责:UI 交互与触控/鼠标手势处理模块
- 依赖的模块:`14766`, `32088`, `37036`, `52943`, `31908`, `92980`, `71105`, `7426`, `59131`
- 顶层类 / 其它顶层函数:26 / 13
- 锚点:`Not found icon for `、`getBattleModeLocalizedShortName`、`intertialScroll`
### `module_45686`(15.0 KB)
- 职责:游戏客户端的模型注册与空间(Space)加载系统,用 Kotlin 编写。它负责把服务器下发的模型数据注册到客户端的模型注册表中,并管理"空间"(游戏场景/实例)的加载与切换。
- 依赖的模块:`37036`, `61860`, `29554`, `52943`, `71105`, `24635`
- 顶层类 / 其它顶层函数:3 / 24
- 锚点:`onError`、`gameTypeRegistry`、`modelRegister`
### `module_41164`(13.2 KB)
- 职责:游戏客户端的分析(Analytics)与埋点系统,用 Kotlin 编写。它负责向多个分析平台(Google Analytics、Yandex Metrica、自有分析服务)上报用户行为事件,包括登录、注册、战斗开始、购买、教程进度等。
- 依赖的模块:`37036`, `61430`, `97675`, `52943`
- 顶层类 / 其它顶层函数:8 / 22
- 锚点:`currencyCode`、`ecommerce`、`substage`
### `module_99527`(10.9 KB)
- 职责:Protobuf媒体编解码器 + 时间周期配置 + 参数校验
- 依赖的模块:`37036`, `24635`, `52943`, `61860`
- 顶层类 / 其它顶层函数:20 / 11
- 锚点:`FORBIDDEN`、`Enum item not found for code `、`modelRegistry`
### `module_29554`(9.7 KB)
- 职责:游戏客户端的模型/对象数据加载协议编解码模块
- 依赖的模块:`37036`, `24635`, `61860`, `71105`, `52943`
- 顶层类 / 其它顶层函数:18 / 0
- 锚点:`modelsData`、`modelService`、`_loadDependencies_dependenciesCodec`
### `module_67222`(8.1 KB)
- 职责:社交网络登录与账号绑定的网络协议模块
- 依赖的模块:`37036`, `24635`, `52943`, `61860`
- 顶层类 / 其它顶层函数:19 / 2
- 锚点:`failedRedirectUrl`、`codec_failedRedirectUrl`、`Enum item not found for code `
### `module_85745`(4.0 KB)
- 职责:资源图片帧动画相关的网络序列化 / 反序列化
- 依赖的模块:`37036`, `24635`, `52943`
- 顶层类 / 其它顶层函数:5 / 2
- 锚点:`Enum item not found for code `、` +
this.y1we_1 +
`、` +
this.z1we_1 +
`
### `module_26482`(0.7 KB)
- 职责:Service Worker 注册模块
- 依赖的模块:无(DependsOn 为空)
- 顶层类 / 其它顶层函数:0 / 0
- 锚点:`serviceWorker`、`SW registration failed: `、`manifest`
## 4. 依赖层次(由 DependsOn 计算,非手工绘制)
Level 定义:无依赖 = Level 0,否则 = 1 + 依赖的最大 Level(存在少量循环依赖,回边被递归守卫截断,取近似最长链)。
- **Level 0**(29 个):游戏本体 1 个(`26482`);Kotlin生态库/第三方库 28 个
- **Level 1**(24 个):游戏本体 0 个;Kotlin生态库/第三方库 24 个
- **Level 2**(16 个):游戏本体 1 个(`32088`);Kotlin生态库/第三方库 15 个
- **Level 3**(10 个):游戏本体 0 个;Kotlin生态库/第三方库 10 个
- **Level 4**(5 个):游戏本体 1 个(`9345`);Kotlin生态库/第三方库 4 个
- **Level 5**(3 个):游戏本体 0 个;Kotlin生态库/第三方库 3 个
- **Level 6**(5 个):游戏本体 4 个(`92980`、`55088`、`97675`、`36734`);Kotlin生态库/第三方库 1 个
- **Level 7**(5 个):游戏本体 5 个(`3569`、`61860`、`16100`、`41164`、`85745`);Kotlin生态库/第三方库 0 个
- **Level 8**(8 个):游戏本体 7 个(`71105`、`37504`、`61378`、`88693`、`1510`、`99527`、`67222`);Kotlin生态库/第三方库 1 个
- **Level 9**(6 个):游戏本体 6 个(`36659`、`10283`、`85667`、`31908`、`54199`、`29554`);Kotlin生态库/第三方库 0 个
- **Level 10**(5 个):游戏本体 5 个(`85196`、`89803`、`25377`、`57128`、`45686`);Kotlin生态库/第三方库 0 个
- **Level 11**(2 个):游戏本体 2 个(`85183`、`47243`);Kotlin生态库/第三方库 0 个
- **Level 12**(1 个):游戏本体 1 个(`10032`);Kotlin生态库/第三方库 0 个
- **Level 13**(2 个):游戏本体 2 个(`87072`、`89679`);Kotlin生态库/第三方库 0 个
- **Level 14**(1 个):游戏本体 1 个(`18334`);Kotlin生态库/第三方库 0 个
- **Level 15**(2 个):游戏本体 2 个(`82347`、`70130`);Kotlin生态库/第三方库 0 个
- **Level 16**(1 个):游戏本体 1 个(`21300`);Kotlin生态库/第三方库 0 个
- **Level 17**(1 个):游戏本体 1 个(`13431`);Kotlin生态库/第三方库 0 个
### 4.1 骨架图(Mermaid,节点 = 入度最高的 12 个游戏本体模块 + 入口 `13431`/装配 `18334`,边取自 DependsOn 且两端都在节点集内)
```mermaid
graph LR
N61860["61860"]
N32088["32088"]
N71105["71105"]
N31908["31908"]
N92980["92980"]
N3569["3569"]
N85196["85196"]
N85667["85667"]
N88693["88693"]
N10032["10032"]
N36734["36734"]
N36659["36659"]
N13431["13431"]
N18334["18334"]
N61860 --> N32088
N71105 --> N61860
N71105 --> N92980
N71105 --> N32088
N71105 --> N36734
N31908 --> N88693
N31908 --> N61860
N92980 --> N32088
N3569 --> N32088
N3569 --> N92980
N85196 --> N71105
N85196 --> N85667
N85196 --> N32088
N85196 --> N61860
N85667 --> N71105
N85667 --> N61860
N88693 --> N61860
N10032 --> N31908
N10032 --> N85667
N10032 --> N88693
N10032 --> N85196
N10032 --> N32088
N10032 --> N3569
N10032 --> N36659
N10032 --> N92980
N10032 --> N36734
N10032 --> N61860
N36734 --> N32088
N36659 --> N92980
N36659 --> N71105
N13431 --> N10032
N13431 --> N3569
N13431 --> N92980
N13431 --> N61860
N13431 --> N31908
N13431 --> N85667
N13431 --> N88693
N13431 --> N85196
N13431 --> N36734
N13431 --> N71105
N13431 --> N18334
N13431 --> N36659
N18334 --> N10032
N18334 --> N61860
N18334 --> N85667
N18334 --> N88693
N18334 --> N85196
N18334 --> N32088
N18334 --> N3569
N18334 --> N71105
N18334 --> N92980
N18334 --> N31908
N18334 --> N36734
```
### 4.2 节点对照表
| 节点 | 入度 | 出度 | 职责 |
|---|---|---|---|
| `61860` | 24 | 8 | 网络连接与游戏对象模型的核心模块,Kotlin/JS 编译产物。它把客户端和服务端连接起来,并管理游戏对象的生命周期。 |
| `32088` | 18 | 2 | Alternativa3D,3D 数学库,用 Kotlin 编写 |
| `71105` | 15 | 16 | 游戏资源加载系统(Resource Loader) |
| `31908` | 12 | 5 | 游戏战斗(Battle)域的协议编解码模块 |
| `92980` | 11 | 6 | 使用 WebGL 技术开发的3D 游戏引擎或大型 3D 交互应用的渲染核心模块 |
| `3569` | 8 | 8 | 游戏客户端的输入与坦克预览模块 |
| `85196` | 8 | 7 | 游戏车库(Garage)系统的协议编解码模块,属于同一个坦克对战类游戏。它负责定义和注册与“车库/装备/商店”相关的网络协议消息的二进制编解码器。 |
| `85667` | 8 | 5 | 客户端协议编解码模块 |
| `88693` | 8 | 4 | 网络协议与数据模型层的实现,主要负责处理客户端与服务器之间关于社交、好友、举报、通知权限等非战斗数据的序列化与反序列化,并管理相关的网络路由和回调。 |
| `10032` | 7 | 25 | 游戏客户端核心 Redux 状态管理模块。Redux 架构 |
| `36734` | 7 | 5 | 游戏客户端的音频系统,用 Kotlin 编写。它基于 Web Audio API 实现了完整的音效播放引擎,包括:音频解码、声道管理、优先级调度、3D 空间音效、音量/静音控制、分组增益 |
| `36659` | 6 | 5 | 游戏客户端的纹理/资源注册与加载系统 |
| `13431` | 0 | 46 | Kotlin/JS 编译后的游戏客户端启动入口模块,负责整个游戏的初始化、渲染和启动流程。 |
| `18334` | 3 | 28 | 游戏客户端的总装配/依赖注入模块 |
## 5. 未能确认 / 需注意的点
1. **dir.file 中有两处职责与源码内容不符**(JSON 的 Duty 仍按 dir.file 原样抄录,未擅自改写):
- `module_96480` 标注“Underscore.js 1.13.6”,但源码是 inline-style-prefixer 的 transition 前缀插件(`Webkit`/`Moz` 分支、`-webkit-`/`-moz-` 常量)。
- `module_94965` 标注“ReactHTML HTML 标签构建器”,但源码是两段 UMD 包裹的 underscore 实现(导出映射含 `VERSION`、`restArguments`,字面量 `1.13.6`),且没有任何 `div`/`span` 等 HTML 标签字面量。
2. 6 个模块的 `Anchors` 为空数组(源码里没有可用的稳定字面量,且没有高辨识度标识符):17323、19074、29545、31584、58309、99295。
3. 5 个模块有 require 参数但抽不到依赖:7793、14766、17323、55920、94965。推测是 webpack 模块连接(concatenation)把 require 调用内联消除,或依赖指向资源模块 id(本调研只统计代码模块 id)。
4. DependsOn 只覆盖静态 `i(<id>)` 调用:动态取模块(55920 的 `t.require(n)`)不计入;对 `static/` 资源模块的引用也不计入。
5. 顶层类/函数是按缩进 + 首字母大写的启发式统计(`TopLevelClasses` 不等于真实 Kotlin 类数量,压缩后部分类名非 PascalCase);Kind 中有若干边界判定(如 89679 Google Play 计费、52943 通用工具库、94965)按“代码来源 + 职责”人工定案,规则见 1.1。
6. 分层为最长依赖链的近似值:图中存在循环依赖,回边在递归中被截断,Level 仅供骨架参考。
+200
View File
@@ -0,0 +1,200 @@
# 3DTank 客户端关键运行链路(逆向)
范围:只覆盖「运行链路」这一半(启动/网络/状态/UI/渲染/空间)。模块依赖图见另一份产出。
证据约定:
- `[证据]` = 有稳定字符串、协议表(`names/`)、导出表或代码结构佐证,可复现。
- `[推断]` = 由调用点语义推出,未被字符串/表直接证实。
- 行号 = `3DTank/src/module_XXXX.js` 当前美化后的行号。
权威旁证:`names/method_table.json`(681) / `downlink_table.json`(680 命令, 4 容器) / `uplink_table.json`(360) / `codec_fields.json`(643 codec, SlotTypes 394) / `regdump.json`(ModelReg=`mr`, SpaceReg=`or`, Conn=`ur`) / `command_alias.json`(Spaces 别名) / `evidence.json`(126 模块, 139 DI 服务名)。
---
## 1. 启动与引导
**起点**:`module_13431`(IIFE 首参为 `t.exports`,模块体内未见 `$_$` 导出赋值 = 纯副作用入口模块,被 bundler 首次 import 即执行)
**终点**:`app-root` 上挂载 React root,转入 React/Redux 屏幕切换
| # | 关键函数 | 做什么 | 可信度 |
|---|---|---|---|
| 1 | `module_13431:1657-1718` | 按 `location.host` 判定运行环境并写 config 键 `resources`/`config-template`/`balancer`:`3dtank.com`→`https://res.3dtank.com`、`https://c{server}.3dtank.com/config.xml`、`https://3dtank.com/s/status.js`;另有 test-eu / tankionline.com(EU) / 其它(location 同源) 分支 | `[证据]` |
| 2 | `module_13431:1727-1731` | `wt(t)` = Sentry.init,dsn `sentry.tankionline.com/33`,`environment="china"` | `[证据]` |
| 3 | `module_13431:1734-1742` | 组装 DI 容器 `new Ln(Fu())`,`Bn(St(Mn),…)`、`Bn(St(Un),…)`,暴露 `window.skipEntranceAnyKey`/`noLogoutConfirmation` | `[证据]` |
| 4 | `module_13431:1776-1786` | `In(Tn, ui().fy(), hn, si(), cb)` 启动引导协程 `ss`(状态机 `ss.r9` 在 1400-1435) | `[证据]` |
| 5 | `module_13431:1400` | 引导 case0:`fr(Fu())` = 网络子系统初始化(→ `module_61860:2710` `$_$.z`) | `[证据]` |
| 6 | `module_13431:1404` | `dr(pr(Fu(),"resources"), WeakRef)` = 资源加载器初始化(`dr`=`module_71105.$_$.v`) | `[证据]`(映射)/`[推断]`(语义) |
| 7 | `module_13431:578-597` `Vu()` | 依次 `new X().t80()` × 17:按固定顺序注册 17 个协议域的 codec | `[证据]` |
| 8 | `module_13431:1408` | `yr(Fu())` = 创建 Redux store(`yr = module_10032.$_$.w1r`,见 `13431:242`) | `[证据]` |
| 9 | `module_13431:1424` | `Qn().x74() < 3`(GLES<3)→ `Ju()` 降级根;否则走完整分支 | `[证据]` |
| 10 | `module_13431:625-637` `Xu`/`Ju` | `Ju`:移除 `#preloader`,`pt(document.getElementById("app-root")).render(Xi(hs))` 挂 React root | `[证据]` |
| 11 | `module_13431:638-643` `Zu` | 正式根:`pt(#app-root).render(Xi(cs))`(`pt = i.createRoot`,`i`= `module_81886` ReactDOM client,见 13431:78) | `[证据]` |
| 12 | `module_13431:644-734` `Qu` | 注册 `visibilitychange`、`pointerup`→`requestFullscreen`、`fullscreenchange`→`screen.orientation.lock("landscape")`、`resize` | `[证据]` |
| 13 | `module_13431:565-567` `Wu` | 原生桥就绪回调:取 DI `"serverConnector"` 并 `l7i(new _s(bridge))`,写入 `bridge/bridge_version` | `[证据]` |
| 14 | `module_13431:1517+`(`Is.n1h7` 分支,1270-1302) | 按 store 的 `screen` 字段 instanceof 分派渲染登录/车库/战斗等屏幕 | `[推断]` |
**首次网络连接**:由步骤 5 的 `fr(config)` 触发——`module_61860:2710` `$_$.z(config)` 注册 `Ne` 协议并对 `ae`(网络协调器/balancer) 绑定 `yi`/`an`;`ae` 通过 `module_61860:2632` `new Ie(proto,host,port,…)` 建连。WebSocket 本体在 `Ie` 构造器 `module_61860:981-993`。`[证据]`(2710/981/992) + `[推断]`(「fr 即首个连接触发点」)。
---
## 2. 网络层
**起点**:`ae`(网络协调器,`module_61860:$_\$.z` 创建)→ `Ie`(单条 WebSocket 连接)
**终点**:`ModelReg.l7n` 把一帧记录派发到具体 model codec 的 `q7i`,状态写回 Redux
**codec 注册顺序**(连接建立前一次性完成)
1. `module_13431:578` `Vu()`:顺序即 17 个 `new X().t80()` 的书写顺序。`[证据]`
2. 每个协议域的 `t80()`(例:`module_85183:23527` `Ln(fq).t80`)做两类注册:
- `modelService.h7n(Xn(ModelId), Xn(MethodId))` → 写 `ModelReg.v7n_1`(methodId→modelId)。`module_85183:23529` 起共 249 条;全工程 `h7n` ≈700 条,与 `method_table.json` 681 条对应。`[证据]`
- `<svc>.j7n(<modelId 槽位>, <codecProvider>.n7f(new TypeCodecInfo(kClass(类),!1)))` → 写 `ModelReg.w7n_1`(modelId→codec 实例),例 `module_85183:24559,24596,24784`。`[证据]`(与 `codec_fields.json.ModelIdBinding` 的说明逐字一致)
3. `module_29554:75-85` ModelService 自身的 4 个 methodId 槽位(`x1wc_1`…`e1wd_1` = `$(lo,hi)`:`163351191/191355032` 等),在 `module_29554:212-215` 用 `h7n` 绑定。`[证据]`
4. `module_61860:2716` `$_$.a1()` 注册基础类型 codec(`W/Ii/Ni/xr/K`)。`[证据]`
**收到一帧后的分派**(分发链)
| # | 关键函数 | 做什么 | 可信度 |
|---|---|---|---|
| 1 | `module_61860:981-1012` `Ie` ctor | 拼 URL `proto://host:port/`,`new WebSocket`,`binaryType="arraybuffer"`,挂 `onopen/onclose/onmessage` | `[证据]` |
| 2 | `module_61860:1012-1030` `onmessage` | 建帧读取器 `m7t_1.f7m(frame,data)`,循环 `Ee(t).r7f(reader,frame)` 逐条解码,再交 `n7t_1.u7m(cmd)` | `[证据]` |
| 3 | `module_61860:2111-2131` `Vr.u7m` | **帧解析**:先读 nullmap 1 bit;bit=1 → 1 字节短命令查 `Gr(space)[255&k]`(`Gr`=DI `"aliases"`,写入点 `e7p` `module_61860:2146`);bit=0 → 读 `[objectId i64][methodId i64]`;随后 `this.x7o(objectId)` 取对象、`Br(this).l7n(modelId, methodId, buf)`。`Vr.toString`=`"Space(id=…)"`(2148) | `[证据]` |
| 4 | `module_61860:1681-1692` `mr.l7n` | **最终分发函数**:`v7n_1.z2(methodId)`→modelId,`u7n_1.z2(modelId)`→codec,`codec.q7i(methodId, buf)`;失败抛 `"Model not found for methodId = …, modelId = …"`(1685 字符串) | `[证据]` |
| 5 | `module_29554`(ModelService 4 命令) | 容器命令 `LoadDependencies`/`LoadObjectsData`/`UnloadObjects`/`SetModelCommandAlias` 的负载布局 = `names/downlink_table.json.Containers` 的 `zt/pt/lt/alias`;非按字段顺序,须走容器布局 | `[证据]`(表)/`未确认`(`zt/pt/lt` 具体类所在模块,见第 6 节) |
| 6 | `module_61860:1672-1680` `mr.i7n` | 注册对象实例:写主表 `s7n_1`、`modelId→实例 u7n_1`、`类→实例 t7n_1` | `[证据]` |
**上行组帧发送(统一入口)**
| # | 关键函数 | 做什么 | 可信度 |
|---|---|---|---|
| 1 | `module_61860:2597-2613` `Ie.i7q(cmd)` | **唯一发送入口**:`l7t_1.o7g(bufReader, cmd)` 写字段 → `DataView` → `this.q7t_1.send(view)`;若中间件存在再包一层 | `[证据]` |
| 2 | `uplink_table.json` | 每条上行命令 = `Module`/`OwnerClass`/`SendFn`/`BufferField`/`Fields`;360 条命令的 `SendFn` 即该命令类自己的序列化方法 | `[证据]` |
| 3 | `module_24635` | 二进制序列化框架(`n7f`/`p7g`/`ptg`/`q7g` 家族),被 61860/85183/29554 共用 | `[推断]`(未取到行号锚点,见「未能确认」) |
---
## 3. 状态管理(Redux)
**起点**:`module_13431:1408` `yr(Fu())`(`yr = module_10032.$_$.w1r`)
**终点**:`store.qao(action)` 分发;`v8` 根 reducer 产出新 `TOState`;订阅组件更新
| # | 关键函数 | 做什么 | 可信度 |
|---|---|---|---|
| 1 | `module_10032:52887-52988` `$_$.w1r` | **store 创建点**:构造 3 个中间件(`Px`,`Vx` thunk,`i8()`)+ 初始 `TOState` + 根 reducer,`new rC(...)`,再 `Nx.ear(o)`/`Ox(ag(rC),o)` 注册进 DI | `[证据]` |
| 2 | `module_10032:1605` `rC` | store 子类,`ld.call(this,t,n,i)` 转发到框架基类(`ld` = `module_10849:91` `vt`) | `[证据]`(映射)/`[推断]`(参数角色) |
| 3 | `module_10849:91-119` `vt` | 框架 Store 构造:`this.lao_1=n`(reducer)、`this.pao_1=t`(initialState)、`this.mao_1`(订阅者),用 middlewares 折叠出 dispatch 链 `nao_1`;内层 `invokeReducer`(99-104) = `state = reducer(action, state)` 并通知订阅者 | `[证据]` |
| 4 | `module_10849:320` `qao` | **dispatch**(调用点:`module_13431:621` `as(t).qao(new Cr(i))`;`13431:819` `store.qao(new ue(p))`) | `[证据]` |
| 5 | `module_10032:14307-1440` `v8(action,state)` | **根 reducer**:逐切片委派 `T6().bt7(t,n.app_1)`、`cY().wry(t,n.bpp_1)`… 返回合并后的新状态 | `[证据]`(结构)/`[推断]`(`v8`=根 reducer) |
| 6 | `module_10032:6455-6471+` `sF` | 单个切片 reducer:`if (action instanceof X) return state.vrb(field=…)`,`vrb` = 数据类 `copy` | `[证据]` |
| 7 | `module_10032:43519-43650` `vd(w8).toString` | **根状态 = `TOState`**,字段名即根 reducer 字段(见下) | `[证据]` |
| 8 | `module_10032:52898-52903` | `thunkMiddleware`(`Vx`);`module_10849:748` 另有 `com.alternativaplatform.redux.middlewares.ConsoleDebug.DebugType.*` | `[证据]` |
| 9 | `module_10849:146-156` `$t` / `157-178` `jt` / `598` | 组件订阅表(`baq_1/caq_1`)+ 更新循环:`window.requestAnimationFrame(jt)` 调度,遍历订阅组件调 `j3t()` | `[证据]` |
| 10 | `module_10849:414,440,453,623` | 运行时断言:`"GameObject already subscribed! modelId="`、`"Model already subscribed!"`、`"Key already subscribed!"`、`"Component not subscribed"` → 订阅模型/键/组件 | `[证据]` |
**根 reducer 字段(`TOState`,只列名)**:
`user, screen, modalScreen, battleStatistics, battleTutorial, garage, garagePreview, challenges, loginByPassword, register, migrationState, inviteState, captchaState, passwordChangeState, <npp_1 未取到名>, system, quests, chatState, clanChatState, clan, clansStorage, tipsState, news, settings, accountSettings, lootBoxes, notifications, friends, remoteUserDataStorage, battlesSelect, maps, matchmaking, battleCreateState, battleSearchState, lobbyLoader, battleResult, battleUsers, suppliesState, matchmakingGroup, shop, battlePauseState, battleViewState, rankUpDialogState, clanUserInfo, tankActionsLogState, battleMessage, battleChat, droppableGold, home, localBattleUserState, soundSettings, gameSettings, battleSettings, graphicsSettings, controlSettings, securitySettings, purchaseDialogState, …, keyboardSettings, touchscreenHudSettings, mainQuest`
**action 形态**:Kotlin 数据类,`new XxxAction(args)`(例 `module_13431:621` `new Cr(i)`、`new Er(!1)`、`new Tr(!1)`;`13431:819` `new ue(p)`);分派统一走 `store.qao(action)`(`module_10849:320`)。副作用机制 = 中间件,无 saga/effect 层。
---
## 4. UI 系统
**起点**:`module_90875`(RBuilder 核心)+ `module_09963`(`create()` 工厂)
**终点**:React 元素树 → `module_81886 createRoot().render()`
| # | 关键函数 | 做什么 | 可信度 |
|---|---|---|---|
| 1 | `module_90875:23-53,130` `x(fn)` | 把「builder 回调」编译成 React 元素:`fn(RBuilderImpl)` → 取 children → `React.createElement` | `[证据]` |
| 2 | `module_90875:54-58` | 类名注册:`RBuilder`/`RElementBuilder`/`RBuilderImpl`/`RElementBuilderImpl`/`RComponent`(`RComponent` 继承 `React.Component`) | `[证据]` |
| 3 | `module_90875:59-115` | RBuilder API:`ban()`取 children、`can(t)`挂子、`ean`/`fan`建元素、`ian(KClass,λ)`按组件类型挂子、`gan(tag)`按标签建元素、`han(KClass,λ)`、`jan` 取 `Children.toArray`;`RElementBuilder`:`lan(key?)`/`man(key)`/`nan(ref)` | `[证据]` |
| 4 | `module_90875:76-78,67-75` | `RComponent.render()` → `uan()` → `x(this::tan)`:Kotlin 组件只需实现 `tan(RBuilder)` | `[证据]` |
| 5 | `module_90875:117-126` | `$_$.a`/`$_$.b` = RComponent 工厂(`ran`/`san` 初始化 state) | `[证据]` |
| 6 | `module_90875:131-145` | `$_$.g` = `React.forwardRef` 包装 | `[证据]` |
| 7 | `module_09963:21-32` | `create(name, fn)` = 函数组件工厂(`displayName=name`,渲染 `Fragment` 包裹的 children) | `[证据]` |
| 8 | `module_13431:774-784` | 最小真实组件渲染体示例(见下) | `[证据]` |
| 9 | `module_82347:1308-1311` | `hq = Co.$_$.c`、`fq = Co.$_$.kd`、`jq = Co.$_$.fd`(`Co` = 被 import 的 stdlib/UI 导出包) | `[证据]`(映射) |
| 10 | `module_37036:1249,1252,1257,1328` | `Qr`(=`fd`)=defineClass、`ne`(=`kd`)=**defineObject**、`ge`(=`cd`)=defineProperty/delegate、`te`=公共实现 | `[证据]` |
**最小真实组件示例**(`module_13431:774-784`,去掉 Kotlin 的 `ps()` 初始化后即此形):
```js
function hs(t) { // t = RBuilder
return (ps(), t.gan(Ir()), Ct); // gan(tag) 建一个标签元素
}
function cs(t) {
var n = St(Uu); // St(...) 取组件类
return (t.ian(n, ls), Ct); // ian(组件类, 渲染λ) 挂子节点
}
```
另一形态是 `kt(Uu).tan = function(t){ t.ian(...) }`(`module_13431:1238+`):组件类实现 `tan(RBuilder)`。
**样式注入 / 类名生成**
- `module_24923` = CSS-in-JS 引擎:`StyledDOMBuilder`(626)、`Styled`(627)、`StyledDOMBuilderImpl`(628)、`globalThis.StyledNext`(716);类名与使用追踪见 `"UsedCssInfo(className=…, usedBy=…, groupId=…)"`(893)、`"CssInfo(isFresh=…, className=…)"`(913);注入点 `document.styleSheets`(650-655)。`[证据]`
- `module_63383` = CSS 类型核心:`"StyledElement"`(5792)、`"BorderStyle"`(5825)、`"FontStyle"`(5830)、`"ListStyleType"`(5844)、`"StyleList"`(5852)、`"StyledElementImpl"`(5855)。`[证据]`
- `module_59131` = kotlinx.html DSL(标签/属性构建),`module_50824` 用 `React.createElement`(`50824:14`)。`[证据]`(仅导入事实)/`未确认`(具体标签绑定行号)
**`82347` 里 248 个 `fq(cls, "XxxStyle", …)` 是什么、谁消费(关键)**
- 它们是 **Kotlin `object` 声明**,不是组件、也不是 CSS 类名注册:`fq = Co.$_$.kd`,而 `module_37036:1257` `ne(t,n,…)` → `te("object", …)`,`te`(`module_37036:1252-1255`) 只做 `prototype`/`$metadata$` 挂载。所以 `fq(y7,"AnnouncementComponentStyle",hq,K$)` = 「定义一个名为 AnnouncementComponentStyle 的对象」。`[证据]`
- 数量实测:`module_82347` 内 `fq(` 442 次,其中带 `"…Style"` 字面量 248 次(与挖出的 267 个 `*Style` 类名同一来源)。`[证据]`
- **消费方**:这些 `*Style` 对象是 CSS 声明集合(`module_63383` 的 StyleList/StyledElement 树),由 CSS-in-JS 运行时(`module_24923` 的 `Styled`/`StyledDOMBuilderImpl`)在组件渲染时读取并生成 className、注入 `document.styleSheets`。`[推断]`(未抓到「某组件读某 Style 对象」的具体赋值行,见「未能确认」)
- 类似地 `module_13431:1128` `du(Gs,"StartScreenComponentStyle",hn,wu)`(`du = m.$_$.kd`)也是同一 defineObject 调用,印证命名族一致。`[证据]`
---
## 5. 资源与渲染
**起点**:`module_13431:1404` `dr(<resources URL>, WeakRef 实现)`
**终点**:WebGL 提交(`module_92980`)由每帧 ticker 驱动
| # | 关键函数 | 做什么 | 可信度 |
|---|---|---|---|
| 1 | `module_45686:73-87` `ut` | DI 取 `"resourceLoaderService"` | `[证据]` |
| 2 | `module_45686:145-159` `gt` | DI 取 `"resourceRegistry"`(同一服务也被 `module_71105:12407` 取出并 `.z83()`) | `[证据]` |
| 3 | `module_71105`(`.z2(key)` 大量调用点,如 `4009/4048/4060/5475/5488`) | 资源按 **Map key 查表**:`registry.z2(key)`(Kotlin `Map.get`);未在任何模块发现字符串字面量 key(`module_36659` 内 `"…"` key 为 0),key 是对象/KClass 而非字符串 | `[证据]`(z2 即 Map.get、无字符串 key)/`[推断]`(key 具体类型) |
| 4 | `module_92980:20348,20357,20360` | WebGL 提交面:`clear(t)`、`drawElements(t,n,i,r)`、`drawElementsInstanced(...)`,全部转发到内部 GL 上下文对象 `b74_1` | `[证据]` |
| 5 | `module_52943:2284-2323` `Tc` | **帧循环**:`animationFrameCallback` 里 `tick(); requestAnimationFrame(自)` 自续;另有 33ms `setInterval` 后台计时(`Ec.m2o_1=33`)与 `visibilitychange` 切换 | `[证据]` |
| 6 | `module_36734:1460`、`module_70130:1225` | DI `"tickerService"` + `.u2f(callback, priority)`:游戏逻辑/音频按优先级挂每帧回调 | `[证据]` |
| 7 | `module_10849:157-178,598` | UI 侧每帧循环:`requestAnimationFrame(jt)` → 遍历订阅组件调 `j3t()`(与 3D ticker 是两条独立循环) | `[证据]` |
| 8 | `module_32088` | Alternativa3D 数学库(Vector/Matrix/四元数),被动调用 | `[推断]`(未取行号) |
**每帧做的事(合并两条循环)**:①`tickerService` 回调链(优先序)更新逻辑/物理/战斗;②`module_92980` GL 上下文 `clear`+`drawElements*` 提交;③UI 侧 `jt()` 刷新订阅组件。`[证据]`(①②③各自) + `[推断]`(顺序)
**场景图 / 相机 / 实体组织**
| # | 关键函数 | 做什么 | 可信度 |
|---|---|---|---|
| 1 | `module_61378` | World/ECS 核心:`"World.addGameEffect cannot add effect during update."`(1806)、`"Entity is not set"`(2329)、`"Entity is already initialized"`(2414)、`"BattleEntity.createComponent duplicate component class …"`(2441) → World 持有 Entity,BattleEntity 由 Component 组成 | `[证据]` |
| 2 | `module_87072` | 战斗域:底盘/物理/模式/物品池/特效/空投/地图场景;内含 17 处 `.t80()` 注册(与 13431 的 17 个协议域同构) | `[证据]`(17×t80)/`[推断]`(其余职责描述来自目录表) |
| 3 | `module_61860:2111-2131` `Vr.u7m` | 网络对象挂在 **Space** 上(`Vr`=`Space`),Space 是场景容器,持有对象表 `Gr`(aliases)+spaceRegistry | `[证据]` |
| 4 | `61378`(ECS) ↔ `87072`(战斗) | `87072` 依赖 `61378` 的 World/Entity 组织战斗实体(`BattleEntity`);相机与空间对象随 Space 创建 | `[推断]` |
---
## 6. 空间与对象模型
**起点**:服务端下发 SpaceCommand(`module_61860:1315-1324` `Ni.toString` 含 `objectId/methodId/protocolBuffer`)
**终点**:对象实例进入 Space 的对象表 / ModelReg
| # | 关键函数 | 做什么 | 可信度 |
|---|---|---|---|
| 1 | `module_61860:361-363` `or` | **SpaceReg**(regdump 确认 ctor=`or`),两张表 `x7m_1`/`y7m_1`(regdump size=2,key 为 `"1"`,`"2"`) | `[证据]` |
| 2 | `module_61860:1597-1606` `or.p7g` | SpaceReg 的 codec:先解基类再解登记表 | `[证据]` |
| 3 | `module_61860:2111-2150` `Vr` | **Space 本体**:`toString` = `"Space(id=<spaceId>)"`;`u7m` 处理帧、`e7p` 写短命令别名表、`b7p`/`r7m`/`s7m`/`c7p` 为 Space 级事件 | `[证据]` |
| 4 | `module_61860:1326-1328` `Oi` | `"[SpaceOpenedCommand spaceId=… hash=…]"`:Space 打开命令(含 hash) | `[证据]` |
| 5 | `module_61860:547-561` `Ur` | DI 取 `"spaceRegistry"`(`module_45686:175-189` 同名访问器) | `[证据]` |
| 6 | `module_45686:111-120` `ft(conn, cmd)` | **对象/模型数据入注册表**:读 `[i64,i64]`(`a7g_1.q2u()`×2),前一个是当前 Space 已有对象则先 `z7o` 移除;`wt().o1we().f7n(r)` 按类 id 查类,失败抛 `"Class not found exception class=…, object=…"`,成功后 `e.y7o(objectId, class, "")` 在 Space 里建对象 | `[证据]` |
| 7 | `module_61860:1672-1680` `mr.i7n` | 模型实例注册(`s7n_1`/`u7n_1`/`t7n_1`);`module_70130` 内 `i7n` 202 次 = 战斗模型注册入口 | `[证据]`(结构)/`[推断]`(202 次归属战斗模型) |
| 8 | `module_45686:60-72,160-174` | DI 取 `"modelRegister"`/`"modelRegistry"` | `[证据]` |
| 9 | `command_alias.json.Spaces` | 已证空间别名:`100045260`=Entrance、`100045720`=Chat、`100072911`=Shop、`100073070`=Garage、`100119080`=Unknown1、`920007382011`=Queue、`3074457345817190050`=Battle(未证) | `[证据]`(表) |
**服务器下发数据 → 注册表,与已逆向容器的对应**
- 4 个 ModelService 容器命令(`names/downlink_table.json.Containers`)与布局:`LoadDependencies`(methodId 3216143066888387731, layout `zt`)、`LoadObjectsData`(7640916300855664666, `pt`)、`UnloadObjects`(9038756763225879447, `lt`)、`SetModelCommandAlias`(8179162314198150518, `alias`)。`[证据]`
- `lt`/`pt`/`zt` = 这 4 条命令的**负载容器布局类**,其负载**不按字段顺序**排列,必须按容器布局解;因此 `downlink_table` 把它们单列为 `Kind=container`。`[证据]`(表)
- 绑定流程:容器命令解出 modelId/methodId → `mr.l7n` 找到 codec(`v7n_1`/`u7n_1`)→ `codec.q7i` 解出的模型实例经 `mr.i7n` 进 `s7n_1/u7n_1/t7n_1`(模块内模型注册),对象则经 `module_45686:ft` 进当前 Space。`[证据]`(各步) / `[推断]`(串联顺序)
---
## 未能确认清单
1. **`fr(config)` 是否就是「首次建立网络连接」的唯一触发点**:`module_61860:2710` 只证实它注册协议 + 创建 `ae`;`ae`→`new Ie` 的调用点在 `module_61860:2632`(某 `ae` 方法内),未确认该方法是被 balancer 轮询还是被上层直接调用。卡点:`module_61860:2626-2632`。
2. **`module_24635` 二进制框架**:只确认被 61860/85183/29554 共用(`n7f`/`p7g`/`q7g`/`o7g`),未定位「写字节的统一函数」行号(该模块 27KB,未逐段读)。
3. **`zt`/`pt`/`lt` 三个容器类的宿主模块**:`downlink_table` 给出布局名,但 `function zt(` 在同名局部类里遍布 17 个模块,无法唯一定位到 ModelService 的容器实现(可能在 `module_29554`/`module_45686` 的局部类中,未被锚点锁定)。
4. **`TOState` 字段 `npp_1` 的名字**:`module_10032:43519-43650` 的 toString 序列中该行未读到(读取窗口边缘),其余字段名齐全。
5. **`*Style` 对象的具体消费点**:只证明「`*Style` 是 Kotlin object(defineObject),CSS 由 24923/63383 注入」,未抓到「组件读某 `*Style` 生成 className」的赋值行。卡点:`module_82347` UI 渲染体(5.5MB,只按 `fq(` 锚点取样)。
6. **`module_59131`/`module_50824` 的标签/属性绑定行号**:`50824:14` 只见 `React.createElement` 取用,`59131` 内无 `setAttribute/appendChild/createElement`,怀疑其只做 kotlinx.html DSL 结构,未取到具体绑定点。
7. **`module_32088`(3D 数学)**、**`module_87072`(战斗/物理)内部主流程**:均未展开(体积/预算),只确认其对外连接点(`87072` 的 17×`t80`、被 `13431:245` `qr=rt.$_$.h2` 以挂起函数方式调用)。
8. **`response 帧的 CS 侧封装**(`module_21300` 战斗 HUD 的 rAF 循环 `21300:12112`)未追踪**:属 UI 侧次要循环。
+292
View File
@@ -0,0 +1,292 @@
# TOState 字段 ↔ reducer 映射表
本文是 `names/tostate-map.json` 的人类可读版,用途:**重写客户端时对照根状态的每个分片该由哪个 reducer / 哪些 action 驱动**。
## 1. TOState 是什么
- **根状态类**:`TOState`,即 `module_10032.js` 里的类 `w8`;其 `toString` 以 `"TOState(user="` 开头,定义在 **`module_10032.js:43519`**(`vd(w8).toString`)。
- **字段数**:**89 个**(每个字段在源码里是 `this.<槽位>_1`,形如 `app_1` / `bpp_1` … `kps_1`)。
- **根 reducer**:`module_10032.js:14307-15449` 的函数 **`v8(action, state)`**(`[证据]`:`module_10849:91` 的 Store 构造把 `v8` 作为 reducer 传入,见 `ArchitectureRuntime.md` 第 3 节)。它把 89 个分片逐一委派给各自的分片 reducer,最后用 `state.ntb(...)`(`vd(w8).ntb`,定义 `module_10032.js:42968`,方法体就是一串 `return new w8(参数…)`)合成新 `TOState`。
- **store / dispatch**:`module_10032.js:52887` 的 `$_$.w1r` 创建 store(被 `module_13431:1408` 调用);`dispatch` 即 `module_10849:320` 的 `qao`。无 saga/effect,只有 thunk + ConsoleDebug 中间件。
- **action 形态**:Kotlin 数据类实例,`new XxxAction(...)`,经 `store.qao(action)` 分发;分片 reducer 内部用 `if (t instanceof XxxAction)` 长链分派。
> 一致性交叉验证:`v8` 末尾 `return state.ntb(i, r, N, s, o, e, u, _, c, $, q, h, a, …)` 的**实参顺序**与 `TOState` 的**字段顺序**逐位对齐(第 1 位 `i`=`app_1`=`user`,第 2 位 `r`=`bpp_1`=`screen`,…),且每一位实参本身调用的 reducer 恰好读取同一个槽位。两套独立证据吻合,故下表映射记为 `证据`。
## 2. 主表(全部 89 个字段)
「reducer 函数」列 = `v8` 中处理该槽位的调用式(源码形态);「位置」= 该调用在根 reducer 中的行;「相关 action」见第 3 节分组表。
| 业务域 | 字段 | 槽位 | reducer 函数 | 位置 | 可信度 |
|---|---|---|---|---|---|
| 登录与账号 | `user` | `app_1` | `T6().bt7` | module_10032.js:14308 | 证据 |
| UI/面板 | `screen` | `bpp_1` | `cY().wry` | module_10032.js:14309 | 证据 |
| UI/面板 | `modalScreen` | `cpp_1` | `Us.wru` | module_10032.js:14342 | 证据 |
| 战斗 | `battleStatistics` | `dpp_1` | `rr.tqk` | module_10032.js:14312 | 证据 |
| 战斗 | `battleTutorial` | `epp_1` | `Q.uq2` | module_10032.js:14313 | 证据 |
| 车库与物品 | `garage` | `fpp_1` | `sF` | module_10032.js:14310 | 证据 |
| 车库与物品 | `garagePreview` | `gpp_1` | `ls.rrn` | module_10032.js:14311 | 证据 |
| 任务与内容 | `challenges` | `hpp_1` | `ta.tsc` | module_10032.js:14314 | 证据 |
| 登录与账号 | `loginByPassword` | `ipp_1` | `nu.qqz` | module_10032.js:14317 | 证据 |
| 登录与账号 | `register` | `jpp_1` | `fw.dta` | module_10032.js:14330 | 证据 |
| 登录与账号 | `migrationState` | `kpp_1` | `Gv.cta` | module_10032.js:14329 | 证据 |
| 登录与账号 | `inviteState` | `lpp_1` | `Ke.sqy` | module_10032.js:14316 | 证据 |
| 登录与账号 | `captchaState` | `mpp_1` | `hr.wqm` | module_10032.js:14315 | 证据 |
| 登录与账号 | `passwordChangeState` | `npp_1` | `uu.dr0` | module_10032.js:14341 | 证据 |
| 网络/系统 | `system` | `opp_1` | `l4().jt3` | module_10032.js:14318 | 证据 |
| 任务与内容 | `quests` | `ppp_1` | `(null == qw && new Vrt(), qw).zt9` | module_10032.js:14321 | 证据 |
| 聊天/社交 | `chatState` | `qpp_1` | `jr.kqp` | module_10032.js:14322 | 证据 |
| 聊天/社交 | `clanChatState` | `rpp_1` | `xv.bta` | module_10032.js:14323 | 证据 |
| 聊天/社交 | `clan` | `spp_1` | `pe.vqt` | module_10032.js:14325 | 证据 |
| 聊天/社交 | `clansStorage` | `tpp_1` | `me.jqv` | module_10032.js:14326 | 证据 |
| UI/面板 | `tipsState` | `upp_1` | `Vp.gtb` | module_10032.js:15348 | 证据 |
| UI/面板 | `news` | `vpp_1` | `C_.ms6` | module_10032.js:14327 | 证据 |
| 设置 | `settings` | `wpp_1` | `Sp.wt9` | module_10032.js:14319 | 证据 |
| 设置 | `accountSettings` | `xpp_1` | `Uw.xt9` | module_10032.js:14320 | 证据 |
| 车库与物品 | `lootBoxes` | `ypp_1` | `ws.jrp` | module_10032.js:14328 | 证据 |
| UI/面板 | `notifications` | `zpp_1` | `L_.us7` | module_10032.js:14331 | 证据 |
| 聊天/社交 | `friends` | `apq_1` | `AL().qr2` | module_10032.js:14332 | 证据 |
| 网络/系统 | `remoteUserDataStorage` | `bpq_1` | `fa.use` | module_10032.js:14333 | 证据 |
| 空间与场景 | `battlesSelect` | `cpq_1` | `Oi.wqe` | module_10032.js:14334 | 证据 |
| 空间与场景 | `maps` | `dpq_1` | `Mi.gqf` | module_10032.js:14335 | 证据 |
| 空间与场景 | `matchmaking` | `epq_1` | `WH().drr` | module_10032.js:14336 | 证据 |
| 空间与场景 | `battleCreateState` | `fpq_1` | `Uf.eta` | module_10032.js:14338 | 证据 |
| 空间与场景 | `battleSearchState` | `gpq_1` | `Gtt().gta` | module_10032.js:14339 | 证据 |
| UI/面板 | `lobbyLoader` | `hpq_1` | `Ls.rru` | module_10032.js:14340 | 证据 |
| 战斗 | `battleResult` | `ipq_1` | `Fi.oqi` | module_10032.js:14343 | 证据 |
| 战斗 | `battleUsers` | `jpq_1` | `sr.vql` | module_10032.js:14344 | 证据 |
| 战斗 | `suppliesState` | `kpq_1` | `jt.hq3` | module_10032.js:15249 | 证据 |
| 空间与场景 | `matchmakingGroup` | `lpq_1` | `TW().vrt` | module_10032.js:14337 | 证据 |
| 车库与物品 | `shop` | `mpq_1` | `(inline IIFE)@module_10032.js:14345` | module_10032.js:14345 | 证据 |
| 战斗 | `battlePauseState` | `npq_1` | `RT().oq6` | module_10032.js:15251 | 证据 |
| 战斗 | `battleViewState` | `opq_1` | `(null == jn && new eI(), jn).lq7` | module_10032.js:15250 | 证据 |
| 战斗 | `rankUpDialogState` | `ppq_1` | `Se.pqw` | module_10032.js:15252 | 证据 |
| 聊天/社交 | `clanUserInfo` | `qpq_1` | `de.uqu` | module_10032.js:14324 | 证据 |
| 战斗 | `tankActionsLogState` | `rpq_1` | `pi.iq8` | module_10032.js:15253 | 证据 |
| 战斗 | `battleMessage` | `spq_1` | `lT().tq5` | module_10032.js:15254 | 证据 |
| 聊天/社交 | `battleChat` | `tpq_1` | `Kf.hta` | module_10032.js:15255 | 证据 |
| 战斗 | `droppableGold` | `upq_1` | `Kw.vta` | module_10032.js:15271 | 证据 |
| UI/面板 | `home` | `vpq_1` | `(inline IIFE)@module_10032.js:15256` | module_10032.js:15256 | 证据 |
| 战斗 | `localBattleUserState` | `wpq_1` | `(inline IIFE)@module_10032.js:15265` | module_10032.js:15265 | 证据 |
| 设置 | `soundSettings` | `xpq_1` | `Get().uta` | module_10032.js:15268 | 证据 |
| 设置 | `gameSettings` | `ypq_1` | `v2().osm` | module_10032.js:15270 | 证据 |
| 设置 | `battleSettings` | `zpq_1` | `W0().tsj` | module_10032.js:15269 | 证据 |
| 设置 | `graphicsSettings` | `apr_1` | `lc.wsn` | module_10032.js:15272 | 证据 |
| 设置 | `controlSettings` | `bpr_1` | `e2().tsl` | module_10032.js:15273 | 证据 |
| 设置 | `securitySettings` | `cpr_1` | `wc.dsp` | module_10032.js:15275 | 证据 |
| 车库与物品 | `purchaseDialogState` | `dpr_1` | `ze.iqw` | module_10032.js:15276 | 证据 |
| 战斗 | `battleInviteState` | `epr_1` | `Itt().yta` | module_10032.js:15277 | 证据 |
| 设置 | `keyboardSettings` | `fpr_1` | `$p.zta` | module_10032.js:15278 | 证据 |
| 设置 | `touchscreenHudSettings` | `gpr_1` | `I7().ksp` | module_10032.js:15274 | 证据 |
| 任务与内容 | `mainQuest` | `hpr_1` | `na.nsd` | module_10032.js:15279 | 证据 |
| 设置 | `language` | `ipr_1` | `f8().ut9` | module_10032.js:15330 | 证据 |
| 登录与账号 | `partner` | `kpr_1` | `(inline IIFE)@module_10032.js:15280` | module_10032.js:15280 | 证据 |
| UI/面板 | `entrance` | `lpr_1` | `Fv.atb` | module_10032.js:15331 | 证据 |
| 登录与账号 | `antiAddiction` | `mpr_1` | `W.kq2` | module_10032.js:15332 | 证据 |
| 战斗 | `spectator` | `npr_1` | `Li.xqf` | module_10032.js:15334 | 证据 |
| 登录与账号 | `moderation` | `opr_1` | `Ei.qqb` | module_10032.js:15333 | 证据 |
| 登录与账号 | `rulesUpdated` | `ppr_1` | `Sa.psf` | module_10032.js:15335 | 证据 |
| UI/面板 | `announcement` | `qpr_1` | `AC().tpz` | module_10032.js:15336 | 证据 |
| UI/面板 | `notificationByCountry` | `rpr_1` | `A_.es7` | module_10032.js:15337 | 证据 |
| 登录与账号 | `userRenameState` | `spr_1` | `Af.at8` | module_10032.js:15338 | 证据 |
| 车库与物品 | `eventGoods` | `tpr_1` | `Wc.rsq` | module_10032.js:15339 | 证据 |
| 任务与内容 | `creatorTagState` | `upr_1` | `ke.sqv` | module_10032.js:15340 | 证据 |
| 登录与账号 | `twoStepVerificationState` | `vpr_1` | `_f.mt4` | module_10032.js:15341 | 证据 |
| 任务与内容 | `rewardedVideosState` | `wpr_1` | `ja.lsf` | module_10032.js:15342 | 证据 |
| 任务与内容 | `twitchState` | `xpr_1` | `uf.st3` | module_10032.js:15343 | 证据 |
| UI/面板 | `icons` | `ypr_1` | `fs.xrn` | module_10032.js:15344 | 证据 |
| UI/面板 | `itemNotifications` | `zpr_1` | `C1().ssh` | module_10032.js:15345 | 证据 |
| 战斗 | `contextTutorial` | `aps_1` | `Yst().etb` | module_10032.js:15346 | 证据 |
| 战斗 | `suppliesTutorial` | `bps_1` | `Gp.ftb` | module_10032.js:15347 | 证据 |
| 任务与内容 | `questCategories` | `cps_1` | `Q_.xsa` | module_10032.js:15349 | 证据 |
| 空间与场景 | `proBattle` | `dps_1` | `_v.htb` | module_10032.js:15350 | 证据 |
| 空间与场景 | `availabilityByRank` | `eps_1` | `zw.itb` | module_10032.js:15351 | 证据 |
| 登录与账号 | `userReportsState` | `fps_1` | `(null == _d && new kot(), _d).ktb` | module_10032.js:15352 | 证据 |
| 车库与物品 | `tankBuilderState` | `gps_1` | `gw.ltb` | module_10032.js:15353 | 证据 |
| 任务与内容 | `contractState` | `hps_1` | `Sv.mtb` | module_10032.js:15354 | 证据 |
| 任务与内容 | `promoCode` | `ips_1` | `Z_.ksa` | module_10032.js:15355 | 证据 |
| 任务与内容 | `referralEvent` | `jps_1` | `ca.ase` | module_10032.js:15356 | 证据 |
| 登录与账号 | `supportUrl` | `kps_1` | `Yl.ot0` | module_10032.js:15357 | 证据 |
| 网络/系统 | `launchParams` | `jpr_1` | —(透传,无 reducer) | module_10032.js:15328 | 未映射 |
## 3. 按业务域分组
每组给出字段 → reducer 函数(`(inline IIFE)@…` 表示根 reducer 内联的匿名 reducer,其函数体直接写在 `v8` 里,没有独立函数名)。相关 action 见 `names/tostate-map.json` 各 entry 的 `Actions`;下表为高频代表。
### 3.1 登录与账号(15 字段)
- `user`→`T6().bt7`、`loginByPassword`→`nu.qqz`、`register`→`fw.dta`、`migrationState`→`Gv.cta`、`inviteState`→`Ke.sqy`、`captchaState`→`hr.wqm`、`passwordChangeState`→`uu.dr0`、`userRenameState`→`Af.at8`、`twoStepVerificationState`→`_f.mt4`、`antiAddiction`→`W.kq2`、`moderation`→`Ei.qqb`、`rulesUpdated`→`Sa.psf`、`partner`→`(inline IIFE)@15280`、`supportUrl`→`Yl.ot0`、`userReportsState`→`(null == _d && new kot(), _d).ktb`
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `user` | `T6().bt7` @14308 | SetClanInfo, UpdateCrystals, SpendCrystals, AddCoins, SetCoins, SpendCoins, SetHasTemporaryCash, AddRubies …(共 27 个) |
| `loginByPassword` | `nu.qqz` @14317 | SetLogin, SetPassword, SetRememberMe, SetStatus, IncorrectLogin2FA …(共 9 个) |
| `register` | `fw.dta` @14330 | SetUid, ForceSetUid, SetPassword, EnteredUidIsBusy …(共 19 个) |
| `migrationState` | `Gv.cta` @14329 | SetUid, ForceSetUid, SetPassword …(共 11 个) |
| `inviteState` | `Ke.sqy` @14316 | SetInviteNeed, SetInviteStatus, ApplyInvite |
| `captchaState` | `hr.wqm` @14315 | SetLocations, LocationsUpdated |
| `passwordChangeState` | `uu.dr0` @14341 | SetPasswordChangeHash |
| `userRenameState` | `Af.at8` @15338 | ChangeUidInputState, SetValidationStatus, SetRubiesForRename, ResponseDelivered …(共 11 个) |
| `twoStepVerificationState` | `_f.mt4` @15341 | SetUse2FA, ActivationStatus2FA, SetSecretAndQRCode, VerificationStatus, Enable2FaRequest |
| `antiAddiction` | `W.kq2` @15332 | SetRealNameAndIdNumber, SetValidateAntiAddictionUserDataStatus |
| `moderation` | `Ei.qqb` @15333 | …(共 13 个) |
| `rulesUpdated` | `Sa.psf` @15335 | UpdateRules |
| `partner` | `(inline IIFE)@15280` | SetPartnerId, SetLoginParams, SetPartnerParameters, InitPartnerInviteAction, SetLoginStatusForBinding …(共 11 个) |
| `supportUrl` | `Yl.ot0` @15357 | SetUrl |
| `userReportsState` | `(…, _d).ktb` @15352 | SetAvailableReports, SetAvailableGameReports, ReportProBattleName, ReportLobbyChatMessage, ReportBattleChatMessage |
### 3.2 UI/面板(12 字段)
- `screen`→`cY().wry`、`modalScreen`→`Us.wru`、`home`→`(inline IIFE)@15256`、`lobbyLoader`→`Ls.rru`、`tipsState`→`Vp.gtb`、`news`→`C_.ms6`、`notifications`→`L_.us7`、`announcement`→`AC().tpz`、`notificationByCountry`→`A_.es7`、`icons`→`fs.xrn`、`itemNotifications`→`C1().ssh`、`entrance`→`Fv.atb`
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `screen` | `cY().wry` @14309 | GoTo, PressedAnyButton, DisplayMainEntranceScreen, DisplayReauthorizationMessage …(共 15 个) |
| `modalScreen` | `Us.wru` @14342 | AddModal, RemoveModal, RemoveCurrentByName |
| `home` | `(inline IIFE)@15256` | (3 个:`i1`/`n1`/`r1`) |
| `lobbyLoader` | `Ls.rru` @14340 | (2 个) |
| `tipsState` | `Vp.gtb` @15348 | UpdateTips |
| `news` | `C_.ms6` @14327 | SetNewsItemAction, AddNewsItemAction, RemoveNewsItemAction, SetUnreadNewsCounter |
| `notifications` | `L_.us7` @14331 | InitNotificationSound, AddNotification |
| `announcement` | `AC().tpz` @15336 | Init, SetSkipNextPromotedAnnounce |
| `notificationByCountry` | `A_.es7` @15337 | (2 个) |
| `icons` | `fs.xrn` @15344 | SetIcons |
| `itemNotifications` | `C1().ssh` @15345 | AddItemNotifications |
| `entrance` | `Fv.atb` @15331 | SetAgeWarnShow …(共 7 个) |
### 3.3 空间与场景(8 字段)
- `battlesSelect`→`Oi.wqe`、`maps`→`Mi.gqf`、`matchmaking`→`WH().drr`、`matchmakingGroup`→`TW().vrt`、`battleCreateState`→`Uf.eta`、`battleSearchState`→`Gtt().gta`、`proBattle`→`_v.htb`、`availabilityByRank`→`zw.itb`
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `battlesSelect` | `Oi.wqe` @14334 | RoundStarted, SwapTeam, RoundFinish, RemoveBattle, ChangeName …(共 20 个) |
| `maps` | `Mi.gqf` @14335 | AddMap |
| `matchmaking` | `WH().drr` @14336 | SelectMatchmakingMode, WasRegistered, EnterToBattleApply …(共 9 个) |
| `matchmakingGroup` | `TW().vrt` @14337 | InitMatchmakingGroup, SetAllowedFromFriends, AddUser, UserRemoved, Invite, AcceptedInviteToGroup …(共 13 个) |
| `battleCreateState` | `Uf.eta` @14338 | Configure, SetName, SetMode, SetMaxPeopleInTeam, SetScoreLimit, SetSearch, SetRanks, SetAvailableRanks …(共 17 个) |
| `battleSearchState` | `Gtt().gta` @14339 | SetFilter, SetSearch, SetSuspiciousFilter, Fight, EnterToBattle, SelectBattle, SelectBattleFormat, RemoveBattle …(共 9 个) |
| `proBattle` | `_v.htb` @15350 | Fight, SetPassword |
| `availabilityByRank` | `zw.itb` @15351 | SetLoadedData |
> 注意:`battleCreateState` 的 reducer `Uf.eta(t, n.fpq_1, n.dpq_1, n.app_1)` 是**三槽位联合 reducer**(同时读 `maps`、`user`),重写时不可只传自身分片。
### 3.4 车库与物品(7 字段)
- `garage`→`sF`、`garagePreview`→`ls.rrn`、`shop`→`(inline IIFE)@14345`、`lootBoxes`→`ws.jrp`、`eventGoods`→`Wc.rsq`、`purchaseDialogState`→`ze.iqw`、`tankBuilderState`→`gw.ltb`
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `garage` | `sF` @14310 | SetGarageSkinsAction …(共 60 个;`sF` 是读整个 state 的跨分片 reducer,见 `module_10032.js:6455`,内部取 `n.fpp_1`) |
| `garagePreview` | `ls.rrn` @14311 | UpdatePreviewSettings, InitGarageResources, SetExpectedItemId, AddRequestedItem …(共 22 个) |
| `shop` | `(inline IIFE)@14345` | SetCoinPaymentProviderUrl, PaymentUrlReceived, SetBraintreeToken, SetStripeIntentParams, BraintreeLoadingInterrupt …(共 69 个,是全表 action 最多的分片) |
| `lootBoxes` | `ws.jrp` @14328 | SetPossibleRewards, SetCurrentContainer, SetKeys, AddKeys, SetGoToGarage, SetRewards …(共 18 个) |
| `eventGoods` | `Wc.rsq` @15339 | InitEventGoods |
| `purchaseDialogState` | `ze.iqw` @15276 | ChangeEmailStatus …(共 3 个) |
| `tankBuilderState` | `gw.ltb` @15353 | SetKode, SetParts, SetPartsInfo |
### 3.5 战斗(16 字段)
- `battleStatistics`→`rr.tqk`、`battleTutorial`→`Q.uq2`、`battleResult`→`Fi.oqi`、`battleUsers`→`sr.vql`、`suppliesState`→`jt.hq3`、`battlePauseState`→`RT().oq6`、`battleViewState`→`(…, jn).lq7`、`rankUpDialogState`→`Se.pqw`、`tankActionsLogState`→`pi.iq8`、`battleMessage`→`lT().tq5`、`droppableGold`→`Kw.vta`、`localBattleUserState`→`(inline IIFE)@15265`、`battleInviteState`→`Itt().yta`、`spectator`→`Li.xqf`、`contextTutorial`→`Yst().etb`、`suppliesTutorial`→`Gp.ftb`
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `battleStatistics` | `rr.tqk` @14312 | SetDisplayClientInfo, SetBattleMode, SetReArmor, SetRankRange, UpdateScore, UpdateFund …(共 22 个) |
| `battleTutorial` | `Q.uq2` @14313 | ShowMessage, SetFragsCount |
| `battleResult` | `Fi.oqi` @14343 | SetRewards, SetHasBattleResult |
| `battleUsers` | `sr.vql` @14344 | UpdateUserRank, UpdateUsersClanTag, UpdateUserScoreKillsAndDeaths, SetGearScore …(共 18 个) |
| `suppliesState` | `jt.hq3` @15249 | (`kE`) |
| `battlePauseState` | `RT().oq6` @15251 | OnPauseMode, SetIdleKickPeriod |
| `battleViewState` | `(…, jn).lq7` @15250 | SetVisibility, SetElementsVisibility …(共 14 个) |
| `rankUpDialogState` | `Se.pqw` @15252 | ShowAvailableItems |
| `tankActionsLogState` | `pi.iq8` @15253 | OnUserJoinTheBattle, OnUserLeaveTheBattle, UpdateLog |
| `battleMessage` | `lT().tq5` @15254 | HideMessage, RemoveMessage |
| `droppableGold` | `Kw.vta` @15271 | (2 个) |
| `localBattleUserState` | `(inline IIFE)@15265` | SetLocalWeaponResistanceProperty, SetReArmorTemporaryDisabled |
| `battleInviteState` | `Itt().yta` @15277 | SelectUserToInvite, RemoveUserFromInvited, EnableSetBattleInviteNotifications |
| `spectator` | `Li.xqf` @15334 | SetSpectator, UpdateHudLayout |
| `contextTutorial` | `Yst().etb` @15346 | LoadCurrentElement, SetUnViewedElements, MarkElementAsViewed, SetLoadedElements, SetMissionToElement |
| `suppliesTutorial` | `Gp.ftb` @15347 | ActivatedInCurrentBattle, SetLoadedElement |
### 3.6 聊天/社交(7 字段)
- `chatState`→`jr.kqp`、`clanChatState`→`xv.bta`、`clan`→`pe.vqt`、`clansStorage`→`me.jqv`、`clanUserInfo`→`de.uqu`、`friends`→`AL().qr2`、`battleChat`→`Kf.hta`
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `chatState` | `jr.kqp` @14322 | Configure, ChangeChannel, SaveChatMessage, SetMessages, CleanUsersMessage, UpdateUsers …(共 24 个) |
| `clanChatState` | `xv.bta` @14323 | Configure, DoAddMessages |
| `clan` | `pe.vqt` @14325 | AddUsersToAccepted, RemoveUserFromAccepted, SetMinRankForCreate, MembersFetched, ClanInfoLoaded, IncomingAdded …(共 43 个) |
| `clansStorage` | `me.jqv` @14326 | ClanLoaded, ClanUnloaded, MinimalRankChanged, RecruitmentChanged, AddUser, RemoveUser …(共 13 个) |
| `clanUserInfo` | `de.uqu` @14324 | Loaded |
| `friends` | `AL().qr2` @14332 | ChangeFriendAddFindState, SetLimits, AddUsersToAccepted, RemoveUserFromAccepted ≥ …(共 13 个) |
| `battleChat` | `Kf.hta` @15255 | AddMessage …(共 6 个) |
### 3.7 设置(11 字段)
- `settings`→`Sp.wt9`、`accountSettings`→`Uw.xt9`、`soundSettings`→`Get().uta`、`gameSettings`→`v2().osm`、`battleSettings`→`W0().tsj`、`graphicsSettings`→`lc.wsn`、`controlSettings`→`e2().tsl`、`securitySettings`→`wc.dsp`、`keyboardSettings`→`$p.zta`、`touchscreenHudSettings`→`I7().ksp`、`language`→`f8().ut9`
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `settings` | `Sp.wt9` @14319 | (2 个) |
| `accountSettings` | `Uw.xt9` @14320 | …(共 13 个) |
| `soundSettings` | `Get().uta` @15268 | UpdateSettings, SetEntranceSoundEnabled |
| `gameSettings` | `v2().osm` @15270 | UpdateSettings |
| `battleSettings` | `W0().tsj` @15269 | UpdateSettings |
| `graphicsSettings` | `lc.wsn` @15272 | UpdateSettings |
| `controlSettings` | `e2().tsl` @15273 | UpdateSettings |
| `securitySettings` | `wc.dsp` @15275 | SetProfileBlockStatus |
| `keyboardSettings` | `$p.zta` @15278 | RemoveAction, ResetAction |
| `touchscreenHudSettings` | `I7().ksp` @15274 | UpdateSettings |
| `language` | `f8().ut9` @15330 | (`c8`) |
> 各 `*Settings` 分片共用同一个 `UpdateSettings` 类 action,靠 reducer 内部区分目标分片;重写时不要把它当成单一 store。
### 3.8 任务与内容(10 字段)
- `challenges`→`ta.tsc`、`quests`→`(…, qw).zt9`、`mainQuest`→`na.nsd`、`questCategories`→`Q_.xsa`、`contractState`→`Sv.mtb`、`promoCode`→`Z_.ksa`、`referralEvent`→`ca.ase`、`rewardedVideosState`→`ja.lsf`、`twitchState`→`uf.st3`、`creatorTagState`→`ke.sqv`
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `challenges` | `ta.tsc` @14314 | UpdateStars, SetChallenge, RemoveChallenge, SetBattlePass, SetRewardForDialog, SetBattlePassPackages, SetTakenFreeRewardIndex …(共 9 个) |
| `quests` | `(…, qw).zt9` @14321 | SetCurrentCategory, GoTo, QuestCategory |
| `mainQuest` | `na.nsd` @15279 | QuestsLoaded, SetQuests, UpdateQuests, AddRewardedQuest, SetEventPassShopItemId, SetBattleUpdatedQuests |
| `questCategories` | `Q_.xsa` @15349 | SetQuestCategories, RemoveQuestCategory, RequestQuestCategoriesStatistic, SetQuestCategoriesStatistic |
| `contractState` | `Sv.mtb` @15354 | SetActiveContract, SetContract, UpdateProgress, SetBattleProgress, GetReward |
| `promoCode` | `Z_.ksa` @15355 | ActivatePromoCode, SetCodeValidateStatus |
| `referralEvent` | `ca.ase` @15356 | SetReferralEventData, SetPromoCode, SetReferrals, SetReferrer |
| `rewardedVideosState` | `ja.lsf` @15342 | SetDisplayState |
| `twitchState` | `uf.st3` @15343 | SetTwitchEnabled, SetAccount, SetDaysForNextLinking |
| `creatorTagState` | `ke.sqv` @15340 | SetCreatorTags, SetCreatorTag, SetAttachedCreatorTag |
### 3.9 网络/系统(2 字段 + 1 未映射)
- `system`→`l4().jt3`、`remoteUserDataStorage`→`fa.use`(`launchParams` 未映射,见第 4 节)
| 字段 | reducer | 相关 action(代表) |
|---|---|---|
| `system` | `l4().jt3` @14318 | DoShowMessage, SetFatalError |
| `remoteUserDataStorage` | `fa.use` @14333 | UpdateUids, UpdateRanks, UpdatePremiumStatus, UpdateClanTags, UpdateOnlineStatus, UserEnterBattle, UserLeaveBattle, UserLeaveGroup |
## 4. 未映射字段
| 字段 | 槽位 | 原因 |
|---|---|---|
| `launchParams` | `jpr_1` | 根 reducer `v8` 在 `module_10032.js:15328` 用 `ht = n.jpr_1` **原样透传**:没有分片 reducer,也没有任何 `instanceof` 分支写它。该字段只在初始化(`module_10032.js:52909` `new w8().vt9(...)`)时被写入,之后不可变。重写时按只读启动参数处理即可。 |
其余 88 个字段全部定位到分片 reducer(`Stats`: 证据 88 / 推断 0 / 未映射 1)。
## 5. 方法说明(如何把 TOState 字段与根 reducer 分支对上)
**关键难点**:`v8` 是 1143 行的巨型函数,且不以 `if (t instanceof …)` 长链覆盖全部字段 —— 前 38 行是「直线式委派」,其余散落在 14345–15357 的续行里,且**顺序与 `TOState` 字段顺序完全不同**(例如 `battlePauseState` 在 15341、`modalScreen` 在 14342)。因此不能靠"读顺序"对齐,必须做双向锚定。
实际用到的四步与锚点:
1. **抽出字段→槽位表**。定位 `module_10032.js:43519` 的 `vd(w8).toString`,用正则
`/([A-Za-z][A-Za-z0-9]*)="\s*\+\s*(?:this\.([A-Za-z0-9_]+)|qd\(this\.([A-Za-z0-9_]+)\))/g`
在 `"TOState(user="` 到 `hashCode` 之间的片段上匹配,得到 89 组 `字段名 → this.<槽位>`。注意字段名在 `="` 之前(形如 `..., screen=" + this.bpp_1 ...`),直接抓 `"name="` 会失败。
2. **抽出 `v8` 的委派语句**。定位 `function v8(t, n)`,用花括号配对取出函数体;再以「括号深度 0 处的逗号」切分变量声明链(`var i = ..., r = ..., e = ...;`),得到 89 条 `变量 = 表达式`。每条记录其起始偏移,换算成文件行号(注意函数体第一个换行导致 **+1 修正**——行号在起草稿时曾整体偏 −1,已校准)。
3. **把变量接到字段上(主锚点)**。`v8` 末尾 `return n.ntb(...)` 调用 `vd(w8).ntb`(`module_10032.js:42968`),而 `ntb` 的形参顺序 = `new w8(...)` 的实参顺序 = `TOState` 字段顺序(`app_1, bpp_1, cpp_1, …`)。于是**第 k 个实参 ↔ 第 k 个字段**。同时每个实参变量(如第 1 个 `i = T6().bt7(t, n.app_1)`)自身读取的槽位又是 `app_1` —— 两个方向的独立结果逐位一致(89/89),故此映射可直接标 `证据`,无需 `推断`。
4. **抽 action 类名**。对每个分片 reducer 取方法名(如 `bt7`),在 `module_10032.js` 里找 `.bt7 = function` / `function bt7(` 定义点,花括号配对取出函数体,用 `/\binstanceof\s+([A-Za-z_$][A-Za-z0-9_$]*)/g` 收集分支上的类名,再用 `names/evidence.json` 的 `byModule.10032.classNames`(混淆名→真名,如 `E6→State`)翻译成可读 action 名。分片状态类同理取自定义点的 `vd(<Class>).<method>`。
- 内联匿名 reducer(`shop`/`home`/`localBattleUserState`/`partner`)无独立函数名,直接从 `v8` 内的 IIFE 函数体取 `instanceof` 链。
- 特殊形态 `(null == qw && new Vrt(), qw).zt9(t, n.ppp_1)`(惰性单例)需要从 `(…, <名字>).<方法>` 里剥出方法名,普通 `^X.y(t` 正则会漏掉。
**复现命令**(只读,无临时文件):对 `module_10032.js` 用 `node -e` 跑上述四步的正则/配对逻辑即可重建 `names/tostate-map.json`;`Stats` 与 `Entries` 应逐条一致。
**已知不确定点**:
- `SliceClass` 有 5 项为空(`garage` 由顶层函数 `sF` 处理而非类方法;`shop`/`home`/`localBattleUserState`/`partner` 是内联 IIFE),其分片状态类名未在本模块内定位。
- `Actions` 中部分仍是混淆名(如 `c6`/`p6`/`S6`),因为 `evidence.json` 的 `classNames` 未覆盖这些类;它们**确实是**能改该字段的 action 类,只是真名缺失。
- `garage` 的 reducer `sF` 读整个 state(`n.fpp_1` 等)而非仅自身分片,属跨分片 reducer;`battleCreateState` 的 `Uf.eta` 显式接收 `maps`/`user` 两个额外槽位。重写时这两个是重点。
File diff suppressed because it is too large. Load diff