feat(state): 迁移 challenges(复用 Challenge 模型)
This commit is contained in:
1 parent
7d1c808af9
commit
25f8cd431e
8 files changed
+212
-21
No files matched your search
@@ -143,7 +143,7 @@
|
||||
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
|
||||
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
|
||||
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
|
||||
- 已迁移切片 **82/89**:
|
||||
- 已迁移切片 **83/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)
|
||||
@@ -413,12 +413,17 @@
|
||||
用户加入时同时写 onlineUsers / uids / ranks / clanTags / usersPremium / teams / stats;
|
||||
stats 值是生成物 `UserStat`(deaths / killScore / kills / score / user);
|
||||
坦克信息与抗性复用 `TankInfoCC` / `TankResistance`;`sP`(旧队伍枚举迁移)按 no-op 收口。
|
||||
- `challenges` → `CZ` / `nh.hsj`(`ChallengesReducer.kt`):4 字段,9 个分支。
|
||||
`stars` / `battlePassPackages` 整体替换;`SetChallenge` 按 `version` 建索引、`RemoveChallenge` 按 `version` 删;
|
||||
`SetBattlePass(flavor)` 追加一个 `Challenge` 战令条目,两个「已领取索引」动作按 flavor 命中后 copy;
|
||||
`SetRewardForDialog` / `LZ` 依赖 `FZ`/`GZ` 构造与外部刷新,纯 reducer 里按 no-op 收口。
|
||||
`Challenge` 直接复用生成物模型(9 字段)。
|
||||
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
|
||||
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 81 个有 reducer 的切片,
|
||||
`launchParams` 与其余 **7 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 82/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **902 项全绿**,含 89 槽位完整性、81 个归约切片一次归约互不串片、未迁移字段透传,
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 82 个有 reducer 的切片,
|
||||
`launchParams` 与其余 **6 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 83/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **913 项全绿**,含 89 槽位完整性、82 个归约切片一次归约互不串片、未迁移字段透传,
|
||||
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
|
||||
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
|
||||
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-10-24T13:07:20.497Z",
|
||||
"generatedAt": "2026-10-24T13:23:52.900Z",
|
||||
"module": "module_10032.js",
|
||||
"RootState": {
|
||||
"Class": "z6",
|
||||
@@ -88,7 +88,8 @@
|
||||
"remoteUserDataStorage",
|
||||
"matchmaking",
|
||||
"battlesSelect",
|
||||
"battleUsers"
|
||||
"battleUsers",
|
||||
"challenges"
|
||||
],
|
||||
"AnonymousActions": [
|
||||
{
|
||||
@@ -1482,6 +1483,12 @@
|
||||
"Name": "MigrateBattleUserTeams",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "旧队伍枚举迁移(`qm/bm/em` 映射),离线重建按 no-op 收口"
|
||||
},
|
||||
{
|
||||
"Local": "LZ",
|
||||
"Name": "RefreshBattlePasses",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "战令条目批量刷新(`GZ`),离线重建按 no-op 收口"
|
||||
}
|
||||
],
|
||||
"Markers": [
|
||||
@@ -1761,7 +1768,7 @@
|
||||
"Reducer": "nh.hsj",
|
||||
"Delegate": "nh.hsj(t, n.fpv_1)",
|
||||
"Actions": [
|
||||
"?LZ",
|
||||
"RefreshBattlePasses",
|
||||
"RemoveChallenge",
|
||||
"SetBattlePass",
|
||||
"SetBattlePassPackages",
|
||||
@@ -1772,7 +1779,7 @@
|
||||
"UpdateStars"
|
||||
],
|
||||
"Default": "new CZ()",
|
||||
"Migrated": false
|
||||
"Migrated": true
|
||||
},
|
||||
{
|
||||
"Field": "loginByPassword",
|
||||
|
||||
@@ -337,6 +337,8 @@ const AnonymousActionNames = new Map([
|
||||
['qP', 'RemoveOnlineUser'],
|
||||
['lP', 'SetUserPremium'],
|
||||
['sP', 'MigrateBattleUserTeams'],
|
||||
// challenges 切片(CZ / nh.hsj):LZ 没有 toString。
|
||||
['LZ', 'RefreshBattlePasses'],
|
||||
]);
|
||||
|
||||
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
|
||||
@@ -823,6 +825,7 @@ const Migrated = {
|
||||
matchmaking: { Type: 'MatchmakingStateSlice', Reduce: 'ReduceMatchmaking' },
|
||||
battlesSelect: { Type: 'BattlesSelectStateSlice', Reduce: 'ReduceBattlesSelect' },
|
||||
battleUsers: { Type: 'BattleUsersStateSlice', Reduce: 'ReduceBattleUsers' },
|
||||
challenges: { Type: 'ChallengesStateSlice', Reduce: 'ReduceChallenges' },
|
||||
};
|
||||
|
||||
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
|
||||
@@ -1246,6 +1249,7 @@ const AppSettingsAnonymousEffects = {
|
||||
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 收口',
|
||||
LZ: '战令条目批量刷新(`GZ`),离线重建按 no-op 收口',
|
||||
};
|
||||
fs.writeFileSync(
|
||||
path.join(P, 'names', 'root_wiring.json'),
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// ChallengesReducer.kt —— 挑战 / 战令切片(对应 module_10032 的 CZ 状态 + nh.hsj)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 CZ:4 个字段,toString 名 "Challenges(…)"。
|
||||
// 默认:stars=emptyMap()、challenge=emptyMap()、battlePassPackages=emptyMap()、battlePasses=emptySet()。
|
||||
// 根委派:`nh.hsj(t, n.fpv_1)`。
|
||||
// reducer(9 个实例臂):
|
||||
// AZ(UpdateStars) → stars(整体替换)
|
||||
// BZ(SetChallenge) → challenge[challenge.version]
|
||||
// UZ(RemoveChallenge) → challenge -= challengeVersion
|
||||
// OZ(SetBattlePass) → battlePasses += 该 flavor 的战令条目
|
||||
// Jrt(SetRewardForDialog) → 战令条目的弹窗奖励(原 `FZ/GZ` 构造,离线重建按 no-op)
|
||||
// LZ(无 toString) → 战令条目批量刷新(`GZ`),离线重建按 no-op
|
||||
// DZ(SetBattlePassPackages) → battlePassPackages(整体替换)
|
||||
// NZ(SetTakenFreeRewardIndex) → 命中 flavor 的战令条目写 takenFreeRewardIndex
|
||||
// MZ(SetTakenBattlePassRewardIndex)→ 命中 flavor 的战令条目写 takenBattlePassRewardIndex
|
||||
//
|
||||
// `Challenge` 直接复用生成物模型(version / endTime / tiers / battlePassFlavor /
|
||||
// mainPrizeImage / background / tierReward / takenFreeRewardIndex / takenBattlePassRewardIndex)。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.Challenge
|
||||
import App.State.RemoveChallenge
|
||||
import App.State.SetBattlePass
|
||||
import App.State.SetBattlePassPackages
|
||||
import App.State.SetChallenge
|
||||
import App.State.SetRewardForDialog
|
||||
import App.State.SetTakenBattlePassRewardIndex
|
||||
import App.State.SetTakenFreeRewardIndex
|
||||
import App.State.UpdateStars
|
||||
|
||||
/* ------------------------------------------------------------------ 匿名动作 */
|
||||
|
||||
/** 原 `LZ`:战令条目批量刷新(离线重建按 no-op)。 */
|
||||
object RefreshBattlePasses
|
||||
|
||||
/* ------------------------------------------------------------------ 状态 */
|
||||
|
||||
data class ChallengesStateSlice(
|
||||
val Stars: Map<String, Int> = emptyMap(),
|
||||
val Challenge: Map<Any?, Any?> = emptyMap(),
|
||||
val BattlePassPackages: Map<String, Long> = emptyMap(),
|
||||
val BattlePasses: Set<Challenge> = emptySet(),
|
||||
)
|
||||
|
||||
/* ------------------------------------------------------------------ reducer */
|
||||
|
||||
/** 原 `hsi`:按 flavor 造一个新的战令条目。 */
|
||||
private fun NewBattlePass(Flavor: Any?): Challenge = Challenge(
|
||||
version = Flavor,
|
||||
endTime = null,
|
||||
tiers = emptyList<Any?>(),
|
||||
battlePassFlavor = (Flavor as? Int) ?: 0,
|
||||
mainPrizeImage = 0L,
|
||||
background = 0L,
|
||||
tierReward = null,
|
||||
takenFreeRewardIndex = 0,
|
||||
takenBattlePassRewardIndex = 0,
|
||||
)
|
||||
|
||||
/** 原 `esi`:按 flavor 命中后替换战令条目。 */
|
||||
private fun MapBattlePass(
|
||||
Passes: Set<Challenge>,
|
||||
Flavor: Any?,
|
||||
Transform: (Challenge) -> Challenge,
|
||||
): Set<Challenge> = Passes.map { if (it.version == Flavor) Transform(it) else it }.toSet()
|
||||
|
||||
fun ReduceChallenges(
|
||||
State0: ChallengesStateSlice,
|
||||
Action: Any,
|
||||
): ChallengesStateSlice = when (Action) {
|
||||
is UpdateStars -> State0.copy(Stars = Action.stars)
|
||||
is SetChallenge -> State0.copy(
|
||||
Challenge = State0.Challenge + (Action.challenge.version to Action.challenge),
|
||||
)
|
||||
is RemoveChallenge -> State0.copy(Challenge = State0.Challenge - Action.challengeVersion)
|
||||
is SetBattlePass -> State0.copy(
|
||||
BattlePasses = State0.BattlePasses + NewBattlePass(Action.battlePassFlavor),
|
||||
)
|
||||
is SetRewardForDialog -> State0
|
||||
is RefreshBattlePasses -> State0
|
||||
is SetBattlePassPackages -> State0.copy(BattlePassPackages = Action.battlePassPackages)
|
||||
is SetTakenFreeRewardIndex -> State0.copy(
|
||||
BattlePasses = MapBattlePass(State0.BattlePasses, Action.flavor) {
|
||||
it.copy(takenFreeRewardIndex = (Action.index as? Int) ?: it.takenFreeRewardIndex)
|
||||
},
|
||||
)
|
||||
is SetTakenBattlePassRewardIndex -> State0.copy(
|
||||
BattlePasses = MapBattlePass(State0.BattlePasses, Action.flavor) {
|
||||
it.copy(takenBattlePassRewardIndex = (Action.index as? Int) ?: it.takenBattlePassRewardIndex)
|
||||
},
|
||||
)
|
||||
else -> State0
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
|
||||
//
|
||||
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
|
||||
// 这里采用同一形状。已迁移的 82 个切片里,81 个真正调用 reducer,launchParams 只做类型收口:
|
||||
// 这里采用同一形状。已迁移的 83 个切片里,82 个真正调用 reducer,launchParams 只做类型收口:
|
||||
//
|
||||
// inviteState → ReduceInvite(aD / pD.er5)
|
||||
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
|
||||
@@ -84,9 +84,10 @@
|
||||
// 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)`)
|
||||
// challenges → ReduceChallenges(CZ / nh.hsj,根委派 `nh.hsj(t, n.fpv_1)`)
|
||||
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
|
||||
//
|
||||
// 其余 7 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 其余 6 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
|
||||
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
|
||||
package App.State
|
||||
@@ -107,6 +108,7 @@ import App.State.Reducers.ReduceBattleUsers
|
||||
import App.State.Reducers.ReduceBattleView
|
||||
import App.State.Reducers.ReduceBattleInvite
|
||||
import App.State.Reducers.ReduceCaptcha
|
||||
import App.State.Reducers.ReduceChallenges
|
||||
import App.State.Reducers.ReduceClanUserInfo
|
||||
import App.State.Reducers.ReduceClanChat
|
||||
import App.State.Reducers.ReduceContextTutorial
|
||||
@@ -267,6 +269,7 @@ object RootReducer {
|
||||
Matchmaking = ReduceMatchmaking(State0.Matchmaking, Action),
|
||||
BattlesSelect = ReduceBattlesSelect(State0.BattlesSelect, Action),
|
||||
BattleUsers = ReduceBattleUsers(State0.BattleUsers, Action),
|
||||
Challenges = ReduceChallenges(State0.Challenges, Action),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import App.State.Reducers.BattleUsersStateSlice
|
||||
import App.State.Reducers.BattleViewStateSlice
|
||||
import App.State.Reducers.BattlesSelectStateSlice
|
||||
import App.State.Reducers.CaptchaStateSlice
|
||||
import App.State.Reducers.ChallengesStateSlice
|
||||
import App.State.Reducers.ClanChatStateSlice
|
||||
import App.State.Reducers.ClanUserInfoStateSlice
|
||||
import App.State.Reducers.ContextTutorialStateSlice
|
||||
@@ -104,8 +105,8 @@ data class RootState(
|
||||
val Garage: GarageStateSlice = GarageStateSlice(),
|
||||
/** 原 garagePreview(IG / fs.eru)。已迁移。 */
|
||||
val GaragePreview: GaragePreviewStateSlice = GaragePreviewStateSlice(),
|
||||
/** 原 challenges(CZ / nh.hsj)。未迁移:原样透传。 */
|
||||
val Challenges: Any? = null,
|
||||
/** 原 challenges(CZ / nh.hsj)。已迁移。 */
|
||||
val Challenges: ChallengesStateSlice = ChallengesStateSlice(),
|
||||
/** 原 loginByPassword(gD / nu.cr6)。已迁移。 */
|
||||
val LoginByPassword: LoginByPasswordStateSlice = LoginByPasswordStateSlice(),
|
||||
/** 原 register(Cit / cp.stg)。已迁移。 */
|
||||
|
||||
@@ -21,7 +21,7 @@ object RootWiring {
|
||||
SliceWiring("battleTutorial", "cpv_1", "rE", "Q.sq8", "Q.sq8(t, n.cpv_1)", listOf("MarkMovementStarted", "ResetBattleTutorial", "SetFragsCount", "ShowMessage")),
|
||||
SliceWiring("garage", "dpv_1", "UB", "hF", "hF(t, n)", listOf("AddGarageItemCount", "AddNewItem", "AddOverdriveDescription", "AddRelatedBazooka", "AddRelatedResistanceModule", "AddToFavoriteItem", "BuyProtectionSlot", "ClearWaitServerItemMount", "GarageAddItemToDepot", "GarageApplyResistanceMount", "GarageBuyDevice", "GarageDeviceTrySetInfinityLifetime", "GarageDevicesLoadedAction", "GarageLoaded", "GarageMountItem", "GarageMountItems", "GarageRemoveDevice", "GarageRemoveDiscountForDevice", "GarageRemoveItemFromDepot", "GarageRemoveItemFromStore", "GarageResistanceUnMount", "GarageSelectAlteration", "GarageSelectItem", "GarageSelectShotSkin", "GarageSelectSkin", "GarageUnmountItem", "GarageUpdateItemCount", "GarageUpgradeItemAction", "GarageUpgradeItemConfirm", "GarageUpgradeItemCoolDownFinished", "GarageUpgradeItemCoolDownStarted", "GoTo", "HideVideoInDescription", "InitState", "RemoveFromFavoriteItem", "RemoveFromSale", "RemoveNewItem", "RemoveWaitServerItemMount", "ResetGarage", "ResetMountDelay", "SelectGarageCategory", "SetCooldownReduceEndTime", "SetGarageNotLoaded", "SetGarageObject", "SetGarageSkinsAction", "SetItemRarity", "SetItemsOnDepot", "SetItemsOnMarket", "SetMountedItems", "SetMountedShotSkinForItems", "SetMountedSkinForItems", "SetNewItems", "SetResistanceReadyToMount", "SetRestrictionMount", "ShowGarageDiscountNotification", "StartCooldownReduce", "SwitchingRowsScroll", "UpdateDiscount")),
|
||||
SliceWiring("garagePreview", "epv_1", "IG", "fs.eru", "fs.eru(t, n.epv_1)", listOf("AddRequestedItem", "CaptureScreenshot", "CaptureVideo", "ClearDroneData", "ClearGrenadeData", "ClearHasGrenades", "ClearHullData", "ClearWeaponData", "InitGarageResources", "ResetItem3dModelActionCache", "SetCaptureActions", "SetDrone", "SetExpectedItemId", "SetGrenadeData", "SetHasGrenades", "SetHullData", "SetMountedItemsProperties", "SetPaint", "SetWeaponData", "ShowRequest", "UpdatePreviewSettings", "UpdateViewStyle")),
|
||||
SliceWiring("challenges", "fpv_1", "CZ", "nh.hsj", "nh.hsj(t, n.fpv_1)", listOf("?LZ", "RemoveChallenge", "SetBattlePass", "SetBattlePassPackages", "SetChallenge", "SetRewardForDialog", "SetTakenBattlePassRewardIndex", "SetTakenFreeRewardIndex", "UpdateStars")),
|
||||
SliceWiring("challenges", "fpv_1", "CZ", "nh.hsj", "nh.hsj(t, n.fpv_1)", listOf("RefreshBattlePasses", "RemoveChallenge", "SetBattlePass", "SetBattlePassPackages", "SetChallenge", "SetRewardForDialog", "SetTakenBattlePassRewardIndex", "SetTakenFreeRewardIndex", "UpdateStars")),
|
||||
SliceWiring("loginByPassword", "gpv_1", "gD", "nu.cr6", "nu.cr6(t, n.gpv_1)", listOf("?ND", "?PD", "?RD", "IncorrectLogin2FA", "RequestsNumber2FA", "SetLogin", "SetPassword", "SetRememberMe", "SetStatus")),
|
||||
SliceWiring("register", "hpv_1", "Cit", "cp.stg", "cp.stg(t, n.hpv_1)", listOf("DisableRegister", "EnteredUidIsBusy", "ForceSetUid", "ReceiveOAuthUri", "RegisterFailed", "RegisterPasswordCorrect", "RegisterPasswordIncorrect", "ResetAuthenticationForm", "ResetRegisterStatus", "ResetUidStatus", "SetOpenedPassword", "SetPassword", "SetPasswordRestrictions", "SetUid", "SocialNetworkAvailability", "SocialNetworkClientIds", "UidIsFree", "UidIsIncorrect", "UidTooShort")),
|
||||
SliceWiring("migrationState", "ipv_1", "\$it", "Uv.rtg", "Uv.rtg(t, n.ipv_1)", listOf("EnteredUidIsBusy", "ForceSetUid", "RegisterFailed", "RegisterPasswordIncorrect", "ResetAuthenticationForm", "ResetUidStatus", "SetPassword", "SetUid", "UidIsFree", "UidIsIncorrect", "UidTooShort")),
|
||||
@@ -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", "battleUsers")
|
||||
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", "challenges")
|
||||
|
||||
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
|
||||
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 / BattleUsers;
|
||||
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage / Matchmaking / BattlesSelect / BattleUsers / Challenges;
|
||||
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
|
||||
//
|
||||
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
||||
@@ -814,6 +814,18 @@ import App.State.Reducers.ResetBattleUsers
|
||||
import App.State.Reducers.SetUserPremium
|
||||
import App.State.RemoveTankInfo
|
||||
import App.State.UserStat
|
||||
import App.State.Challenge
|
||||
import App.State.RemoveChallenge
|
||||
import App.State.SetBattlePass
|
||||
import App.State.SetBattlePassPackages
|
||||
import App.State.SetChallenge
|
||||
import App.State.SetRewardForDialog
|
||||
import App.State.SetTakenBattlePassRewardIndex
|
||||
import App.State.SetTakenFreeRewardIndex
|
||||
import App.State.UpdateStars
|
||||
import App.State.Reducers.ChallengesStateSlice
|
||||
import App.State.Reducers.ReduceChallenges
|
||||
import App.State.Reducers.RefreshBattlePasses
|
||||
import App.State.Reducers.UserRankBounds
|
||||
import App.State.Reducers.UserStateSlice
|
||||
import App.State.Reducers.ClearHullData
|
||||
@@ -1241,7 +1253,7 @@ object StateCheck {
|
||||
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
|
||||
}
|
||||
|
||||
// ⑭ 根 reducer:89 个槽位的委派表 + 82 个已迁移切片(81 归约 + launchParams 透传)
|
||||
// ⑭ 根 reducer:89 个槽位的委派表 + 83 个已迁移切片(82 归约 + launchParams 透传)
|
||||
run {
|
||||
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
|
||||
Check(
|
||||
@@ -1305,7 +1317,7 @@ object StateCheck {
|
||||
LocalBattleW.Reducer == "(inline@localBattleUserState)"
|
||||
)
|
||||
Check(
|
||||
"根:迁移表 = 82 个已迁移字段,其余 7 个待迁",
|
||||
"根:迁移表 = 83 个已迁移字段,其余 6 个待迁",
|
||||
RootReducer.Migrated ==
|
||||
setOf(
|
||||
"inviteState", "loginByPassword", "twitchState", "home",
|
||||
@@ -1329,9 +1341,9 @@ object StateCheck {
|
||||
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
|
||||
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
|
||||
"tankBuilderState", "battleMessage", "maps", "battleChat",
|
||||
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers",
|
||||
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges",
|
||||
) &&
|
||||
RootWiring.Unmigrated.size == 7
|
||||
RootWiring.Unmigrated.size == 6
|
||||
)
|
||||
|
||||
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
|
||||
@@ -1387,7 +1399,7 @@ object StateCheck {
|
||||
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
|
||||
)
|
||||
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 82/89")
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 83/89")
|
||||
|
||||
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
|
||||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||||
@@ -6914,6 +6926,71 @@ object StateCheck {
|
||||
)
|
||||
}
|
||||
|
||||
// 93 切片 reducer:Challenges(CZ / nh.hsj,4 字段;9 个动作)
|
||||
run {
|
||||
fun ChallengeOf(Version: Any?): Challenge = Challenge(
|
||||
version = Version, endTime = null, tiers = emptyList<Any?>(),
|
||||
battlePassFlavor = 1, mainPrizeImage = 0L, background = 0L,
|
||||
tierReward = null, takenFreeRewardIndex = 0,
|
||||
takenBattlePassRewardIndex = 0,
|
||||
)
|
||||
|
||||
val S0 = ChallengesStateSlice()
|
||||
Check("切片 Challenges:默认值完整", S0.Stars.isEmpty() && S0.Challenge.isEmpty())
|
||||
val S1 = ReduceChallenges(S0, UpdateStars(mapOf("a" to 3)))
|
||||
Check("切片 Challenges:UpdateStars 整体替换", S1.Stars == mapOf("a" to 3))
|
||||
val S2 = ReduceChallenges(S1, SetChallenge(ChallengeOf(1)))
|
||||
Check(
|
||||
"切片 Challenges:SetChallenge 按 version 建索引",
|
||||
(S2.Challenge[1] as Challenge).version == 1
|
||||
)
|
||||
Check(
|
||||
"切片 Challenges:RemoveChallenge 按 version 删",
|
||||
ReduceChallenges(S2, RemoveChallenge(1)).Challenge.isEmpty()
|
||||
)
|
||||
Check(
|
||||
"切片 Challenges:SetBattlePassPackages 写包表",
|
||||
ReduceChallenges(S2, SetBattlePassPackages(mapOf("x" to 5L)))
|
||||
.BattlePassPackages == mapOf("x" to 5L)
|
||||
)
|
||||
val S3 = ReduceChallenges(S2, SetBattlePass(1))
|
||||
Check(
|
||||
"切片 Challenges:SetBattlePass 追加战令条目",
|
||||
S3.BattlePasses.size == 1 && S3.BattlePasses.first().version == 1
|
||||
)
|
||||
val S4 = ReduceChallenges(
|
||||
ReduceChallenges(S3, SetTakenFreeRewardIndex(1, 2)),
|
||||
SetTakenBattlePassRewardIndex(1, 4),
|
||||
)
|
||||
Check(
|
||||
"切片 Challenges:两个已领取索引按 flavor 命中",
|
||||
S4.BattlePasses.first().takenFreeRewardIndex == 2 &&
|
||||
S4.BattlePasses.first().takenBattlePassRewardIndex == 4
|
||||
)
|
||||
Check(
|
||||
"切片 Challenges:SetRewardForDialog / RefreshBattlePasses 是 no-op",
|
||||
ReduceChallenges(S4, SetRewardForDialog("1", 1)) == S4 &&
|
||||
ReduceChallenges(S4, RefreshBattlePasses) == S4
|
||||
)
|
||||
Check(
|
||||
"切片 Challenges:无关动作不改状态",
|
||||
ReduceChallenges(S4, AddFrags(1)) == S4
|
||||
)
|
||||
|
||||
val Root0 = RootState()
|
||||
val Root1 = ReduceRoot(Root0, UpdateStars(mapOf("b" to 1)))
|
||||
Check(
|
||||
"根:UpdateStars 只动 challenges",
|
||||
Root1.Challenges.Stars == mapOf("b" to 1) &&
|
||||
Root1.BattleUsers == Root0.BattleUsers
|
||||
)
|
||||
val W = RootWiring.ByField("challenges")!!
|
||||
Check(
|
||||
"根:challenges 委派到 CZ / nh.hsj 且 9 个动作",
|
||||
W.SliceClass == "CZ" && W.Reducer == "nh.hsj" && W.Actions.size == 9
|
||||
)
|
||||
}
|
||||
|
||||
val Failed = Results.filter { !it.second }
|
||||
println("TankiOffline 状态层自检:${Results.size} 项")
|
||||
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)
|
||||
|
||||
Reference in new issue
Block a user