feat(state): 迁移 battleUsers(复用 RemoteUserData/UserStat/TankInfoCC)

This commit is contained in:
TermiNexus committed 2026-10-24 21:21:36 +08:00
1 parent ddb3ef324b
commit 0ae39eebcd
8 files changed
+366 -26

No files matched your search

+9 -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` 动作名。
- 已迁移切片 **81/89**:
- 已迁移切片 **82/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)
@@ -409,12 +409,16 @@
`SwapTeam` 换队并把该战斗内用户分数归零;`RemoveBattle` / `RemoveUser` 清对应的四张子表;
其余为名字 / 可疑度 / 队伍分 / 个人分 / 资金 / 奖励上限 / 观战标记。
模型全部复用生成物:`BattleParams` / `BattleInfoUser` / `BattleCreateParameters` / `ProBattleRewardLimitData`。
- `battleUsers` → `uP` / `sr.hqs`(`BattleUsersReducer.kt`):13 字段,18 个分支。
用户加入时同时写 onlineUsers / uids / ranks / clanTags / usersPremium / teams / stats;
stats 值是生成物 `UserStat`(deaths / killScore / kills / score / user);
坦克信息与抗性复用 `TankInfoCC` / `TankResistance`;`sP`(旧队伍枚举迁移)按 no-op 收口。
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 80 个有 reducer 的切片,
`launchParams` 与其余 **8 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 81/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **888 项全绿**,含 89 槽位完整性、80 个归约切片一次归约互不串片、未迁移字段透传,
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 81 个有 reducer 的切片,
`launchParams` 与其余 **7 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 82/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **902 项全绿**,含 89 槽位完整性、81 个归约切片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
+46 -9
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-24T12:57:54.249Z",
"generatedAt": "2026-10-24T13:07:20.497Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -87,7 +87,8 @@
"battleChat",
"remoteUserDataStorage",
"matchmaking",
"battlesSelect"
"battlesSelect",
"battleUsers"
],
"AnonymousActions": [
{
@@ -1445,6 +1446,42 @@
"Name": "ResetBattlesSelect",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new fA() 整片复位"
},
{
"Local": "oP",
"Name": "ResetBattleUsers",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new uP() 整片复位"
},
{
"Local": "_P",
"Name": "AddBattleUser",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "单个用户写 onlineUsers / uids / ranks / clanTags / usersPremium / teams / stats"
},
{
"Local": "hP",
"Name": "AddBattleUsers",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "批量用户同上(循环调用 `_P` 的逻辑)"
},
{
"Local": "qP",
"Name": "RemoveOnlineUser",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "onlineUsers -= userId(`n.hqr(Jg(n.iqa_1, t.gqs_1))`)"
},
{
"Local": "lP",
"Name": "SetUserPremium",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "usersPremium[userId](`cg(n.mqa_1, Vd(t.iqs_1, t.jqs_1))`)"
},
{
"Local": "sP",
"Name": "MigrateBattleUserTeams",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "旧队伍枚举迁移(`qm/bm/em` 映射),离线重建按 no-op 收口"
}
],
"Markers": [
@@ -2310,18 +2347,18 @@
"Reducer": "sr.hqs",
"Delegate": "sr.hqs(t, n.hpw_1)",
"Actions": [
"?_P",
"?hP",
"?lP",
"?oP",
"?qP",
"?sP",
"AddBattleUser",
"AddBattleUsers",
"InitSuspiciousState",
"InitUserLevels",
"MigrateBattleUserTeams",
"RemoveOnlineUser",
"RemoveTankInfo",
"ResetBattleUsers",
"SetGearScore",
"SetTankInfo",
"SetTankResistance",
"SetUserPremium",
"UpdateUserLevel",
"UpdateUserRank",
"UpdateUserScoreKillsAndDeaths",
@@ -2330,7 +2367,7 @@
"UpdateUsersScoreKillsAndDeaths"
],
"Default": "new uP()",
"Migrated": false
"Migrated": true
},
{
"Field": "suppliesState",
+14
View File
@@ -330,6 +330,13 @@ const AnonymousActionNames = new Map([
// battlesSelect 切片(fA / MA.vqk):gA/dA 没有 toString。
['gA', 'MarkBattlesLoaded'],
['dA', 'ResetBattlesSelect'],
// battleUsers 切片(uP / yP.hqs):oP/_P/hP/qP/lP/sP 没有 toString。
['oP', 'ResetBattleUsers'],
['_P', 'AddBattleUser'],
['hP', 'AddBattleUsers'],
['qP', 'RemoveOnlineUser'],
['lP', 'SetUserPremium'],
['sP', 'MigrateBattleUserTeams'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
@@ -815,6 +822,7 @@ const Migrated = {
},
matchmaking: { Type: 'MatchmakingStateSlice', Reduce: 'ReduceMatchmaking' },
battlesSelect: { Type: 'BattlesSelectStateSlice', Reduce: 'ReduceBattlesSelect' },
battleUsers: { Type: 'BattleUsersStateSlice', Reduce: 'ReduceBattleUsers' },
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -1232,6 +1240,12 @@ const AppSettingsAnonymousEffects = {
nW: 'startLoadingBattle=false(`n.brw(cd, cd, cd, cd, !1)`)',
gA: 'loaded=true(`n.hqj(!0)`)',
dA: 'new fA() 整片复位',
oP: 'new uP() 整片复位',
_P: '单个用户写 onlineUsers / uids / ranks / clanTags / usersPremium / teams / stats',
hP: '批量用户同上(循环调用 `_P` 的逻辑)',
qP: 'onlineUsers -= userId(`n.hqr(Jg(n.iqa_1, t.gqs_1))`)',
lP: 'usersPremium[userId](`cg(n.mqa_1, Vd(t.iqs_1, t.jqs_1))`)',
sP: '旧队伍枚举迁移(`qm/bm/em` 映射),离线重建按 no-op 收口',
};
fs.writeFileSync(
path.join(P, 'names', 'root_wiring.json'),
@@ -0,0 +1,164 @@
// BattleUsersReducer.kt —— 战斗内用户表切片(对应 module_10032 的 uP 状态 + yP.hqs)
//
// 证据链:
// 状态类 uP:13 个字段,toString 名 "BattleUsers(…)";copy 是 hqr(13 字段 + 回调)。
// 根委派:`sr.hqs(t, n.hpw_1)`。
// reducer(18 个实例臂):
// oP(无 toString) → `new uP()` 整片复位
// _P / hP(无 toString) → 单个 / 批量把用户写进 onlineUsers / uids / ranks /
// clanTags / usersPremium / teams / stats
// aP(UpdateUserRank) → ranks[id]
// cP(UpdateUsersClanTag) → clanTags 合并
// dP / gP(UpdateUserScoreKillsAndDeaths / …Users…)→ stats[id] = userStat
// mP(SetGearScore) → gearScores[user]
// bP(SetTankResistance) → tankResistance[user]
// qP(无 toString) → onlineUsers -= userId
// lP(无 toString) → usersPremium[userId]
// fP(UpdateUserSuspiciousState)→ suspiciousUsers / suspiciousByIpUsers 按开关增删
// vP(InitSuspiciousState) → 两个集合整体替换
// sP(无 toString) → 队伍枚举迁移(原 `qm/bm/em` 映射):离线重建按 no-op 收口
// pP(InitUserLevels) → tankLevels 合并
// wP(UpdateUserLevel) → tankLevels[userId]
// kP(SetTankInfo) → tankInfo[user]
// zP(RemoveTankInfo) → tankInfo -= user
//
// 用户模型复用生成物 `RemoteUserData`(userId / uid / rank / hasPremium / clanTag / …);
// 坦克信息与抗性复用 `TankInfoCC` / `TankResistance`。
package App.State.Reducers
import App.State.InitSuspiciousState
import App.State.InitUserLevels
import App.State.RemoteUserData
import App.State.SetGearScore
import App.State.RemoveTankInfo
import App.State.SetTankInfo
import App.State.SetTankResistance
import App.State.TankInfoCC
import App.State.TankResistance
import App.State.UpdateUserLevel
import App.State.UpdateUserRank
import App.State.UpdateUserScoreKillsAndDeaths
import App.State.UpdateUserSuspiciousState
import App.State.UpdateUsersClanTag
import App.State.UpdateUsersScoreKillsAndDeaths
import App.State.UserStat
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `oP`:整片复位(`new uP()`)。 */
object ResetBattleUsers
/** 原 `_P`:加入一个战斗用户。 */
data class AddBattleUser(val user: RemoteUserData, val team: Any?)
/** 原 `hP`:批量加入战斗用户。 */
data class AddBattleUsers(val users: List<Any?>, val team: Any?)
/** 原 `qP`:把用户从在线列表移除。 */
data class RemoveOnlineUser(val userId: Any?)
/** 原 `lP`:写用户的会员标记。 */
data class SetUserPremium(val userId: Any?, val premium: Any?)
/** 原 `sP`:旧队伍枚举迁移(离线重建按 no-op)。 */
object MigrateBattleUserTeams
/* ------------------------------------------------------------------ 状态 */
data class BattleUsersStateSlice(
val OnlineUsers: Set<Any?> = emptySet(),
val Uids: Map<Any?, Any?> = emptyMap(),
val Ranks: Map<Any?, Any?> = emptyMap(),
val ClanTags: Map<Any?, Any?> = emptyMap(),
val UsersPremium: Map<Any?, Any?> = emptyMap(),
val Teams: Map<Any?, Any?> = emptyMap(),
val Stats: Map<Any?, Any?> = emptyMap(),
val GearScores: Map<Any?, Any?> = emptyMap(),
val TankInfo: Map<Any?, Any?> = emptyMap(),
val TankResistance: Map<Any?, Any?> = emptyMap(),
val TankLevels: Map<Any?, Any?> = emptyMap(),
val SuspiciousUsers: Set<Any?> = emptySet(),
val SuspiciousByIpUsers: Set<Any?> = emptySet(),
)
/* ------------------------------------------------------------------ reducer */
private fun ApplyUser(
State0: BattleUsersStateSlice,
User: RemoteUserData,
Team: Any?,
): BattleUsersStateSlice {
val Id = User.userId
return State0.copy(
OnlineUsers = State0.OnlineUsers + Id,
Uids = State0.Uids + (Id to User.uid),
Ranks = State0.Ranks + (Id to User.rank),
ClanTags = State0.ClanTags + (Id to User.clanTag),
UsersPremium = State0.UsersPremium + (Id to User.hasPremium),
Teams = State0.Teams + (Id to Team),
Stats = State0.Stats + (Id to User),
)
}
fun ReduceBattleUsers(
State0: BattleUsersStateSlice,
Action: Any,
): BattleUsersStateSlice = when (Action) {
is ResetBattleUsers -> BattleUsersStateSlice()
is AddBattleUser -> ApplyUser(State0, Action.user, Action.team)
is AddBattleUsers -> {
var Result = State0
for (Item in Action.users) {
val User = Item as? RemoteUserData ?: continue
Result = ApplyUser(Result, User, Action.team)
}
Result
}
is UpdateUserRank -> State0.copy(Ranks = State0.Ranks + (Action.id to Action.rank))
is UpdateUsersClanTag -> {
val Incoming = Action.clans as? Map<Any?, Any?> ?: emptyMap()
State0.copy(ClanTags = State0.ClanTags + Incoming)
}
is UpdateUserScoreKillsAndDeaths ->
State0.copy(Stats = State0.Stats + (Action.userStat.user to Action.userStat))
is UpdateUsersScoreKillsAndDeaths -> {
var Stats = State0.Stats
for (Item in (Action.userScores as? Iterable<*> ?: emptyList<Any?>())) {
val Stat = Item as? UserStat ?: continue
Stats = Stats + (Stat.user to Stat)
}
State0.copy(Stats = Stats)
}
is SetGearScore -> State0.copy(GearScores = State0.GearScores + (Action.user to Action.score))
is SetTankResistance -> State0.copy(
TankResistance = State0.TankResistance + (Action.user to Action.tankResistance),
)
is RemoveOnlineUser -> State0.copy(OnlineUsers = State0.OnlineUsers - Action.userId)
is SetUserPremium -> State0.copy(
UsersPremium = State0.UsersPremium + (Action.userId to Action.premium),
)
is UpdateUserSuspiciousState -> State0.copy(
SuspiciousUsers =
if (Action.suspicious == true) State0.SuspiciousUsers + Action.id
else State0.SuspiciousUsers - Action.id,
SuspiciousByIpUsers =
if (Action.suspiciousByIp == true) State0.SuspiciousByIpUsers + Action.id
else State0.SuspiciousByIpUsers - Action.id,
)
is InitSuspiciousState -> State0.copy(
SuspiciousUsers = (Action.suspiciousUserIds as? Iterable<*>)?.toSet() ?: emptySet(),
SuspiciousByIpUsers =
(Action.suspiciousByIpUserIds as? Iterable<*>)?.toSet() ?: emptySet(),
)
is MigrateBattleUserTeams -> State0
is InitUserLevels -> {
val Incoming = (Action.levels as? Map<Any?, Any?>) ?: emptyMap()
State0.copy(TankLevels = State0.TankLevels + Incoming)
}
is UpdateUserLevel -> State0.copy(
TankLevels = State0.TankLevels + (Action.userId to Action.level),
)
is SetTankInfo -> State0.copy(TankInfo = State0.TankInfo + (Action.user to Action.info))
is RemoveTankInfo -> State0.copy(TankInfo = State0.TankInfo - Action.user)
else -> State0
}
@@ -1,7 +1,7 @@
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
//
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
// 这里采用同一形状。已迁移的 81 个切片里,80 个真正调用 reducer,launchParams 只做类型收口:
// 这里采用同一形状。已迁移的 82 个切片里,81 个真正调用 reducer,launchParams 只做类型收口:
//
// inviteState → ReduceInvite(aD / pD.er5)
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
@@ -83,9 +83,10 @@
// 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)`)
// battleUsers → ReduceBattleUsers(uP / sr.hqs,根委派 `sr.hqs(t, n.hpw_1)`)
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
//
// 其余 8 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 7 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
@@ -102,6 +103,7 @@ import App.State.Reducers.ReduceBattlesSelect
import App.State.Reducers.ReduceBattleSettings
import App.State.Reducers.ReduceBattleStatistics
import App.State.Reducers.ReduceBattleTutorial
import App.State.Reducers.ReduceBattleUsers
import App.State.Reducers.ReduceBattleView
import App.State.Reducers.ReduceBattleInvite
import App.State.Reducers.ReduceCaptcha
@@ -264,6 +266,7 @@ object RootReducer {
RemoteUserDataStorage = ReduceRemoteUserDataStorage(State0.RemoteUserDataStorage, Action),
Matchmaking = ReduceMatchmaking(State0.Matchmaking, Action),
BattlesSelect = ReduceBattlesSelect(State0.BattlesSelect, Action),
BattleUsers = ReduceBattleUsers(State0.BattleUsers, Action),
)
}
@@ -17,6 +17,7 @@ import App.State.Reducers.BattleSearchStateSlice
import App.State.Reducers.BattleSettingsStateSlice
import App.State.Reducers.BattleStatisticsStateSlice
import App.State.Reducers.BattleTutorialStateSlice
import App.State.Reducers.BattleUsersStateSlice
import App.State.Reducers.BattleViewStateSlice
import App.State.Reducers.BattlesSelectStateSlice
import App.State.Reducers.CaptchaStateSlice
@@ -159,8 +160,8 @@ data class RootState(
val LobbyLoader: LobbyLoaderStateSlice = LobbyLoaderStateSlice(),
/** 原 battleResult(sR / Fi.aqp)。已迁移。 */
val BattleResult: BattleResultStateSlice = BattleResultStateSlice(),
/** 原 battleUsers(uP / sr.hqs)。未迁移:原样透传。 */
val BattleUsers: Any? = null,
/** 原 battleUsers(uP / sr.hqs)。已迁移。 */
val BattleUsers: BattleUsersStateSlice = BattleUsersStateSlice(),
/** 原 suppliesState(wE / $t.fq9)。已迁移。 */
val SuppliesState: SuppliesStateSlice = SuppliesStateSlice(),
/** 原 matchmakingGroup(rW / NW().is0)。未迁移:原样透传。 */
@@ -49,7 +49,7 @@ object RootWiring {
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")),
SliceWiring("battleResult", "gpw_1", "sR", "Fi.aqp", "Fi.aqp(t, n.gpw_1)", listOf("DoInit", "ResetBattleRewards", "SetHasBattleResult", "SetIncreasedReward", "SetRewards", "SetSimpleReward")),
SliceWiring("battleUsers", "hpw_1", "uP", "sr.hqs", "sr.hqs(t, n.hpw_1)", listOf("?_P", "?hP", "?lP", "?oP", "?qP", "?sP", "InitSuspiciousState", "InitUserLevels", "RemoveTankInfo", "SetGearScore", "SetTankInfo", "SetTankResistance", "UpdateUserLevel", "UpdateUserRank", "UpdateUserScoreKillsAndDeaths", "UpdateUserSuspiciousState", "UpdateUsersClanTag", "UpdateUsersScoreKillsAndDeaths")),
SliceWiring("battleUsers", "hpw_1", "uP", "sr.hqs", "sr.hqs(t, n.hpw_1)", listOf("AddBattleUser", "AddBattleUsers", "InitSuspiciousState", "InitUserLevels", "MigrateBattleUserTeams", "RemoveOnlineUser", "RemoveTankInfo", "ResetBattleUsers", "SetGearScore", "SetTankInfo", "SetTankResistance", "SetUserPremium", "UpdateUserLevel", "UpdateUserRank", "UpdateUserScoreKillsAndDeaths", "UpdateUserSuspiciousState", "UpdateUsersClanTag", "UpdateUsersScoreKillsAndDeaths")),
SliceWiring("suppliesState", "ipw_1", "wE", "\$t.fq9", "\$t.fq9(t, n.ipw_1)", listOf("SetSupplyCount")),
SliceWiring("matchmakingGroup", "jpw_1", "rW", "NW().is0", "NW().is0(t, n.jpw_1)", listOf("?zW", "AcceptedInviteToGroup", "AddUser", "AddUserToSelected", "InitMatchmakingGroup", "Invite", "RejectedInviteToGroup", "RemoveUserToSelected", "SetAllowedFromFriends", "SetSelectedUsers", "UpdateMountItems", "UpdateUserReadiness", "UserRemoved")),
SliceWiring("shop", "kpw_1", "j4", "(inline@shop)", "(function (t, n) { var i; if (t instanceof w4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, t.lt6_1); else if (t instanceof Yut) i = n.qt6(cd, cd, cd, cd, !0); else if (t instanceof F5) i = new j4(cd, cd, cd, cd, cd, cd, cd, n.qq3_1); else if (t instanceof h4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.st5_1); else if (t instanceof c4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.ut5_1); else if (t instanceof u4) i = n.qt6( cd, cd, cd, cd, cd, cd, …", listOf("?x4", "AddCategoriesWithBonus", "AddItemToOrderProcessing", "BlockBackButton", "BraintreeLoadingInterrupt", "ClearPreviousScreen", "ClearTargetCategory", "ClearTargetProductId", "CurrentPayMode", "DeleteOneTimePurchaseItem", "InitAdditionalDescription", "InitAmountString", "InitCategory", "InitCoinPackage", "InitCompensation", "InitCrystalKitPackage", "InitFeatureItem", "InitGroup", "InitItemViewType", "InitKeysPackage", "InitKitBundle", "InitKitPackage", "InitLootBoxAndPaint", "InitOneTimePurchaseItem", "InitPayModeAction", "InitPaymentModeAsShopItem", "InitPaymentProviderItems", "InitPaymentViewType", "InitRestrictionByPayMode", "InitRewardedVideosBlocks", "InitShopCard", "InitShopCardDiscount", "InitShopCardView", "InitShopItem", "InitShopItemName", "InitSingleItemKit", "InitSpecialKitPackage", "MobilePaymentStateChanged", "PaymentProviderFinishFail", "PaymentProviderFinishSuccess", "PaymentProviderInProgress", "PaymentUrlReceiveFailed", "PaymentUrlReceived", "RemoveDiscountItemForItem", "RemoveItemFromOrderProcessing", "ResetPaymentState", "ResetShop", "SavePreviousScreen", "SaveTargetProductId", "SaveTargetVisitedCategory", "SelectCategory", "SelectCategoryId", "SelectItem", "SetBraintreeToken", "SetCoinPaymentProviderUrl", "SetGoToHomeAfterBuy", "SetInitShopItemId", "SetPurchaseCompleted", "SetStripeIntentParams", "SetupDiscountCategoryByItem", "SetupDiscountItem", "SetupDiscountItemForItem", "ShopCardExpired", "ShopCardRemoved", "ShopCardSold", "ShopLoaded", "ToggleShopActivity", "TryToBuyOneTimePurchase", "UnblockBackButton")),
@@ -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", "battlesSelect")
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", "battleUsers")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
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 / BattlesSelect;
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage / Matchmaking / BattlesSelect / BattleUsers;
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
@@ -791,6 +791,29 @@ import App.State.Reducers.BattlesSelectStateSlice
import App.State.Reducers.MarkBattlesLoaded
import App.State.Reducers.ReduceBattlesSelect
import App.State.Reducers.ResetBattlesSelect
import App.State.InitSuspiciousState
import App.State.InitUserLevels
import App.State.SetGearScore
import App.State.SetTankInfo
import App.State.SetTankResistance
import App.State.TankInfoCC
import App.State.TankResistance
import App.State.UpdateUserLevel
import App.State.UpdateUserRank
import App.State.UpdateUserScoreKillsAndDeaths
import App.State.UpdateUserSuspiciousState
import App.State.UpdateUsersClanTag
import App.State.UpdateUsersScoreKillsAndDeaths
import App.State.Reducers.AddBattleUser
import App.State.Reducers.AddBattleUsers
import App.State.Reducers.BattleUsersStateSlice
import App.State.Reducers.MigrateBattleUserTeams
import App.State.Reducers.ReduceBattleUsers
import App.State.Reducers.RemoveOnlineUser
import App.State.Reducers.ResetBattleUsers
import App.State.Reducers.SetUserPremium
import App.State.RemoveTankInfo
import App.State.UserStat
import App.State.Reducers.UserRankBounds
import App.State.Reducers.UserStateSlice
import App.State.Reducers.ClearHullData
@@ -1218,7 +1241,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 81 个已迁移切片(80 归约 + launchParams 透传)
// ⑭ 根 reducer:89 个槽位的委派表 + 82 个已迁移切片(81 归约 + launchParams 透传)
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -1282,7 +1305,7 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 81 个已迁移字段,其余 8 个待迁",
"根:迁移表 = 82 个已迁移字段,其余 7 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
@@ -1306,9 +1329,9 @@ object StateCheck {
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
"tankBuilderState", "battleMessage", "maps", "battleChat",
"remoteUserDataStorage", "matchmaking", "battlesSelect",
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers",
) &&
RootWiring.Unmigrated.size == 8
RootWiring.Unmigrated.size == 7
)
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
@@ -1364,7 +1387,7 @@ object StateCheck {
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 81/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 82/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -6797,6 +6820,100 @@ object StateCheck {
)
}
// 92 切片 reducer:BattleUsers(uP / sr.hqs,13 字段;18 个动作)
run {
fun User(Id: Long): RemoteUserData = RemoteUserData(
userId = Id, uid = "u$Id", rank = 5, hasPremium = true,
clanTag = "T", onlineStatus = "online", battle = null,
)
val TankInfo = TankInfoCC(1L, null, 2L, 3L, null, 4L)
val Resistance = TankResistance(10, "fire")
val S0 = BattleUsersStateSlice()
Check("切片 BattleUsers:默认值完整", S0.OnlineUsers.isEmpty() && S0.Stats.isEmpty())
val S1 = ReduceBattleUsers(S0, AddBattleUser(User(1L), "RED"))
Check(
"切片 BattleUsers:AddBattleUser 写七张表",
S1.OnlineUsers == setOf<Any?>(1L) && S1.Uids[1L] == "u1" &&
S1.Ranks[1L] == 5 && S1.ClanTags[1L] == "T" &&
S1.UsersPremium[1L] == true && S1.Teams[1L] == "RED" &&
S1.Stats[1L] == User(1L)
)
Check(
"切片 BattleUsers:AddBattleUsers 批量加入",
ReduceBattleUsers(S1, AddBattleUsers(listOf(User(2L), User(3L)), "BLUE"))
.OnlineUsers == setOf<Any?>(1L, 2L, 3L)
)
Check(
"切片 BattleUsers:UpdateUserRank / UpdateUsersClanTag",
ReduceBattleUsers(S1, UpdateUserRank(1L, 9)).Ranks[1L] == 9 &&
ReduceBattleUsers(S1, UpdateUsersClanTag(mapOf(1L to "X")))
.ClanTags[1L] == "X"
)
Check(
"切片 BattleUsers:两种 stats 写入",
ReduceBattleUsers(S1, UpdateUserScoreKillsAndDeaths(UserStat(0, 0, 0, 0, 4L)))
.Stats[4L] == UserStat(0, 0, 0, 0, 4L) &&
ReduceBattleUsers(S1, UpdateUsersScoreKillsAndDeaths(listOf(UserStat(0, 0, 0, 0, 5L))))
.Stats[5L] == UserStat(0, 0, 0, 0, 5L)
)
Check(
"切片 BattleUsers:SetGearScore / SetTankResistance",
ReduceBattleUsers(S1, SetGearScore(1L, 100)).GearScores[1L] == 100 &&
ReduceBattleUsers(S1, SetTankResistance(1L, listOf(Resistance)))
.TankResistance[1L] == listOf(Resistance)
)
Check(
"切片 BattleUsers:RemoveOnlineUser / SetUserPremium",
ReduceBattleUsers(S1, RemoveOnlineUser(1L)).OnlineUsers.isEmpty() &&
ReduceBattleUsers(S1, SetUserPremium(1L, false)).UsersPremium[1L] == false
)
Check(
"切片 BattleUsers:可疑用户开关与整体替换",
ReduceBattleUsers(S1, UpdateUserSuspiciousState(1L, true, false))
.let { it.SuspiciousUsers == setOf<Any?>(1L) && it.SuspiciousByIpUsers.isEmpty() } &&
ReduceBattleUsers(S1, InitSuspiciousState(listOf(2L), listOf(3L)))
.let {
it.SuspiciousUsers == setOf<Any?>(2L) &&
it.SuspiciousByIpUsers == setOf<Any?>(3L)
}
)
Check(
"切片 BattleUsers:等级两臂",
ReduceBattleUsers(S1, InitUserLevels(mapOf(1L to 3))).TankLevels[1L] == 3 &&
ReduceBattleUsers(S1, UpdateUserLevel(1L, 4)).TankLevels[1L] == 4
)
Check(
"切片 BattleUsers:坦克信息写入与移除",
ReduceBattleUsers(S1, SetTankInfo(1L, TankInfo)).TankInfo[1L] == TankInfo &&
ReduceBattleUsers(S1, SetTankInfo(1L, TankInfo)).let {
ReduceBattleUsers(it, RemoveTankInfo(1L)).TankInfo.isEmpty()
}
)
Check(
"切片 BattleUsers:枚举迁移是 no-op、复位清空",
ReduceBattleUsers(S1, MigrateBattleUserTeams) == S1 &&
ReduceBattleUsers(S1, ResetBattleUsers) == BattleUsersStateSlice()
)
Check(
"切片 BattleUsers:无关动作不改状态",
ReduceBattleUsers(S1, AddFrags(1)) == S1
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, AddBattleUser(User(7L), "RED"))
Check(
"根:AddBattleUser 只动 battleUsers",
Root1.BattleUsers.OnlineUsers == setOf<Any?>(7L) &&
Root1.BattlesSelect == Root0.BattlesSelect
)
val W = RootWiring.ByField("battleUsers")!!
Check(
"根:battleUsers 委派到 uP / sr.hqs 且 18 个动作",
W.SliceClass == "uP" && W.Reducer == "sr.hqs" && W.Actions.size == 18
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)