feat(state): 迁移 garagePreview 切片(22 字段 / 25 分支)

This commit is contained in:
TermiNexus committed 2026-10-23 15:12:10 +08:00
1 parent 3c0ed0a998
commit 1194c395b4
14 files changed
+1574 -49

No files matched your search

+14 -5
View File
@@ -143,7 +143,7 @@
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
- 已迁移切片 **9/89**:
- 已迁移切片 **10/89**:
- `inviteState` → `aD` / `pD.er5`(`InviteReducer.kt`)
- `loginByPassword` → `gD` / `MD.cr6`(`LoginReducer.kt`,含 `SetLogin`/`SetPassword` 把 status 打回 `NONE` 的原语义)
- `twitchState` → `g9` / `y9.gta`(`TwitchReducer.kt`;`SetAccount` 是 Twitch 账号,不是 UID)
@@ -172,9 +172,18 @@
`dU`/`wU` 写整组 `mountedSkin`/`mountedShotSkin`;`GoTo` 仅在目标屏幕为 Garage 时复位 `currentCategory`。
无 `toString` 的 25 个匿名类按效果定名(`rF`→`ResetGarage` … `FB`→`ClearWaitServerItemMount`);
`FX`(Garage 屏幕)与 `UB`(状态类)已从动作表剔除并记入 `Markers`(`screen-id-class` / `state-class`)。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述九片,其余 **80 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 9/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **234 项全绿**,含 89 槽位完整性、九片一次归约互不串片、未迁移字段透传,
- `garagePreview` → `IG` / `fs.eru`(`GaragePreviewReducer.kt`):22 字段,25 个顶层臂
(22 个 `instanceof` + 3 个单例 `yd(t, is/es/rs)`)。`ShowRequest` 按 `withDelay` 决定
`isVisible = !withDelay && 原值`、`needVisible = withDelay || 原值`;`UpdateViewStyle` 无字段,
原 `t.fru_1` 是 `undefined`,`trs` 视作省略,状态不变。
`SetHullData` / `SetWeaponData` / `SetDrone` / `SetPaint` 先查 `rrs(category, itemId)`
(`expectedItemId[category] == null || == itemId`);命中才写 3D 数据,但无论命中都缓存动作并删 `requestedItems`。
`TankPartData` / `TankPreviewSettings` / `HullEngineType` / `TurretMechanism` 见 `GaragePreviewModels.kt`;
12 个匿名动作按效果定名(`BG`→`CaptureScreenshot` … `tH`→`ClearDroneData`),
3 个单例臂定名 `ClearGaragePreviewParams` / `ShowGaragePreview` / `HideGaragePreview`,不在 `Actions` 表里。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述十片,其余 **79 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 10/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **275 项全绿**,含 89 槽位完整性、十片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
@@ -183,7 +192,7 @@
`o1` → `ShowShopDiscountNotification`(`$_$.gl` 单例,由 module_18334 的 `Qx.qge` 派发;
效果是把 `HomeScreen.shopDiscountNotification` 置 true,对应主菜单商店项的折扣提示);
shop 的 17 个匿名类按 reducer 效果定名并登记(`Yut`→`ShopLoaded`、`F5`→`ResetShop` 等);
screen 的 9 个、lootBoxes 的 6 个、garage 的 25 个匿名类同样按效果定名并登记。
screen 的 9 个、lootBoxes 的 6 个、garage 的 25 个、garagePreview 的 15 个匿名类同样按效果定名并登记。
### S4 引擎核心
- 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入)
+107 -16
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-23T05:35:58.037Z",
"generatedAt": "2026-10-23T06:35:15.369Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -15,7 +15,8 @@
"shop",
"screen",
"lootBoxes",
"garage"
"garage",
"garagePreview"
],
"AnonymousActions": [
{
@@ -366,6 +367,96 @@
"Name": "ClearWaitServerItemMount",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "waitServerItemMount = emptyMap()"
},
{
"Local": "BG",
"Name": "CaptureScreenshot",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "调用 captureScreenshotAction(),状态不变(`null == y || y(), i = n`)"
},
{
"Local": "UG",
"Name": "CaptureVideo",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "调用 captureVideoAction(),状态不变(`null == $ || $(), i = n`)"
},
{
"Local": "JG",
"Name": "ClearHasGrenades",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "hasGrenades = false(第 13 个构造参数 !1)"
},
{
"Local": "KG",
"Name": "SetWeaponData",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "若 expectedItemId[WEAPON] 命中,则 weaponData = TankPartData(resource, emissionColors)、turretMechanism = action;并缓存动作、从 requestedItems 删除 itemId"
},
{
"Local": "OG",
"Name": "UpdateViewStyle",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "UpdateViewStyle:动作类没有字段,原 `t.fru_1` 为 undefined,`trs` 视作省略,状态不变"
},
{
"Local": "QG",
"Name": "ClearHullData",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "hullData = null(第 9 个构造参数 null)"
},
{
"Local": "VG",
"Name": "SetHullData",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "若 expectedItemId[ARMOR] 命中,则 hullData = TankPartData(6 字段)、hullEngineType = action;并缓存动作、从 requestedItems 删除 itemId"
},
{
"Local": "XG",
"Name": "SetHasGrenades",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "hasGrenades = true(第 13 个构造参数 !0)"
},
{
"Local": "YG",
"Name": "SetGrenadeData",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "grenadeData = resource == null ? null : TankPartData(resource, emissionColors)(第 12 个构造参数)"
},
{
"Local": "ZG",
"Name": "ClearWeaponData",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "weaponData = null(第 10 个构造参数 null)"
},
{
"Local": "nH",
"Name": "ClearGrenadeData",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "grenadeData = null(第 12 个构造参数 null)"
},
{
"Local": "tH",
"Name": "ClearDroneData",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "droneData = null(第 11 个构造参数 null)"
},
{
"Local": "is",
"Name": "ClearGaragePreviewParams",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "params = null(第 8 个构造参数 null)"
},
{
"Local": "es",
"Name": "ShowGaragePreview",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "isVisible = true、needVisible = false(`n.trs(!0, cd, !1)`)"
},
{
"Local": "rs",
"Name": "HideGaragePreview",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "isVisible = false(`n.trs(!1)`)"
}
],
"Markers": [
@@ -602,31 +693,31 @@
"Reducer": "fs.eru",
"Delegate": "fs.eru(t, n.epv_1)",
"Actions": [
"?BG",
"?JG",
"?KG",
"?OG",
"?QG",
"?UG",
"?VG",
"?XG",
"?YG",
"?ZG",
"?nH",
"?tH",
"AddRequestedItem",
"CaptureScreenshot",
"CaptureVideo",
"ClearDroneData",
"ClearGrenadeData",
"ClearHasGrenades",
"ClearHullData",
"ClearWeaponData",
"InitGarageResources",
"ResetItem3dModelActionCache",
"SetCaptureActions",
"SetDrone",
"SetExpectedItemId",
"SetGrenadeData",
"SetHasGrenades",
"SetHullData",
"SetMountedItemsProperties",
"SetPaint",
"SetWeaponData",
"ShowRequest",
"UpdatePreviewSettings"
"UpdatePreviewSettings",
"UpdateViewStyle"
],
"Default": "new IG()",
"Migrated": false
"Migrated": true
},
{
"Field": "challenges",
+696
View File
@@ -0,0 +1,696 @@
{
"generatedAt": "2026-10-23T07:01:46.681Z",
"module": "module_10032.js",
"Field": "garagePreview",
"Slot": "epv_1",
"Class": "IG",
"ClassSource": "new IG()",
"NamedReducer": "fs.eru",
"Params": [
"t",
"n",
"i",
"r",
"e",
"u",
"s",
"o",
"_",
"h",
"a",
"c",
"l",
"f",
"v",
"p",
"w",
"d",
"g",
"m",
"k",
"z"
],
"Fields": [
{
"Field": "isVisible",
"Slot": "vrr_1",
"Kotlin": "IsVisible",
"Default": "false"
},
{
"Field": "photoMode",
"Slot": "wrr_1",
"Kotlin": "PhotoMode",
"Default": "false"
},
{
"Field": "needVisible",
"Slot": "xrr_1",
"Kotlin": "NeedVisible",
"Default": "false"
},
{
"Field": "viewStyle",
"Slot": "yrr_1",
"Kotlin": "ViewStyle",
"Default": "null"
},
{
"Field": "dynViewStyle",
"Slot": "zrr_1",
"Kotlin": "DynViewStyle",
"Default": "null"
},
{
"Field": "tankPreviewSettings",
"Slot": "ars_1",
"Kotlin": "TankPreviewSettings",
"Default": "new Ny()"
},
{
"Field": "needShowDrone",
"Slot": "brs_1",
"Kotlin": "NeedShowDrone",
"Default": "false"
},
{
"Field": "params",
"Slot": "crs_1",
"Kotlin": "Params",
"Default": "null"
},
{
"Field": "hullData",
"Slot": "drs_1",
"Kotlin": "HullData",
"Default": "null"
},
{
"Field": "weaponData",
"Slot": "ers_1",
"Kotlin": "WeaponData",
"Default": "null"
},
{
"Field": "droneData",
"Slot": "frs_1",
"Kotlin": "DroneData",
"Default": "null"
},
{
"Field": "grenadeData",
"Slot": "grs_1",
"Kotlin": "GrenadeData",
"Default": "null"
},
{
"Field": "hasGrenades",
"Slot": "hrs_1",
"Kotlin": "HasGrenades",
"Default": "false"
},
{
"Field": "hullEngineType",
"Slot": "irs_1",
"Kotlin": "HullEngineType",
"Default": "My()"
},
{
"Field": "turretMechanism",
"Slot": "jrs_1",
"Kotlin": "TurretMechanism",
"Default": "Oy()"
},
{
"Field": "paint",
"Slot": "krs_1",
"Kotlin": "Paint",
"Default": "null"
},
{
"Field": "properties",
"Slot": "lrs_1",
"Kotlin": "Properties",
"Default": "md()"
},
{
"Field": "captureScreenshotAction",
"Slot": "mrs_1",
"Kotlin": "CaptureScreenshotAction",
"Default": "null"
},
{
"Field": "captureVideoAction",
"Slot": "nrs_1",
"Kotlin": "CaptureVideoAction",
"Default": "null"
},
{
"Field": "item3dModelActionCache",
"Slot": "ors_1",
"Kotlin": "Item3dModelActionCache",
"Default": "ag()"
},
{
"Field": "requestedItems",
"Slot": "prs_1",
"Kotlin": "RequestedItems",
"Default": "Yg()"
},
{
"Field": "expectedItemId",
"Slot": "qrs_1",
"Kotlin": "ExpectedItemId",
"Default": "ag()"
}
],
"CopyMethod": "trs",
"Branches": [
{
"Guard": "yd(t, is)",
"Locals": [],
"Kind": "copy",
"Changed": [
{
"Index": 7,
"Field": "params",
"Slot": "crs_1",
"Kotlin": "Params",
"Value": "null"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, null)"
},
{
"Guard": "t instanceof NG",
"Locals": [
"NG"
],
"Kind": "copy",
"Changed": [
{
"Index": 0,
"Field": "isVisible",
"Slot": "vrr_1",
"Kotlin": "IsVisible",
"Value": "r"
},
{
"Index": 1,
"Field": "photoMode",
"Slot": "wrr_1",
"Kotlin": "PhotoMode",
"Value": "_"
},
{
"Index": 2,
"Field": "needVisible",
"Slot": "xrr_1",
"Kotlin": "NeedVisible",
"Value": "e"
},
{
"Index": 3,
"Field": "viewStyle",
"Slot": "yrr_1",
"Kotlin": "ViewStyle",
"Value": "u"
},
{
"Index": 5,
"Field": "tankPreviewSettings",
"Slot": "ars_1",
"Kotlin": "TankPreviewSettings",
"Value": "s"
},
{
"Index": 6,
"Field": "needShowDrone",
"Slot": "brs_1",
"Kotlin": "NeedShowDrone",
"Value": "o"
}
],
"Expr": "n.trs(r, _, e, u, cd, s, o)"
},
{
"Guard": "t instanceof MG",
"Locals": [
"MG"
],
"Kind": "copy",
"Changed": [
{
"Index": 5,
"Field": "tankPreviewSettings",
"Slot": "ars_1",
"Kotlin": "TankPreviewSettings",
"Value": "t.zrs_1"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, t.zrs_1)"
},
{
"Guard": "t instanceof OG",
"Locals": [
"OG"
],
"Kind": "copy",
"Changed": [
{
"Index": 4,
"Field": "dynViewStyle",
"Slot": "zrr_1",
"Kotlin": "DynViewStyle",
"Value": "t.fru_1"
}
],
"Expr": "n.trs(cd, cd, cd, cd, t.fru_1)"
},
{
"Guard": "yd(t, es)",
"Locals": [],
"Kind": "copy",
"Changed": [
{
"Index": 0,
"Field": "isVisible",
"Slot": "vrr_1",
"Kotlin": "IsVisible",
"Value": "!0"
},
{
"Index": 2,
"Field": "needVisible",
"Slot": "xrr_1",
"Kotlin": "NeedVisible",
"Value": "!1"
}
],
"Expr": "n.trs(!0, cd, !1)"
},
{
"Guard": "yd(t, rs)",
"Locals": [],
"Kind": "copy",
"Changed": [
{
"Index": 0,
"Field": "isVisible",
"Slot": "vrr_1",
"Kotlin": "IsVisible",
"Value": "!1"
}
],
"Expr": "n.trs(!1)"
},
{
"Guard": "t instanceof FG",
"Locals": [
"FG"
],
"Kind": "copy",
"Changed": [
{
"Index": 7,
"Field": "params",
"Slot": "crs_1",
"Kotlin": "Params",
"Value": "t.crt_1"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, t.crt_1)"
},
{
"Guard": "t instanceof HG",
"Locals": [
"HG"
],
"Kind": "copy-helper",
"Changed": [
{
"Index": 21,
"Field": "expectedItemId",
"Slot": "qrs_1",
"Kotlin": "ExpectedItemId",
"Value": "am(n.qrs_1, t.grt_1)"
}
],
"Expr": "null == t.hrt_1 ? n.trs( cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, am(n.qrs_1, t.grt_1), ) : n.trs( cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cg(n.qrs_1, Vd(t.grt_1, t.hrt_1)), )"
},
{
"Guard": "t instanceof WG",
"Locals": [
"WG"
],
"Kind": "copy-helper",
"Changed": [
{
"Index": 20,
"Field": "requestedItems",
"Slot": "prs_1",
"Kotlin": "RequestedItems",
"Value": "Xg(n.prs_1, t.irt_1)"
}
],
"Expr": "n.trs( cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, Xg(n.prs_1, t.irt_1), )"
},
{
"Guard": "t instanceof VG",
"Locals": [
"VG"
],
"Kind": "conditional",
"Changed": [],
"Expr": "h"
},
{
"Guard": "t instanceof KG",
"Locals": [
"KG"
],
"Kind": "conditional",
"Changed": [],
"Expr": "v"
},
{
"Guard": "t instanceof iH",
"Locals": [
"iH"
],
"Kind": "conditional",
"Changed": [],
"Expr": "m"
},
{
"Guard": "t instanceof ZG",
"Locals": [
"ZG"
],
"Kind": "copy",
"Changed": [
{
"Index": 9,
"Field": "weaponData",
"Slot": "ers_1",
"Kotlin": "WeaponData",
"Value": "null"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, cd, null)"
},
{
"Guard": "t instanceof QG",
"Locals": [
"QG"
],
"Kind": "copy",
"Changed": [
{
"Index": 8,
"Field": "hullData",
"Slot": "drs_1",
"Kotlin": "HullData",
"Value": "null"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, null)"
},
{
"Guard": "t instanceof tH",
"Locals": [
"tH"
],
"Kind": "copy",
"Changed": [
{
"Index": 10,
"Field": "droneData",
"Slot": "frs_1",
"Kotlin": "DroneData",
"Value": "null"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, null)"
},
{
"Guard": "t instanceof nH",
"Locals": [
"nH"
],
"Kind": "copy",
"Changed": [
{
"Index": 11,
"Field": "grenadeData",
"Slot": "grs_1",
"Kotlin": "GrenadeData",
"Value": "null"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, null)"
},
{
"Guard": "t instanceof YG",
"Locals": [
"YG"
],
"Kind": "copy",
"Changed": [
{
"Index": 11,
"Field": "grenadeData",
"Slot": "grs_1",
"Kotlin": "GrenadeData",
"Value": "b"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, b)"
},
{
"Guard": "t instanceof XG",
"Locals": [
"XG"
],
"Kind": "copy",
"Changed": [
{
"Index": 12,
"Field": "hasGrenades",
"Slot": "hrs_1",
"Kotlin": "HasGrenades",
"Value": "!0"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, !0)"
},
{
"Guard": "t instanceof JG",
"Locals": [
"JG"
],
"Kind": "copy",
"Changed": [
{
"Index": 12,
"Field": "hasGrenades",
"Slot": "hrs_1",
"Kotlin": "HasGrenades",
"Value": "!1"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, !1)"
},
{
"Guard": "t instanceof rH",
"Locals": [
"rH"
],
"Kind": "copy-helper",
"Changed": [
{
"Index": 15,
"Field": "paint",
"Slot": "krs_1",
"Kotlin": "Paint",
"Value": "t.bru_1"
},
{
"Index": 19,
"Field": "item3dModelActionCache",
"Slot": "ors_1",
"Kotlin": "Item3dModelActionCache",
"Value": "cg(n.ors_1, Vd(t.aru_1, t))"
},
{
"Index": 20,
"Field": "requestedItems",
"Slot": "prs_1",
"Kotlin": "RequestedItems",
"Value": "Jg(n.prs_1, t.aru_1)"
}
],
"Expr": "n.rrs(Vk(), t.aru_1) ? n.trs( cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.bru_1, cd, cd, cd, cg(n.ors_1, Vd(t.aru_1, t)), Jg(n.prs_1, t.aru_1), ) : n.trs( cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cg(n.ors_1, Vd(t.aru_1, t)), Jg(n.prs_1, t.aru_1), "
},
{
"Guard": "t instanceof eH",
"Locals": [
"eH"
],
"Kind": "copy-helper",
"Changed": [
{
"Index": 19,
"Field": "item3dModelActionCache",
"Slot": "ors_1",
"Kotlin": "Item3dModelActionCache",
"Value": "am(n.ors_1, t.cru_1)"
}
],
"Expr": "n.trs( cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, am(n.ors_1, t.cru_1), )"
},
{
"Guard": "t instanceof uH",
"Locals": [
"uH"
],
"Kind": "copy",
"Changed": [
{
"Index": 16,
"Field": "properties",
"Slot": "lrs_1",
"Kotlin": "Properties",
"Value": "t.dru_1"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.dru_1)"
},
{
"Guard": "t instanceof LG",
"Locals": [
"LG"
],
"Kind": "copy",
"Changed": [
{
"Index": 17,
"Field": "captureScreenshotAction",
"Slot": "mrs_1",
"Kotlin": "CaptureScreenshotAction",
"Value": "t.art_1"
},
{
"Index": 18,
"Field": "captureVideoAction",
"Slot": "nrs_1",
"Kotlin": "CaptureVideoAction",
"Value": "t.brt_1"
}
],
"Expr": "n.trs(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.art_1, t.brt_1)"
},
{
"Guard": "t instanceof BG",
"Locals": [
"BG"
],
"Kind": "noop",
"Changed": [],
"Expr": "n"
},
{
"Guard": "t instanceof UG",
"Locals": [
"UG"
],
"Kind": "noop",
"Changed": [],
"Expr": "n"
},
{
"Guard": "(else)",
"Locals": [],
"Kind": "noop",
"Changed": [],
"Expr": "n"
}
],
"SubReducers": [],
"Counts": {
"TotalInstanceof": 22,
"TopLevelBranches": 26,
"SubReducerArms": 0
},
"Locals": {
"NG": {
"Name": "ShowRequest"
},
"MG": {
"Name": "UpdatePreviewSettings"
},
"OG": {
"Name": null
},
"FG": {
"Name": "InitGarageResources"
},
"HG": {
"Name": "SetExpectedItemId"
},
"WG": {
"Name": "AddRequestedItem"
},
"VG": {
"Name": null
},
"KG": {
"Name": null
},
"iH": {
"Name": "SetDrone"
},
"ZG": {
"Name": null
},
"QG": {
"Name": null
},
"tH": {
"Name": null
},
"nH": {
"Name": null
},
"YG": {
"Name": null
},
"XG": {
"Name": null
},
"JG": {
"Name": null
},
"rH": {
"Name": "SetPaint"
},
"eH": {
"Name": "ResetItem3dModelActionCache"
},
"uH": {
"Name": "SetMountedItemsProperties"
},
"LG": {
"Name": "SetCaptureActions"
},
"BG": {
"Name": null
},
"UG": {
"Name": null
}
},
"Delegate": "fs.eru(t, n.epv_1)",
"CopyParamCount": 23,
"CopyMatchesFields": false
}
+6
View File
@@ -635,6 +635,12 @@ const FieldTypeOverrides = new Map([
['AddToFavoriteItem.itemId', 'Long'],
['RemoveFromFavoriteItem.itemId', 'Long'],
['BuyProtectionSlot.slotToUnlock', 'Int'],
// garagePreview 切片:状态里这些字段参与 Long/Boolean/Set 运算;动作字段来自真实派发。
['ShowRequest.needShowDrone', 'Boolean'],
['AddRequestedItem.itemId', 'Long'],
['ResetItem3dModelActionCache.itemId', 'Long'],
['SetCaptureActions.screenshotAction', '(() -> Unit)?'],
['SetCaptureActions.videoAction', '(() -> Unit)?'],
]);
const Emitted = [];
+43
View File
@@ -102,6 +102,23 @@ const AnonymousActionNames = new Map([
['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'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
@@ -471,6 +488,7 @@ const Migrated = {
screen: { Type: 'ScreenStateSlice', Reduce: 'ReduceScreen' },
lootBoxes: { Type: 'LootBoxesStateSlice', Reduce: 'ReduceLootBoxes' },
garage: { Type: 'GarageStateSlice', Reduce: 'ReduceGarage' },
garagePreview: { Type: 'GaragePreviewStateSlice', Reduce: 'ReduceGaragePreview' },
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -564,6 +582,25 @@ const GarageAnonymousEffects = {
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)`)',
};
fs.writeFileSync(
path.join(P, 'names', 'root_wiring.json'),
JSON.stringify(
@@ -604,6 +641,12 @@ fs.writeFileSync(
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,
})),
],
Markers: [
{
+26 -7
View File
@@ -201,7 +201,11 @@ function FindGuards(Text) {
const C = M[I];
if ('([{'.includes(C)) { Depth++; I++; continue; }
if (')]}'.includes(C)) { Depth--; I++; continue; }
if (Depth === 0 && M.startsWith('if (', I) && M.slice(I, I + 80).includes('instanceof')) {
if (
Depth === 0 &&
M.startsWith('if (', I) &&
(M.slice(I, I + 80).includes('instanceof') || M.slice(I, I + 80).includes('yd(t,'))
) {
const OpenP = I + 3;
let D = 0, J = OpenP;
for (; J < Text.length; J++) { const D0 = M[J]; if (D0 === '(') D++; else if (D0 === ')') { D--; if (D === 0) break; } }
@@ -274,6 +278,7 @@ function PlanSlice(Field) {
// 第 2 种需要回查被调函数的函数体,确认它确实读写 `n.<slot>`。
let A = Assignments.find((X) => new RegExp('\\bn\\.' + F.Slot + '\\b').test(X.Expr));
let NamedReducer = null;
let NamedBody = null;
if (!A) {
for (const X of Assignments) {
const Call = /^([A-Za-z0-9_$]+)\(t,\s*n\)$/.exec(X.Expr.replace(/\s+/g, ' ').trim());
@@ -282,12 +287,28 @@ function PlanSlice(Field) {
if (new RegExp('\\bn\\.' + F.Slot + '\\b').test(T.slice(Fn.Body, Fn.End))) {
A = X;
NamedReducer = Call[1];
NamedBody = T.slice(Fn.Body, Fn.End);
break;
}
}
}
if (!A) throw new Error(Field + ' 在根 reducer 里找不到委派');
// 第 3 种:根 reducer 直接调用对象方法,`fs.eru(t, n.epv_1)`(garagePreview)。
// 方法体在 Methods 表里,和具名 reducer 一样按字段数锁定 copy 方法。
if (!NamedBody) {
const MCall = /^([A-Za-z0-9_$]+)\.([A-Za-z0-9_$]+)\(t,\s*n\.[A-Za-z0-9_$]+\)$/.exec(
A.Expr.replace(/\s+/g, ' ').trim(),
);
if (MCall) {
const M = FindMethod(MCall[1], MCall[2]) || Methods.find((X) => X.Method === MCall[2]);
if (M) {
NamedReducer = MCall[1] + '.' + MCall[2];
NamedBody = T.slice(M.Body, M.End);
}
}
}
const Param = [...SlotOfParam.keys()].find((P2) => SlotOfParam.get(P2) === F.Slot);
const SliceClass = (DefaultOfParam.get(Param) || '').match(/^new ([A-Za-z0-9_$]+)\(/);
const ClassName = SliceClass ? SliceClass[1] : null;
@@ -319,13 +340,11 @@ function PlanSlice(Field) {
}
const CopyName = (A.Expr.match(/\bn\.([A-Za-z0-9_$]+)\(/) || [])[1] || null;
// 具名 reducer 的副本方法只在函数体里(`o.hri(...)`),按「参数个数 == 字段数」锁定。
let NamedBody = null;
if (NamedReducer) {
const Fn = Fns.get(NamedReducer);
NamedBody = T.slice(Fn.Body, Fn.End);
// 具名 reducer / 对象方法 reducer 的副本方法只在函数体里(`o.hri(...)` 或 `n.trs(...)`),
// 按「参数个数 == 字段数」锁定。
if (NamedBody) {
const ByArity = [];
for (const M of NamedBody.matchAll(/\bo\.([A-Za-z0-9_$]+)\(/g)) {
for (const M of NamedBody.matchAll(/\b(?:o|n)\.([A-Za-z0-9_$]+)\(/g)) {
let O = M.index + M[0].length, D = 0, E = -1;
for (let J = O; J < NamedBody.length; J++) {
const C2 = NamedBody[J];
@@ -331,16 +331,16 @@ object ActionCatalog {
ActionMeta("UpgradableItemParams", UpgradableItemParams::class, listOf(ActionFieldMeta("itemPropertyParams", "Any?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("currentLevel", "Int"), ActionFieldMeta("upgradeParams", "Any?"), ActionFieldMeta("upgradeDiscount", "Int"), ActionFieldMeta("upgradeConfirmed", "Any?"))),
ActionMeta("ItemPropertyParams", ItemPropertyParams::class, listOf(ActionFieldMeta("sortIndex", "Int"), ActionFieldMeta("name", "Any?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("aggregationMode", "Any?"), ActionFieldMeta("multiplier", "Any?"), ActionFieldMeta("additiveShift", "Any?"), ActionFieldMeta("isKernelProperty", "Boolean"), ActionFieldMeta("unitName", "Any?"))),
ActionMeta("GaragePreview", GaragePreview::class, listOf(ActionFieldMeta("isVisible", "Any?"), ActionFieldMeta("photoMode", "Any?"), ActionFieldMeta("needVisible", "Any?"), ActionFieldMeta("viewStyle", "Any?"), ActionFieldMeta("dynViewStyle", "Any?"), ActionFieldMeta("tankPreviewSettings", "Any?"), ActionFieldMeta("needShowDrone", "Any?"), ActionFieldMeta("params", "Any?"), ActionFieldMeta("hullData", "Any?"), ActionFieldMeta("weaponData", "Any?"), ActionFieldMeta("droneData", "Any?"), ActionFieldMeta("grenadeData", "Any?"), ActionFieldMeta("hasGrenades", "Any?"), ActionFieldMeta("hullEngineType", "Any?"), ActionFieldMeta("turretMechanism", "Any?"), ActionFieldMeta("paint", "Any?"), ActionFieldMeta("properties", "Any?"), ActionFieldMeta("captureScreenshotAction", "Any?"), ActionFieldMeta("captureVideoAction", "Any?"), ActionFieldMeta("item3dModelActionCache", "Any?"), ActionFieldMeta("requestedItems", "Any?"), ActionFieldMeta("expectedItemId", "Any?"))),
ActionMeta("ShowRequest", ShowRequest::class, listOf(ActionFieldMeta("viewStyle", "Any?"), ActionFieldMeta("tankPreviewSettings", "Any?"), ActionFieldMeta("needShowDrone", "Any?"), ActionFieldMeta("photoMode", "Boolean"), ActionFieldMeta("withDelay", "Boolean"))),
ActionMeta("ShowRequest", ShowRequest::class, listOf(ActionFieldMeta("viewStyle", "Any?"), ActionFieldMeta("tankPreviewSettings", "Any?"), ActionFieldMeta("needShowDrone", "Boolean"), ActionFieldMeta("photoMode", "Boolean"), ActionFieldMeta("withDelay", "Boolean"))),
ActionMeta("UpdatePreviewSettings", UpdatePreviewSettings::class, listOf(ActionFieldMeta("tankPreviewSettings", "Any?"))),
ActionMeta("SetCaptureActions", SetCaptureActions::class, listOf(ActionFieldMeta("screenshotAction", "Any?"), ActionFieldMeta("videoAction", "Any?"))),
ActionMeta("SetCaptureActions", SetCaptureActions::class, listOf(ActionFieldMeta("screenshotAction", "(() -> Unit)?"), ActionFieldMeta("videoAction", "(() -> Unit)?"))),
ActionMeta("InitGarageResources", InitGarageResources::class, listOf(ActionFieldMeta("params", "GaragePreviewModelCC"))),
ActionMeta("SetFromCacheOrRequest", SetFromCacheOrRequest::class, listOf(ActionFieldMeta("item", "Any?"), ActionFieldMeta("request", "Any?"))),
ActionMeta("SetExpectedItemId", SetExpectedItemId::class, listOf(ActionFieldMeta("category", "Any?"), ActionFieldMeta("itemId", "Any?"))),
ActionMeta("AddRequestedItem", AddRequestedItem::class, listOf(ActionFieldMeta("itemId", "Any?"))),
ActionMeta("AddRequestedItem", AddRequestedItem::class, listOf(ActionFieldMeta("itemId", "Long"))),
ActionMeta("SetDrone", SetDrone::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("resource", "Any?"), ActionFieldMeta("emissionColors", "EmissionColors"))),
ActionMeta("SetPaint", SetPaint::class, listOf(ActionFieldMeta("itemId", "Long"), ActionFieldMeta("paint", "Coloring"))),
ActionMeta("ResetItem3dModelActionCache", ResetItem3dModelActionCache::class, listOf(ActionFieldMeta("itemId", "Any?"))),
ActionMeta("ResetItem3dModelActionCache", ResetItem3dModelActionCache::class, listOf(ActionFieldMeta("itemId", "Long"))),
ActionMeta("SetMountedItemsProperties", SetMountedItemsProperties::class, listOf(ActionFieldMeta("properties", "List<String>"))),
ActionMeta("IconsState", IconsState::class, listOf(ActionFieldMeta("icons", "Any?"))),
ActionMeta("SetIcons", SetIcons::class, listOf(ActionFieldMeta("icons", "List<IconData>"))),
@@ -0,0 +1,61 @@
// GaragePreviewModels.kt —— garagePreview 切片里来自其它模块的嵌套模型(原 module_10032 的 IG / fs.eru)
//
// 证据链:
// · TankPartData(module_03569 的 th,导出名 s7):构造 6 字段
// `function th(t, n, i, r, e, u)`,toString 为
// `TankPartData(resource, emissionColors, turretMountPoint, skinOffset,
// leftTrackTexToWorldRatio, rightTrackTexToWorldRatio)`。
// 原 reducer 的 VG/KG/iH/YG 分支都用 `new Dy(…)` 构造它。
// · TankPreviewSettings(module_03569 的 lh,导出名 v7):构造 10 字段,
// toString 为 `TankPreviewSettings(cameraHeight, distanceToTank, lookAtZOffset,
// lookAtXOffset, cameraFov, tankZ, lightSettings, hasReflection,
// reflectionAlpha, reflectionAlphaDistant)`。
// · HullEngineType:module_85196 的 `zw` 枚举,`TRACKED(0)` / `HOVER(1)`;
// 原 `My = p.$_$.k3` 返回 `TRACKED`,是 IG 的默认值。
// · TurretMechanism:module_85196 的 `Fw` 枚举,`ROTATING_TURRET(0)` / `ELEVATING_BARREL(1)`;
// 原 `Oy = p.$_$.m3` 返回 `ROTATING_TURRET`,是 IG 的默认值。
package App.State
/** 原 `TankPreviewLightSettings`(module_03569 的 fh):只占位,本切片不读取其字段。 */
data class TankPreviewLightSettings(
val ambientColor: Any? = null,
val lightColor: Any? = null,
val lightDirection: Any? = null,
val additionalLightDirection: Any? = null,
)
/** 原 `TankPreviewSettings`(module_03569 的 lh):默认值取构造函数。 */
data class TankPreviewSettings(
val cameraHeight: Int = 300,
val distanceToTank: Int = 1750,
val lookAtZOffset: Int = 90,
val lookAtXOffset: Int = 0,
val cameraFov: Double = 0.5,
val tankZ: Int = 120,
val lightSettings: TankPreviewLightSettings = TankPreviewLightSettings(),
val hasReflection: Boolean = true,
val reflectionAlpha: Double = 0.2,
val reflectionAlphaDistant: Double = 0.08,
)
/** 原 `TankPartData`(module_03569 的 th):车体 / 炮塔 / 无人机 / 手雷的 3D 资源数据。 */
data class TankPartData(
val resource: Any? = null,
val emissionColors: EmissionColors? = null,
val turretMountPoint: Any? = null,
val skinOffset: Any? = null,
val leftTrackTexToWorldRatio: Int = 0,
val rightTrackTexToWorldRatio: Int = 0,
)
/** 原 `zw` 枚举:履带式 / 悬浮式。 */
enum class HullEngineType {
TRACKED,
HOVER,
}
/** 原 `Fw` 枚举:旋转炮塔 / 俯仰炮管。 */
enum class TurretMechanism {
ROTATING_TURRET,
ELEVATING_BARREL,
}
@@ -5,7 +5,7 @@ package App.State
import Core.Vector3
// 动作/状态类共 816 个,字段 2150 个(已定类型 567 个)
// 动作/状态类共 816 个,字段 2150 个(已定类型 572 个)
data class AnnouncementState(
val isEnabled: Any?, // 仅 copy 转发(无类型信息)
val modelLoaded: Any?, // 仅 copy 转发(无类型信息)
@@ -1934,7 +1934,7 @@ data class GaragePreview(
data class ShowRequest(
val viewStyle: Any?, // 调用点: new nE(hNn().x1iy()) | t
val tankPreviewSettings: Any?, // 调用点: new tE(350, 1200, 180, $q, 0.6, 0, $q, ! | Mrt(this)
val needShowDrone: Any?, // 调用点: $q | $q
val needShowDrone: Boolean, // override
val photoMode: Boolean, // callsite
val withDelay: Boolean, // callsite
)
@@ -1944,8 +1944,8 @@ data class UpdatePreviewSettings(
)
data class SetCaptureActions(
val screenshotAction: Any?, // 调用点: r | null
val videoAction: Any?, // 调用点: (function (t) { var n = func | null
val screenshotAction: (() -> Unit)?, // override
val videoAction: (() -> Unit)?, // override
)
data class InitGarageResources(
@@ -1963,7 +1963,7 @@ data class SetExpectedItemId(
)
data class AddRequestedItem(
val itemId: Any?, // 调用点: n | n
val itemId: Long, // override
)
data class SetDrone(
@@ -1978,7 +1978,7 @@ data class SetPaint(
)
data class ResetItem3dModelActionCache(
val itemId: Any?, // 调用点: t.zas_1.r1pm_1.zr9_1
val itemId: Long, // override
)
data class SetMountedItemsProperties(
@@ -0,0 +1,258 @@
// GaragePreviewReducer.kt —— 车库 3D 预览切片(对应 module_10032 的 IG 状态 + fs.eru reducer)
//
// 证据链:
// 状态类 IG:`function IG(t, n, i, r, e, u, s, o, _, h, a, c, l, f, v, p, w, d, g, m, k, z)`,
// 22 个字段,toString 名为 `GaragePreview(isVisible=…, …, expectedItemId=…)`。默认值取自构造:
// isVisible / photoMode / needVisible = false,viewStyle / dynViewStyle = null,
// tankPreviewSettings = new Ny()(TankPreviewSettings()),needShowDrone = false,
// params / hullData / weaponData / droneData / grenadeData / paint = null,hasGrenades = false,
// hullEngineType = My()(TRACKED),turretMechanism = Oy()(ROTATING_TURRET),
// properties = md()(emptyList),captureScreenshotAction / captureVideoAction = null,
// item3dModelActionCache / expectedItemId = ag()(emptyMap),requestedItems = Yg()(HashSet)。
//
// 根委派:`fs.eru(t, n.epv_1)`。eru 有 25 个顶层臂:
// 22 个 instanceof 动作(10 个具名 + 12 个匿名) + 3 个单例动作
// (`yd(t, is)` / `yd(t, es)` / `yd(t, rs)`,不在 root_wiring 的 Actions 表里)。
//
// 关键语义(`trs` 是 IG.copy,参数省略时保留原字段):
// · ShowRequest 不是简单展示:withDelay 为 true 时 isVisible=false、needVisible=true。
// · UpdateViewStyle(OG)没有字段,原 `t.fru_1` 是 undefined,`trs` 把它当省略,状态不变。
// · SetHullData / SetWeaponData / SetDrone / SetPaint 先查 `rrs(category, itemId)`:
// 仅当 expectedItemId[category] 为 null 或等于 itemId 时才写 3D 数据;
// 无论是否命中,都会把动作写入 item3dModelActionCache 并从 requestedItems 删除 itemId。
// · ClearHullData / ClearWeaponData / ClearDroneData / ClearGrenadeData 分别置空对应数据。
// · SetGrenadeData 不写缓存、不删 requestedItems,只按 resource 是否为空写 grenadeData。
// · CaptureScreenshot / CaptureVideo 只调用回调,返回原状态。
// · ClearGaragePreviewParams / ShowGaragePreview / HideGaragePreview 是三个单例臂。
package App.State.Reducers
import App.State.AddRequestedItem
import App.State.Coloring
import App.State.EmissionColors
import App.State.GaragePreviewModelCC
import App.State.HullEngineType
import App.State.InitGarageResources
import App.State.ResetItem3dModelActionCache
import App.State.SetCaptureActions
import App.State.SetDrone
import App.State.SetExpectedItemId
import App.State.SetMountedItemsProperties
import App.State.SetPaint
import App.State.ShowRequest
import App.State.TankPartData
import App.State.TankPreviewSettings
import App.State.TurretMechanism
import App.State.UpdatePreviewSettings
/** 原 `vo` 物品分类枚举里本切片用到的键(module_85667 的 `Wt/Vt/Kt/on`)。 */
const val PreviewWeaponCategory: Int = 0
const val PreviewHullCategory: Int = 1
const val PreviewPaintCategory: Int = 2
const val PreviewDroneCategory: Int = 14
/* ------------------------------------------------------------------ 匿名动作 */
// 12 个没有 toString 的 instanceof 动作,按 reducer 分支效果定名(证据见 gen_root_wiring.js)。
class CaptureScreenshot
class CaptureVideo
class ClearHasGrenades
class SetHasGrenades
class UpdateViewStyle
class ClearHullData
class ClearWeaponData
class ClearDroneData
class ClearGrenadeData
data class SetHullData(
val itemId: Long,
val hullEngineType: HullEngineType,
val resource: Any?,
val emissionColors: EmissionColors?,
val turretMountPoint: Any?,
val skinOffset: Any?,
val leftTrackTexToWorldRatio: Int,
val rightTrackTexToWorldRatio: Int,
) {
fun ToTankPartData(): TankPartData = TankPartData(
resource = resource,
emissionColors = emissionColors,
turretMountPoint = turretMountPoint,
skinOffset = skinOffset,
leftTrackTexToWorldRatio = leftTrackTexToWorldRatio,
rightTrackTexToWorldRatio = rightTrackTexToWorldRatio,
)
}
data class SetWeaponData(
val itemId: Long,
val turretMechanism: TurretMechanism,
val resource: Any?,
val emissionColors: EmissionColors?,
) {
fun ToTankPartData(): TankPartData = TankPartData(
resource = resource,
emissionColors = emissionColors,
)
}
data class SetGrenadeData(
val resource: Any?,
val emissionColors: EmissionColors?,
) {
fun ToTankPartData(): TankPartData = TankPartData(
resource = resource,
emissionColors = emissionColors,
)
}
/** 三个单例动作(原 `is` / `es` / `rs`,不在 root_wiring 的 Actions 表里)。 */
class ClearGaragePreviewParams
class ShowGaragePreview
class HideGaragePreview
/* ------------------------------------------------------------------ 状态 */
data class GaragePreviewStateSlice(
val IsVisible: Boolean = false,
val PhotoMode: Boolean = false,
val NeedVisible: Boolean = false,
val ViewStyle: Any? = null,
val DynViewStyle: Any? = null,
val TankPreviewSettings: Any? = TankPreviewSettings(),
val NeedShowDrone: Boolean = false,
val Params: GaragePreviewModelCC? = null,
val HullData: TankPartData? = null,
val WeaponData: TankPartData? = null,
val DroneData: TankPartData? = null,
val GrenadeData: TankPartData? = null,
val HasGrenades: Boolean = false,
val HullEngineType: HullEngineType = App.State.HullEngineType.TRACKED,
val TurretMechanism: TurretMechanism = App.State.TurretMechanism.ROTATING_TURRET,
val Paint: Coloring? = null,
val Properties: List<String> = emptyList(),
val CaptureScreenshotAction: (() -> Unit)? = null,
val CaptureVideoAction: (() -> Unit)? = null,
val Item3dModelActionCache: Map<Long, Any> = emptyMap(),
val RequestedItems: Set<Long> = emptySet(),
val ExpectedItemId: Map<Any?, Any?> = emptyMap(),
)
/* ------------------------------------------------------------------ 小工具 */
/** 原 `IG.rrs(category, itemId)`:expectedItemId[category] 为 null 或等于 itemId。 */
private fun GaragePreviewStateSlice.Rrs(Category: Any?, ItemId: Any?): Boolean {
val Expected = ExpectedItemId[Category]
return Expected == null || Expected == ItemId
}
private fun <T> Map<Long, T>.With(ItemId: Long, Value: T): Map<Long, T> = this + (ItemId to Value)
/* ------------------------------------------------------------------ reducer */
fun ReduceGaragePreview(State0: GaragePreviewStateSlice, Action: Any): GaragePreviewStateSlice = when (Action) {
is ClearGaragePreviewParams -> State0.copy(Params = null)
is ShowGaragePreview -> State0.copy(IsVisible = true, NeedVisible = false)
is HideGaragePreview -> State0.copy(IsVisible = false)
is ShowRequest -> State0.copy(
// 原:`var r = !t.yrs_1 && n.vrr_1, e = !!t.yrs_1 || n.xrr_1`。
IsVisible = !Action.withDelay && State0.IsVisible,
PhotoMode = Action.photoMode,
NeedVisible = Action.withDelay || State0.NeedVisible,
ViewStyle = Action.viewStyle,
TankPreviewSettings = Action.tankPreviewSettings,
NeedShowDrone = Action.needShowDrone,
)
is UpdatePreviewSettings -> State0.copy(TankPreviewSettings = Action.tankPreviewSettings)
is UpdateViewStyle -> State0
is InitGarageResources -> State0.copy(Params = Action.params)
is SetExpectedItemId -> {
// 原:itemId == null ? am(map, category) : cg(map, category to itemId)。
val NewExpected = if (Action.itemId == null) {
State0.ExpectedItemId - Action.category
} else {
State0.ExpectedItemId + (Action.category to Action.itemId)
}
State0.copy(ExpectedItemId = NewExpected)
}
is AddRequestedItem -> State0.copy(RequestedItems = State0.RequestedItems + Action.itemId)
is SetHullData -> {
val Matches = State0.Rrs(PreviewHullCategory, Action.itemId)
State0.copy(
HullData = if (Matches) Action.ToTankPartData() else State0.HullData,
HullEngineType = if (Matches) Action.hullEngineType else State0.HullEngineType,
Item3dModelActionCache = State0.Item3dModelActionCache.With(Action.itemId, Action),
RequestedItems = State0.RequestedItems - Action.itemId,
)
}
is SetWeaponData -> {
val Matches = State0.Rrs(PreviewWeaponCategory, Action.itemId)
State0.copy(
WeaponData = if (Matches) Action.ToTankPartData() else State0.WeaponData,
TurretMechanism = if (Matches) Action.turretMechanism else State0.TurretMechanism,
Item3dModelActionCache = State0.Item3dModelActionCache.With(Action.itemId, Action),
RequestedItems = State0.RequestedItems - Action.itemId,
)
}
is SetDrone -> {
val Matches = State0.Rrs(PreviewDroneCategory, Action.itemId)
val Data = if (Action.resource == null) null else TankPartData(
resource = Action.resource,
emissionColors = Action.emissionColors,
)
State0.copy(
DroneData = if (Matches) Data else State0.DroneData,
Item3dModelActionCache = State0.Item3dModelActionCache.With(Action.itemId, Action),
RequestedItems = State0.RequestedItems - Action.itemId,
)
}
is SetGrenadeData -> State0.copy(
GrenadeData = if (Action.resource == null) null else Action.ToTankPartData(),
)
is ClearWeaponData -> State0.copy(WeaponData = null)
is ClearHullData -> State0.copy(HullData = null)
is ClearDroneData -> State0.copy(DroneData = null)
is ClearGrenadeData -> State0.copy(GrenadeData = null)
is SetHasGrenades -> State0.copy(HasGrenades = true)
is ClearHasGrenades -> State0.copy(HasGrenades = false)
is SetPaint -> {
val Matches = State0.Rrs(PreviewPaintCategory, Action.itemId)
State0.copy(
Paint = if (Matches) Action.paint else State0.Paint,
Item3dModelActionCache = State0.Item3dModelActionCache.With(Action.itemId, Action),
RequestedItems = State0.RequestedItems - Action.itemId,
)
}
is ResetItem3dModelActionCache ->
State0.copy(Item3dModelActionCache = State0.Item3dModelActionCache - Action.itemId)
is SetMountedItemsProperties -> State0.copy(Properties = Action.properties)
is SetCaptureActions -> State0.copy(
CaptureScreenshotAction = Action.screenshotAction,
CaptureVideoAction = Action.videoAction,
)
is CaptureScreenshot -> {
State0.CaptureScreenshotAction?.invoke()
State0
}
is CaptureVideo -> {
State0.CaptureVideoAction?.invoke()
State0
}
else -> State0
}
@@ -12,13 +12,15 @@
// screen → ReduceScreen(WK / dY().ks5)
// lootBoxes → ReduceLootBoxes(aH / EH.wrv)
// garage → ReduceGarage(UB / hF,根委派 `i = hF(t, n)`)
// garagePreview → ReduceGaragePreview(IG / fs.eru,根委派 `fs.eru(t, n.epv_1)`)
//
// 其余 80 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 79 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
import App.State.Reducers.ReduceGarage
import App.State.Reducers.ReduceGaragePreview
import App.State.Reducers.ReduceHome
import App.State.Reducers.ReduceInvite
import App.State.Reducers.ReduceLocalBattleUser
@@ -50,6 +52,7 @@ object RootReducer {
Screen = ReduceScreen(State0.Screen, Action),
LootBoxes = ReduceLootBoxes(State0.LootBoxes, Action),
Garage = ReduceGarage(State0.Garage, Action),
GaragePreview = ReduceGaragePreview(State0.GaragePreview, Action),
)
}
@@ -5,6 +5,7 @@
package App.State
import App.State.Reducers.GarageStateSlice
import App.State.Reducers.GaragePreviewStateSlice
import App.State.Reducers.HomeStateSlice
import App.State.Reducers.InviteStateSlice
import App.State.Reducers.LocalBattleUserStateSlice
@@ -31,8 +32,8 @@ data class RootState(
val BattleTutorial: Any? = null,
/** 原 garage(UB / hF)。已迁移。 */
val Garage: GarageStateSlice = GarageStateSlice(),
/** 原 garagePreview(IG / fs.eru)。未迁移:原样透传。 */
val GaragePreview: Any? = null,
/** 原 garagePreview(IG / fs.eru)。已迁移。 */
val GaragePreview: GaragePreviewStateSlice = GaragePreviewStateSlice(),
/** 原 challenges(CZ / nh.hsj)。未迁移:原样透传。 */
val Challenges: Any? = null,
/** 原 loginByPassword(gD / nu.cr6)。已迁移。 */
@@ -20,7 +20,7 @@ object RootWiring {
SliceWiring("battleStatistics", "bpv_1", "jR", "rr.fqr", "rr.fqr(t, n.bpv_1)", listOf("?DR", "?HR", "?KR", "?PR", "?QR", "?UR", "?XR", "?YR", "?ZR", "?nP", "?tP", "Init", "InitBattleModeWithLevels", "SetBattleMode", "SetDisplayClientInfo", "SetFlagOrBallOwner", "SetRankRange", "SetReArmor", "SetRemainingTime", "SetRoundRestartTime", "UpdateFund", "UpdateScore")),
SliceWiring("battleTutorial", "cpv_1", "rE", "Q.sq8", "Q.sq8(t, n.cpv_1)", listOf("?oE", "?sE", "SetFragsCount", "ShowMessage")),
SliceWiring("garage", "dpv_1", "UB", "hF", "hF(t, n)", listOf("AddGarageItemCount", "AddNewItem", "AddOverdriveDescription", "AddRelatedBazooka", "AddRelatedResistanceModule", "AddToFavoriteItem", "BuyProtectionSlot", "ClearWaitServerItemMount", "GarageAddItemToDepot", "GarageApplyResistanceMount", "GarageBuyDevice", "GarageDeviceTrySetInfinityLifetime", "GarageDevicesLoadedAction", "GarageLoaded", "GarageMountItem", "GarageMountItems", "GarageRemoveDevice", "GarageRemoveDiscountForDevice", "GarageRemoveItemFromDepot", "GarageRemoveItemFromStore", "GarageResistanceUnMount", "GarageSelectAlteration", "GarageSelectItem", "GarageSelectShotSkin", "GarageSelectSkin", "GarageUnmountItem", "GarageUpdateItemCount", "GarageUpgradeItemAction", "GarageUpgradeItemConfirm", "GarageUpgradeItemCoolDownFinished", "GarageUpgradeItemCoolDownStarted", "GoTo", "HideVideoInDescription", "InitState", "RemoveFromFavoriteItem", "RemoveFromSale", "RemoveNewItem", "RemoveWaitServerItemMount", "ResetGarage", "ResetMountDelay", "SelectGarageCategory", "SetCooldownReduceEndTime", "SetGarageNotLoaded", "SetGarageObject", "SetGarageSkinsAction", "SetItemRarity", "SetItemsOnDepot", "SetItemsOnMarket", "SetMountedItems", "SetMountedShotSkinForItems", "SetMountedSkinForItems", "SetNewItems", "SetResistanceReadyToMount", "SetRestrictionMount", "ShowGarageDiscountNotification", "StartCooldownReduce", "SwitchingRowsScroll", "UpdateDiscount")),
SliceWiring("garagePreview", "epv_1", "IG", "fs.eru", "fs.eru(t, n.epv_1)", listOf("?BG", "?JG", "?KG", "?OG", "?QG", "?UG", "?VG", "?XG", "?YG", "?ZG", "?nH", "?tH", "AddRequestedItem", "InitGarageResources", "ResetItem3dModelActionCache", "SetCaptureActions", "SetDrone", "SetExpectedItemId", "SetMountedItemsProperties", "SetPaint", "ShowRequest", "UpdatePreviewSettings")),
SliceWiring("garagePreview", "epv_1", "IG", "fs.eru", "fs.eru(t, n.epv_1)", listOf("AddRequestedItem", "CaptureScreenshot", "CaptureVideo", "ClearDroneData", "ClearGrenadeData", "ClearHasGrenades", "ClearHullData", "ClearWeaponData", "InitGarageResources", "ResetItem3dModelActionCache", "SetCaptureActions", "SetDrone", "SetExpectedItemId", "SetGrenadeData", "SetHasGrenades", "SetHullData", "SetMountedItemsProperties", "SetPaint", "SetWeaponData", "ShowRequest", "UpdatePreviewSettings", "UpdateViewStyle")),
SliceWiring("challenges", "fpv_1", "CZ", "nh.hsj", "nh.hsj(t, n.fpv_1)", listOf("?LZ", "RemoveChallenge", "SetBattlePass", "SetBattlePassPackages", "SetChallenge", "SetRewardForDialog", "SetTakenBattlePassRewardIndex", "SetTakenFreeRewardIndex", "UpdateStars")),
SliceWiring("loginByPassword", "gpv_1", "gD", "nu.cr6", "nu.cr6(t, n.gpv_1)", listOf("?ND", "?PD", "?RD", "IncorrectLogin2FA", "RequestsNumber2FA", "SetLogin", "SetPassword", "SetRememberMe", "SetStatus")),
SliceWiring("register", "hpv_1", "Cit", "cp.stg", "cp.stg(t, n.hpv_1)", listOf("?Bit", "?Dit", "?Fit", "?Git", "?Hit", "?Lit", "?Mit", "?Uit", "?_rt", "?hrt", "EnteredUidIsBusy", "ForceSetUid", "ReceiveOAuthUri", "SetOpenedPassword", "SetPassword", "SetPasswordRestrictions", "SetUid", "SocialNetworkAvailability", "SocialNetworkClientIds")),
@@ -106,7 +106,7 @@ object RootWiring {
)
/** 已迁移(由 RootReducer 真正归约)的字段。 */
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage")
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
@@ -9,8 +9,9 @@
// ⑥ 动作目录:816 个类与 138 个真实派发名对齐
// ⑦ 回放:真实 toString 样例的类/字段/类型校验
// ⑧ 全目录动作类都能按 KClass 路由
// ⑨~⑲ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage;⑭ 根 reducer 的 89 槽位委派
// ⑨~⑳ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage / GaragePreview;
// ⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
package App
@@ -209,19 +210,52 @@ import App.State.SetRestrictionMount
import App.State.SwitchingRowsScroll
import App.State.UpdateDiscount
import App.State.UpgradableItemParams
import App.State.AddRequestedItem
import App.State.Coloring
import App.State.EmissionColors
import App.State.GaragePreviewModelCC
import App.State.HullEngineType
import App.State.InitGarageResources
import App.State.ResetItem3dModelActionCache
import App.State.SetCaptureActions
import App.State.SetDrone
import App.State.SetExpectedItemId
import App.State.SetMountedItemsProperties
import App.State.SetPaint
import App.State.ShowRequest
import App.State.TankPartData
import App.State.TankPreviewSettings
import App.State.TextureFrameAnimation
import App.State.TurretMechanism
import App.State.UpdatePreviewSettings
import App.State.Reducers.AddGarageItemCount
import App.State.Reducers.AddNewItem
import App.State.Reducers.CaptureScreenshot
import App.State.Reducers.CaptureVideo
import App.State.Reducers.ClearDroneData
import App.State.Reducers.ClearGaragePreviewParams
import App.State.Reducers.ClearGrenadeData
import App.State.Reducers.ClearHasGrenades
import App.State.Reducers.ClearHullData
import App.State.Reducers.ClearUpgradeItemInProcessing
import App.State.Reducers.ClearWaitServerItemMount
import App.State.Reducers.ClearWeaponData
import App.State.Reducers.GarageDefaultCategory
import App.State.Reducers.GarageLoaded
import App.State.Reducers.GarageMountItem
import App.State.Reducers.GarageMountItems
import App.State.Reducers.GaragePreviewStateSlice
import App.State.Reducers.GarageResistanceCategoryKey
import App.State.Reducers.GarageStateSlice
import App.State.Reducers.GarageUnmountItem
import App.State.Reducers.HideGaragePreview
import App.State.Reducers.HideVideoInDescription
import App.State.Reducers.PreviewDroneCategory
import App.State.Reducers.PreviewHullCategory
import App.State.Reducers.PreviewPaintCategory
import App.State.Reducers.PreviewWeaponCategory
import App.State.Reducers.ReduceGarage
import App.State.Reducers.ReduceGaragePreview
import App.State.Reducers.RemoveNewItem
import App.State.Reducers.RemoveWaitServerItemMount
import App.State.Reducers.ResetGarage
@@ -230,6 +264,9 @@ import App.State.Reducers.SelectGarageCategory
import App.State.Reducers.SetCooldownReduceEndTime
import App.State.Reducers.SetGarageNotLoaded
import App.State.Reducers.SetGarageObject
import App.State.Reducers.SetGrenadeData
import App.State.Reducers.SetHasGrenades
import App.State.Reducers.SetHullData
import App.State.Reducers.SetItemsOnDepot
import App.State.Reducers.SetItemsOnMarket
import App.State.Reducers.SetMountedItems
@@ -237,8 +274,11 @@ import App.State.Reducers.SetMountedShotSkinForItems
import App.State.Reducers.SetMountedSkinForItems
import App.State.Reducers.SetNewItems
import App.State.Reducers.SetResistanceReadyToMount
import App.State.Reducers.SetWeaponData
import App.State.Reducers.ShowGarageDiscountNotification
import App.State.Reducers.ShowGaragePreview
import App.State.Reducers.StartCooldownReduce
import App.State.Reducers.UpdateViewStyle
import State.InitState
private data class AddFrags(val fragsCount: Int)
@@ -613,7 +653,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 九个已迁移切片一次归约
// ⑭ 根 reducer:89 个槽位的委派表 + 十个已迁移切片一次归约
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -677,13 +717,14 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 九个已迁移字段,其余 80 个待迁",
"根:迁移表 = 十个已迁移字段,其余 79 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
"localBattleUserState", "shop", "screen", "lootBoxes", "garage",
"garagePreview",
) &&
RootWiring.Unmigrated.size == 80
RootWiring.Unmigrated.size == 79
)
// 一次 ReduceRoot 里八个切片各归约自己的动作,互不串片
@@ -739,7 +780,7 @@ object StateCheck {
S5.User == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 9/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 10/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -1954,6 +1995,303 @@ object StateCheck {
)
}
// ⑳ 切片 reducer:GaragePreview(原 module_10032 的 IG / fs.eru,22 字段;25 个顶层臂)
run {
val S0 = GaragePreviewStateSlice()
Check(
"切片 GaragePreview:默认值与原构造一致(22 字段)",
!S0.IsVisible && !S0.PhotoMode && !S0.NeedVisible &&
S0.ViewStyle == null && S0.DynViewStyle == null &&
S0.TankPreviewSettings is TankPreviewSettings && !S0.NeedShowDrone &&
S0.Params == null && S0.HullData == null && S0.WeaponData == null &&
S0.DroneData == null && S0.GrenadeData == null && !S0.HasGrenades &&
S0.HullEngineType == HullEngineType.TRACKED &&
S0.TurretMechanism == TurretMechanism.ROTATING_TURRET &&
S0.Paint == null && S0.Properties.isEmpty() &&
S0.CaptureScreenshotAction == null && S0.CaptureVideoAction == null &&
S0.Item3dModelActionCache.isEmpty() && S0.RequestedItems.isEmpty() &&
S0.ExpectedItemId.isEmpty()
)
val Colors = EmissionColors(1, 2, 3, 4, "STRICT")
val OldSettings = TankPreviewSettings(cameraHeight = 111)
val NewSettings = TankPreviewSettings(cameraHeight = 777)
val Params = GaragePreviewModelCC(80, 1000, 1.57, -110, null, null)
val OldPaint = Coloring(null, TextureFrameAnimation(32, 4, 8, 16))
val NewPaint = Coloring(null, TextureFrameAnimation(64, 8, 8, 32))
val OldPart = TankPartData(resource = "old")
val Base = S0.copy(
IsVisible = true,
NeedVisible = false,
ViewStyle = "old",
DynViewStyle = "dyn-old",
TankPreviewSettings = OldSettings,
Params = Params,
HullData = OldPart,
WeaponData = OldPart,
DroneData = OldPart,
GrenadeData = OldPart,
Paint = OldPaint,
Properties = listOf("old"),
)
val ClearParams = ReduceGaragePreview(Base, ClearGaragePreviewParams())
Check("切片 GaragePreview:ClearGaragePreviewParams 只清 params", ClearParams.Params == null)
val Shown = ReduceGaragePreview(Base.copy(NeedVisible = true), ShowGaragePreview())
Check(
"切片 GaragePreview:ShowGaragePreview 置 isVisible=true、needVisible=false",
Shown.IsVisible && !Shown.NeedVisible
)
Check(
"切片 GaragePreview:HideGaragePreview 置 isVisible=false",
!ReduceGaragePreview(Base, HideGaragePreview()).IsVisible
)
val Req = ShowRequest(
viewStyle = "new",
tankPreviewSettings = NewSettings,
needShowDrone = true,
photoMode = true,
withDelay = false,
)
val S1 = ReduceGaragePreview(Base, Req)
Check(
"切片 GaragePreview:ShowRequest(无延迟)保留 isVisible 并写 view/settings/photo/drone",
S1.IsVisible && !S1.NeedVisible && S1.ViewStyle == "new" &&
S1.TankPreviewSettings == NewSettings && S1.PhotoMode &&
S1.NeedShowDrone && S1.DynViewStyle == "dyn-old" &&
S1.Params == Params && S1.HullData == OldPart
)
val S2 = ReduceGaragePreview(Base, Req.copy(withDelay = true))
Check(
"切片 GaragePreview:ShowRequest(延迟)置 isVisible=false、needVisible=true",
!S2.IsVisible && S2.NeedVisible && S2.ViewStyle == "new"
)
Check(
"切片 GaragePreview:UpdatePreviewSettings 只写 tankPreviewSettings",
ReduceGaragePreview(Base, UpdatePreviewSettings(NewSettings)).TankPreviewSettings == NewSettings
)
Check(
"切片 GaragePreview:UpdateViewStyle 无字段,原 fru_1=undefined 被 trs 视作省略",
ReduceGaragePreview(Base, UpdateViewStyle()) == Base
)
val Init = ReduceGaragePreview(S0, InitGarageResources(Params))
Check("切片 GaragePreview:InitGarageResources 写 params", Init.Params == Params)
val Expected1 = ReduceGaragePreview(S0, SetExpectedItemId(PreviewHullCategory, 42L))
Check(
"切片 GaragePreview:SetExpectedItemId 写分类键",
Expected1.ExpectedItemId[PreviewHullCategory] == 42L
)
val Expected2 = ReduceGaragePreview(Expected1, SetExpectedItemId(PreviewHullCategory, null))
Check(
"切片 GaragePreview:SetExpectedItemId(itemId=null) 删除分类键",
!Expected2.ExpectedItemId.containsKey(PreviewHullCategory)
)
Check(
"切片 GaragePreview:AddRequestedItem 加入 requestedItems",
ReduceGaragePreview(S0, AddRequestedItem(42L)).RequestedItems == setOf(42L)
)
val HullAction = SetHullData(
itemId = 42L,
hullEngineType = HullEngineType.HOVER,
resource = "hull",
emissionColors = Colors,
turretMountPoint = "mount",
skinOffset = "skin",
leftTrackTexToWorldRatio = 7,
rightTrackTexToWorldRatio = 8,
)
val HullBase = S0.copy(RequestedItems = setOf(42L))
val HullHit = ReduceGaragePreview(HullBase, HullAction)
Check(
"切片 GaragePreview:SetHullData 命中时写 hullData/hullEngineType、缓存动作并删 requested",
HullHit.HullData == TankPartData("hull", Colors, "mount", "skin", 7, 8) &&
HullHit.HullEngineType == HullEngineType.HOVER &&
HullHit.Item3dModelActionCache[42L] == HullAction &&
HullHit.RequestedItems.isEmpty()
)
val HullMiss = ReduceGaragePreview(
HullBase.copy(ExpectedItemId = mapOf<Any?, Any?>(PreviewHullCategory to 99L)),
HullAction,
)
Check(
"切片 GaragePreview:SetHullData 未命中只缓存动作,不写 hullData",
HullMiss.HullData == null && HullMiss.HullEngineType == HullEngineType.TRACKED &&
HullMiss.Item3dModelActionCache[42L] == HullAction &&
HullMiss.RequestedItems.isEmpty()
)
val WeaponAction = SetWeaponData(
itemId = 43L,
turretMechanism = TurretMechanism.ELEVATING_BARREL,
resource = "weapon",
emissionColors = Colors,
)
val WeaponBase = S0.copy(RequestedItems = setOf(43L))
val WeaponHit = ReduceGaragePreview(WeaponBase, WeaponAction)
Check(
"切片 GaragePreview:SetWeaponData 命中时写 weaponData/turretMechanism、缓存动作并删 requested",
WeaponHit.WeaponData == TankPartData("weapon", Colors) &&
WeaponHit.TurretMechanism == TurretMechanism.ELEVATING_BARREL &&
WeaponHit.Item3dModelActionCache[43L] == WeaponAction &&
WeaponHit.RequestedItems.isEmpty()
)
val WeaponMiss = ReduceGaragePreview(
WeaponBase.copy(ExpectedItemId = mapOf<Any?, Any?>(PreviewWeaponCategory to 99L)),
WeaponAction,
)
Check(
"切片 GaragePreview:SetWeaponData 未命中只缓存动作,不写 weaponData",
WeaponMiss.WeaponData == null &&
WeaponMiss.TurretMechanism == TurretMechanism.ROTATING_TURRET &&
WeaponMiss.Item3dModelActionCache[43L] == WeaponAction
)
val DroneAction = SetDrone(44L, "drone", Colors)
val DroneHit = ReduceGaragePreview(S0.copy(RequestedItems = setOf(44L)), DroneAction)
Check(
"切片 GaragePreview:SetDrone 命中时写 droneData 并删 requested",
DroneHit.DroneData == TankPartData("drone", Colors) &&
DroneHit.Item3dModelActionCache[44L] == DroneAction &&
DroneHit.RequestedItems.isEmpty()
)
val DroneMiss = ReduceGaragePreview(
S0.copy(
DroneData = OldPart,
ExpectedItemId = mapOf<Any?, Any?>(PreviewDroneCategory to 99L),
),
DroneAction,
)
Check(
"切片 GaragePreview:SetDrone 未命中保留 droneData",
DroneMiss.DroneData == OldPart && DroneMiss.Item3dModelActionCache[44L] == DroneAction
)
Check(
"切片 GaragePreview:SetDrone(resource=null) 命中时写 null",
ReduceGaragePreview(S0.copy(DroneData = OldPart), SetDrone(45L, null, Colors)).DroneData == null
)
Check(
"切片 GaragePreview:SetGrenadeData 写 grenadeData",
ReduceGaragePreview(S0, SetGrenadeData("grenade", Colors)).GrenadeData ==
TankPartData("grenade", Colors)
)
Check(
"切片 GaragePreview:SetGrenadeData(resource=null) 写 null",
ReduceGaragePreview(S0.copy(GrenadeData = OldPart), SetGrenadeData(null, Colors)).GrenadeData == null
)
Check("切片 GaragePreview:ClearHullData", ReduceGaragePreview(Base, ClearHullData()).HullData == null)
Check("切片 GaragePreview:ClearWeaponData", ReduceGaragePreview(Base, ClearWeaponData()).WeaponData == null)
Check("切片 GaragePreview:ClearDroneData", ReduceGaragePreview(Base, ClearDroneData()).DroneData == null)
Check("切片 GaragePreview:ClearGrenadeData", ReduceGaragePreview(Base, ClearGrenadeData()).GrenadeData == null)
Check(
"切片 GaragePreview:SetHasGrenades",
ReduceGaragePreview(S0, SetHasGrenades()).HasGrenades
)
Check(
"切片 GaragePreview:ClearHasGrenades",
!ReduceGaragePreview(S0.copy(HasGrenades = true), ClearHasGrenades()).HasGrenades
)
val PaintAction = SetPaint(46L, NewPaint)
val PaintHit = ReduceGaragePreview(S0.copy(RequestedItems = setOf(46L)), PaintAction)
Check(
"切片 GaragePreview:SetPaint 命中时写 paint、缓存动作并删 requested",
PaintHit.Paint == NewPaint && PaintHit.Item3dModelActionCache[46L] == PaintAction &&
PaintHit.RequestedItems.isEmpty()
)
val PaintMiss = ReduceGaragePreview(
S0.copy(
Paint = OldPaint,
ExpectedItemId = mapOf<Any?, Any?>(PreviewPaintCategory to 99L),
),
PaintAction,
)
Check(
"切片 GaragePreview:SetPaint 未命中保留 paint",
PaintMiss.Paint == OldPaint && PaintMiss.Item3dModelActionCache[46L] == PaintAction
)
Check(
"切片 GaragePreview:ResetItem3dModelActionCache 删缓存",
!ReduceGaragePreview(PaintHit, ResetItem3dModelActionCache(46L))
.Item3dModelActionCache.containsKey(46L)
)
Check(
"切片 GaragePreview:SetMountedItemsProperties 写 properties",
ReduceGaragePreview(S0, SetMountedItemsProperties(listOf("ROTATING_WEAPON", "HOVER_CHASSIS")))
.Properties == listOf("ROTATING_WEAPON", "HOVER_CHASSIS")
)
var Shots = 0
var Videos = 0
val Capture = ReduceGaragePreview(S0, SetCaptureActions({ Shots += 1 }, { Videos += 1 }))
Check(
"切片 GaragePreview:SetCaptureActions 写两个回调",
Capture.CaptureScreenshotAction != null && Capture.CaptureVideoAction != null
)
Check(
"切片 GaragePreview:CaptureScreenshot 只调用回调、状态不变",
ReduceGaragePreview(Capture, CaptureScreenshot()) == Capture && Shots == 1
)
Check(
"切片 GaragePreview:CaptureVideo 只调用回调、状态不变",
ReduceGaragePreview(Capture, CaptureVideo()) == Capture && Videos == 1
)
Check(
"切片 GaragePreview:无关动作返回等值状态",
ReduceGaragePreview(Base, AddFrags(1)) == Base
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, AddRequestedItem(7L))
Check(
"根:AddRequestedItem 只动 garagePreview",
Root1.GaragePreview.RequestedItems == setOf(7L) && Root1.Garage == Root0.Garage &&
Root1.Shop == Root0.Shop && Root1.Screen == Root0.Screen && Root1.LootBoxes == Root0.LootBoxes
)
val Root2 = ReduceRoot(Root1, SetHasGrenades())
Check(
"根:SetHasGrenades 保留 garagePreview 前一次归约",
Root2.GaragePreview.HasGrenades && Root2.GaragePreview.RequestedItems == setOf(7L)
)
Check("根:garagePreview 迁移后无关动作仍返回等值状态", ReduceRoot(Root2, AddFrags(1)) == Root2)
val W = RootWiring.ByField("garagePreview")!!
Check(
"根:garagePreview 委派到 IG / fs.eru 且 22 个动作全部具名",
W.SliceClass == "IG" && W.Reducer == "fs.eru" && W.Actions.size == 22 &&
W.Actions.none { it.startsWith("?") }
)
Check(
"根:garagePreview 的动作名覆盖 22 个 instanceof 臂",
listOf(
"AddRequestedItem", "CaptureScreenshot", "CaptureVideo", "ClearDroneData",
"ClearGrenadeData", "ClearHasGrenades", "ClearHullData", "ClearWeaponData",
"InitGarageResources", "ResetItem3dModelActionCache", "SetCaptureActions",
"SetDrone", "SetExpectedItemId", "SetGrenadeData", "SetHasGrenades",
"SetHullData", "SetMountedItemsProperties", "SetPaint", "SetWeaponData",
"ShowRequest", "UpdatePreviewSettings", "UpdateViewStyle",
).all { it in W.Actions }
)
Check(
"根:garagePreview 的动作表不含 IG 标识和 3 个单例臂",
W.Actions.none {
it == "IG" || it == "GaragePreview" ||
it == "ClearGaragePreviewParams" || it == "ShowGaragePreview" || it == "HideGaragePreview"
}
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)