From 71aa9a525626a283fee0f808faa2b091e351c22b Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 24 Oct 2026 20:42:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(state):=20=E8=BF=81=E7=A7=BB=20matchmaking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DTank/docs/KtRebuild_Plan.md | 15 ++- 3DTank/names/root_wiring.json | 31 ++++- 3DTank/tools/gen_root_wiring.js | 8 ++ .../App/State/Reducers/MatchmakingReducer.kt | 102 +++++++++++++++ .../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 | 118 +++++++++++++++++- 8 files changed, 267 insertions(+), 23 deletions(-) create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/MatchmakingReducer.kt diff --git a/3DTank/docs/KtRebuild_Plan.md b/3DTank/docs/KtRebuild_Plan.md index 1354da9..1366dce 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` 动作名。 - - 已迁移切片 **79/89**: + - 已迁移切片 **80/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) @@ -398,12 +398,17 @@ 没有缓存条目的 userId 不新建(原 reducer 直接返回原状态)。 用户数据模型直接复用生成物 `RemoteUserData`,战斗 / 在线 / 会员状态用 `BattleNotifierData` / `OnlineNotifierData` / `PremiumNotifierData`。 + - `matchmaking` → `TH` / `JH().qrx`(`MatchmakingReducer.kt`):19 字段,9 个实例臂。 + `Configure` 一次写 12 个字段(模式表 / 禁用列表 / 节日与活动 / 团队人数); + `WasRegistered` 写等待时间与模式并清 `requestSent`;`EnterToBattleApply` 同时置两个进行中位; + `WH`→`ResetMatchmakingProgress`、`tW`/`nW`→`StartLoadingBattle`/`CancelLoadingBattle`; + 模式表复用生成物 `MatchmakingModeRank`,`rankIndexToMinGS` 收口为 `Map`。 - `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map; `RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map`,不新增 reducer/动作。 - - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 78 个有 reducer 的切片, - `launchParams` 与其余 **10 个字段原样透传**; - 迁移进度用 `RootReducer.Coverage`(`已迁移 79/89`)与 `RootWiring.Unmigrated` 暴露。 - - 自检:`TankiOffline/tools/RunStateTest.ps1` **859 项全绿**,含 89 槽位完整性、78 个归约切片一次归约互不串片、未迁移字段透传, + - `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 79 个有 reducer 的切片, + `launchParams` 与其余 **9 个字段原样透传**; + 迁移进度用 `RootReducer.Coverage`(`已迁移 80/89`)与 `RootWiring.Unmigrated` 暴露。 + - 自检:`TankiOffline/tools/RunStateTest.ps1` **871 项全绿**,含 89 槽位完整性、79 个归约切片一次归约互不串片、未迁移字段透传, 以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。 - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, 已按字段具名为 `(inline@)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); diff --git a/3DTank/names/root_wiring.json b/3DTank/names/root_wiring.json index c270776..8a0055c 100644 --- a/3DTank/names/root_wiring.json +++ b/3DTank/names/root_wiring.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-10-24T12:12:10.770Z", + "generatedAt": "2026-10-24T12:37:28.603Z", "module": "module_10032.js", "RootState": { "Class": "z6", @@ -85,7 +85,8 @@ "battleMessage", "maps", "battleChat", - "remoteUserDataStorage" + "remoteUserDataStorage", + "matchmaking" ], "AnonymousActions": [ { @@ -1413,6 +1414,24 @@ "Name": "ResetBattleChat", "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", "Effect": "new Q6() 整片复位" + }, + { + "Local": "WH", + "Name": "ResetMatchmakingProgress", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "matchmakingAndLoadingBattleInProgress=false、startLoadingBattle=false、requestSent=false" + }, + { + "Local": "tW", + "Name": "StartLoadingBattle", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "startLoadingBattle=true(`n.brw(cd, cd, cd, cd, !0)`)" + }, + { + "Local": "nW", + "Name": "CancelLoadingBattle", + "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", + "Effect": "startLoadingBattle=false(`n.brw(cd, cd, cd, cd, !1)`)" } ], "Markers": [ @@ -2180,18 +2199,18 @@ "Reducer": "JH().qrx", "Delegate": "JH().qrx(t, n.cpw_1)", "Actions": [ - "?WH", - "?nW", - "?tW", + "CancelLoadingBattle", "Configure", "EnterToBattleApply", "Play", + "ResetMatchmakingProgress", "SelectMatchmakingMode", "SetRankIndexToMinGS", + "StartLoadingBattle", "WasRegistered" ], "Default": "new TH()", - "Migrated": false + "Migrated": true }, { "Field": "battleCreateState", diff --git a/3DTank/tools/gen_root_wiring.js b/3DTank/tools/gen_root_wiring.js index b3c2cfc..b7dc8de 100644 --- a/3DTank/tools/gen_root_wiring.js +++ b/3DTank/tools/gen_root_wiring.js @@ -323,6 +323,10 @@ const AnonymousActionNames = new Map([ ['utt', 'CloseBattleChatInput'], ['stt', 'ToggleBattleChatHistory'], ['rtt', 'ResetBattleChat'], + // matchmaking 切片(TH / XH.qrx):WH/tW/nW 没有 toString。 + ['WH', 'ResetMatchmakingProgress'], + ['tW', 'StartLoadingBattle'], + ['nW', 'CancelLoadingBattle'], ]); // 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。 @@ -806,6 +810,7 @@ const Migrated = { Type: 'RemoteUserDataStorageStateSlice', Reduce: 'ReduceRemoteUserDataStorage', }, + matchmaking: { Type: 'MatchmakingStateSlice', Reduce: 'ReduceMatchmaking' }, }; for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field); @@ -1218,6 +1223,9 @@ const AppSettingsAnonymousEffects = { 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() 整片复位', + WH: 'matchmakingAndLoadingBattleInProgress=false、startLoadingBattle=false、requestSent=false', + tW: 'startLoadingBattle=true(`n.brw(cd, cd, cd, cd, !0)`)', + nW: 'startLoadingBattle=false(`n.brw(cd, cd, cd, cd, !1)`)', }; fs.writeFileSync( diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/MatchmakingReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/MatchmakingReducer.kt new file mode 100644 index 0000000..b3e9823 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/Reducers/MatchmakingReducer.kt @@ -0,0 +1,102 @@ +// MatchmakingReducer.kt —— 匹配大厅切片(对应 module_10032 的 TH 状态 + XH.qrx) +// +// 证据链: +// 状态类 TH:19 个字段,toString 名 "MatchmakingState(…)";copy 是 brw(19 字段 + 回调)。 +// 根委派:`JH().qrx(t, n.cpw_1)`。 +// reducer(7 个实例臂): +// MH(Play) → requestSent=true +// NH(Configure) → 模式表 / 禁用列表 / 节日与活动 12 个字段 +// LH(SelectMatchmakingMode)→ selectedMatchmakingMode +// DH(WasRegistered) → 4th=true、avgWaitTimeInSeconds、mode、requestSent=false +// GH(EnterToBattleApply) → 4th=true、5th=true +// WH(无 toString) → 4th=false、5th=false、requestSent=false +// tW / nW(无 toString) → startLoadingBattle = true / false +// VH(SetRankIndexToMinGS) → rankIndexToMinGS +// +// 模式表模型直接复用生成物 `MatchmakingModeRank`(matchmakingMode, rank)。 +package App.State.Reducers + +import App.State.Configure +import App.State.EnterToBattleApply +import App.State.MatchmakingModeRank +import App.State.Play +import App.State.SelectMatchmakingMode +import App.State.SetRankIndexToMinGS +import App.State.WasRegistered + +/* ------------------------------------------------------------------ 匿名动作 */ + +/** 原 `WH`:退出匹配 / 进入战斗失败,清掉进行中的标记。 */ +object ResetMatchmakingProgress + +/** 原 `tW` / `nW`:开始 / 取消读条。 */ +object StartLoadingBattle +object CancelLoadingBattle + +/* ------------------------------------------------------------------ 状态 */ + +data class MatchmakingStateSlice( + val MatchmakingModeRanks: List = emptyList(), + val SelectedMatchmakingMode: Any? = null, + val DisabledModes: Set = emptySet(), + val MatchmakingAndLoadingBattleInProgress: Boolean = false, + val StartLoadingBattle: Boolean = false, + val HolidayEnabled: Boolean = false, + val HolidayTitle: String = "", + val HolidayIcon: Any? = null, + val EventBigTile: Any? = null, + val HolidayDescription: String = "", + val MinRankForProBattle: Int = Int.MAX_VALUE, + val AvgWaitTimeInSeconds: Any? = 0, + val Mode: Any? = null, + val MaxUsersInGroup: Int = 3, + val GroupsInEventMode: Boolean = false, + val EventEndDate: Any? = null, + val BorderColorForEventButton: Any? = null, + val RequestSent: Boolean = false, + val RankIndexToMinGS: Map = emptyMap(), +) + +/* ------------------------------------------------------------------ reducer */ + +fun ReduceMatchmaking( + State0: MatchmakingStateSlice, + Action: Any, +): MatchmakingStateSlice = when (Action) { + is Play -> State0.copy(RequestSent = true) + is Configure -> State0.copy( + MatchmakingModeRanks = Action.modes, + DisabledModes = Action.disabledModes.toSet(), + HolidayEnabled = Action.holidayEnabled, + HolidayTitle = Action.holidayTitle, + HolidayIcon = Action.holidayIcon, + EventBigTile = Action.eventBigTile, + HolidayDescription = Action.holidayDescription, + MinRankForProBattle = Action.minRankForProBattle, + MaxUsersInGroup = Action.maxUsersInGroup, + GroupsInEventMode = Action.groupsInEventMode, + EventEndDate = Action.eventEndDate, + BorderColorForEventButton = Action.borderColorForEventButton, + ) + is SelectMatchmakingMode -> + State0.copy(SelectedMatchmakingMode = Action.matchmakingMode) + is WasRegistered -> State0.copy( + MatchmakingAndLoadingBattleInProgress = true, + AvgWaitTimeInSeconds = Action.avgWaitTimeInSeconds, + Mode = Action.mode, + RequestSent = false, + ) + is EnterToBattleApply -> State0.copy( + MatchmakingAndLoadingBattleInProgress = true, + StartLoadingBattle = true, + ) + is ResetMatchmakingProgress -> State0.copy( + MatchmakingAndLoadingBattleInProgress = false, + StartLoadingBattle = false, + RequestSent = false, + ) + is StartLoadingBattle -> State0.copy(StartLoadingBattle = true) + is CancelLoadingBattle -> State0.copy(StartLoadingBattle = false) + is SetRankIndexToMinGS -> State0.copy(RankIndexToMinGS = Action.rankIndexToMinGS) + 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 13cf80a..18c107f 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 出新状态)。 -// 这里采用同一形状。已迁移的 79 个切片里,78 个真正调用 reducer,launchParams 只做类型收口: +// 这里采用同一形状。已迁移的 80 个切片里,79 个真正调用 reducer,launchParams 只做类型收口: // // inviteState → ReduceInvite(aD / pD.er5) // loginByPassword → ReduceLoginByPassword(gD / MD.cr6) @@ -81,9 +81,10 @@ // maps → ReduceMaps(OA / Oi.sql,根委派 `Oi.sql(t, n.bpw_1)`) // battleChat → ReduceBattleChat(Q6 / Hf.wtg,根委派 `Hf.wtg(t, n.rpw_1)`) // remoteUserDataStorage → ReduceRemoteUserDataStorage($Q / vh.isl,根委派 `vh.isl(t, n.zpv_1)`) +// matchmaking → ReduceMatchmaking(TH / JH().qrx,根委派 `JH().qrx(t, n.cpw_1)`) // launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap()) // -// 其余 10 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, +// 其余 9 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, // 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。 // 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。 package App.State @@ -125,6 +126,7 @@ import App.State.Reducers.ReduceLocalBattleUser import App.State.Reducers.ReduceLoginByPassword import App.State.Reducers.ReduceLootBoxes import App.State.Reducers.ReduceMainQuest +import App.State.Reducers.ReduceMatchmaking import App.State.Reducers.ReduceMaps import App.State.Reducers.ReduceAvailabilityByRank import App.State.Reducers.ReduceLobbyLoader @@ -258,6 +260,7 @@ object RootReducer { Maps = ReduceMaps(State0.Maps, Action), BattleChat = ReduceBattleChat(State0.BattleChat, Action), RemoteUserDataStorage = ReduceRemoteUserDataStorage(State0.RemoteUserDataStorage, Action), + Matchmaking = ReduceMatchmaking(State0.Matchmaking, Action), ) } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt index 2995e3f..b8919af 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt @@ -43,6 +43,7 @@ import App.State.Reducers.LoginByPasswordStateSlice import App.State.Reducers.LootBoxesStateSlice import App.State.Reducers.MainQuestStateSlice import App.State.Reducers.MapsStateSlice +import App.State.Reducers.MatchmakingStateSlice import App.State.Reducers.MigrationStateSlice import App.State.Reducers.ModalScreenStateSlice import App.State.Reducers.ModerationStateSlice @@ -147,8 +148,8 @@ data class RootState( val BattlesSelect: Any? = null, /** 原 maps(OA / Oi.sql)。已迁移。 */ val Maps: MapsStateSlice = MapsStateSlice(), - /** 原 matchmaking(TH / JH().qrx)。未迁移:原样透传。 */ - val Matchmaking: Any? = null, + /** 原 matchmaking(TH / JH().qrx)。已迁移。 */ + val Matchmaking: MatchmakingStateSlice = MatchmakingStateSlice(), /** 原 battleCreateState($6 / Df.ttg)。未迁移:原样透传。 */ val BattleCreateState: Any? = null, /** 原 battleSearchState(Itt / Htt().vtg)。已迁移。 */ diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt index 59cd757..bfadce5 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt @@ -44,7 +44,7 @@ object RootWiring { SliceWiring("remoteUserDataStorage", "zpv_1", "\$Q", "vh.isl", "vh.isl(t, n.zpv_1)", listOf("UpdateClanTags", "UpdateOnlineStatus", "UpdatePremiumStatus", "UpdateRanks", "UpdateUids", "UserEnterBattle", "UserLeaveBattle", "UserLeaveGroup")), SliceWiring("battlesSelect", "apw_1", "fA", "Mi.vqk", "Mi.vqk(t, n.apw_1)", listOf("?dA", "?gA", "AddBattle", "AddUser", "AddUsers", "ChangeName", "EnterToBattleInfo", "InitProBattleRewardLimit", "LimitReached", "RemoveBattle", "RemoveUser", "RoundFinish", "RoundStarted", "SetReEnterProtectBattleId", "SwapTeam", "UpdateFund", "UpdateSuspicion", "UpdateTeamScore", "UpdateUserScore", "UpdateUserSuspicion")), SliceWiring("maps", "bpw_1", "OA", "Oi.sql", "Oi.sql(t, n.bpw_1)", listOf("AddMap")), - SliceWiring("matchmaking", "cpw_1", "TH", "JH().qrx", "JH().qrx(t, n.cpw_1)", listOf("?WH", "?nW", "?tW", "Configure", "EnterToBattleApply", "Play", "SelectMatchmakingMode", "SetRankIndexToMinGS", "WasRegistered")), + SliceWiring("matchmaking", "cpw_1", "TH", "JH().qrx", "JH().qrx(t, n.cpw_1)", listOf("CancelLoadingBattle", "Configure", "EnterToBattleApply", "Play", "ResetMatchmakingProgress", "SelectMatchmakingMode", "SetRankIndexToMinGS", "StartLoadingBattle", "WasRegistered")), SliceWiring("battleCreateState", "dpw_1", "\$6", "Df.ttg", "Df.ttg(t, n.dpw_1, n.bpw_1, n.ypu_1)", listOf("?A6", "?B6", "?C6", "?D6", "?E6", "?F6", "?I6", "?L6", "?M6", "?N6", "?P6", "?R6", "?T6", "?U6", "Configure", "CreateBattle", "SetRestrictions")), SliceWiring("battleSearchState", "epw_1", "Itt", "Htt().vtg", "Htt().vtg(t, n.epw_1)", listOf("ClearSelectedBattle", "EnterToBattle", "Fight", "RemoveBattle", "SelectBattle", "SelectBattleFormat", "SetFilter", "SetSearch", "SetSuspiciousFilter")), SliceWiring("lobbyLoader", "fpw_1", "KW", "Bs.es1", "Bs.es1(t, n.fpw_1)", listOf("HideLoader", "ShowLoader")), @@ -106,7 +106,7 @@ object RootWiring { ) /** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */ - val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "user", "settings", "accountSettings", "proBattle", "language", "announcement", "launchParams", "news", "notifications", "quests", "lobbyLoader", "purchaseDialogState", "promoCode", "supportUrl", "rulesUpdated", "notificationByCountry", "availabilityByRank", "icons", "questCategories", "mainQuest", "securitySettings", "referralEvent", "tipsState", "rankUpDialogState", "battleResult", "captchaState", "modalScreen", "droppableGold", "eventGoods", "creatorTagState", "rewardedVideosState", "passwordChangeState", "system", "contextTutorial", "battleTutorial", "spectator", "twoStepVerificationState", "antiAddiction", "moderation", "migrationState", "register", "userReportsState", "friends", "suppliesState", "clanUserInfo", "battleInviteState", "partner", "userRenameState", "battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial", "clanChatState", "battleSearchState", "battleStatistics", "soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings", "graphicsSettings", "controlSettings", "battleSettings", "contractState", "tankBuilderState", "battleMessage", "maps", "battleChat", "remoteUserDataStorage") + val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "entrance", "user", "settings", "accountSettings", "proBattle", "language", "announcement", "launchParams", "news", "notifications", "quests", "lobbyLoader", "purchaseDialogState", "promoCode", "supportUrl", "rulesUpdated", "notificationByCountry", "availabilityByRank", "icons", "questCategories", "mainQuest", "securitySettings", "referralEvent", "tipsState", "rankUpDialogState", "battleResult", "captchaState", "modalScreen", "droppableGold", "eventGoods", "creatorTagState", "rewardedVideosState", "passwordChangeState", "system", "contextTutorial", "battleTutorial", "spectator", "twoStepVerificationState", "antiAddiction", "moderation", "migrationState", "register", "userReportsState", "friends", "suppliesState", "clanUserInfo", "battleInviteState", "partner", "userRenameState", "battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial", "clanChatState", "battleSearchState", "battleStatistics", "soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings", "graphicsSettings", "controlSettings", "battleSettings", "contractState", "tankBuilderState", "battleMessage", "maps", "battleChat", "remoteUserDataStorage", "matchmaking") /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */ 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 eae9093..6a78699 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt @@ -19,7 +19,7 @@ // System / ContextTutorial / BattleTutorial / Spectator / TwoStepVerification / AntiAddiction / // Moderation / MigrationState / Register / UserReports / // Friends / Supplies / ClanUserInfo / BattleInvite / Partner / UserRename / -// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage; +// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage / Matchmaking; // ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派 // // 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1) @@ -756,6 +756,17 @@ import App.State.UpdateUids import App.State.UserEnterBattle import App.State.UserLeaveBattle import App.State.UserLeaveGroup +import App.State.EnterToBattleApply +import App.State.MatchmakingModeRank +import App.State.Play +import App.State.SelectMatchmakingMode +import App.State.SetRankIndexToMinGS +import App.State.WasRegistered +import App.State.Reducers.CancelLoadingBattle +import App.State.Reducers.MatchmakingStateSlice +import App.State.Reducers.ReduceMatchmaking +import App.State.Reducers.ResetMatchmakingProgress +import App.State.Reducers.StartLoadingBattle import App.State.Reducers.UserRankBounds import App.State.Reducers.UserStateSlice import App.State.Reducers.ClearHullData @@ -1183,7 +1194,7 @@ object StateCheck { Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled) } - // ⑭ 根 reducer:89 个槽位的委派表 + 79 个已迁移切片(78 归约 + launchParams 透传) + // ⑭ 根 reducer:89 个槽位的委派表 + 80 个已迁移切片(79 归约 + launchParams 透传) run { Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89) Check( @@ -1247,7 +1258,7 @@ object StateCheck { LocalBattleW.Reducer == "(inline@localBattleUserState)" ) Check( - "根:迁移表 = 79 个已迁移字段,其余 10 个待迁", + "根:迁移表 = 80 个已迁移字段,其余 9 个待迁", RootReducer.Migrated == setOf( "inviteState", "loginByPassword", "twitchState", "home", @@ -1271,9 +1282,9 @@ object StateCheck { "soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings", "graphicsSettings", "controlSettings", "battleSettings", "contractState", "tankBuilderState", "battleMessage", "maps", "battleChat", - "remoteUserDataStorage", + "remoteUserDataStorage", "matchmaking", ) && - RootWiring.Unmigrated.size == 10 + RootWiring.Unmigrated.size == 9 ) // 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片 @@ -1329,7 +1340,7 @@ object StateCheck { S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader") ) Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5) - Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 79/89") + Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 80/89") val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) }) Store.Dispatch(SetStatus("LOGGING_IN")) @@ -6546,6 +6557,101 @@ object StateCheck { ) } + // 90 切片 reducer:Matchmaking(TH / JH().qrx,19 字段;9 个动作) + run { + val S0 = MatchmakingStateSlice() + Check( + "切片 Matchmaking:默认值完整", + S0.MatchmakingModeRanks.isEmpty() && S0.SelectedMatchmakingMode == null && + S0.DisabledModes.isEmpty() && !S0.MatchmakingAndLoadingBattleInProgress && + !S0.StartLoadingBattle && !S0.HolidayEnabled && S0.HolidayTitle == "" && + S0.HolidayIcon == null && S0.EventBigTile == null && + S0.HolidayDescription == "" && S0.MinRankForProBattle == Int.MAX_VALUE && + S0.AvgWaitTimeInSeconds == 0 && S0.Mode == null && S0.MaxUsersInGroup == 3 && + !S0.GroupsInEventMode && S0.EventEndDate == null && + S0.BorderColorForEventButton == null && !S0.RequestSent && + S0.RankIndexToMinGS.isEmpty() + ) + val S1 = ReduceMatchmaking(S0, Configure( + modes = listOf(MatchmakingModeRank("dm", 1)), + disabledModes = listOf("tdm"), + holidayEnabled = true, + holidayTitle = "NY", + holidayIcon = "icon", + eventBigTile = "tile", + holidayDescription = "desc", + minRankForProBattle = 5, + maxUsersInGroup = 4, + groupsInEventMode = true, + eventEndDate = "date", + borderColorForEventButton = "#fff", + )) + Check( + "切片 Matchmaking:Configure 写 12 个字段", + S1.MatchmakingModeRanks == listOf(MatchmakingModeRank("dm", 1)) && + S1.DisabledModes == setOf("tdm") && S1.HolidayEnabled && + S1.HolidayTitle == "NY" && S1.HolidayIcon == "icon" && + S1.EventBigTile == "tile" && S1.HolidayDescription == "desc" && + S1.MinRankForProBattle == 5 && S1.MaxUsersInGroup == 4 && + S1.GroupsInEventMode && S1.EventEndDate == "date" && + S1.BorderColorForEventButton == "#fff" + ) + Check( + "切片 Matchmaking:SelectMatchmakingMode 写选中模式", + ReduceMatchmaking(S1, SelectMatchmakingMode("dm")).SelectedMatchmakingMode == "dm" + ) + Check( + "切片 Matchmaking:Play 只置 requestSent", + ReduceMatchmaking(S1, Play(true, null, null)).RequestSent + ) + val S2 = ReduceMatchmaking(S1, WasRegistered(42, "dm")) + Check( + "切片 Matchmaking:WasRegistered 写等待时间 / 模式并清 requestSent", + S2.MatchmakingAndLoadingBattleInProgress && + S2.AvgWaitTimeInSeconds == 42 && S2.Mode == "dm" && !S2.RequestSent + ) + Check( + "切片 Matchmaking:EnterToBattleApply 同时置两个进行中位", + ReduceMatchmaking(S1, EnterToBattleApply(7L)).let { + it.MatchmakingAndLoadingBattleInProgress && it.StartLoadingBattle + } + ) + Check( + "切片 Matchmaking:ResetMatchmakingProgress 清三个位", + ReduceMatchmaking(S2, ResetMatchmakingProgress).let { + !it.MatchmakingAndLoadingBattleInProgress && !it.StartLoadingBattle && + !it.RequestSent + } + ) + Check( + "切片 Matchmaking:读条开关两态", + ReduceMatchmaking(S1, StartLoadingBattle).StartLoadingBattle && + !ReduceMatchmaking(S1, CancelLoadingBattle).StartLoadingBattle + ) + Check( + "切片 Matchmaking:SetRankIndexToMinGS 写表", + ReduceMatchmaking(S1, SetRankIndexToMinGS(mapOf("1" to 100))) + .RankIndexToMinGS == mapOf("1" to 100) + ) + Check( + "切片 Matchmaking:无关动作不改状态", + ReduceMatchmaking(S2, AddFrags(1)) == S2 + ) + + val Root0 = RootState() + val Root1 = ReduceRoot(Root0, StartLoadingBattle) + Check( + "根:StartLoadingBattle 只动 matchmaking", + Root1.Matchmaking.StartLoadingBattle && + Root1.RemoteUserDataStorage == Root0.RemoteUserDataStorage + ) + val W = RootWiring.ByField("matchmaking")!! + Check( + "根:matchmaking 委派到 TH / JH().qrx 且 9 个动作", + W.SliceClass == "TH" && W.Reducer == "JH().qrx" && W.Actions.size == 9 + ) + } + val Failed = Results.filter { !it.second } println("TankiOffline 状态层自检:${Results.size} 项") for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)