From 77a90fdffff77328c2affa72d27de3e178e86684 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 23 Oct 2026 15:47:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(state):=20=E8=BF=81=E7=A7=BB=20entrance=20?= =?UTF-8?q?=E5=88=87=E7=89=87=EF=BC=888=20=E5=AD=97=E6=AE=B5=20/=207=20?= =?UTF-8?q?=E8=87=82=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DTank/docs/KtRebuild_Plan.md | 18 ++- 3DTank/names/root_wiring.json | 55 +++++++-- 3DTank/tools/gen_root_wiring.js | 25 ++++ .../App/State/Reducers/EntranceReducer.kt | 58 +++++++++ .../jsMain/kotlin/App/State/RootReducer.kt | 7 +- .../src/jsMain/kotlin/App/State/RootState.kt | 5 +- .../src/jsMain/kotlin/App/State/RootWiring.kt | 4 +- .../app/src/jsMain/kotlin/App/StateCheck.kt | 112 ++++++++++++++++-- 8 files changed, 255 insertions(+), 29 deletions(-) create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/EntranceReducer.kt diff --git a/3DTank/docs/KtRebuild_Plan.md b/3DTank/docs/KtRebuild_Plan.md index e641c71..00d0090 100644 --- a/3DTank/docs/KtRebuild_Plan.md +++ b/3DTank/docs/KtRebuild_Plan.md @@ -143,7 +143,7 @@ `names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。 - 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。 - 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。 - - 已迁移切片 **11/89**: + - 已迁移切片 **12/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) @@ -181,12 +181,17 @@ `TankPartData` / `TankPreviewSettings` / `HullEngineType` / `TurretMechanism` 见 `GaragePreviewModels.kt`; 12 个匿名动作按效果定名(`BG`→`CaptureScreenshot` … `tH`→`ClearDroneData`), 3 个单例臂定名 `ClearGaragePreviewParams` / `ShowGaragePreview` / `HideGaragePreview`,不在 `Actions` 表里。 + - `entrance` → `hD` / `Bv.pth`(`EntranceReducer.kt`):8 个布尔字段,7 个 `instanceof` 臂。 + `showSocialNetworks` 缺省 true,其余 false;`HideSocialNetworks` 置 false, + `SetControlsForChina` / `SetRunningInChinaDataCenter` / `SetRunningInChinaPartners` / + `SetAuthorizedByChinaSdkToken` / `SetClassic` 各置一个 true,`SetAgeWarnShow` 写入 `isAgeWarnShow`。 + 原 reducer 用 `ur4` 的 `cd` 参数保留其余字段;`authorizedByClassicEarlyAccessToken` 没有写入臂。 - `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map; `RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map`,不新增 reducer/动作。 - - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 10 个有 reducer 的切片, - `launchParams` 与其余 **78 个字段原样透传**; - 迁移进度用 `RootReducer.Coverage`(`已迁移 11/89`)与 `RootWiring.Unmigrated` 暴露。 - - 自检:`TankiOffline/tools/RunStateTest.ps1` **280 项全绿**,含 89 槽位完整性、10 个归约切片一次归约互不串片、未迁移字段透传, + - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 11 个有 reducer 的切片, + `launchParams` 与其余 **77 个字段原样透传**; + 迁移进度用 `RootReducer.Coverage`(`已迁移 12/89`)与 `RootWiring.Unmigrated` 暴露。 + - 自检:`TankiOffline/tools/RunStateTest.ps1` **294 项全绿**,含 89 槽位完整性、11 个归约切片一次归约互不串片、未迁移字段透传, 以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。 - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, 已按字段具名为 `(inline@)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); @@ -195,7 +200,8 @@ `o1` → `ShowShopDiscountNotification`(`$_$.gl` 单例,由 module_18334 的 `Qx.qge` 派发; 效果是把 `HomeScreen.shopDiscountNotification` 置 true,对应主菜单商店项的折扣提示); shop 的 17 个匿名类按 reducer 效果定名并登记(`Yut`→`ShopLoaded`、`F5`→`ResetShop` 等); - screen 的 9 个、lootBoxes 的 6 个、garage 的 25 个、garagePreview 的 15 个匿名类同样按效果定名并登记。 + screen 的 9 个、lootBoxes 的 6 个、garage 的 25 个、garagePreview 的 15 个、 + entrance 的 6 个匿名类同样按效果定名并登记。 ### S4 引擎核心 - 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入) diff --git a/3DTank/names/root_wiring.json b/3DTank/names/root_wiring.json index 82b06d4..f9bf064 100644 --- a/3DTank/names/root_wiring.json +++ b/3DTank/names/root_wiring.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-10-23T07:24:48.002Z", + "generatedAt": "2026-10-23T07:37:58.358Z", "module": "module_10032.js", "RootState": { "Class": "z6", @@ -17,6 +17,7 @@ "lootBoxes", "garage", "garagePreview", + "entrance", "launchParams" ], "AnonymousActions": [ @@ -458,6 +459,42 @@ "Name": "HideGaragePreview", "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", "Effect": "isVisible = false(`n.trs(!1)`)" + }, + { + "Local": "eit", + "Name": "HideSocialNetworks", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "showSocialNetworks = false(`n.ur4(!1)`)" + }, + { + "Local": "uit", + "Name": "SetControlsForChina", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "controlsForChina = true(`n.ur4(cd, !0)`)" + }, + { + "Local": "sit", + "Name": "SetRunningInChinaDataCenter", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "isRunningInChinaDataCenter = true(`n.ur4(cd, cd, !0)`)" + }, + { + "Local": "oit", + "Name": "SetRunningInChinaPartners", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "isRunningInChinaPartners = true(`n.ur4(cd, cd, cd, !0)`)" + }, + { + "Local": "lit", + "Name": "SetAuthorizedByChinaSdkToken", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "authorizedByChinaSdkToken = true(`n.ur4(cd, cd, cd, cd, cd, !0)`)" + }, + { + "Local": "fit", + "Name": "SetClassic", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "isClassic = true(`n.ur4(cd, cd, cd, cd, cd, cd, !0)`)" } ], "Markers": [ @@ -1813,16 +1850,16 @@ "Reducer": "Bv.pth", "Delegate": "Bv.pth(t, n.jpx_1)", "Actions": [ - "?eit", - "?fit", - "?lit", - "?oit", - "?sit", - "?uit", - "SetAgeWarnShow" + "HideSocialNetworks", + "SetAgeWarnShow", + "SetAuthorizedByChinaSdkToken", + "SetClassic", + "SetControlsForChina", + "SetRunningInChinaDataCenter", + "SetRunningInChinaPartners" ], "Default": "new hD()", - "Migrated": false + "Migrated": true }, { "Field": "antiAddiction", diff --git a/3DTank/tools/gen_root_wiring.js b/3DTank/tools/gen_root_wiring.js index dded6b8..20106fe 100644 --- a/3DTank/tools/gen_root_wiring.js +++ b/3DTank/tools/gen_root_wiring.js @@ -119,6 +119,14 @@ const AnonymousActionNames = new Map([ ['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'], ]); // 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。 @@ -489,6 +497,7 @@ const Migrated = { lootBoxes: { Type: 'LootBoxesStateSlice', Reduce: 'ReduceLootBoxes' }, garage: { Type: 'GarageStateSlice', Reduce: 'ReduceGarage' }, garagePreview: { Type: 'GaragePreviewStateSlice', Reduce: 'ReduceGaragePreview' }, + entrance: { Type: 'EntranceStateSlice', Reduce: 'ReduceEntrance' }, // 原根 reducer 对这一片没有调用,只有默认值 `ag()`(空 Map);这里仅做类型收口。 launchParams: { Type: 'LaunchParamsStateSlice', @@ -608,6 +617,16 @@ const GaragePreviewAnonymousEffects = { 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)`)', +}; + fs.writeFileSync( path.join(P, 'names', 'root_wiring.json'), JSON.stringify( @@ -654,6 +673,12 @@ fs.writeFileSync( 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, + })), ], Markers: [ { diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/EntranceReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/EntranceReducer.kt new file mode 100644 index 0000000..e2f1c1b --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/EntranceReducer.kt @@ -0,0 +1,58 @@ +// EntranceReducer.kt —— 启动入口切片(对应 module_10032 的 hD 状态 + Bv.pth reducer) +// +// 证据链: +// 状态类 hD:`function hD(t, n, i, r, e, u, s, o)`,8 个布尔字段, +// toString 名为 `EntranceState(showSocialNetworks=…, …, authorizedByClassicEarlyAccessToken=…)`。 +// 默认值:showSocialNetworks = true(`t = t === cd || t`),其余 7 个都是 false +// (`x = x !== cd && x`)。 +// +// 根委派:`Bv.pth(t, n.jpx_1)`。pth 有 7 个 instanceof 臂 + 默认透传: +// eit → showSocialNetworks = false +// uit → controlsForChina = true +// sit → isRunningInChinaDataCenter = true +// oit → isRunningInChinaPartners = true +// _it → isAgeWarnShow = action.isShow(SetAgeWarnShow) +// lit → authorizedByChinaSdkToken = true +// fit → isClassic = true +// 所有臂都走 `ur4`:参数为 `cd` 时保留原值,所以每次只改一个字段。 +// `authorizedByClassicEarlyAccessToken` 在原 reducer 里没有写入臂,保持默认 false。 +// +// 6 个匿名动作没有 toString / 不在 action_inventory,按 reducer 效果定名; +// `_it` 在 action_inventory 里已有名字,直接复用生成的 SetAgeWarnShow。 +package App.State.Reducers + +import App.State.SetAgeWarnShow + +/* ------------------------------------------------------------------ 匿名动作 */ +class HideSocialNetworks +class SetControlsForChina +class SetRunningInChinaDataCenter +class SetRunningInChinaPartners +class SetAuthorizedByChinaSdkToken +class SetClassic + +/* ------------------------------------------------------------------ 状态 */ + +data class EntranceStateSlice( + val ShowSocialNetworks: Boolean = true, + val ControlsForChina: Boolean = false, + val IsRunningInChinaDataCenter: Boolean = false, + val IsRunningInChinaPartners: Boolean = false, + val IsAgeWarnShow: Boolean = false, + val AuthorizedByChinaSdkToken: Boolean = false, + val IsClassic: Boolean = false, + val AuthorizedByClassicEarlyAccessToken: Boolean = false, +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceEntrance(State0: EntranceStateSlice, Action: Any): EntranceStateSlice = when (Action) { + is HideSocialNetworks -> State0.copy(ShowSocialNetworks = false) + is SetControlsForChina -> State0.copy(ControlsForChina = true) + is SetRunningInChinaDataCenter -> State0.copy(IsRunningInChinaDataCenter = true) + is SetRunningInChinaPartners -> State0.copy(IsRunningInChinaPartners = true) + is SetAgeWarnShow -> State0.copy(IsAgeWarnShow = Action.isShow) + is SetAuthorizedByChinaSdkToken -> State0.copy(AuthorizedByChinaSdkToken = true) + is SetClassic -> State0.copy(IsClassic = true) + else -> State0 +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt index cf3477e..bc941db 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt @@ -1,7 +1,7 @@ // RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6) // // 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。 -// 这里采用同一形状。已迁移的 11 个切片里,10 个真正调用 reducer,launchParams 只做类型收口: +// 这里采用同一形状。已迁移的 12 个切片里,11 个真正调用 reducer,launchParams 只做类型收口: // // inviteState → ReduceInvite(aD / pD.er5) // loginByPassword → ReduceLoginByPassword(gD / MD.cr6) @@ -13,13 +13,15 @@ // lootBoxes → ReduceLootBoxes(aH / EH.wrv) // garage → ReduceGarage(UB / hF,根委派 `i = hF(t, n)`) // garagePreview → ReduceGaragePreview(IG / fs.eru,根委派 `fs.eru(t, n.epv_1)`) +// entrance → ReduceEntrance(hD / Bv.pth,根委派 `Bv.pth(t, n.jpx_1)`) // launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap()) // -// 其余 78 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, +// 其余 77 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, // 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。 // 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。 package App.State +import App.State.Reducers.ReduceEntrance import App.State.Reducers.ReduceGarage import App.State.Reducers.ReduceGaragePreview import App.State.Reducers.ReduceHome @@ -54,6 +56,7 @@ object RootReducer { LootBoxes = ReduceLootBoxes(State0.LootBoxes, Action), Garage = ReduceGarage(State0.Garage, Action), GaragePreview = ReduceGaragePreview(State0.GaragePreview, Action), + Entrance = ReduceEntrance(State0.Entrance, Action), ) } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt index 5311c49..7c9199d 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt @@ -4,6 +4,7 @@ // 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。 package App.State +import App.State.Reducers.EntranceStateSlice import App.State.Reducers.GarageStateSlice import App.State.Reducers.GaragePreviewStateSlice import App.State.Reducers.HomeStateSlice @@ -146,8 +147,8 @@ data class RootState( val LaunchParams: LaunchParamsStateSlice = emptyMap(), /** 原 partner(UJ / (inline@partner))。未迁移:原样透传。 */ val Partner: Any? = null, - /** 原 entrance(hD / Bv.pth)。未迁移:原样透传。 */ - val Entrance: Any? = null, + /** 原 entrance(hD / Bv.pth)。已迁移。 */ + val Entrance: EntranceStateSlice = EntranceStateSlice(), /** 原 antiAddiction(UC / W.iq8)。未迁移:原样透传。 */ val AntiAddiction: Any? = null, /** 原 spectator(HA / Li.jqm)。未迁移:原样透传。 */ diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt index f4efe3f..8f83baf 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt @@ -77,7 +77,7 @@ object RootWiring { SliceWiring("language", "gpx_1", "?", "m6().jtg", "m6().jtg(t, n.gpx_1)", listOf("?d6")), SliceWiring("launchParams", "hpx_1", "?", "(pass-through)", "n.hpx_1", listOf()), SliceWiring("partner", "ipx_1", "UJ", "(inline@partner)", "(function (t, n) { var i; if (t instanceof VJ) i = n.osf(t.rsf_1, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.ssf_1); else if (t instanceof nZ) { var r = t.bsg_1, e = t.csg_1, u = t.dsg_1, s = t.esg_1, o = t.fsg_1, _ = t.gsg_1; i = n.osf(cd, cd, cd, r, e, u, cd, cd, o, _, s); } else i = t instanceof iZ ? n.osf(cd, t.hsg_1) : t instanceof uZ ? n.osf(cd, cd, t.ksg_1) : t instanceof eZ ? n.osf(cd, cd, cd, cd, cd, cd, cd, t.jsg_1) : t instanceof _Z ? n.osf(cd, cd, cd, cd, cd, cd, t.osg_1) : t instanceof X …", listOf("?JJ", "?_Z", "?hZ", "?tZ", "InitPartnerInviteAction", "InitSocialNetworkParameters", "SetLoginParams", "SetLoginStatusForBinding", "SetPartnerId", "SetPartnerParameters", "SetPartnerReady")), - SliceWiring("entrance", "jpx_1", "hD", "Bv.pth", "Bv.pth(t, n.jpx_1)", listOf("?eit", "?fit", "?lit", "?oit", "?sit", "?uit", "SetAgeWarnShow")), + SliceWiring("entrance", "jpx_1", "hD", "Bv.pth", "Bv.pth(t, n.jpx_1)", listOf("HideSocialNetworks", "SetAgeWarnShow", "SetAuthorizedByChinaSdkToken", "SetClassic", "SetControlsForChina", "SetRunningInChinaDataCenter", "SetRunningInChinaPartners")), SliceWiring("antiAddiction", "kpx_1", "UC", "W.iq8", "W.iq8(t, n.kpx_1)", listOf("?FC", "?XC", "?YC", "SetRealNameAndIdNumber", "SetValidateAntiAddictionUserDataStatus")), SliceWiring("spectator", "lpx_1", "HA", "Li.jqm", "Li.jqm(t, n.lpx_1)", listOf("?JA", "?XA", "?YA", "?ZA", "SetSpectator", "UpdateHudLayout")), SliceWiring("moderation", "mpx_1", "NI", "Ei.oqh", "Ei.oqh(t, n.mpx_1)", listOf("?FI", "?GI", "?JI", "?KI", "?QI", "?UI", "?WI", "?XI", "?YI", "?ZI", "?nA", "?tA", "?uA")), @@ -106,7 +106,7 @@ object RootWiring { ) /** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */ - val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "launchParams") + val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "launchParams") /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */ val Unmigrated: List get() = All.filter { it.Field !in Migrated } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt index bb47e12..2024357 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt @@ -9,8 +9,8 @@ // ⑥ 动作目录:816 个类与 138 个真实派发名对齐 // ⑦ 回放:真实 toString 样例的类/字段/类型校验 // ⑧ 全目录动作类都能按 KClass 路由 -// ⑨~㉑ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState / -// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage / GaragePreview; +// ⑨~㉒ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState / +// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage / GaragePreview / Entrance; // ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派 // // 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1) @@ -217,6 +217,7 @@ import App.State.GaragePreviewModelCC import App.State.HullEngineType import App.State.InitGarageResources import App.State.ResetItem3dModelActionCache +import App.State.SetAgeWarnShow import App.State.SetCaptureActions import App.State.SetDrone import App.State.SetExpectedItemId @@ -255,8 +256,16 @@ import App.State.Reducers.PreviewHullCategory import App.State.Reducers.PreviewPaintCategory import App.State.Reducers.PreviewWeaponCategory import App.State.Reducers.ReduceGarage +import App.State.Reducers.EntranceStateSlice +import App.State.Reducers.HideSocialNetworks +import App.State.Reducers.ReduceEntrance import App.State.Reducers.ReduceGaragePreview import App.State.Reducers.RemoveNewItem +import App.State.Reducers.SetAuthorizedByChinaSdkToken +import App.State.Reducers.SetClassic +import App.State.Reducers.SetControlsForChina +import App.State.Reducers.SetRunningInChinaDataCenter +import App.State.Reducers.SetRunningInChinaPartners import App.State.Reducers.RemoveWaitServerItemMount import App.State.Reducers.ResetGarage import App.State.Reducers.ResetMountDelay @@ -653,7 +662,7 @@ object StateCheck { Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled) } - // ⑭ 根 reducer:89 个槽位的委派表 + 11 个已迁移切片(10 归约 + launchParams 透传) + // ⑭ 根 reducer:89 个槽位的委派表 + 12 个已迁移切片(11 归约 + launchParams 透传) run { Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89) Check( @@ -717,17 +726,17 @@ object StateCheck { LocalBattleW.Reducer == "(inline@localBattleUserState)" ) Check( - "根:迁移表 = 11 个已迁移字段,其余 78 个待迁", + "根:迁移表 = 12 个已迁移字段,其余 77 个待迁", RootReducer.Migrated == setOf( "inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", - "garagePreview", "launchParams", + "garagePreview", "entrance", "launchParams", ) && - RootWiring.Unmigrated.size == 78 + RootWiring.Unmigrated.size == 77 ) - // 一次 ReduceRoot 里八个切片各归约自己的动作,互不串片 + // 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片 val S0 = RootState(User = "user-1", Screen = ScreenStateSlice(Current = NamedScreen("Preloader"))) val S1 = ReduceRoot(S0, ApplyInvite("uid-1")) Check( @@ -780,7 +789,7 @@ object StateCheck { S5.User == "user-1" && S5.Screen.Current == NamedScreen("Preloader") ) Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5) - Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 11/89") + Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 12/89") val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) }) Store.Dispatch(SetStatus("LOGGING_IN")) @@ -2329,6 +2338,93 @@ object StateCheck { ) } + // ㉒ 切片 reducer:Entrance(原 module_10032 的 hD / Bv.pth,8 字段;7 个 instanceof 臂) + run { + val S0 = EntranceStateSlice() + Check( + "切片 Entrance:默认值与原构造一致(showSocialNetworks 缺省 true,其余 false)", + S0.ShowSocialNetworks && !S0.ControlsForChina && + !S0.IsRunningInChinaDataCenter && !S0.IsRunningInChinaPartners && + !S0.IsAgeWarnShow && !S0.AuthorizedByChinaSdkToken && + !S0.IsClassic && !S0.AuthorizedByClassicEarlyAccessToken + ) + + Check( + "切片 Entrance:HideSocialNetworks 置 showSocialNetworks=false", + !ReduceEntrance(S0, HideSocialNetworks()).ShowSocialNetworks + ) + Check( + "切片 Entrance:SetControlsForChina 置 controlsForChina=true", + ReduceEntrance(S0, SetControlsForChina()).ControlsForChina + ) + Check( + "切片 Entrance:SetRunningInChinaDataCenter 置对应字段", + ReduceEntrance(S0, SetRunningInChinaDataCenter()).IsRunningInChinaDataCenter + ) + Check( + "切片 Entrance:SetRunningInChinaPartners 置对应字段", + ReduceEntrance(S0, SetRunningInChinaPartners()).IsRunningInChinaPartners + ) + Check( + "切片 Entrance:SetAgeWarnShow 写入 isAgeWarnShow", + ReduceEntrance(S0, SetAgeWarnShow(true)).IsAgeWarnShow && + !ReduceEntrance(S0.copy(IsAgeWarnShow = true), SetAgeWarnShow(false)).IsAgeWarnShow + ) + Check( + "切片 Entrance:SetAuthorizedByChinaSdkToken 置对应字段", + ReduceEntrance(S0, SetAuthorizedByChinaSdkToken()).AuthorizedByChinaSdkToken + ) + Check( + "切片 Entrance:SetClassic 置 isClassic", + ReduceEntrance(S0, SetClassic()).IsClassic + ) + + val All = listOf( + HideSocialNetworks(), + SetControlsForChina(), + SetRunningInChinaDataCenter(), + SetRunningInChinaPartners(), + SetAgeWarnShow(true), + SetAuthorizedByChinaSdkToken(), + SetClassic(), + ).fold(S0) { S, A -> ReduceEntrance(S, A) } + Check( + "切片 Entrance:七个动作依次归约后只改对应字段", + !All.ShowSocialNetworks && All.ControlsForChina && + All.IsRunningInChinaDataCenter && All.IsRunningInChinaPartners && + All.IsAgeWarnShow && All.AuthorizedByChinaSdkToken && + All.IsClassic && !All.AuthorizedByClassicEarlyAccessToken + ) + Check("切片 Entrance:无关动作不改状态", ReduceEntrance(All, AddFrags(1)) == All) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, SetClassic()) + Check( + "根:SetClassic 只动 entrance", + Root1.Entrance.IsClassic && Root1.LoginByPassword == Root0.LoginByPassword && + Root1.GaragePreview == Root0.GaragePreview && Root1.Garage == Root0.Garage + ) + Check( + "根:entrance 无关动作保留前一次归约", + ReduceRoot(Root1, AddFrags(1)).Entrance == Root1.Entrance + ) + + val W = RootWiring.ByField("entrance")!! + Check( + "根:entrance 委派到 hD / Bv.pth 且 7 个动作全部具名", + W.SliceClass == "hD" && W.Reducer == "Bv.pth" && W.Actions.size == 7 && + W.Actions.none { it.startsWith("?") } + ) + Check( + "根:entrance 的动作名覆盖 7 个 instanceof 臂", + listOf( + "HideSocialNetworks", "SetAgeWarnShow", "SetAuthorizedByChinaSdkToken", + "SetClassic", "SetControlsForChina", "SetRunningInChinaDataCenter", + "SetRunningInChinaPartners", + ).all { it in W.Actions } + ) + } + val Failed = Results.filter { !it.second } println("TankiOffline 状态层自检:${Results.size} 项") for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)