feat(state): 迁移 battlesSelect(复用 BattleParams/BattleInfoUser)

This commit is contained in:
TermiNexus committed 2026-10-24 21:03:47 +08:00
1 parent 71aa9a5256
commit cd597b4693
8 files changed
+434 -23

No files matched your search

+11 -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` 动作名。
- 已迁移切片 **80/89**:
- 已迁移切片 **81/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)
@@ -403,12 +403,18 @@
`WasRegistered` 写等待时间与模式并清 `requestSent`;`EnterToBattleApply` 同时置两个进行中位;
`WH`→`ResetMatchmakingProgress`、`tW`/`nW`→`StartLoadingBattle`/`CancelLoadingBattle`;
模式表复用生成物 `MatchmakingModeRank`,`rankIndexToMinGS` 收口为 `Map<String, Int>`。
- `battlesSelect` → `fA` / `Mi.vqk`(`BattlesSelectReducer.kt`):15 字段,20 个实例臂。
`AddBattle` 把 `BattleCreateParameters`(+ suspicionLevel)转成 `BattleParams` 并建 users / score 空表;
`AddUsers` 批量写 users / usersTeam 并同步个人分与可疑标记(`AddUser` 复用它);
`SwapTeam` 换队并把该战斗内用户分数归零;`RemoveBattle` / `RemoveUser` 清对应的四张子表;
其余为名字 / 可疑度 / 队伍分 / 个人分 / 资金 / 奖励上限 / 观战标记。
模型全部复用生成物:`BattleParams` / `BattleInfoUser` / `BattleCreateParameters` / `ProBattleRewardLimitData`。
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 79 个有 reducer 的切片,
`launchParams` 与其余 **9 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 80/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **871 项全绿**,含 89 槽位完整性、79 个归约切片一次归约互不串片、未迁移字段透传,
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 80 个有 reducer 的切片,
`launchParams` 与其余 **8 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 81/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **888 项全绿**,含 89 槽位完整性、80 个归约切片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
+18 -5
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-24T12:37:28.603Z",
"generatedAt": "2026-10-24T12:57:54.249Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -86,7 +86,8 @@
"maps",
"battleChat",
"remoteUserDataStorage",
"matchmaking"
"matchmaking",
"battlesSelect"
],
"AnonymousActions": [
{
@@ -1432,6 +1433,18 @@
"Name": "CancelLoadingBattle",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "startLoadingBattle=false(`n.brw(cd, cd, cd, cd, !1)`)"
},
{
"Local": "gA",
"Name": "MarkBattlesLoaded",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "loaded=true(`n.hqj(!0)`)"
},
{
"Local": "dA",
"Name": "ResetBattlesSelect",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new fA() 整片复位"
}
],
"Markers": [
@@ -2156,8 +2169,6 @@
"Reducer": "Mi.vqk",
"Delegate": "Mi.vqk(t, n.apw_1)",
"Actions": [
"?dA",
"?gA",
"AddBattle",
"AddUser",
"AddUsers",
@@ -2165,8 +2176,10 @@
"EnterToBattleInfo",
"InitProBattleRewardLimit",
"LimitReached",
"MarkBattlesLoaded",
"RemoveBattle",
"RemoveUser",
"ResetBattlesSelect",
"RoundFinish",
"RoundStarted",
"SetReEnterProtectBattleId",
@@ -2178,7 +2191,7 @@
"UpdateUserSuspicion"
],
"Default": "new fA()",
"Migrated": false
"Migrated": true
},
{
"Field": "maps",
+6 -1
View File
@@ -327,6 +327,9 @@ const AnonymousActionNames = new Map([
['WH', 'ResetMatchmakingProgress'],
['tW', 'StartLoadingBattle'],
['nW', 'CancelLoadingBattle'],
// battlesSelect 切片(fA / MA.vqk):gA/dA 没有 toString。
['gA', 'MarkBattlesLoaded'],
['dA', 'ResetBattlesSelect'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
@@ -811,6 +814,7 @@ const Migrated = {
Reduce: 'ReduceRemoteUserDataStorage',
},
matchmaking: { Type: 'MatchmakingStateSlice', Reduce: 'ReduceMatchmaking' },
battlesSelect: { Type: 'BattlesSelectStateSlice', Reduce: 'ReduceBattlesSelect' },
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -1226,8 +1230,9 @@ const AppSettingsAnonymousEffects = {
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)`)',
gA: 'loaded=true(`n.hqj(!0)`)',
dA: 'new fA() 整片复位',
};
fs.writeFileSync(
path.join(P, 'names', 'root_wiring.json'),
JSON.stringify(
@@ -0,0 +1,238 @@
// BattlesSelectReducer.kt —— 战斗列表切片(对应 module_10032 的 fA 状态 + MA.vqk)
//
// 证据链:
// 状态类 fA:15 个字段,toString 名 "BattlesSelect(…)";copy 是 hqj(15 字段 + 回调)。
// 根委派:`Mi.vqk(t, n.apw_1)`。
// reducer(20 个实例臂):
// TA(RoundStarted) → roundEndTimes[battleId] = 当前时间 + 剩余秒;score[battleId] 清空
// IA(SwapTeam) → 该战斗内所有用户交换队伍,并把他们的个人分数归零
// AA(RoundFinish) → roundEndTimes / fund 删掉该战斗
// gA(无 toString) → loaded=true
// mA(AddBattle) → battles[battleId]=BattleParams;users / score 建空表;creators 写创建者
// kA(RemoveBattle) → battles / users / creators / alreadySeeBattleInfo 删该战斗
// zA(ChangeName) → battles[battleId].name
// bA(UpdateSuspicion) → battles[battleId].suspicionLevel
// yA(AddUsers) → 批量写 users / usersTeam(`$A` AddUser 复用它)
// xA(RemoveUser) → users / usersTeam / userScore / userSuspicion / userSuspicionByIp 删该用户
// jA(UpdateUserScore) → userScore[userId]
// CA(UpdateUserSuspicion)→ userSuspicion / userSuspicionByIp
// qA(UpdateTeamScore) → score[battleId][team]
// dA(无 toString) → `new fA()` 整片复位
// RA(EnterToBattleInfo) → alreadySeeBattleInfo += battleId
// SA(UpdateFund) → fund[battleId]
// EA(InitProBattleRewardLimit)→ proLimit
// PA(LimitReached) → isLimitReached
// NA(SetReEnterProtectBattleId)→ reEnterProtectBattleId
//
// 模型直接复用生成物:`BattleParams`(战斗描述)、`BattleInfoUser`(列表里的用户)、
// `BattleCreateParameters`(AddBattle 载荷)、`ProBattleRewardLimitData`。
package App.State.Reducers
import App.State.AddBattle
import App.State.AddUser
import App.State.AddUsers
import App.State.BattleCreateParameters
import App.State.BattleInfoUser
import App.State.BattleParams
import App.State.ChangeName
import App.State.EnterToBattleInfo
import App.State.InitProBattleRewardLimit
import App.State.LimitReached
import App.State.ProBattleRewardLimitData
import App.State.RemoveBattle
import App.State.RemoveUser
import App.State.RoundFinish
import App.State.RoundStarted
import App.State.SetReEnterProtectBattleId
import App.State.SwapTeam
import App.State.UpdateFund
import App.State.UpdateSuspicion
import App.State.UpdateTeamScore
import App.State.UpdateUserScore
import App.State.UpdateUserSuspicion
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `gA`:战斗列表已加载。 */
object MarkBattlesLoaded
/** 原 `dA`:整片复位(`new fA()`)。 */
object ResetBattlesSelect
/* ------------------------------------------------------------------ 状态 */
data class BattlesSelectStateSlice(
val Loaded: Boolean = false,
val Battles: Map<Any?, Any?> = emptyMap(),
val Score: Map<Any?, Any?> = emptyMap(),
val Users: Map<Any?, Any?> = emptyMap(),
val Creators: Map<Any?, Any?> = emptyMap(),
val UsersTeam: Map<Any?, Any?> = emptyMap(),
val UserScore: Map<Any?, Any?> = emptyMap(),
val UserSuspicion: Map<Any?, Any?> = emptyMap(),
val UserSuspicionByIp: Map<Any?, Any?> = emptyMap(),
val RoundEndTimes: Map<Any?, Any?> = emptyMap(),
val AlreadySeeBattleInfo: Set<Any?> = emptySet(),
val Fund: Map<Any?, Any?> = emptyMap(),
val ProLimit: ProBattleRewardLimitData? = null,
val IsLimitReached: Boolean = false,
val ReEnterProtectBattleId: Any? = null,
)
/* ------------------------------------------------------------------ reducer */
@Suppress("UNCHECKED_CAST")
private fun Inner(Map: Map<Any?, Any?>, Key: Any?): Map<Any?, Any?> =
Map[Key] as? Map<Any?, Any?> ?: emptyMap()
/** `BattleCreateParameters` → 列表用的 `BattleParams`(原 `new vA(...)`)。 */
private fun ParamsOf(
Params: BattleCreateParameters,
SuspicionLevel: Any?,
): BattleParams = BattleParams(
map = Params.mapId,
battleMode = 0,
format = Params.formatMode,
limits = Params.limits,
mapId = Params.mapId,
maxPeopleCount = Params.maxPeopleCount,
name = Params.name?.toString() ?: "",
parkourMode = Params.battleMode == "PARKOUR",
valuableBattle = Params.battleOptions.contains("VALUABLE"),
privateBattle = Params.battleOptions.contains("PRIVATE"),
proBattle = Params.proBattle,
rankRange = Params.rankRange,
theme = Params.theme.toIntOrNull() ?: 0,
suspicionLevel = SuspicionLevel,
options = Params.battleOptions,
)
/** 原 `eP`:队伍互换(字符串队伍名)。 */
private fun Swap(Team: Any?): Any? = when (Team) {
"RED", "red" -> "BLUE"
"BLUE", "blue" -> "RED"
else -> Team
}
fun ReduceBattlesSelect(
State0: BattlesSelectStateSlice,
Action: Any,
): BattlesSelectStateSlice = when (Action) {
is RoundStarted -> State0.copy(
RoundEndTimes = State0.RoundEndTimes + (Action.battleId to Action.timeLeftInSec),
Score = State0.Score + (Action.battleId to emptyMap<Any?, Any?>()),
)
is SwapTeam -> {
val Teams = Inner(State0.UsersTeam, Action.battleId)
val Swapped = Teams.mapValues { (_, Team) -> Swap(Team) }
val Users = Inner(State0.Users, Action.battleId)
val Zeroed = State0.UserScore.mapValues { (UserId, _) ->
if (Users.containsKey(UserId)) 0 else State0.UserScore[UserId]
}
State0.copy(
UsersTeam = State0.UsersTeam + (Action.battleId to Swapped),
UserScore = Zeroed,
)
}
is RoundFinish -> State0.copy(
RoundEndTimes = State0.RoundEndTimes - Action.battleId,
Fund = State0.Fund - Action.battleId,
)
is MarkBattlesLoaded -> State0.copy(Loaded = true)
is AddBattle -> State0.copy(
Battles = State0.Battles + (
Action.battleId to ParamsOf(Action.params, Action.suspicionLevel)
),
Users = State0.Users + (Action.battleId to emptyMap<Any?, Any?>()),
Score = State0.Score + (Action.battleId to emptyMap<Any?, Any?>()),
Creators = State0.Creators + (Action.battleId to Action.creatorId),
)
is RemoveBattle -> State0.copy(
Battles = State0.Battles - Action.battleId,
Users = State0.Users - Action.battleId,
Creators = State0.Creators - Action.battleId,
AlreadySeeBattleInfo = State0.AlreadySeeBattleInfo - Action.battleId,
)
is ChangeName -> {
val Battle = State0.Battles[Action.battleId] as? BattleParams
if (Battle == null) State0
else State0.copy(
Battles = State0.Battles + (
Action.battleId to Battle.copy(name = Action.name?.toString() ?: Battle.name)
),
)
}
is UpdateSuspicion -> {
val Battle = State0.Battles[Action.battleId] as? BattleParams
if (Battle == null) State0
else State0.copy(
Battles = State0.Battles + (
Action.battleId to Battle.copy(suspicionLevel = Action.suspicionLevel)
),
)
}
is AddUsers -> {
var Users = State0.Users
var Teams = State0.UsersTeam
var Scores = State0.UserScore
var Suspicion = State0.UserSuspicion
var SuspicionByIp = State0.UserSuspicionByIp
val CurrentUsers = LinkedHashMap<Any?, Any?>(Inner(State0.Users, Action.battleId))
val CurrentTeams = LinkedHashMap<Any?, Any?>(Inner(State0.UsersTeam, Action.battleId))
for (User in Action.users) {
CurrentUsers[User.user] = User
CurrentTeams[User.user] = Action.team
Scores = Scores + (User.user to User.score)
Suspicion = Suspicion + (User.user to User.suspicious)
SuspicionByIp = SuspicionByIp + (User.user to User.suspiciousByIp)
}
Users = Users + (Action.battleId to CurrentUsers)
Teams = Teams + (Action.battleId to CurrentTeams)
State0.copy(
Users = Users,
UsersTeam = Teams,
UserScore = Scores,
UserSuspicion = Suspicion,
UserSuspicionByIp = SuspicionByIp,
)
}
is AddUser -> ReduceBattlesSelect(
State0,
AddUsers(
battleId = Action.battleId,
users = listOf(Action.infoUser as BattleInfoUser),
team = Action.team,
),
)
is RemoveUser -> State0.copy(
Users = State0.Users + (
Action.battleId to Inner(State0.Users, Action.battleId) - Action.userId
),
UsersTeam = State0.UsersTeam + (
Action.battleId to Inner(State0.UsersTeam, Action.battleId) - Action.userId
),
UserScore = State0.UserScore - Action.userId,
UserSuspicion = State0.UserSuspicion - Action.userId,
UserSuspicionByIp = State0.UserSuspicionByIp - Action.userId,
)
is UpdateUserScore -> State0.copy(
UserScore = State0.UserScore + (Action.userId to Action.score),
)
is UpdateUserSuspicion -> State0.copy(
UserSuspicion = State0.UserSuspicion + (Action.userId to Action.suspicious),
UserSuspicionByIp = State0.UserSuspicionByIp + (Action.userId to Action.suspiciousByIp),
)
is UpdateTeamScore -> {
val Teams = LinkedHashMap<Any?, Any?>(Inner(State0.Score, Action.battleId))
Teams[Action.team] = Action.score
State0.copy(Score = State0.Score + (Action.battleId to Teams))
}
is ResetBattlesSelect -> BattlesSelectStateSlice()
is EnterToBattleInfo ->
State0.copy(AlreadySeeBattleInfo = State0.AlreadySeeBattleInfo + Action.battleId)
is UpdateFund -> State0.copy(Fund = State0.Fund + (Action.battleId to Action.fund))
is InitProBattleRewardLimit -> State0.copy(ProLimit = Action.proLimit)
is LimitReached -> State0.copy(IsLimitReached = Action.isLimitReached)
is SetReEnterProtectBattleId -> State0.copy(ReEnterProtectBattleId = Action.battleId)
else -> State0
}
@@ -1,7 +1,7 @@
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
//
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
// 这里采用同一形状。已迁移的 80 个切片里,79 个真正调用 reducer,launchParams 只做类型收口:
// 这里采用同一形状。已迁移的 81 个切片里,80 个真正调用 reducer,launchParams 只做类型收口:
//
// inviteState → ReduceInvite(aD / pD.er5)
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
@@ -82,9 +82,10 @@
// 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)`)
// battlesSelect → ReduceBattlesSelect(fA / Mi.vqk,根委派 `Mi.vqk(t, n.apw_1)`)
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
//
// 其余 9 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 8 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
@@ -97,6 +98,7 @@ import App.State.Reducers.ReduceBattleChat
import App.State.Reducers.ReduceBattleMessage
import App.State.Reducers.ReduceBattlePause
import App.State.Reducers.ReduceBattleSearch
import App.State.Reducers.ReduceBattlesSelect
import App.State.Reducers.ReduceBattleSettings
import App.State.Reducers.ReduceBattleStatistics
import App.State.Reducers.ReduceBattleTutorial
@@ -261,6 +263,7 @@ object RootReducer {
BattleChat = ReduceBattleChat(State0.BattleChat, Action),
RemoteUserDataStorage = ReduceRemoteUserDataStorage(State0.RemoteUserDataStorage, Action),
Matchmaking = ReduceMatchmaking(State0.Matchmaking, Action),
BattlesSelect = ReduceBattlesSelect(State0.BattlesSelect, Action),
)
}
@@ -18,6 +18,7 @@ import App.State.Reducers.BattleSettingsStateSlice
import App.State.Reducers.BattleStatisticsStateSlice
import App.State.Reducers.BattleTutorialStateSlice
import App.State.Reducers.BattleViewStateSlice
import App.State.Reducers.BattlesSelectStateSlice
import App.State.Reducers.CaptchaStateSlice
import App.State.Reducers.ClanChatStateSlice
import App.State.Reducers.ClanUserInfoStateSlice
@@ -144,8 +145,8 @@ data class RootState(
val Friends: FriendsStateSlice = FriendsStateSlice(),
/** 原 remoteUserDataStorage($Q / vh.isl)。已迁移。 */
val RemoteUserDataStorage: RemoteUserDataStorageStateSlice = RemoteUserDataStorageStateSlice(),
/** 原 battlesSelect(fA / Mi.vqk)。未迁移:原样透传。 */
val BattlesSelect: Any? = null,
/** 原 battlesSelect(fA / Mi.vqk)。已迁移。 */
val BattlesSelect: BattlesSelectStateSlice = BattlesSelectStateSlice(),
/** 原 maps(OA / Oi.sql)。已迁移。 */
val Maps: MapsStateSlice = MapsStateSlice(),
/** 原 matchmaking(TH / JH().qrx)。已迁移。 */
@@ -42,7 +42,7 @@ object RootWiring {
SliceWiring("notifications", "xpv_1", "jJ", "B_.ise", "B_.ise(t, n.xpv_1)", listOf("AddNotification", "InitNotificationSound", "RemoveFirstNotification", "RemoveNotifications")),
SliceWiring("friends", "ypv_1", "KD", "NL().cr9", "NL().cr9(t, n.ypv_1)", listOf("AddUsersToAccepted", "AddUsersToIncoming", "AddUsersToNewAccepted", "AddUsersToNewIncoming", "ChangeFriendAddFindState", "ClearContextMenuUser", "RemoveUserFromAccepted", "RemoveUserFromIncoming", "RemoveUserFromNewAccepted", "RemoveUsersFromNewIncoming", "SetContextMenuUser", "SetLimits", "SetValidationStatus")),
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("battlesSelect", "apw_1", "fA", "Mi.vqk", "Mi.vqk(t, n.apw_1)", listOf("AddBattle", "AddUser", "AddUsers", "ChangeName", "EnterToBattleInfo", "InitProBattleRewardLimit", "LimitReached", "MarkBattlesLoaded", "RemoveBattle", "RemoveUser", "ResetBattlesSelect", "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("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")),
@@ -106,7 +106,7 @@ object RootWiring {
)
/** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */
val Migrated: Set<String> = 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 Migrated: Set<String> = 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", "battlesSelect")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
@@ -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 / Matchmaking;
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage / Matchmaking / BattlesSelect;
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
@@ -767,6 +767,30 @@ import App.State.Reducers.MatchmakingStateSlice
import App.State.Reducers.ReduceMatchmaking
import App.State.Reducers.ResetMatchmakingProgress
import App.State.Reducers.StartLoadingBattle
import App.State.AddBattle
import App.State.AddUser
import App.State.AddUsers
import App.State.BattleCreateParameters
import App.State.BattleInfoUser
import App.State.BattleParams
import App.State.ChangeName
import App.State.EnterToBattleInfo
import App.State.InitProBattleRewardLimit
import App.State.LimitReached
import App.State.ProBattleRewardLimitData
import App.State.RemoveUser
import App.State.RoundFinish
import App.State.RoundStarted
import App.State.SetReEnterProtectBattleId
import App.State.SwapTeam
import App.State.UpdateSuspicion
import App.State.UpdateTeamScore
import App.State.UpdateUserScore
import App.State.UpdateUserSuspicion
import App.State.Reducers.BattlesSelectStateSlice
import App.State.Reducers.MarkBattlesLoaded
import App.State.Reducers.ReduceBattlesSelect
import App.State.Reducers.ResetBattlesSelect
import App.State.Reducers.UserRankBounds
import App.State.Reducers.UserStateSlice
import App.State.Reducers.ClearHullData
@@ -1194,7 +1218,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 80 个已迁移切片(79 归约 + launchParams 透传)
// ⑭ 根 reducer:89 个槽位的委派表 + 81 个已迁移切片(80 归约 + launchParams 透传)
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -1258,7 +1282,7 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 80 个已迁移字段,其余 9 个待迁",
"根:迁移表 = 81 个已迁移字段,其余 8 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
@@ -1282,9 +1306,9 @@ object StateCheck {
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
"tankBuilderState", "battleMessage", "maps", "battleChat",
"remoteUserDataStorage", "matchmaking",
"remoteUserDataStorage", "matchmaking", "battlesSelect",
) &&
RootWiring.Unmigrated.size == 9
RootWiring.Unmigrated.size == 8
)
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
@@ -1340,7 +1364,7 @@ object StateCheck {
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 80/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 81/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -6652,6 +6676,127 @@ object StateCheck {
)
}
// 91 切片 reducer:BattlesSelect(fA / Mi.vqk,15 字段;20 个动作)
run {
fun Params(): BattleCreateParameters = BattleCreateParameters(
battleMode = "DM", battleModeForMatchmaking = null,
battleOptions = listOf("VALUABLE"), crossPlayMode = "", formatMode = "fmt",
limits = "10", timeLimitInSec = "600", mapId = 1L, maxPeopleCount = 8,
name = "b1", proBattle = true, rankRange = "1-5", min = "1", theme = "2",
)
val S0 = BattlesSelectStateSlice()
Check("切片 BattlesSelect:默认值完整", !S0.Loaded && S0.Battles.isEmpty())
val S1 = ReduceBattlesSelect(S0, AddBattle(1L, 99L, Params(), "LOW"))
val Battle = S1.Battles[1L] as BattleParams
Check(
"切片 BattlesSelect:AddBattle 建列表项与四个子表",
Battle.name == "b1" && Battle.mapId == 1L && Battle.proBattle &&
Battle.parkourMode == false && S1.Users[1L] == emptyMap<Any?, Any?>() &&
S1.Score[1L] == emptyMap<Any?, Any?>() && S1.Creators[1L] == 99L
)
val S2 = ReduceBattlesSelect(S1, RoundStarted(1L, 60))
Check(
"切片 BattlesSelect:RoundStarted 写回合计时并清分数",
S2.RoundEndTimes[1L] == 60 && S2.Score[1L] == emptyMap<Any?, Any?>()
)
val S3 = ReduceBattlesSelect(S2, UpdateTeamScore(1L, "RED", 3))
Check(
"切片 BattlesSelect:UpdateTeamScore 写队伍分",
(S3.Score[1L] as Map<Any?, Any?>)["RED"] == 3
)
val S4 = ReduceBattlesSelect(
S3,
AddUsers(1L, listOf(BattleInfoUser(0L, 0, false, false, 7L)), "RED"),
)
Check(
"切片 BattlesSelect:AddUsers 写用户 / 队伍 / 个人分",
(S4.Users[1L] as Map<Any?, Any?>).keys == setOf<Any?>(7L) &&
(S4.UsersTeam[1L] as Map<Any?, Any?>)[7L] == "RED" &&
S4.UserScore[7L] == 0
)
val S5 = ReduceBattlesSelect(
ReduceBattlesSelect(S4, UpdateUserScore(1L, 7L, 9)),
UpdateUserSuspicion(1L, 7L, true, false),
)
Check(
"切片 BattlesSelect:个人分与可疑标记",
S5.UserScore[7L] == 9 && S5.UserSuspicion[7L] == true &&
S5.UserSuspicionByIp[7L] == false
)
val S6 = ReduceBattlesSelect(S5, SwapTeam(1L))
Check(
"切片 BattlesSelect:SwapTeam 换队并把该用户分数归零",
(S6.UsersTeam[1L] as Map<Any?, Any?>)[7L] == "BLUE" && S6.UserScore[7L] == 0
)
Check(
"切片 BattlesSelect:ChangeName / UpdateSuspicion",
(ReduceBattlesSelect(S1, ChangeName(1L, "renamed"))
.Battles[1L] as BattleParams).name == "renamed" &&
(ReduceBattlesSelect(S1, UpdateSuspicion(1L, "HIGH"))
.Battles[1L] as BattleParams).suspicionLevel == "HIGH"
)
Check(
"切片 BattlesSelect:EnterToBattleInfo / UpdateFund / LimitReached",
ReduceBattlesSelect(S1, EnterToBattleInfo(1L))
.AlreadySeeBattleInfo == setOf<Any?>(1L) &&
ReduceBattlesSelect(S1, UpdateFund(1L, 20)).Fund[1L] == 20 &&
ReduceBattlesSelect(S1, LimitReached(true)).IsLimitReached
)
Check(
"切片 BattlesSelect:InitProBattleRewardLimit / SetReEnterProtectBattleId",
ReduceBattlesSelect(
S1,
InitProBattleRewardLimit(ProBattleRewardLimitData(1, 2, 3, 4)),
).ProLimit == ProBattleRewardLimitData(1, 2, 3, 4) &&
ReduceBattlesSelect(S1, SetReEnterProtectBattleId(5L))
.ReEnterProtectBattleId == 5L
)
Check(
"切片 BattlesSelect:RemoveUser 清四个个人表",
ReduceBattlesSelect(S5, RemoveUser(1L, 7L)).let {
(it.Users[1L] as Map<Any?, Any?>).isEmpty() &&
it.UserScore[7L] == null && it.UserSuspicion[7L] == null
}
)
Check(
"切片 BattlesSelect:RemoveBattle 清四个战斗表",
ReduceBattlesSelect(
ReduceBattlesSelect(S5, EnterToBattleInfo(1L)),
RemoveBattle(1L),
).let {
it.Battles.isEmpty() && it.Users.isEmpty() && it.Creators.isEmpty() &&
it.AlreadySeeBattleInfo.isEmpty()
}
)
Check(
"切片 BattlesSelect:RoundFinish 删计时与资金",
ReduceBattlesSelect(ReduceBattlesSelect(S2, UpdateFund(1L, 5)), RoundFinish(1L))
.let { it.RoundEndTimes[1L] == null && it.Fund[1L] == null }
)
Check(
"切片 BattlesSelect:MarkBattlesLoaded / ResetBattlesSelect",
ReduceBattlesSelect(S0, MarkBattlesLoaded).Loaded &&
ReduceBattlesSelect(S5, ResetBattlesSelect) == BattlesSelectStateSlice()
)
Check(
"切片 BattlesSelect:无关动作不改状态",
ReduceBattlesSelect(S5, AddFrags(1)) == S5
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, MarkBattlesLoaded)
Check(
"根:MarkBattlesLoaded 只动 battlesSelect",
Root1.BattlesSelect.Loaded && Root1.Matchmaking == Root0.Matchmaking
)
val W = RootWiring.ByField("battlesSelect")!!
Check(
"根:battlesSelect 委派到 fA / Mi.vqk 且 20 个动作",
W.SliceClass == "fA" && W.Reducer == "Mi.vqk" && W.Actions.size == 20
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)