Files
TankIC/3DTank/docs/ArchitectureRuntime.md
T
TermiNexus 61b57a3d11 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。
2026-09-18 09:30:30 +08:00

22 KiB
Raw Blame History

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() 初始化后即此形):

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 侧次要循环。