// gen_root_wiring.js —— 从 module_10032 抽取「根状态 → 切片 reducer」委派表 // // 产出: // 3DTank/names/root_wiring.json 证据表(字段 / 槽位 / 切片类 / reducer / 动作名) // TankiOffline/app/.../App/State/RootState.kt 原 z6 的 89 个切片字段(已迁移的用强类型,其余保留 Any?) // TankiOffline/app/.../App/State/RootWiring.kt 委派表 + 迁移覆盖统计 // // 解析口径(全部取自源码,不猜): // · 根 reducer:body 含 `return n.cti(` 的 function(当前是 k6) // · 根状态类:定义 .cti 的类(当前是 z6),字段顺序 = 构造函数参数顺序 // · 槽位:构造函数里的 `(this._1 = )` // · 字段名:toString 里的 `"=" + this._1` // · 委派:根 reducer 顶层赋值 `x = (t, n.…)`;hF(t, n) 这种传整个状态的, // 到被调函数体里找它实际读的 n. // · 未具名形态:内联 IIFE 记 `(inline@)` 并到函数体抓顶层动作名,纯 `n.` 透传记 `(pass-through)` // · 动作:被调 reducer 顶层 `instanceof ` → action_inventory 里的类名; // 没 toString 的少数默认动作由 AnonymousActionNames 按「效果 + 派发点」定名(证据同时写进 JSON) // // 用法:cd 3DTank/tools && node gen_root_wiring.js 'use strict'; const fs = require('fs'); const path = require('path'); const P = path.resolve(__dirname, '..'); const T = fs.readFileSync(path.join(P, 'src', 'module_10032.js'), 'utf8'); const Inv = JSON.parse(fs.readFileSync(path.join(P, 'names', 'action_inventory_10032.json'), 'utf8')); const LocalToName = new Map(); for (const R of Inv.Rows) if (!LocalToName.has(R.Local)) LocalToName.set(R.Local, R.Name); // module_10849 的 Redux 初始化动作(本模块只 import,不在 action_inventory_10032 里) LocalToName.set('iz', 'InitState'); // 没有 toString / 不在 action inventory 的动作类:按「reducer 效果 + 派发点」人工定名。 // 每条都必须能在源码里指出证据,否则保持 `?`。 const AnonymousActionNames = new Map([ // o1:无字段、无 toString;10032 以 `$_$.gl` 导出单例 `Eh`; // module_18334:153600 的 `nt(Qx).qge = function () { this.dwg_1.uar(c_); }` 派发它 // (Qx 继承 module_47243 的 my,my.u7j 按面板项 id 选择第 2 个可选项时调用 qge)。 // 归约效果:根 reducer home 分支 `n.f7j(!0)`,而 `ld(s1).f7j = function (t) { return new s1(t); }`、 // `ld(s1).toString = "HomeScreen(shopDiscountNotification=" + this.esm_1 + ")"`, // 即把 HomeScreen.shopDiscountNotification 置 true(主菜单商店项上的折扣提示)。 ['o1', 'ShowShopDiscountNotification'], // 商店切片里没有 toString / 不在 action_inventory 的类:按 reducer 效果定名(原名见 ShopReducer.kt 注释)。 ['Yut', 'ShopLoaded'], ['F5', 'ResetShop'], ['g4', 'PaymentUrlReceiveFailed'], ['X5', 'ResetPaymentState'], ['w5', 'SetGoToHomeAfterBuy'], ['o4', 'PaymentProviderInProgress'], ['m4', 'PaymentProviderFinishSuccess'], ['k4', 'PaymentProviderFinishFail'], ['i4', 'BlockBackButton'], ['t4', 'UnblockBackButton'], ['f5', 'ClearPreviousScreen'], ['v5', 'ClearTargetCategory'], ['p5', 'ClearTargetProductId'], ['H7', 'AddCategoriesWithBonus'], ['s5', 'InitPaymentModeAsShopItem'], ['o5', 'SetupDiscountItemForItem'], ['_5', 'RemoveDiscountItemForItem'], // 屏幕切片(WK / wY.ks5 第 34950 行):没有 toString / 不在 action_inventory 的 9 个类。 ['sC', 'DisableForeground'], ['oC', 'EnableForeground'], ['eY', 'ClearScreenStack'], ['uY', 'ResetScreenStackOnNextGoTo'], ['sY', 'DelayNextGoTo'], ['oY', 'FilterScreenStack'], ['lY', 'EnableBack'], ['cY', 'DisableBack'], ['fY', 'PopScreen'], // 补给箱切片(aH / EH.wrv):没有 toString / 不在 action_inventory 的 6 个类。 ['mH', 'SetTimeToGiveLootBox'], ['kH', 'SetPeriodOfGiveLootBox'], ['zH', 'ClearRewards'], ['gH', 'LootBoxOpeningFailed'], ['CH', 'SetIsReopening'], ['SH', 'SetIsBlockOpenButtons'], // garage 切片(UB / hF):没有 toString / 不在 action_inventory 的 25 个类。 // 括号里是原 reducer 的效果;实现见 GarageReducer.kt。 ['rF', 'ResetGarage'], ['EU', 'SetGarageObject'], ['RU', 'SetItemsOnDepot'], ['PU', 'SetItemsOnMarket'], ['NU', 'SetMountedItems'], ['eF', 'GarageLoaded'], ['VB', 'SetGarageNotLoaded'], ['JB', 'SelectGarageCategory'], ['QU', 'AddGarageItemCount'], ['SU', 'GarageMountItem'], ['iU', 'GarageUnmountItem'], ['MU', 'GarageMountItems'], ['dU', 'SetMountedSkinForItems'], ['wU', 'SetMountedShotSkinForItems'], ['aF', 'SetNewItems'], ['cF', 'AddNewItem'], ['lF', 'RemoveNewItem'], ['fF', 'SetResistanceReadyToMount'], ['bU', 'ResetMountDelay'], ['vF', 'StartCooldownReduce'], ['pF', 'SetCooldownReduceEndTime'], ['yU', 'ShowGarageDiscountNotification'], ['WB', 'HideVideoInDescription'], ['OU', 'RemoveWaitServerItemMount'], ['FB', 'ClearWaitServerItemMount'], // garagePreview 切片(IG / fs.eru):12 个没有 toString 的 instanceof 动作 + 3 个单例动作。 // 括号里是原 reducer 的效果;实现见 GaragePreviewReducer.kt。 ['BG', 'CaptureScreenshot'], ['UG', 'CaptureVideo'], ['JG', 'ClearHasGrenades'], ['KG', 'SetWeaponData'], ['OG', 'UpdateViewStyle'], ['QG', 'ClearHullData'], ['VG', 'SetHullData'], ['XG', 'SetHasGrenades'], ['YG', 'SetGrenadeData'], ['ZG', 'ClearWeaponData'], ['nH', 'ClearGrenadeData'], ['tH', 'ClearDroneData'], ['is', 'ClearGaragePreviewParams'], ['es', 'ShowGaragePreview'], ['rs', 'HideGaragePreview'], // entrance 切片(hD / Bv.pth):6 个没有 toString / 不在 action_inventory 的类。 // 括号里是原 reducer 的效果;实现见 EntranceReducer.kt。 ['eit', 'HideSocialNetworks'], ['uit', 'SetControlsForChina'], ['sit', 'SetRunningInChinaDataCenter'], ['oit', 'SetRunningInChinaPartners'], ['lit', 'SetAuthorizedByChinaSdkToken'], ['fit', 'SetClassic'], // user 切片(t8 / M8().qtd):3 个没有 toString / 不在 action_inventory 的类。 // 括号里是原 reducer 的效果;实现见 UserReducer.kt。 ['R8', 'DisableLobbyTutorial'], ['v8', 'SetInDebt'], ['p8', 'ClearInDebt'], // settings 切片(Uut / xw.ltg):2 个没有 toString / 不在 action_inventory 的类。 // 括号里是原 reducer 的效果;实现见 SettingsReducer.kt。 ['Fut', 'SetSettingsCategory'], ['Gut', 'DisableAccountSettings'], // accountSettings 切片(oet / Lp.mtg):11 个没有 toString / 不在 action_inventory 的类。 // 括号里是原 reducer 的效果;实现见 AccountSettingsReducer.kt。 ['aet', 'ResetPasswordFields'], ['fet', 'SetCurrentPassword'], ['vet', 'SetNewPassword'], ['pet', 'SetRepeatNewPassword'], ['wet', 'SetPasswordStatus'], ['det', 'SetCurrentPasswordMandatory'], ['get', 'SetEmail'], ['met', 'SetEmailConfirmed'], ['cet', 'SetCountry'], ['ket', 'SetUnlockButtonForRenameUid'], ['xet', 'ClearAccountRemovalTimer'], // language 切片(m6().jtg):1 个没有 toString / 不在 action_inventory 的类。 // 实现见 LanguageReducer.kt。 ['d6', 'SetLanguage'], // announcement 切片(lC / PC().rq5):3 个没有 toString / 不在 action_inventory 的类。 // CC 在 action_inventory 里叫 Init,但生成物 App.State.Init 是另一个切片的同名类,这里改名。 // 实现见 AnnouncementReducer.kt。 ['bC', 'DisableAnnouncement'], ['qC', 'SetAnnouncementModelLoaded'], ['CC', 'SetAnnouncementItems'], // notifications 切片(jJ / B_.ise):2 个没有 toString / 不在 action_inventory 的类。 // PJ 是带谓词的移除,RJ 是丢掉第一条;实现见 NotificationsReducer.kt。 ['PJ', 'RemoveNotifications'], ['RJ', 'RemoveFirstNotification'], // quests 切片(Frt / Yrt.otg):1 个没有 toString / 不在 action_inventory 的类。 // 实现见 QuestsReducer.kt。 ['Hrt', 'SetNotCompletedQuests'], // news 切片(ZX / E_.asd):hJ 没有 toString;两个单例 x_/j_ 按效果定名。 // 实现见 NewsReducer.kt。 ['hJ', 'SetNewsFlag'], ['x_', 'OpenNews'], ['j_', 'CloseNews'], // lobbyLoader 切片(KW / VW.es1):FW/GW 没有 toString,按 reducer 效果定名。 ['FW', 'ShowLoader'], ['GW', 'HideLoader'], // purchaseDialogState 切片(CO / DO.ur2):NO 没有 toString,按 reducer 效果定名。 ['NO', 'ResetPurchaseDialog'], // supportUrl 切片(I4 / P4.ct7):R4 没有 toString,按 reducer 效果定名。 ['R4', 'ClearSupportUrl'], // notificationByCountry 切片(gJ / $J.ssd):qJ 没有 toString,按 reducer 效果定名。 ['qJ', 'MarkHomeScreenDialogShown'], // mainQuest 切片(WZ / ih.bsk):VZ 没有 toString,按 reducer 效果定名。 ['VZ', 'ResetQuestsLoaded'], // rankUpDialogState 切片(LO / GO.br3):FO 没有 toString。 ['FO', 'ResetRankUpDialog'], // battleResult 切片(sR / pR.aqp):cR/lR/fR 没有 toString。 ['cR', 'SetIncreasedReward'], ['lR', 'SetSimpleReward'], ['fR', 'ResetBattleRewards'], // captchaState 切片(jP / MP.iqt):SP/IP/AP/RP/PP 没有 toString。 ['SP', 'ShowFakeCaptcha'], ['IP', 'AddVerifiedCaptchaLocation'], ['AP', 'RemoveVerifiedCaptchaLocation'], ['RP', 'AddCaptchaLocationToReset'], ['PP', 'RemoveCaptchaLocationToReset'], // modalScreen 切片(nV / QW.js1):XW 没有 toString。 ['XW', 'CloseCurrentModal'], // droppableGold 切片(Vet / Xet.kth):Ket/Yet 两个单例。 ['Ket', 'ShowDroppableGoldAuthor'], ['Yet', 'HideDroppableGoldAuthor'], // creatorTagState 切片(qO / SO.er2):jO 没有 toString。 ['jO', 'ClearCreatorTag'], // battleTutorial 切片(rE / hE.sq8):oE/sE 没有 toString。 ['oE', 'MarkMovementStarted'], ['sE', 'ResetBattleTutorial'], // spectator 切片(HA / tR.jqm):XA/JA/ZA/YA 没有 toString。 ['XA', 'SetSpectators'], ['JA', 'AddSpectator'], ['ZA', 'RemoveSpectator'], ['YA', 'SetSpectatorsListVisible'], // antiAddiction 切片(UC / JC.iq8):FC/XC/YC 没有 toString。 ['FC', 'EnableRealNameCheck'], ['XC', 'RequireUserIdVerification'], ['YC', 'UpdatePlayingTime'], // moderation 切片(NI / _A.oqh):13 个类全都没有 toString。 ['UI', 'SetModeratorModes'], ['FI', 'SetModerationUrlTemplates'], ['XI', 'ShowBattleInfo'], ['JI', 'HideBattleInfo'], ['GI', 'AddSelectedUser'], ['WI', 'AddSelectedUserFromChat'], ['KI', 'RemoveSelectedUser'], ['YI', 'ClearSelectedUsers'], ['uA', 'ResetSelectedUsers'], ['ZI', 'ShowModerationSystemMessage'], ['QI', 'HideModerationSystemMessage'], ['tA', 'ShowBlockUserMenu'], ['nA', 'HideBlockUserMenu'], // 迁移 / 注册共用(module_10032 里各只定义一次):Mit/Dit/Lit/Bit/Uit/Fit/Git/Hit/hrt/_rt。 ['Mit', 'DisableRegister'], ['Dit', 'UidIsFree'], ['Lit', 'UidIsIncorrect'], ['Bit', 'ResetUidStatus'], ['Hit', 'UidTooShort'], ['Uit', 'RegisterPasswordCorrect'], ['Fit', 'RegisterPasswordIncorrect'], ['Git', 'RegisterFailed'], ['hrt', 'ResetRegisterStatus'], ['_rt', 'ResetAuthenticationForm'], // friends 切片(KD / PL.cr9):_L/hL 没有 toString。 ['_L', 'SetContextMenuUser'], ['hL', 'ClearContextMenuUser'], // suppliesState 切片(wE / yE.fq9):qE 没有 toString。 ['qE', 'SetSupplyCount'], // battleInviteState 切片(att / Ett.nth):dtt 没有 toString; // mtt 的 toString 名 InviteToBattle 与另一个只有 battleId 的同名动作冲突,这里改用不冲突的类名。 ['mtt', 'AddInvitedUser'], ['dtt', 'ClearSelectedForInvite'], // partner 切片(UJ / 内联 reducer):_Z/tZ/JJ/hZ 没有 toString。 ['_Z', 'UpdatePartnerInviteAction'], ['tZ', 'MarkAccountLinked'], ['JJ', 'MarkAccountNotLinked'], ['hZ', 'SetPartnerItemEnabled'], // userRenameState 切片(O8 / Rf.pte):B8/F8/U8/G8/Q8 没有 toString。 ['B8', 'MarkFreeRenameAvailable'], ['F8', 'MarkRenameSucceeded'], ['U8', 'MarkRenameFailed'], ['G8', 'ResetRenameDialog'], ['Q8', 'HideLinkErrorWarning'], // battlePauseState 切片(dT / PT.mqc):xT / TT 没有 toString。 ['xT', 'ResetBattlePause'], ['TT', 'CancelAutoPause'], // battleViewState 切片(DT / sI.jqd):WT/YT/JT/ZT/QT/tI/nI/iI/nE/tE/rI/eI 没有 toString。 ['WT', 'ResetBattleView'], ['YT', 'ClearInvisibleElements'], ['JT', 'EnableBattleInput'], ['ZT', 'DisableBattleInput'], ['QT', 'EnableBattleStatistics'], ['tI', 'DisableBattleStatistics'], ['nI', 'EnableBattleMenu'], ['iI', 'DisableBattleMenu'], ['nE', 'HideBattleView'], ['tE', 'ShowBattleView'], ['rI', 'EnterBackground'], ['eI', 'LeaveBackground'], // tankActionsLogState 切片(vI / yI.gqe):dI 没有 toString。 ['dI', 'ResetTankActionsLog'], // clanChatState 切片(qnt / Int.qtg):$nt/xnt/Tnt 没有 toString。 ['$nt', 'OpenClanChat'], ['xnt', 'CloseClanChat'], ['Tnt', 'ResetClanChat'], // battleSearchState 切片(Itt / Gtt.vtg):Dtt 没有 toString。 ['Dtt', 'ClearSelectedBattle'], // battleStatistics 切片(jR / rP.fqr):11 个类没有 toString;FR 与通知切片的 Init 同名,改名。 ['FR', 'InitBattle'], ['PR', 'ResetRoundState'], ['KR', 'ShowStat'], ['YR', 'HideStat'], ['XR', 'BeginRound'], ['UR', 'ContinueBattle'], ['QR', 'CloseStatMenu'], ['ZR', 'ToggleStatMenu'], ['DR', 'ClearFlagOrBallOwner'], ['tP', 'MarkBattleLoaded'], ['nP', 'MarkBattleUnloaded'], ['HR', 'ResetBattleStatistics'], // 设置类切片的共用匿名动作与同名类:_C 是各设置切片共用的「从存储重载」动作。 ['_C', 'ResetSettingsFromStorage'], // soundSettings 切片(Det / Het.jth):Bet/Fet 的 toString 名都是 UpdateSettings/…,与其它设置切片同名。 ['Bet', 'ApplySoundSettings'], ['Fet', 'SetEntranceSoundSettings'], ['Uet', 'EnableEntranceSound'], // gameSettings 切片(c2 / d2.zss):v2 的 toString 名也是 UpdateSettings。 ['v2', 'ApplyGameSettings'], // touchscreenHudSettings 切片(j7 / P7.xsv):C7 的 toString 名也是 UpdateSettings;E7 没有 toString。 ['C7', 'ApplyTouchscreenHudSettings'], ['E7', 'ResetTouchscreenHudMapping'], // keyboardSettings 切片(Sut / Nut.oth):Iut 没有 toString。 ['Iut', 'ResetKeyMappingFromDefaults'], // graphicsSettings / controlSettings / battleSettings 的 UpdateSettings 同名类。 ['B2', 'ApplyGraphicsSettings'], ['u2', 'ApplyControlSettings'], ['K0', 'ApplyBattleSettings'], // contractState 切片(Ont / Wnt.bti):Fnt 的 GetReward 与任务切片同名,Hnt 没有 toString。 ['Fnt', 'ClaimContractReward'], ['Hnt', 'RemoveCompletedContracts'], // tankBuilderState 切片(brt / Irt.ati):Trt 没有 toString。 ['Trt', 'ResetTankBuilder'], // battleMessage 切片(FE / fT.rqb):sT 没有 toString。 ['sT', 'ResetBattleMessages'], // battleChat 切片(Q6 / htt.wtg):ett/utt/stt/rtt 没有 toString。 ['ett', 'OpenBattleChatInput'], ['utt', 'CloseBattleChatInput'], ['stt', 'ToggleBattleChatHistory'], ['rtt', 'ResetBattleChat'], ]); // 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。 // 它们从 Actions 列表里剔除,改记到 root_wiring.json 的 Markers。 const NonActionInstanceof = new Map([ ['LX', { Kind: 'screen-id-class', Evidence: '屏幕标识类 Entrance:`function LX(){ this.gs5_1="Entrance" }`,`ld(LX).m()` 返回 "Entrance";GoTo 分支用它判断 analytics 事件是否 isModal。' }], ['RX', { Kind: 'screen-id-class', Evidence: '屏幕标识类 System.Loader:`function RX(){ this.ysb_1="System.Loader" }`,`PX()` 返回该单例,也是 WK.current 的默认值;GoTo 到它时不发 screen_show。' }], ['FX', { Kind: 'screen-id-class', Evidence: '屏幕标识类 Garage:`function FX(t){ … this.irm_1 = t; this.jrm_1 = "Garage" }`,默认分类 `BB().sq5_1`;garage 的 GoTo 分支用它把 currentCategory 复位。' }], ['UB', { Kind: 'state-class', Evidence: 'garage 切片状态类:`function UB(…31 字段)`,toString 名 "Garage(...)";InitState 分支用 `h instanceof UB` 校验灌入状态,不是动作。' }], ['tX', { Kind: 'screen-id-class', Evidence: '屏幕标识类 Lobby.Quests.QuestCategory:`function tX(categoryType, id){ … this.gs8_1 = "Lobby.Quests.QuestCategory." + … }`;quests 的 GoTo 分支用它判断 section,不是 dispatch 的动作。' }], ['nX', { Kind: 'screen-id-class', Evidence: '屏幕标识类 Lobby.Quests.Challenge:`function nX(t){ … this.js8_1 = "Lobby.Quests.Challenge" }`;quests 的 GoTo 分支命中它时把 section 置 CHALLENGE,不是 dispatch 的动作。' }], ]); /** 动作局部类名 → 动作名;未收录的返回 null。 */ function ActionNameOf(Local) { // 匿名表优先:个别同名类(如 announcement 的 CC=Init)会用生成物里的名字, // 但该生成物属于另一个切片,必须在匿名表里显式改名。 return AnonymousActionNames.get(Local) || LocalToName.get(Local) || null; } // ---------------------------------------------------------------- 源码扫描基础 /** 字符串/注释感知的花括号配对。 */ function BracePairs(Text) { const Pairs = new Map(); const Stack = []; let I = 0; while (I < Text.length) { const C = Text[I]; if (C === '"' || C === "'" || C === '`') { const Q = C; I++; while (I < Text.length) { if (Text[I] === '\\') { I += 2; continue; } if (Text[I] === Q) { I++; break; } I++; } continue; } if (C === '/' && Text[I + 1] === '/') { const N = Text.indexOf('\n', I); I = N < 0 ? Text.length : N; continue; } if (C === '/' && Text[I + 1] === '*') { const N = Text.indexOf('*/', I + 2); I = N < 0 ? Text.length : N + 2; continue; } if (C === '{') Stack.push(I); else if (C === '}') { const O = Stack.pop(); if (O !== undefined) Pairs.set(O, I); } I++; } return Pairs; } const Pairs = BracePairs(T); /** 收集所有 function 与 `X.m = function` 的体区间。 */ const Fns = []; for (const M of T.matchAll(/(? F.Name === Name) || null; } function FindMethod(Ref) { const Dot = Ref.lastIndexOf('.'); if (Dot < 0) return null; const Obj = Ref.slice(0, Dot); const Method = Ref.slice(Dot + 1); return Methods.find((M) => M.Obj === Obj && M.Method === Method) || null; } /** 把 [From, To) 内的嵌套 function 体挖空,只留顶层分支(避免把 IIFE 里的 instanceof 算进来)。 */ function MaskNested(Text, From, To) { const Out = Text.split(''); for (const F of Fns) { if (F.Start > From && F.End < To) { for (let I = F.Start; I <= F.End; I++) Out[I] = ' '; } } for (const M of Methods) { if (M.Start > From && M.End < To) { for (let I = M.Start; I <= M.End; I++) Out[I] = ' '; } } return Out.slice(From, To).join(''); } /** 顶层 `name = expr` 赋值;返回 [{Name, Expr}](expr 已 trim)。 */ function TopLevelAssignments(Body) { const Out = []; let I = 0; let Depth = 0; const Strings = []; while (I < Body.length) { const C = Body[I]; if (C === '"' || C === "'" || C === '`') { const Q = C; I++; while (I < Body.length) { if (Body[I] === '\\') { I += 2; continue; } if (Body[I] === Q) { I++; break; } I++; } Strings.push(I); continue; } if (C === '(' || C === '[' || C === '{') { Depth++; I++; continue; } if (C === ')' || C === ']' || C === '}') { Depth--; I++; continue; } if (Depth === 0) { const M = /^([A-Za-z_$][\w$]*)\s*=\s*/.exec(Body.slice(I)); if (M && (I === 0 || /[;{}\s]/.test(Body[I - 1]))) { const Name = M[1]; let J = I + M[0].length; let D = 0; while (J < Body.length) { const D0 = Body[J]; if (D0 === '"' || D0 === "'" || D0 === '`') { const Q = D0; J++; while (J < Body.length) { if (Body[J] === '\\') { J += 2; continue; } if (Body[J] === Q) { J++; break; } J++; } continue; } if (D0 === '(' || D0 === '[' || D0 === '{') D++; else if (D0 === ')' || D0 === ']' || D0 === '}') D--; else if (D0 === ',' && D === 0) break; else if (D0 === ';' && D === 0) break; J++; } Out.push({ Name, Expr: Body.slice(I + M[0].length, J).trim() }); I = J + 1; continue; } } I++; } return Out; } /** 定位根 reducer / 根状态类。 */ const RootReducerFn = Fns.find((F) => T.slice(F.Body, F.End).includes('return n.cti(')); if (!RootReducerFn) throw new Error('找不到根 reducer(body 含 return n.cti()'); const CopyMethod = Methods.find((M) => M.Method === 'cti'); if (!CopyMethod) throw new Error('找不到根状态类的 cti'); const RootClass = CopyMethod.Obj; const RootClassFn = FindFn(RootClass); if (!RootClassFn) throw new Error('找不到根状态类构造函数 ' + RootClass); // ---------------------------------------------------------------- 根状态:参数 / 槽位 / 字段 const Params = RootClassFn.Params.split(',').map((S) => S.trim()).filter(Boolean); const CtorBody = T.slice(RootClassFn.Body, RootClassFn.End); const SlotOfParam = new Map(); for (const M of CtorBody.matchAll(/\(this\.(\w+) = (\w+)\)/g)) SlotOfParam.set(M[2], M[1]); const DefaultOfParam = new Map(); for (const M of CtorBody.matchAll(/\((\w+) = \1 === cd \? ([\s\S]*?) : \1\)/g)) { DefaultOfParam.set(M[1], M[2].replace(/\s+/g, ' ').trim()); } for (const M of CtorBody.matchAll(/\((\w+) = \1 !== cd && \1\)/g)) DefaultOfParam.set(M[1], 'false'); const ToStringStart = T.indexOf(`(ld(${RootClass}).toString = function`); if (ToStringStart < 0) throw new Error('找不到根状态类 toString'); const ToStringEnd = T.indexOf(`(ld(${RootClass}).hashCode`, ToStringStart); const ToStringBody = T.slice(ToStringStart, ToStringEnd); const Fields = []; for (const M of ToStringBody.matchAll(/"([^"]*?)([A-Za-z0-9_]+)="\s*\+\s*(?:[A-Za-z_$][\w$]*\()?this\.(\w+)/g)) { Fields.push({ Field: M[2], Slot: M[3] }); } if (Fields.length !== Params.length) { throw new Error(`字段数 ${Fields.length} 与构造参数数 ${Params.length} 不一致`); } // ---------------------------------------------------------------- 委派表达式 const ReduceBody = T.slice(RootReducerFn.Body, RootReducerFn.End); const Assignments = TopLevelAssignments(ReduceBody); /** 从表达式里找出所有 n. 引用。 */ function SlotsIn(Expr) { return [...new Set([...Expr.matchAll(/\bn\.([A-Za-z0-9_$]+)/g)].map((M) => M[1]))]; } /** 单例持有者 → 实际类,例如 `(Ke = new pD())`、`(nu = new MD())`。 */ const SingletonClass = new Map(); for (const M of T.matchAll(/\(([A-Za-z_$][\w$]*) = new ([A-Za-z_$][\w$]*)\(\)\)/g)) { SingletonClass.set(M[1], M[2]); } /** `X()` 形式的工厂 → 它返回的类,例如 `function M8() { return (null == X && new N8(), X); }`。 */ function FactoryClass(Name) { const F = FindFn(Name); if (!F) return null; const Body = T.slice(F.Body, F.End); const M = /new ([A-Za-z_$][\w$]*)\(/.exec(Body); return M ? M[1] : null; } /** * 表达式对应的 reducer 名字(`M8().qtd(t, n.ypu_1)` → `M8().qtd`)。 * 表达式以别的调用开头、但结尾仍是 `.(t, n.)` 时取末尾调用, * 例如 quests 的 `(null == zp && new Yrt(), zp).otg(t, n.npv_1)` → `zp.otg`;真 IIFE → `(inline)`。 */ function ReducerRef(Expr) { // 懒汉单例:`(null == zp && new Yrt(), zp).otg(t, n.npv_1)` → `Yrt.otg` const Lazy = /^\(null == [A-Za-z_$][\w$]* && new ([A-Za-z_$][\w$]*)\(\), [A-Za-z_$][\w$]*\)\.([A-Za-z_$][\w$]*)\(/.exec(Expr); if (Lazy) return Lazy[1] + '.' + Lazy[2]; // 纯透传:`at = n.hpx_1`(切片没有 reducer,值原样带过去) if (/^n\.[A-Za-z0-9_$]+$/.test(Expr)) return '(pass-through)'; const Head = /^([A-Za-z_$][\w$]*)(\(\))?(?:\.([A-Za-z_$][\w$]*))?\s*\(/.exec(Expr); if (Head && (Head[3] || /^\(\s*t\s*,/.test(Expr.slice(Head[0].length - 1)))) { if (Head[3]) return Head[1] + (Head[2] || '') + '.' + Head[3]; return Head[1]; } const Tail = /([A-Za-z_$][\w$]*(?:\(\))?\.[A-Za-z_$][\w$]*)\(t,\s*n\.\w+\)\s*$/.exec(Expr); if (Tail) return Tail[1]; return Head ? Head[1] + (Head[2] || '') : '(inline)'; } /** 取 reducer 体(function / X().m / X.m;hF 这种传整个状态的也能处理)。 */ function BodyOfRef(Ref) { if (Ref.startsWith('(')) return null; const Dot = Ref.lastIndexOf('.'); if (Dot < 0) { const F = FindFn(Ref); return F ? { From: F.Body, To: F.End } : null; } const ObjRaw = Ref.slice(0, Dot); const Method = Ref.slice(Dot + 1).replace(/\(\)$/, ''); const IsFactory = /\(\)$/.test(ObjRaw); const Obj = ObjRaw.replace(/\(\)$/, ''); let ClassName = null; if (IsFactory) ClassName = FactoryClass(Obj); if (!ClassName) ClassName = SingletonClass.get(Obj) || null; if (!ClassName) ClassName = Obj; const M = Methods.find((X) => X.Obj === ClassName && X.Method === Method) || Methods.find((X) => X.Obj === Obj && X.Method === Method); return M ? { From: M.Body, To: M.End } : null; } /** 从已屏蔽嵌套 function 的片段里收集顶层 instanceof 命中的动作名。 */ function NamesInMasked(Masked) { const Names = new Set(); for (const M of Masked.matchAll(/instanceof ([A-Za-z0-9_$]+)/g)) { if (NonActionInstanceof.has(M[1])) continue; const N = ActionNameOf(M[1]); Names.add(N || '?' + M[1]); } return [...Names].sort(); } /** reducer 顶层 instanceof 命中的动作名(没有 toString 的类记作 `?`)。 */ function ActionsOf(Ref) { const B = BodyOfRef(Ref); if (!B) return []; return NamesInMasked(MaskNested(T, B.From, B.To)); } /** * 内联 IIFE 的动作名:表达式形如 `(function (t, n) { … })(t, n.)`, * 到源码里找到它的函数体,屏蔽嵌套 function 后收集顶层 instanceof。 */ function InlineActions(Pos, Expr) { if (Pos < 0) return []; const FnAt = T.indexOf('function', Pos); if (FnAt < 0 || FnAt > Pos + Expr.length) return []; const Open = T.indexOf('{', FnAt); if (Open < 0 || Open > Pos + Expr.length) return []; const End = Pairs.get(Open); if (End === undefined) return []; return NamesInMasked(MaskNested(T, Open + 1, End)); } // 槽位 → 委派 { Reducer, Expr, Slots } const WiringBySlot = new Map(); for (const A of Assignments) { const Slots = SlotsIn(A.Expr); if (!Slots.length) continue; const Pos = T.indexOf(A.Expr, RootReducerFn.Body); for (const S of Slots) { if (!WiringBySlot.has(S)) { WiringBySlot.set(S, { Reducer: ReducerRef(A.Expr), Expr: A.Expr.replace(/\s+/g, ' ').trim(), Pos, Local: A.Name, }); } } } // hF(t, n):表达式里没有 n.,到被调体里找它读的槽位 for (const A of Assignments) { if (SlotsIn(A.Expr).length) continue; const Ref = ReducerRef(A.Expr); const B = BodyOfRef(Ref); if (!B) continue; const Slots = SlotsIn(T.slice(B.From, B.To)); for (const S of Slots) { if (!WiringBySlot.has(S)) { WiringBySlot.set(S, { Reducer: Ref, Expr: A.Expr.replace(/\s+/g, ' ').trim(), Pos: -1, Local: A.Name, }); } } } // ---------------------------------------------------------------- 汇总 const Rows = Fields.map((F) => { const W = WiringBySlot.get(F.Slot); const Param = Params.find((P) => SlotOfParam.get(P) === F.Slot) || null; return { Field: F.Field, Slot: F.Slot, SliceClass: null, // 由下面补(切片类名在构造函数默认值 / toString 里没有,靠 reducer 签名查) Reducer: W ? W.Reducer : '(未识别)', Delegate: W ? W.Expr.replace(/\s+/g, ' ').trim().slice(0, 500) : '', Actions: [], Default: Param ? DefaultOfParam.get(Param) || '' : '', _W: W, }; }); // 内联 IIFE 没有源码符号,按字段具名成 `(inline@)`,动作名到函数体里现抓 for (const R of Rows) { const W = R._W; delete R._W; if (!W) continue; if (W.Reducer === '(inline)') { R.Reducer = '(inline@' + R.Field + ')'; R.Actions = InlineActions(W.Pos, W.Expr); } else { R.Actions = ActionsOf(W.Reducer); } if (R.Delegate.length === 500) R.Delegate += ' …'; } // 切片类:从 reducer 所在类的 `X(new )` 或根状态构造默认 `new ()` 取 const SliceClassOfParam = new Map(); for (const [Param, Default] of DefaultOfParam) { const M = /^new ([A-Za-z0-9_$]+)\(/.exec(Default); if (M) SliceClassOfParam.set(Param, M[1]); } for (const R of Rows) { const Param = Params.find((P) => SlotOfParam.get(P) === R.Slot); R.SliceClass = Param ? SliceClassOfParam.get(Param) || null : null; } // 根 reducer 里没有出现过的槽位(例如被包在更大表达式里) for (const R of Rows) if (R.Reducer === '(未识别)') console.warn('未识别委派: ' + R.Field + ' / ' + R.Slot); // ---------------------------------------------------------------- 迁移标记(与 Kotlin 侧保持一致) const Migrated = { inviteState: { Type: 'InviteStateSlice', Reduce: 'ReduceInvite' }, loginByPassword: { Type: 'LoginByPasswordStateSlice', Reduce: 'ReduceLoginByPassword' }, twitchState: { Type: 'TwitchStateSlice', Reduce: 'ReduceTwitch' }, home: { Type: 'HomeStateSlice', Reduce: 'ReduceHome' }, localBattleUserState: { Type: 'LocalBattleUserStateSlice', Reduce: 'ReduceLocalBattleUser' }, shop: { Type: 'ShopStateSlice', Reduce: 'ReduceShop' }, screen: { Type: 'ScreenStateSlice', Reduce: 'ReduceScreen' }, lootBoxes: { Type: 'LootBoxesStateSlice', Reduce: 'ReduceLootBoxes' }, garage: { Type: 'GarageStateSlice', Reduce: 'ReduceGarage' }, garagePreview: { Type: 'GaragePreviewStateSlice', Reduce: 'ReduceGaragePreview' }, entrance: { Type: 'EntranceStateSlice', Reduce: 'ReduceEntrance' }, user: { Type: 'UserStateSlice', Reduce: 'ReduceUser' }, settings: { Type: 'SettingsStateSlice', Reduce: 'ReduceSettings' }, accountSettings: { Type: 'AccountSettingsStateSlice', Reduce: 'ReduceAccountSettings' }, proBattle: { Type: 'ProBattleStateSlice', Reduce: 'ReduceProBattle' }, // language 在原根状态里是枚举对象本身,不是对象切片;Default/Import 显式指定。 language: { Type: 'Language', Default: 'App.State.Language.EN', Reduce: 'ReduceLanguage', Import: false, }, announcement: { Type: 'AnnouncementStateSlice', Reduce: 'ReduceAnnouncement' }, // 原根 reducer 对这一片没有调用,只有默认值 `ag()`(空 Map);这里仅做类型收口。 launchParams: { Type: 'LaunchParamsStateSlice', Default: 'emptyMap()', Import: false, PassThrough: true, }, news: { Type: 'NewsStateSlice', Reduce: 'ReduceNews' }, // 原根状态的这一片是 `new jJ()`,生成器没认出切片类,默认值在这里显式补。 notifications: { Type: 'NotificationsStateSlice', Default: 'NotificationsStateSlice()', SliceClass: 'jJ', Reduce: 'ReduceNotifications', }, quests: { Type: 'QuestsStateSlice', Reduce: 'ReduceQuests' }, lobbyLoader: { Type: 'LobbyLoaderStateSlice', Reduce: 'ReduceLobbyLoader' }, purchaseDialogState: { Type: 'PurchaseDialogStateSlice', Reduce: 'ReducePurchaseDialog' }, promoCode: { Type: 'PromoCodeStateSlice', Reduce: 'ReducePromoCode' }, supportUrl: { Type: 'SupportUrlStateSlice', Reduce: 'ReduceSupportUrl' }, rulesUpdated: { Type: 'RulesUpdatedStateSlice', Reduce: 'ReduceRulesUpdated' }, notificationByCountry: { Type: 'NotificationByCountryStateSlice', Reduce: 'ReduceNotificationByCountry', }, availabilityByRank: { Type: 'AvailabilityByRankStateSlice', Reduce: 'ReduceAvailabilityByRank' }, icons: { Type: 'IconsStateSlice', Reduce: 'ReduceIcons' }, questCategories: { Type: 'QuestCategoriesStateSlice', Reduce: 'ReduceQuestCategories' }, mainQuest: { Type: 'MainQuestStateSlice', Reduce: 'ReduceMainQuest' }, securitySettings: { Type: 'SecuritySettingsStateSlice', Reduce: 'ReduceSecuritySettings' }, referralEvent: { Type: 'ReferralEventStateSlice', Reduce: 'ReduceReferralEvent' }, tipsState: { Type: 'TipsStateSlice', Reduce: 'ReduceTips' }, rankUpDialogState: { Type: 'RankUpDialogStateSlice', Reduce: 'ReduceRankUpDialog' }, battleResult: { Type: 'BattleResultStateSlice', Reduce: 'ReduceBattleResult' }, captchaState: { Type: 'CaptchaStateSlice', Reduce: 'ReduceCaptcha' }, modalScreen: { Type: 'ModalScreenStateSlice', Reduce: 'ReduceModalScreen' }, droppableGold: { Type: 'DroppableGoldStateSlice', Reduce: 'ReduceDroppableGold' }, eventGoods: { Type: 'EventGoodsStateSlice', Reduce: 'ReduceEventGoods' }, creatorTagState: { Type: 'CreatorTagStateSlice', Reduce: 'ReduceCreatorTag' }, rewardedVideosState: { Type: 'RewardedVideosStateSlice', Reduce: 'ReduceRewardedVideos' }, passwordChangeState: { Type: 'PasswordChangeStateSlice', Reduce: 'ReducePasswordChange' }, system: { Type: 'SystemStateSlice', Reduce: 'ReduceSystem' }, contextTutorial: { Type: 'ContextTutorialStateSlice', Reduce: 'ReduceContextTutorial' }, battleTutorial: { Type: 'BattleTutorialStateSlice', Reduce: 'ReduceBattleTutorial' }, spectator: { Type: 'SpectatorStateSlice', Reduce: 'ReduceSpectator' }, twoStepVerificationState: { Type: 'TwoStepVerificationStateSlice', Reduce: 'ReduceTwoStepVerification', }, antiAddiction: { Type: 'AntiAddictionStateSlice', Reduce: 'ReduceAntiAddiction' }, moderation: { Type: 'ModerationStateSlice', Reduce: 'ReduceModeration' }, migrationState: { Type: 'MigrationStateSlice', Reduce: 'ReduceMigrationState' }, register: { Type: 'RegisterStateSlice', Reduce: 'ReduceRegister' }, userReportsState: { Type: 'UserReportsStateSlice', Reduce: 'ReduceUserReports' }, friends: { Type: 'FriendsStateSlice', Reduce: 'ReduceFriends' }, suppliesState: { Type: 'SuppliesStateSlice', Reduce: 'ReduceSupplies' }, clanUserInfo: { Type: 'ClanUserInfoStateSlice', Reduce: 'ReduceClanUserInfo' }, battleInviteState: { Type: 'BattleInviteStateSlice', Reduce: 'ReduceBattleInvite' }, partner: { Type: 'PartnerStateSlice', Reduce: 'ReducePartner' }, userRenameState: { Type: 'UserRenameStateSlice', Reduce: 'ReduceUserRename' }, battlePauseState: { Type: 'BattlePauseStateSlice', Reduce: 'ReduceBattlePause' }, battleViewState: { Type: 'BattleViewStateSlice', Reduce: 'ReduceBattleView' }, tankActionsLogState: { Type: 'TankActionsLogStateSlice', Reduce: 'ReduceTankActionsLog' }, // 根构造里 suppliesTutorial 的默认值不是 `new ()`,生成器认不出切片类,这里显式补。 suppliesTutorial: { Type: 'SuppliesTutorialStateSlice', SliceClass: 'ust', Reduce: 'ReduceSuppliesTutorial', }, clanChatState: { Type: 'ClanChatStateSlice', Reduce: 'ReduceClanChat' }, battleSearchState: { Type: 'BattleSearchStateSlice', Reduce: 'ReduceBattleSearch' }, battleStatistics: { Type: 'BattleStatisticsStateSlice', Reduce: 'ReduceBattleStatistics' }, soundSettings: { Type: 'SoundSettingsStateSlice', Reduce: 'ReduceSoundSettings' }, gameSettings: { Type: 'GameSettingsStateSlice', Reduce: 'ReduceGameSettings' }, keyboardSettings: { Type: 'KeyboardSettingsStateSlice', Reduce: 'ReduceKeyboardSettings' }, touchscreenHudSettings: { Type: 'TouchscreenHudSettingsStateSlice', Reduce: 'ReduceTouchscreenHudSettings', }, // 根构造里 graphicsSettings 的默认值不是 `new ()`,生成器认不出切片类,这里显式补。 graphicsSettings: { Type: 'GraphicsSettingsStateSlice', SliceClass: 'L2', Reduce: 'ReduceGraphicsSettings', }, controlSettings: { Type: 'ControlSettingsStateSlice', Reduce: 'ReduceControlSettings' }, battleSettings: { Type: 'BattleSettingsStateSlice', Reduce: 'ReduceBattleSettings' }, contractState: { Type: 'ContractStateSlice', Reduce: 'ReduceContractState' }, tankBuilderState: { Type: 'TankBuilderStateSlice', Reduce: 'ReduceTankBuilder' }, battleMessage: { Type: 'BattleMessageStateSlice', Reduce: 'ReduceBattleMessage' }, maps: { Type: 'MapsStateSlice', Reduce: 'ReduceMaps' }, battleChat: { Type: 'BattleChatStateSlice', Reduce: 'ReduceBattleChat' }, }; for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field); // 少数切片的类名生成器认不出来(例如根构造里是 `new jJ()` 但字段默认值挡住了推断), // 迁移表里可以显式补一个 SliceClass。 for (const R of Rows) { const M = Migrated[R.Field]; if (M && M.SliceClass) R.SliceClass = M.SliceClass; } // 字段名 → Kotlin 属性名(首字母大写,保留原有大小写与数字) function KotlinName(Field) { return Field.charAt(0).toUpperCase() + Field.slice(1); } /** JSON 字符串 → Kotlin 字符串字面量(`$` 要转义,否则会被当成模板起点)。 */ function KStr(S) { return JSON.stringify(String(S)).replace(/\$/g, '\\$'); } const SeenProps = new Set(); for (const R of Rows) { const N = KotlinName(R.Field); if (SeenProps.has(N)) throw new Error('Kotlin 属性名重复: ' + N); SeenProps.add(N); } // ---------------------------------------------------------------- 输出 // shop 切片的匿名动作:没有 toString / 不在 action_inventory,按 reducer 分支效果定名。 // 证据统一来自 module_10032 的 `(inline@shop)` 分支与 U7/w3 两个子 reducer(行号见 ShopReducer.kt)。 const ShopAnonymousEffects = { Yut: 'isLoaded = true(t instanceof Yut → n.qt6(…, !0))', F5: 'new j4(…) 整片复位,只保留 coinPaymentProviderUrl(第 7 个构造参数 n.qq3_1)', g4: 'paymentUrl = null、receivePaymentUrlFailed = true', X5: 'paymentUrl = null、receivePaymentUrlFailed = false、mobilePaymentState = NONE、paymentProviderState = NONE', w5: 'goToHomeAfterBuy = true', o4: 'paymentProviderState = IN_PROGRESS(V5().Rl)', m4: 'paymentProviderState = FINISH_SUCCESS(y4())', k4: 'paymentProviderState = FINISH_FAIL(V5().Nl)', i4: 'isBlockedBackButton = true', t4: 'isBlockedBackButton = false', f5: 'previousScreen = null', v5: 'targetCategory = null', p5: 'targetProductId = null', H7: 'U7.categoriesWithBonus = Zg(现有集合, t.zsw_1)', s5: 'U7/w3.paymentModeAsShopItem[ft3_1] = gt3_1', o5: 'U7/w3.discountItems[dt3_1] = et3_1', _5: 'U7/w3.discountItems 删除键 ct3_1', }; // screen 切片(WK / wY.ks5)的匿名动作:按 reducer 分支效果定名。 const ScreenAnonymousEffects = { sC: 'foreground = false(`n.vs4(cd, cd, !1)`)', oC: 'foreground = true(`n.vs4(cd, cd, !0)`)', eY: 'screenStack = emptyList()(`n.vs4(cd, cd, cd, md())`)', uY: 'isResetBackStackOnNextGoTo = true(第 6 个构造参数 !0)', sY: 'isDelayedGoto = true(第 7 个构造参数 !0)', oY: 'screenStack = stack.filter(predicate)(`for (…) e(o) && u.y(o)`,默认谓词 YK 恒 false)', lY: 'enableBack = true(第 5 个构造参数 !0)', cY: 'enableBack = false(第 5 个构造参数 !1)', fY: 'screenStack = dropLast(1)(`f$(stack, 1)` 是 Kotlin dropLast)', }; // 补给箱切片(aH / EH.wrv)的匿名动作:按 reducer 分支效果定名。 const LootBoxesAnonymousEffects = { mH: 'timeToGiveLootBox = Date.now() + seconds * 1000(第 9 个构造参数 `Fg(Md(), Qk.y2o(Wg(t.zrv_1)))`)', kH: 'periodOfGiveLootBoxInSec = action.yrv_1(第 10 个构造参数)', zH: 'rewards = emptyList()、shownRewards = emptyList()(第 6、7 个构造参数 `md(), md()`)', gH: 'openingFailed = true(第 11 个构造参数 !0)', CH: 'isReopening = true(第 17 个构造参数 !0)', SH: 'isBlockOpenButtons = action.xrv_1(第 18 个构造参数)', }; // garage 切片(UB / hF):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const GarageAnonymousEffects = { rF: 'devices = new UF()、upgradeCoolDownEndTimes = emptyMap()、upgradeItemInProcessingId = null', EU: 'garageObject = action.nrk_1', RU: 'itemsOnDepot = action.rrk_1', PU: 'itemsOnMarket = action.srk_1', NU: 'mountedItems = action.trk_1', eF: '按 itemsOnDepot / mountedItems / itemsOnMarket / skinsItems 重建 items(PB),isLoaded = true', VB: 'isLoaded = false', JB: 'currentCategory = action.qri_1、currentItemId = null', QU: 'items.grg(item, copy(owned=true, count += action.trl_1, devicePreview = 改装等级对应项))', SU: 'items.jrg(action.lrk_1)(同 viewCategory 先卸下已装配项,再装配本项)', iU: 'items.mrg(action.crj_1)(mounted = false)', MU: 'items.krg(action.urk_1)(批量 mounted = true)', dU: 'items.qrg(action.vrj_1, action.wrj_1)(整组 mountedSkin = skinId)', wU: 'items.rrg(action.srj_1, action.trj_1)(整组 mountedShotSkin = skinId)', aF: 'newItems = action.srm_1', cF: 'newItems[itemId] += value(Set 并集)', lF: 'newItems[itemId] -= value,空集则删键', fF: 'resistanceReadyToMount = action.nrm_1', bU: 'delayMountTimeMs = 0', vF: 'cooldownReduceEndTime = now + seconds*1000、timeToReduceUpdateInMin = action.mrm_1', pF: 'cooldownReduceEndTime = now + seconds*1000', yU: 'garageDiscountNotification = true', WB: 'showVideoInDescription = false', OU: 'waitServerItemMount 的给定键全部置 false', FB: 'waitServerItemMount = emptyMap()', }; // garagePreview 切片(IG / fs.eru):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const GaragePreviewAnonymousEffects = { BG: '调用 captureScreenshotAction(),状态不变(`null == y || y(), i = n`)', UG: '调用 captureVideoAction(),状态不变(`null == $ || $(), i = n`)', JG: 'hasGrenades = false(第 13 个构造参数 !1)', KG: '若 expectedItemId[WEAPON] 命中,则 weaponData = TankPartData(resource, emissionColors)、turretMechanism = action;并缓存动作、从 requestedItems 删除 itemId', OG: 'UpdateViewStyle:动作类没有字段,原 `t.fru_1` 为 undefined,`trs` 视作省略,状态不变', QG: 'hullData = null(第 9 个构造参数 null)', VG: '若 expectedItemId[ARMOR] 命中,则 hullData = TankPartData(6 字段)、hullEngineType = action;并缓存动作、从 requestedItems 删除 itemId', XG: 'hasGrenades = true(第 13 个构造参数 !0)', YG: 'grenadeData = resource == null ? null : TankPartData(resource, emissionColors)(第 12 个构造参数)', ZG: 'weaponData = null(第 10 个构造参数 null)', nH: 'grenadeData = null(第 12 个构造参数 null)', tH: 'droneData = null(第 11 个构造参数 null)', is: 'params = null(第 8 个构造参数 null)', es: 'isVisible = true、needVisible = false(`n.trs(!0, cd, !1)`)', rs: 'isVisible = false(`n.trs(!1)`)', }; // entrance 切片(hD / Bv.pth):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const EntranceAnonymousEffects = { eit: 'showSocialNetworks = false(`n.ur4(!1)`)', uit: 'controlsForChina = true(`n.ur4(cd, !0)`)', sit: 'isRunningInChinaDataCenter = true(`n.ur4(cd, cd, !0)`)', oit: 'isRunningInChinaPartners = true(`n.ur4(cd, cd, cd, !0)`)', lit: 'authorizedByChinaSdkToken = true(`n.ur4(cd, cd, cd, cd, cd, !0)`)', fit: 'isClassic = true(`n.ur4(cd, cd, cd, cd, cd, cd, !0)`)', }; // user 切片(t8 / M8().qtd):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const UserAnonymousEffects = { R8: 'lobbyTutorialEnabled = false(`n.qtb(cd × 29, !1)`)', v8: 'inDebt = true(`n.qtb(cd × 30, !0)`)', p8: 'inDebt = false(`n.qtb(cd × 30, !1)`)', }; // settings 切片(Uut / xw.ltg):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const SettingsAnonymousEffects = { Fut: 'currentCategory = action.category(`n.bu8(t.cu8_1)`)', Gut: 'disableAccountSettings = true(`n.bu8(cd, !0)`)', }; // accountSettings 切片(oet / Lp.mtg):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const AccountSettingsAnonymousEffects = { aet: 'currentPassword/newPassword/repeatNewPassword = "",newPasswordStatus = NONE,isUnlockButtonForRenameUid = false', fet: 'currentPassword = action.currentPassword', vet: 'newPassword = action.newPassword', pet: 'repeatNewPassword = action.repeatNewPassword', wet: 'newPasswordStatus = action.status', det: 'isCurrentPasswordMandatory = action.isMandatory', get: 'email = action.email', met: 'emailConfirmed = action.emailConfirmed', cet: 'countryCode / countryName = action 的两个字段', ket: 'isUnlockButtonForRenameUid = action.isUnlockButtonForRenameUid', xet: 'removalTimer = null', }; // language 切片(m6().jtg):只把动作里的语言枚举写回。 const LanguageAnonymousEffects = { d6: 'language = action.language(`t instanceof d6 ? t.itg_1 : n`)', }; // announcement 切片(lC / PC().rq5):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const AnnouncementAnonymousEffects = { bC: 'isEnabled = false', qC: 'modelLoaded = true', CC: 'items = action.items', }; // news 切片(ZX / E_.asd):没有 toString / 不在 action_inventory 的类与单例,按 reducer 分支效果定名。 const NewsAnonymousEffects = { x_: 'isOpen = true(`yd(t, x_)` → `n.psc(!0)`;x_ = new QX() 单例)', j_: 'isOpen = false(`yd(t, j_)` → `n.psc(!1)`;j_ = new tJ() 单例)', hJ: 'p = true(`n.psc(cd, cd, cd, !0)`;动作类无字段,字段语义未定)', }; // notifications 切片(jJ / NJ.ise):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const NotificationsAnonymousEffects = { PJ: 'notifications = notifications.filter {!action.predicate(it)}(`t.hse_1(s) || e.y(s)`)', RJ: 'notifications = notifications.drop(1)(`w$(n.tsd_1, 1)`)', }; // quests 切片(Frt / Yrt.otg):没有 toString / 不在 action_inventory 的类,按 reducer 分支效果定名。 const QuestsAnonymousEffects = { Hrt: '状态不变(`n.au5(cd, cd, t.iu5_1)`;动作类无字段,`t.iu5_1` 与 `cd` 都是 undefined,三个参数全走默认值 → no-op)', }; // lobbyLoader 切片(KW / VW.es1):FW/GW 没有 toString,按 reducer 分支效果定名。 const LobbyLoaderAnonymousEffects = { FW: 'isShowLoader=true、activeLoadersCount = count + 1(`n.fs1(!0, (n.ss0_1 + 1) | 0)`)', GW: 'activeLoadersCount = max(count - 1, 0)、isShowLoader = count != 0(`n.fs1(!(0 === e), e)`)', }; // purchaseDialogState 切片(CO / DO.ur2):NO 没有 toString,按 reducer 分支效果定名。 const PurchaseDialogAnonymousEffects = { NO: 'withEmail=false、goods=emptyList()、emailStatus=null、time=null(`n.jr2(!1, md(), null, null)`)', }; // supportUrl 切片(I4 / P4.ct7):R4 没有 toString,按 reducer 分支效果定名。 const SupportUrlAnonymousEffects = { R4: 'url=""(`n.at7()`)', }; // notificationByCountry 切片(gJ / $J.ssd):qJ 没有 toString,按 reducer 分支效果定名。 const NotificationByCountryAnonymousEffects = { qJ: 'homeScreenDialogShown=true(`n.isd(cd, cd, cd, !0)`)', }; // mainQuest 切片(WZ / ih.bsk):VZ 没有 toString / 不在 action_inventory,按 reducer 分支效果定名。 const MainQuestAnonymousEffects = { VZ: 'isLoaded=false(`n.qsj(!1)`;动作类 rsj_1 在 reducer 里未被读取)', }; // rankUpDialogState 切片(LO / GO.br3):FO 没有 toString。 const RankUpDialogAnonymousEffects = { FO: 'availableItems=emptyList()(`new LO()`)', }; // battleResult 切片(sR / pR.aqp):cR/lR/fR 没有 toString。 const BattleResultAnonymousEffects = { cR: 'increasedReward=动作数值(`n.qqn(cd, cd, cd, t.cqp_1)`)', lR: 'simpleReward=动作数值(`n.qqn(cd, cd, cd, cd, t.bqp_1)`)', fR: 'increasedReward=0、simpleReward=0(`n.qqn(cd, cd, cd, 0, 0)`)', }; // captchaState 切片(jP / MP.iqt):SP/IP/AP/RP/PP 没有 toString。 const CaptchaAnonymousEffects = { SP: 'useFake=true(`n.wqs(!0)`)', IP: 'verifiedLocations + location(`Xg`)', AP: 'verifiedLocations - location(`Jg`)', RP: 'locationsToReset + location(`Xg`)', PP: 'locationsToReset - location(`Jg`)', }; // modalScreen 切片(nV / QW.js1):XW 没有 toString。 const ModalScreenAnonymousEffects = { XW: 'current 非空时 queue -= current、current=queue.firstOrNull()', }; // droppableGold 切片(Vet / Xet.kth):Ket/Yet 是单例。 const DroppableGoldAnonymousEffects = { Ket: 'showDroppableGoldAuthor=true(`n.f7j(!0)`)', Yet: 'showDroppableGoldAuthor=false(`n.f7j(!1)`)', }; // creatorTagState 切片(qO / SO.er2):jO 没有 toString。 const CreatorTagAnonymousEffects = { jO: 'creatorTag=null(`n.ar2(null)`)', }; // battleTutorial 切片(rE / hE.sq8):oE/sE 没有 toString。 const BattleTutorialAnonymousEffects = { oE: 'movementHasStarted=true(`n.pq8(cd, cd, !0)`)', sE: 'new rE() 整片复位(`new rE()`)', }; // spectator 切片(HA / tR.jqm):XA/JA/ZA/YA 没有 toString。 const SpectatorAnonymousEffects = { XA: 'spectators=整表(`n.yql(cd, cd, t.dqm_1)`)', JA: 'spectators += `new iR(userId, uid, inSupportGroup, inPunishersGroup)`(`Hg`)', ZA: 'spectators 按 userId 过滤掉一条(`Qd(s.kqm_1, t.iqm_1) || e.y(s)`)', YA: 'spectatorsListVisible=布尔(`n.yql(cd, cd, cd, t.cqm_1)`)', }; // antiAddiction 切片(UC / JC.iq8):FC/XC/YC 没有 toString。 const AntiAddictionAnonymousEffects = { FC: 'isEnableRealNameCheck=true(`n.lq7(!0)`)', XC: 'unknownUserId=true、urlForEnterUserId=参数(`n.lq7(cd, !0, t.hq8_1)`)', YC: 'playingTimeInMinutes、alertMessage(`n.lq7(cd, cd, cd, t.fq8_1, t.gq8_1)`)', }; // moderation 切片(NI / _A.oqh):13 个类全都没有 toString。 const ModerationAnonymousEffects = { UI: 'moderatorMode / battleModeratorMode / eventOrganizerMode(`n.aqf(a, b, c)`)', FI: '两个 URL 模板(`n.aqf(cd, cd, cd, t.vqg_1, t.wqg_1)`)', XI: 'battleInfo + forceShowBattleInfo=true(`n.aqf(…, t.fqh_1, !0)`)', JI: 'forceShowBattleInfo=false(`n.aqf(…, !1)`)', GI: 'selectedUsers 去重后追加 `new OI(...)`(`Hg(e, new OI(...))`)', WI: '同 GI,另一个派发点(`Hg(_, new OI(...))`)', KI: 'selectedUsers 按 userId 过滤(`Qd(v.oqf_1, t.eqh_1) || l.y(v)`)', YI: 'selectedUsers 清空(`var p = md()`)', uA: 'selectedUsers 清空(`var w = md()`,另一个派发点)', ZI: 'systemMessage + forceShowSystemMessage=true(`n.aqf(…, t.gqh_1, !0)`)', QI: 'forceShowSystemMessage=false(`n.aqf(…, !1)`)', tA: 'forceShowBlockUserMenu=true(`n.aqf(…, !0)`)', nA: 'forceShowBlockUserMenu=false(`n.aqf(…, !1)`)', }; // 迁移 / 注册共用(Sit.rtg 与 art.stg 都 instanceof 同一批类)。 const AuthAnonymousEffects = { Mit: 'isDisabled=true(`n.its(!0)`,只有注册切片读)', Dit: 'loginStatus / uidValidationStatus = FREE(`lrt()`)', Lit: 'loginStatus / uidValidationStatus = INCORRECT(`frt()`)', Bit: 'loginStatus / uidValidationStatus = UNKNOWN(`crt()`)', Hit: 'loginStatus / uidValidationStatus = SHORT_NAME(`prt()`)', Uit: 'registerStatus=PASSWORD_CORRECT(`(Iit(), Xv)`,只有注册切片读)', Fit: 'registerStatus=PASSWORD_INCORRECT(`drt()`)', Git: 'registerStatus=FAILED(`grt()`)', hrt: 'registerStatus=UNKNOWN(`n.its(cd, cd, cd, cd, cd, wrt())`,只有注册切片读)', _rt: 'login=""、两个状态复位(两个切片都读)', }; // friends 切片(KD / PL.cr9):_L/hL 没有 toString。 const FriendsAnonymousEffects = { _L: 'contextMenuUser=参数(`n.er7(cd, t.er9_1)`)', hL: 'contextMenuUser=null(`n.er7(cd, null)`)', }; // suppliesState 切片(wE / yE.fq9):qE 没有 toString。 const SuppliesAnonymousEffects = { qE: 'count 写键值(`n.bq9(cg(n.aq9_1, Vd(t.gq9_1, t.hq9_1)))`)', }; // battleInviteState 切片(att / Ett.nth):dtt 没有 toString;mtt 名字冲突另收口。 const BattleInviteAnonymousEffects = { mtt: 'invitedUsers += receiverId(原名 InviteToBattle,与另一个只有 battleId 的同名动作冲突,改名 AddInvitedUser)', dtt: 'selectedForInvite = emptySet()(`Yg()`)', }; // partner 切片(UJ / 内联 reducer):_Z/tZ/JJ/hZ 没有 toString。 const PartnerAnonymousEffects = { _Z: 'inviteAction=参数(`n.osf(…, t.osg_1)`)', tZ: 'isAccountAlreadyLinked=true(`n.osf(…, !0)`)', JJ: 'isAccountAlreadyLinked=false(`n.osf(…, !1)`)', hZ: 'itemsEnabledForPartner 写键值(`cg(n.ksf_1, new Km(t.msg_1, t.nsg_1))`)', }; // userRenameState 切片(O8 / Rf.pte):B8/F8/U8/G8/Q8 没有 toString。 const UserRenameAnonymousEffects = { B8: 'isFreeRenameAvailable=true(`n.cte(!0)`)', F8: 'isFreeRenameAvailable=false、renameSuccessful=true(`n.cte(!1, cd, cd, !0)`)', U8: 'renameSuccessful=false(`n.cte(cd, cd, cd, !1)`)', G8: '复位 shopItemId / uidInputState / renameSuccessful / 两个 response 位(`n.cte(cd, null, n6(), null, cd, !1, !1)`)', Q8: 'showLinkErrorWarning=false(`n.cte(…, !1)`)', }; // battlePauseState 切片(dT / PT.mqc):xT / TT 没有 toString。 const BattlePauseAnonymousEffects = { xT: 'inAutoPauseMode=false、timeForAutoLeave 复位,保留 idleKickPeriodInMsec(`new dT(cd, cd, n.uqb_1)`)', TT: 'inAutoPauseMode=false(`n.wqb(!1)`)', }; // battleViewState 切片(DT / sI.jqd):12 个类没有 toString。 const BattleViewAnonymousEffects = { WT: 'new DT() 整片复位', YT: 'invisibleElements=emptySet()(`n.vqc(cd, cd, Yg())`)', JT: 'inputEnabled=true(`n.vqc(cd, cd, cd, !0)`)', ZT: 'inputEnabled=false(`n.vqc(cd, cd, cd, !1)`)', QT: 'battleStatisticsActive=true(`n.vqc(cd, cd, cd, cd, !0)`)', tI: 'battleStatisticsActive=false(`n.vqc(cd, cd, cd, cd, !1)`)', nI: 'battleMenuActive=true(`n.vqc(cd, cd, cd, cd, cd, !0)`)', iI: 'battleMenuActive=false(`n.vqc(cd, cd, cd, cd, cd, !1)`)', nE: 'isHidden=true(`n.vqc(cd, !0)`)', tE: 'isHidden=false(`n.vqc(cd, !1)`)', rI: 'inBackgroundState=true(`n.vqc(!0)`)', eI: 'inBackgroundState=false(`n.vqc(!1)`)', }; // tankActionsLogState 切片(vI / yI.gqe):dI 没有 toString。 const TankActionsLogAnonymousEffects = { dI: 'new vI() 整片复位', }; // clanChatState 切片(qnt / Int.qtg):$nt/xnt/Tnt 没有 toString。 const ClanChatAnonymousEffects = { $nt: 'open=true(`n.qtp(!0)`)', xnt: 'open=false(`n.qtp(!1)`)', Tnt: 'new qnt() 整片复位', }; // battleSearchState 切片(Itt / Gtt.vtg):Dtt 没有 toString。 const BattleSearchAnonymousEffects = { Dtt: 'selectedBattleId=null(`n.vtn(cd, cd, cd, cd, null)`)', }; // battleStatistics 切片(jR / rP.fqr):11 个类没有 toString;FR 与通知切片 Init 同名。 const BattleStatisticsAnonymousEffects = { FR: '14 字段战斗初始化(原名 Init,与通知切片的同名类冲突,改名 InitBattle)', PR: 'roundStarted=false、menuVisible=false(`n.cqq(…, !1, cd, !1)`)', KR: 'statVisible=true;menuVisible=!(menu && !stat) && menu(`n.cqq(…, !0, cd, cd, …)`)', YR: 'statVisible=false(`n.cqq(…, !1)`)', XR: 'isContinueBattle=false、score=emptyMap()、flagOrBallOwner=null、statVisible=false、roundStarted=true、menuVisible=false', UR: 'isContinueBattle=true(`n.cqq(…, !0)`)', QR: 'menuVisible=false(`n.cqq(…, !1)`)', ZR: 'menuVisible=!menuVisible(`n.cqq(…, !n.bq2_1)`)', DR: 'flagOrBallOwner=null(`n.cqq(…, null)`)', tP: 'battleLoaded=true(`n.cqq(…, !0)`)', nP: 'battleLoaded=false(`n.cqq(…, !1)`)', HR: 'new jR() 整片复位', }; // 设置类切片:_C 是共用的「从存储重载」,其余是各切片里与 UpdateSettings 同名的动作。 const AppSettingsAnonymousEffects = { _C: '各设置切片共用的重载动作:sound/game/control 读 localStorage,keyboard/touchscreen 恢复默认表', Bet: '听到设置整体替换(原名 UpdateSettings)', Fet: '入口音效开关后整体替换设置(原名 SetEntranceSoundEnabled)', Uet: 'isEntranceSoundOn=true(存储缺省)', v2: '游戏内显示设置整体替换(原名 UpdateSettings)', C7: '触屏布局整体替换(原名 UpdateSettings)', E7: '恢复默认触屏布局(`new j7()`)', Iut: '恢复键位默认绑定(`rut().mu7_1`)', B2: '画质设置整体替换(原名 UpdateSettings)', u2: '操作设置整体替换(原名 UpdateSettings)', K0: '战斗显示设置整体替换(原名 UpdateSettings)', Fnt: '复制该合同并把 rewards 清空(原名 GetReward,与任务切片同名)', Hnt: '只保留 rewards 非空的合同', Trt: 'new brt() 整片复位', sT: 'new FE() 整片复位', FA: '按 info.mapId 新建/复用 MapDescription,并把 MapInfo 写进 themes[themeId](原名 AddMap)', ett: 'isInputActive=true(`n.ctl(cd, cd, cd, cd, !0)`)', utt: 'isInputActive=false(`n.ctl(cd, cd, cd, cd, !1)`)', stt: 'isHistoryOpen=!isHistoryOpen(`n.ctl(cd, cd, cd, cd, cd, !n.atl_1)`)', rtt: 'new Q6() 整片复位', }; fs.writeFileSync( path.join(P, 'names', 'root_wiring.json'), JSON.stringify( { generatedAt: new Date().toISOString(), module: 'module_10032.js', RootState: { Class: RootClass, Reducer: RootReducerFn.Name, FieldCount: Rows.length }, Migrated: Object.keys(Migrated), AnonymousActions: [ { Local: 'o1', Name: 'ShowShopDiscountNotification', Export: '$_$.gl(module_10032 单例 Eh)', DispatchedAt: 'module_18334 的 nt(Qx).qge(Qx 继承 module_47243 的 my,u7j 选中 nge_1 时调用)', Effect: 'HomeScreen(shopDiscountNotification = true)(根 reducer home 分支 n.f7j(!0))', }, ...Object.entries(ShopAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(ScreenAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(LootBoxesAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(GarageAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(GaragePreviewAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(EntranceAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(UserAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(SettingsAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(AccountSettingsAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(LanguageAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(AnnouncementAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(NewsAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(NotificationsAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(QuestsAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(LobbyLoaderAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(PurchaseDialogAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(SupportUrlAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(NotificationByCountryAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(MainQuestAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(RankUpDialogAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(BattleResultAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(CaptchaAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(ModalScreenAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(DroppableGoldAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(CreatorTagAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(BattleTutorialAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(SpectatorAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(AntiAddictionAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(ModerationAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(AuthAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(FriendsAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(SuppliesAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(BattleInviteAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(PartnerAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(UserRenameAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(BattlePauseAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(BattleViewAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(TankActionsLogAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(ClanChatAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(BattleSearchAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(BattleStatisticsAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ...Object.entries(AppSettingsAnonymousEffects).map(([Local, Effect]) => ({ Local, Name: AnonymousActionNames.get(Local), DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', Effect, })), ], Markers: [ { Local: '$4', Kind: 'marker-interface', Evidence: 'module_10032 的 `fd(O7, "", cd, cd, [$4])` 等注册:InitCategory/InitShopCard/… 等商店内容动作都实现它;根 reducer 用 `Xd(t, $4)` 走 U7+w3 快路径。', }, { Local: 'x4', Kind: 'marker-action', Evidence: '`fd(x4, "", cd, Wm)`;根 reducer 的 `Xd(t, $4) || t instanceof x4` 同样走 U7+w3 快路径,本身无字段、无独立效果。', }, ...[...NonActionInstanceof].map(([Local, Info]) => ({ Local, Kind: Info.Kind, Evidence: Info.Evidence, })), ], Rows, }, null, 2 ) + '\n' ); const KT = path.resolve(__dirname, '..', '..', 'TankiOffline', 'app', 'src', 'jsMain', 'kotlin', 'App', 'State'); fs.mkdirSync(KT, { recursive: true }); const Lines = []; Lines.push('// 本文件由 3DTank/tools/gen_root_wiring.js 生成,不要手改。'); Lines.push(`// 来源:module_10032 的根状态 ${RootClass}(toString 名 TOState,${Rows.length} 个切片)`); Lines.push('// 与根 reducer ' + RootReducerFn.Name + '(逐切片归约后 copy 出新状态)。'); Lines.push('// 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。'); Lines.push('package App.State'); Lines.push(''); for (const F of Object.keys(Migrated).sort()) { if (Migrated[F].Import === false) continue; Lines.push('import App.State.Reducers.' + Migrated[F].Type); } Lines.push(''); Lines.push('/**'); Lines.push(' * 原 `' + RootClass + '` / `TOState`:' + Rows.length + ' 个切片字段,顺序与 toString 一致。'); Lines.push(' * 未迁移字段默认 null —— 原包构造时会给它们 `new ()`,这里等对应切片迁移后再补默认值。'); Lines.push(' */'); Lines.push('data class RootState('); for (const R of Rows) { const Prop = KotlinName(R.Field); const M = Migrated[R.Field]; if (M) { const Kind = M.PassThrough ? '已迁移(透传,强类型)' : '已迁移'; const Init = M.Default || M.Type + '()'; Lines.push(` /** 原 ${R.Field}(${R.SliceClass || '?'} / ${R.Reducer})。${Kind}。 */`); Lines.push(` val ${Prop}: ${M.Type} = ${Init},`); } else { Lines.push(` /** 原 ${R.Field}(${R.SliceClass || '?'} / ${R.Reducer})。未迁移:原样透传。 */`); Lines.push(` val ${Prop}: Any? = null,`); } } Lines.push(')'); Lines.push(''); fs.writeFileSync(path.join(KT, 'RootState.kt'), Lines.join('\n')); const WLines = []; WLines.push('// 本文件由 3DTank/tools/gen_root_wiring.js 生成,不要手改。'); WLines.push('// 证据表:module_10032 根 reducer ' + RootReducerFn.Name + ' 的逐切片委派(字段 / 槽位 / 切片类 / reducer / 动作)。'); WLines.push('package App.State'); WLines.push(''); WLines.push('/** 一个切片在根状态里的委派项。 */'); WLines.push('data class SliceWiring('); WLines.push(' val Field: String,'); WLines.push(' val Slot: String,'); WLines.push(' val SliceClass: String,'); WLines.push(' val Reducer: String,'); WLines.push(' val Delegate: String,'); WLines.push(' val Actions: List,'); WLines.push(')'); WLines.push(''); WLines.push('object RootWiring {'); WLines.push(' val All: List = listOf('); for (const R of Rows) { const Acts = R.Actions.map((A) => KStr(A)).join(', '); const Parts = [ KStr(R.Field), KStr(R.Slot), KStr(R.SliceClass || '?'), KStr(R.Reducer), KStr(R.Delegate), 'listOf(' + Acts + ')', ]; WLines.push(' SliceWiring(' + Parts.join(', ') + '),'); } WLines.push(' )'); WLines.push(''); WLines.push(' /** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */'); WLines.push(' val Migrated: Set = setOf(' + Object.keys(Migrated).map((F) => KStr(F)).join(', ') + ')'); WLines.push(''); WLines.push(' /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */'); WLines.push(' val Unmigrated: List get() = All.filter { it.Field !in Migrated }'); WLines.push(''); WLines.push(' fun ByField(Field: String): SliceWiring? = All.firstOrNull { it.Field == Field }'); WLines.push('}'); WLines.push(''); fs.writeFileSync(path.join(KT, 'RootWiring.kt'), WLines.join('\n')); console.log(`根状态 ${RootClass}:${Rows.length} 个切片;已迁移 ${Object.keys(Migrated).length} 个,未迁移 ${Rows.length - Object.keys(Migrated).length} 个`); console.log('未迁移清单:' + Rows.filter((R) => !R.Migrated).map((R) => R.Field).join(', '));