feat(state): 迁移 clansStorage(复用 ClanInfo 模型)
This commit is contained in:
1 parent
25f8cd431e
commit
ad5686241d
8 files changed
+310
-24
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` 动作名。
|
||||
- 已迁移切片 **83/89**:
|
||||
- 已迁移切片 **84/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)
|
||||
@@ -418,12 +418,19 @@
|
||||
`SetBattlePass(flavor)` 追加一个 `Challenge` 战令条目,两个「已领取索引」动作按 flavor 命中后 copy;
|
||||
`SetRewardForDialog` / `LZ` 依赖 `FZ`/`GZ` 构造与外部刷新,纯 reducer 里按 no-op 收口。
|
||||
`Challenge` 直接复用生成物模型(9 字段)。
|
||||
- `clansStorage` → `lO` / `me.vr1`(`ClanStorageReducer.kt`):3 字段,13 个分支。
|
||||
列表项复用生成物 `ClanInfo`(16 字段);各分支都是对 `ClanInfo` 的 copy:
|
||||
`MinimalRankChanged`(第 5 参) / `RecruitmentChanged`(第 6 参) / 成员与待审用户(第 9/16 参) /
|
||||
`UpdateClan`(name/tag/description) / `BuyAdditionalPlace`(maxMembers+1)。
|
||||
`vO`(原名 `ClanLoaded`,与 clan 切片的 `isLoaded` 同名)改名 `LoadClanIntoStorage`;
|
||||
`dO`/`gO`(原名 `AddUser`/`RemoveUser`,与战斗列表同名)改名 `AddClanUser`/`RemoveClanUser`;
|
||||
查找时兼容 Int/Long 两种 clanId。
|
||||
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
|
||||
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 82 个有 reducer 的切片,
|
||||
`launchParams` 与其余 **6 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 83/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **913 项全绿**,含 89 槽位完整性、82 个归约切片一次归约互不串片、未迁移字段透传,
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 83 个有 reducer 的切片,
|
||||
`launchParams` 与其余 **5 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 84/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **924 项全绿**,含 89 槽位完整性、83 个归约切片一次归约互不串片、未迁移字段透传,
|
||||
以及用 `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:23:52.900Z",
|
||||
"generatedAt": "2026-10-24T13:56:08.668Z",
|
||||
"module": "module_10032.js",
|
||||
"RootState": {
|
||||
"Class": "z6",
|
||||
@@ -89,7 +89,8 @@
|
||||
"matchmaking",
|
||||
"battlesSelect",
|
||||
"battleUsers",
|
||||
"challenges"
|
||||
"challenges",
|
||||
"clansStorage"
|
||||
],
|
||||
"AnonymousActions": [
|
||||
{
|
||||
@@ -1489,6 +1490,30 @@
|
||||
"Name": "RefreshBattlePasses",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "战令条目批量刷新(`GZ`),离线重建按 no-op 收口"
|
||||
},
|
||||
{
|
||||
"Local": "mO",
|
||||
"Name": "ClearForeignClan",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "foreignClan=null(`n.lr1(cd, cd, null)`)"
|
||||
},
|
||||
{
|
||||
"Local": "vO",
|
||||
"Name": "LoadClanIntoStorage",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "clans[clan.id]=clan(原名 ClanLoaded,与 clan 切片同名类冲突,改名 LoadClanIntoStorage)"
|
||||
},
|
||||
{
|
||||
"Local": "dO",
|
||||
"Name": "AddClanUser",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "clan.users += userId(原名 AddUser,与战斗列表同名类冲突,改名 AddClanUser)"
|
||||
},
|
||||
{
|
||||
"Local": "gO",
|
||||
"Name": "RemoveClanUser",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"Effect": "clan.users -= userId(原名 RemoveUser,同上改名 RemoveClanUser)"
|
||||
}
|
||||
],
|
||||
"Markers": [
|
||||
@@ -2037,22 +2062,22 @@
|
||||
"Reducer": "me.vr1",
|
||||
"Delegate": "me.vr1(t, n.rpv_1)",
|
||||
"Actions": [
|
||||
"?mO",
|
||||
"AddUser",
|
||||
"AddClanUser",
|
||||
"AddUsersToNewAccepted",
|
||||
"BuyAdditionalPlace",
|
||||
"ClanLoaded",
|
||||
"ClanUnloaded",
|
||||
"ClearForeignClan",
|
||||
"ForeignClanLoaded",
|
||||
"LoadClanIntoStorage",
|
||||
"MinimalRankChanged",
|
||||
"RecruitmentChanged",
|
||||
"RemoveUser",
|
||||
"RemoveClanUser",
|
||||
"RemoveUsersFromNewAccepted",
|
||||
"ShowForeignClan",
|
||||
"UpdateClan"
|
||||
],
|
||||
"Default": "new lO()",
|
||||
"Migrated": false
|
||||
"Migrated": true
|
||||
},
|
||||
{
|
||||
"Field": "tipsState",
|
||||
|
||||
@@ -339,6 +339,12 @@ const AnonymousActionNames = new Map([
|
||||
['sP', 'MigrateBattleUserTeams'],
|
||||
// challenges 切片(CZ / nh.hsj):LZ 没有 toString。
|
||||
['LZ', 'RefreshBattlePasses'],
|
||||
// clansStorage 切片(lO / zO.vr1):mO 没有 toString;dO/gO 与战斗列表的同名类冲突,改名避免字段错配。
|
||||
['mO', 'ClearForeignClan'],
|
||||
['dO', 'AddClanUser'],
|
||||
['gO', 'RemoveClanUser'],
|
||||
// vO 的 toString 名 ClanLoaded 与 clan 切片的 isLoaded 动作同名冲突,改名避免字段错配。
|
||||
['vO', 'LoadClanIntoStorage'],
|
||||
]);
|
||||
|
||||
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
|
||||
@@ -826,6 +832,7 @@ const Migrated = {
|
||||
battlesSelect: { Type: 'BattlesSelectStateSlice', Reduce: 'ReduceBattlesSelect' },
|
||||
battleUsers: { Type: 'BattleUsersStateSlice', Reduce: 'ReduceBattleUsers' },
|
||||
challenges: { Type: 'ChallengesStateSlice', Reduce: 'ReduceChallenges' },
|
||||
clansStorage: { Type: 'ClanStorageStateSlice', Reduce: 'ReduceClanStorage' },
|
||||
};
|
||||
|
||||
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
|
||||
@@ -1250,6 +1257,10 @@ const AppSettingsAnonymousEffects = {
|
||||
lP: 'usersPremium[userId](`cg(n.mqa_1, Vd(t.iqs_1, t.jqs_1))`)',
|
||||
sP: '旧队伍枚举迁移(`qm/bm/em` 映射),离线重建按 no-op 收口',
|
||||
LZ: '战令条目批量刷新(`GZ`),离线重建按 no-op 收口',
|
||||
mO: 'foreignClan=null(`n.lr1(cd, cd, null)`)',
|
||||
vO: 'clans[clan.id]=clan(原名 ClanLoaded,与 clan 切片同名类冲突,改名 LoadClanIntoStorage)',
|
||||
dO: 'clan.users += userId(原名 AddUser,与战斗列表同名类冲突,改名 AddClanUser)',
|
||||
gO: 'clan.users -= userId(原名 RemoveUser,同上改名 RemoveClanUser)',
|
||||
};
|
||||
fs.writeFileSync(
|
||||
path.join(P, 'names', 'root_wiring.json'),
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// ClanStorageReducer.kt —— 战队列表缓存切片(对应 module_10032 的 lO 状态 + zO.vr1)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 lO:3 个字段,toString 名 "ClansStorage(clans=…, foreignClanId=…, foreignClan=…)"。
|
||||
// 默认:clans=emptyMap()、foreignClanId=null、foreignClan=null。
|
||||
// 根委派:`me.vr1(t, n.rpv_1)`。
|
||||
// reducer(13 个实例臂,全部围绕 ClanInfo 的 copy `nr1`):
|
||||
// vO(ClanLoaded) → clans[clan.id] = clan
|
||||
// pO(ClanUnloaded) → clans -= clanId
|
||||
// TM(MinimalRankChanged) → copy(minRankIndexForAddClan) // `nr1` 第 5 参
|
||||
// AM(RecruitmentChanged) → copy(incomingRequestEnabled) // 第 6 参
|
||||
// BN(AddUsersToNewAccepted)→ copy(newUsers += userIds) // 第 16 参
|
||||
// FN(RemoveUsersFromNewAccepted)→ copy(newUsers -= userId) // 第 16 参
|
||||
// dO(AddUser) → copy(users += userId) // 第 9 参
|
||||
// gO(RemoveUser) → copy(users -= userId) // 第 9 参
|
||||
// wO(ForeignClanLoaded) → foreignClan
|
||||
// mO(无 toString) → foreignClan = null
|
||||
// OM(ShowForeignClan) → foreignClanId
|
||||
// nO(UpdateClan) → copy(name / tag / description,null 时保留原值)
|
||||
// iO(BuyAdditionalPlace) → copy(maxMembers + 1) // 第 15 参
|
||||
//
|
||||
// 列表里的战队摘要直接复用生成物 `ClanInfo`(16 字段)。
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.AddUsersToNewAccepted
|
||||
import App.State.BuyAdditionalPlace
|
||||
import App.State.ClanInfo
|
||||
import App.State.ClanUnloaded
|
||||
import App.State.ForeignClanLoaded
|
||||
import App.State.MinimalRankChanged
|
||||
import App.State.RecruitmentChanged
|
||||
import App.State.RemoveUsersFromNewAccepted
|
||||
import App.State.ShowForeignClan
|
||||
import App.State.UpdateClan
|
||||
|
||||
/* ------------------------------------------------------------------ 动作 */
|
||||
|
||||
/**
|
||||
* 原 `vO`(原名 `ClanLoaded`,与 clan 切片的 `isLoaded` 动作同名冲突):把战队摘要写进缓存。
|
||||
*/
|
||||
data class LoadClanIntoStorage(val clan: ClanInfo)
|
||||
|
||||
/** 原 `dO`(原名 `AddUser`,与战斗列表的同名类冲突):把用户加入战队成员表。 */
|
||||
data class AddClanUser(val clanId: Any?, val userId: Any?)
|
||||
|
||||
/** 原 `gO`(原名 `RemoveUser`,与战斗列表的同名类冲突):把用户移出战队成员表。 */
|
||||
data class RemoveClanUser(val clanId: Any?, val userId: Any?)
|
||||
|
||||
/** 原 `mO`:清空外战队详情。 */
|
||||
object ClearForeignClan
|
||||
|
||||
/* ------------------------------------------------------------------ 状态 */
|
||||
|
||||
data class ClanStorageStateSlice(
|
||||
val Clans: Map<Any?, Any?> = emptyMap(),
|
||||
val ForeignClanId: Any? = null,
|
||||
val ForeignClan: Any? = null,
|
||||
)
|
||||
|
||||
/* ------------------------------------------------------------------ reducer */
|
||||
|
||||
private fun Plus(Target: Any?, Value: Any?): Any? = when (Target) {
|
||||
is Set<*> -> if (Value is Iterable<*>) Target + Value.toSet() else Target + Value
|
||||
is List<*> -> if (Value is Iterable<*>) Target + Value.toList() else Target + Value
|
||||
else -> if (Value is Iterable<*>) Value.toSet() else setOf(Value)
|
||||
}
|
||||
|
||||
private fun Minus(Target: Any?, Value: Any?): Any? = when (Target) {
|
||||
is Set<*> -> if (Value is Iterable<*>) Target - Value.toSet() else Target - Value
|
||||
is List<*> -> if (Value is Iterable<*>) Target - Value.toList() else Target - Value
|
||||
else -> emptySet<Any?>()
|
||||
}
|
||||
|
||||
/** 原 key 是 Long,部分动作字段是 Int;按数值兼容查一次。 */
|
||||
private fun Lookup(Clans: Map<Any?, Any?>, ClanId: Any?): Pair<Any?, ClanInfo>? {
|
||||
(Clans[ClanId] as? ClanInfo)?.let { return ClanId to it }
|
||||
val Number = ClanId as? Number ?: return null
|
||||
(Clans[Number.toLong()] as? ClanInfo)?.let { return Number.toLong() to it }
|
||||
(Clans[Number.toInt()] as? ClanInfo)?.let { return Number.toInt() to it }
|
||||
return null
|
||||
}
|
||||
|
||||
private fun Update(
|
||||
Clans: Map<Any?, Any?>,
|
||||
ClanId: Any?,
|
||||
Transform: (ClanInfo) -> ClanInfo,
|
||||
): Map<Any?, Any?> {
|
||||
val (Key, Clan) = Lookup(Clans, ClanId) ?: return Clans
|
||||
return Clans + (Key to Transform(Clan))
|
||||
}
|
||||
|
||||
fun ReduceClanStorage(
|
||||
State0: ClanStorageStateSlice,
|
||||
Action: Any,
|
||||
): ClanStorageStateSlice = when (Action) {
|
||||
is LoadClanIntoStorage -> State0.copy(
|
||||
Clans = State0.Clans + (Action.clan.id to Action.clan),
|
||||
)
|
||||
is ClanUnloaded -> State0.copy(Clans = State0.Clans - Action.clanId)
|
||||
is MinimalRankChanged -> State0.copy(
|
||||
Clans = Update(State0.Clans, Action.clanId) {
|
||||
it.copy(minRankIndexForAddClan = (Action.rankIndex as? Int) ?: it.minRankIndexForAddClan)
|
||||
},
|
||||
)
|
||||
is RecruitmentChanged -> State0.copy(
|
||||
Clans = Update(State0.Clans, Action.clanId) {
|
||||
it.copy(incomingRequestEnabled = Action.enabled == true)
|
||||
},
|
||||
)
|
||||
is AddUsersToNewAccepted -> State0.copy(
|
||||
Clans = Update(State0.Clans, Action.clanId) {
|
||||
it.copy(newUsers = Plus(it.newUsers, Action.userIds))
|
||||
},
|
||||
)
|
||||
is RemoveUsersFromNewAccepted -> State0.copy(
|
||||
Clans = Update(State0.Clans, Action.clanId) {
|
||||
it.copy(newUsers = Minus(it.newUsers, Action.userId))
|
||||
},
|
||||
)
|
||||
is AddClanUser -> State0.copy(
|
||||
Clans = Update(State0.Clans, Action.clanId) {
|
||||
it.copy(users = Plus(it.users, Action.userId))
|
||||
},
|
||||
)
|
||||
is RemoveClanUser -> State0.copy(
|
||||
Clans = Update(State0.Clans, Action.clanId) {
|
||||
it.copy(users = Minus(it.users, Action.userId))
|
||||
},
|
||||
)
|
||||
is ForeignClanLoaded -> State0.copy(ForeignClan = Action.clan)
|
||||
is ClearForeignClan -> State0.copy(ForeignClan = null)
|
||||
is ShowForeignClan -> State0.copy(ForeignClanId = Action.clanId)
|
||||
is UpdateClan -> State0.copy(
|
||||
Clans = Update(State0.Clans, Action.clanId) {
|
||||
it.copy(
|
||||
name = Action.name ?: it.name,
|
||||
tag = Action.tag ?: it.tag,
|
||||
description = (Action.description as? String) ?: it.description,
|
||||
)
|
||||
},
|
||||
)
|
||||
is BuyAdditionalPlace -> State0.copy(
|
||||
Clans = Update(State0.Clans, Action.clanId) {
|
||||
it.copy(maxMembers = it.maxMembers + 1)
|
||||
},
|
||||
)
|
||||
else -> State0
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
|
||||
//
|
||||
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
|
||||
// 这里采用同一形状。已迁移的 83 个切片里,82 个真正调用 reducer,launchParams 只做类型收口:
|
||||
// 这里采用同一形状。已迁移的 84 个切片里,83 个真正调用 reducer,launchParams 只做类型收口:
|
||||
//
|
||||
// inviteState → ReduceInvite(aD / pD.er5)
|
||||
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
|
||||
@@ -85,9 +85,10 @@
|
||||
// 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)`)
|
||||
// clansStorage → ReduceClanStorage(lO / me.vr1,根委派 `me.vr1(t, n.rpv_1)`)
|
||||
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
|
||||
//
|
||||
// 其余 6 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 其余 5 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
|
||||
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
|
||||
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
|
||||
package App.State
|
||||
@@ -109,6 +110,7 @@ import App.State.Reducers.ReduceBattleView
|
||||
import App.State.Reducers.ReduceBattleInvite
|
||||
import App.State.Reducers.ReduceCaptcha
|
||||
import App.State.Reducers.ReduceChallenges
|
||||
import App.State.Reducers.ReduceClanStorage
|
||||
import App.State.Reducers.ReduceClanUserInfo
|
||||
import App.State.Reducers.ReduceClanChat
|
||||
import App.State.Reducers.ReduceContextTutorial
|
||||
@@ -270,6 +272,7 @@ object RootReducer {
|
||||
BattlesSelect = ReduceBattlesSelect(State0.BattlesSelect, Action),
|
||||
BattleUsers = ReduceBattleUsers(State0.BattleUsers, Action),
|
||||
Challenges = ReduceChallenges(State0.Challenges, Action),
|
||||
ClansStorage = ReduceClanStorage(State0.ClansStorage, Action),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import App.State.Reducers.CaptchaStateSlice
|
||||
import App.State.Reducers.ChallengesStateSlice
|
||||
import App.State.Reducers.ClanChatStateSlice
|
||||
import App.State.Reducers.ClanUserInfoStateSlice
|
||||
import App.State.Reducers.ClanStorageStateSlice
|
||||
import App.State.Reducers.ContextTutorialStateSlice
|
||||
import App.State.Reducers.ContractStateSlice
|
||||
import App.State.Reducers.ControlSettingsStateSlice
|
||||
@@ -129,8 +130,8 @@ data class RootState(
|
||||
val ClanChatState: ClanChatStateSlice = ClanChatStateSlice(),
|
||||
/** 原 clan(jN / we.hr0)。未迁移:原样透传。 */
|
||||
val Clan: Any? = null,
|
||||
/** 原 clansStorage(lO / me.vr1)。未迁移:原样透传。 */
|
||||
val ClansStorage: Any? = null,
|
||||
/** 原 clansStorage(lO / me.vr1)。已迁移。 */
|
||||
val ClansStorage: ClanStorageStateSlice = ClanStorageStateSlice(),
|
||||
/** 原 tipsState(dst / Hw.vth)。已迁移。 */
|
||||
val TipsState: TipsStateSlice = TipsStateSlice(),
|
||||
/** 原 news(ZX / E_.asd)。已迁移。 */
|
||||
|
||||
@@ -33,7 +33,7 @@ object RootWiring {
|
||||
SliceWiring("chatState", "opv_1", "LP", "\$r.wqv", "\$r.wqv(t, n.opv_1)", listOf("?QP", "?XP", "?bN", "?fN", "?lN", "?nN", "?pN", "?qN", "?sN", "?vN", "?wN", "ChangeChannel", "CleanUsersMessage", "Configure", "IgnoreUserMessages", "ResetSettingsFromStorage", "SaveChatMessage", "SendMessageToServer", "SetMessages", "SetSelectedUid", "ShowContextMenu", "ShowUserMessages", "UpdateTypingSpeedAntifloodParams", "UpdateUsers")),
|
||||
SliceWiring("clanChatState", "ppv_1", "qnt", "yv.qtg", "yv.qtg(t, n.ppv_1)", listOf("CloseClanChat", "Configure", "DoAddMessages", "OpenClanChat", "ResetClanChat")),
|
||||
SliceWiring("clan", "qpv_1", "jN", "we.hr0", "we.hr0(t, n.qpv_1)", listOf("?FM", "?IM", "?KM", "?PM", "?RM", "?VM", "?YM", "?iM", "AcceptedUserViewed", "AddIncomingClan", "AddNewIncoming", "AddNewIncomingClan", "AddOutgoingClan", "AddUsersToAccepted", "AddUsersToNewAccepted", "ClanBanned", "ClanInfoLoaded", "ClanLoaded", "ClanModalVisible", "IncomingAdded", "IncomingRemoved", "IncomingUserViewed", "MembersFetched", "NewAcceptedUserNotification", "NewClanInviteNotification", "NewIncomingUserNotification", "OutgoingAdded", "OutgoingRemoved", "RemoveIncomingClan", "RemoveNewIncoming", "RemoveNewIncomingClan", "RemoveOutgoingClan", "RemoveUserFromAccepted", "RemoveUsersFromNewAccepted", "SetClanEditPrice", "SetMinRankForCreate", "UpdateClanLicense", "UpdateClanNameValidation", "UpdateClanSearchValidation", "UpdateClanTagValidation", "UpdateHasClan", "UpdateTimeToJoinRestriction", "UpdateValidation")),
|
||||
SliceWiring("clansStorage", "rpv_1", "lO", "me.vr1", "me.vr1(t, n.rpv_1)", listOf("?mO", "AddUser", "AddUsersToNewAccepted", "BuyAdditionalPlace", "ClanLoaded", "ClanUnloaded", "ForeignClanLoaded", "MinimalRankChanged", "RecruitmentChanged", "RemoveUser", "RemoveUsersFromNewAccepted", "ShowForeignClan", "UpdateClan")),
|
||||
SliceWiring("clansStorage", "rpv_1", "lO", "me.vr1", "me.vr1(t, n.rpv_1)", listOf("AddClanUser", "AddUsersToNewAccepted", "BuyAdditionalPlace", "ClanUnloaded", "ClearForeignClan", "ForeignClanLoaded", "LoadClanIntoStorage", "MinimalRankChanged", "RecruitmentChanged", "RemoveClanUser", "RemoveUsersFromNewAccepted", "ShowForeignClan", "UpdateClan")),
|
||||
SliceWiring("tipsState", "spv_1", "dst", "Hw.vth", "Hw.vth(t, n.spv_1)", listOf("UpdateTips")),
|
||||
SliceWiring("news", "tpv_1", "ZX", "E_.asd", "E_.asd(t, n.tpv_1)", listOf("AddNewsItemAction", "RemoveNewsItemAction", "SetNewsFlag", "SetNewsItemAction", "SetUnreadNewsCounter")),
|
||||
SliceWiring("settings", "upv_1", "Uut", "xw.ltg", "xw.ltg(t, n.upv_1)", listOf("DisableAccountSettings", "SetSettingsCategory")),
|
||||
@@ -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", "challenges")
|
||||
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", "clansStorage")
|
||||
|
||||
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
|
||||
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 / Challenges;
|
||||
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage / Maps / BattleChat / RemoteUserDataStorage / Matchmaking / BattlesSelect / BattleUsers / Challenges / ClansStorage;
|
||||
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
|
||||
//
|
||||
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
||||
@@ -826,6 +826,20 @@ import App.State.UpdateStars
|
||||
import App.State.Reducers.ChallengesStateSlice
|
||||
import App.State.Reducers.ReduceChallenges
|
||||
import App.State.Reducers.RefreshBattlePasses
|
||||
import App.State.ClanInfo
|
||||
import App.State.ClanUnloaded
|
||||
import App.State.BuyAdditionalPlace
|
||||
import App.State.ForeignClanLoaded
|
||||
import App.State.MinimalRankChanged
|
||||
import App.State.RecruitmentChanged
|
||||
import App.State.ShowForeignClan
|
||||
import App.State.UpdateClan
|
||||
import App.State.Reducers.AddClanUser
|
||||
import App.State.Reducers.LoadClanIntoStorage
|
||||
import App.State.Reducers.ClanStorageStateSlice
|
||||
import App.State.Reducers.ClearForeignClan
|
||||
import App.State.Reducers.ReduceClanStorage
|
||||
import App.State.Reducers.RemoveClanUser
|
||||
import App.State.Reducers.UserRankBounds
|
||||
import App.State.Reducers.UserStateSlice
|
||||
import App.State.Reducers.ClearHullData
|
||||
@@ -1253,7 +1267,7 @@ object StateCheck {
|
||||
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
|
||||
}
|
||||
|
||||
// ⑭ 根 reducer:89 个槽位的委派表 + 83 个已迁移切片(82 归约 + launchParams 透传)
|
||||
// ⑭ 根 reducer:89 个槽位的委派表 + 84 个已迁移切片(83 归约 + launchParams 透传)
|
||||
run {
|
||||
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
|
||||
Check(
|
||||
@@ -1317,7 +1331,7 @@ object StateCheck {
|
||||
LocalBattleW.Reducer == "(inline@localBattleUserState)"
|
||||
)
|
||||
Check(
|
||||
"根:迁移表 = 83 个已迁移字段,其余 6 个待迁",
|
||||
"根:迁移表 = 84 个已迁移字段,其余 5 个待迁",
|
||||
RootReducer.Migrated ==
|
||||
setOf(
|
||||
"inviteState", "loginByPassword", "twitchState", "home",
|
||||
@@ -1341,9 +1355,9 @@ object StateCheck {
|
||||
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
|
||||
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
|
||||
"tankBuilderState", "battleMessage", "maps", "battleChat",
|
||||
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges",
|
||||
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges", "clansStorage",
|
||||
) &&
|
||||
RootWiring.Unmigrated.size == 6
|
||||
RootWiring.Unmigrated.size == 5
|
||||
)
|
||||
|
||||
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
|
||||
@@ -1399,7 +1413,7 @@ object StateCheck {
|
||||
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
|
||||
)
|
||||
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 83/89")
|
||||
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 84/89")
|
||||
|
||||
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
|
||||
Store.Dispatch(SetStatus("LOGGING_IN"))
|
||||
@@ -6991,6 +7005,83 @@ object StateCheck {
|
||||
)
|
||||
}
|
||||
|
||||
// 94 切片 reducer:ClansStorage(lO / me.vr1,3 字段;13 个动作)
|
||||
run {
|
||||
fun Clan(Id: Any? = 1L): ClanInfo = ClanInfo(
|
||||
id = Id, name = "n", tag = "t", description = "d",
|
||||
minRankIndexForAddClan = 1, incomingRequestEnabled = true,
|
||||
creatorId = 9L, createTime = 0L, users = emptySet<Any?>(),
|
||||
gameObject = null, maxCharactersDescription = 100, blocked = false,
|
||||
reasonForBlocking = "", self = false, maxMembers = 10,
|
||||
newUsers = emptySet<Any?>(),
|
||||
)
|
||||
|
||||
val S0 = ClanStorageStateSlice()
|
||||
Check("切片 ClansStorage:默认值完整", S0.Clans.isEmpty() && S0.ForeignClan == null)
|
||||
val S1 = ReduceClanStorage(S0, LoadClanIntoStorage(Clan()))
|
||||
Check(
|
||||
"切片 ClansStorage:ClanLoaded 按 id 入表",
|
||||
(S1.Clans[1L] as ClanInfo).name == "n"
|
||||
)
|
||||
Check(
|
||||
"切片 ClansStorage:MinimalRankChanged / RecruitmentChanged 就地 copy",
|
||||
(ReduceClanStorage(S1, MinimalRankChanged(1L, 5)).Clans[1L] as ClanInfo)
|
||||
.minRankIndexForAddClan == 5 &&
|
||||
!(ReduceClanStorage(S1, RecruitmentChanged(1L, false)).Clans[1L] as ClanInfo)
|
||||
.incomingRequestEnabled
|
||||
)
|
||||
Check(
|
||||
"切片 ClansStorage:成员与待审用户的增删",
|
||||
(ReduceClanStorage(
|
||||
ReduceClanStorage(S1, AddClanUser(1L, 7L)),
|
||||
AddUsersToNewAccepted(1, listOf(8L)),
|
||||
).Clans[1L] as ClanInfo).let {
|
||||
it.users == setOf<Any?>(7L) && it.newUsers == setOf<Any?>(8L)
|
||||
} &&
|
||||
(ReduceClanStorage(
|
||||
ReduceClanStorage(S1, AddClanUser(1L, 7L)),
|
||||
RemoveClanUser(1L, 7L),
|
||||
).Clans[1L] as ClanInfo).users == emptySet<Any?>()
|
||||
)
|
||||
Check(
|
||||
"切片 ClansStorage:UpdateClan 用 null 保留原值",
|
||||
(ReduceClanStorage(S1, UpdateClan(1L, "n2", null, null))
|
||||
.Clans[1L] as ClanInfo).let { it.name == "n2" && it.tag == "t" }
|
||||
)
|
||||
Check(
|
||||
"切片 ClansStorage:BuyAdditionalPlace 让 maxMembers +1",
|
||||
(ReduceClanStorage(S1, BuyAdditionalPlace(1L)).Clans[1L] as ClanInfo)
|
||||
.maxMembers == 11
|
||||
)
|
||||
Check(
|
||||
"切片 ClansStorage:外战队三臂",
|
||||
ReduceClanStorage(S1, ForeignClanLoaded(Clan(2L))).ForeignClan != null &&
|
||||
ReduceClanStorage(S1, ClearForeignClan).ForeignClan == null &&
|
||||
ReduceClanStorage(S1, ShowForeignClan(2L)).ForeignClanId == 2L
|
||||
)
|
||||
Check(
|
||||
"切片 ClansStorage:ClanUnloaded 删表",
|
||||
ReduceClanStorage(S1, ClanUnloaded(1L)).Clans.isEmpty()
|
||||
)
|
||||
Check(
|
||||
"切片 ClansStorage:无关动作不改状态",
|
||||
ReduceClanStorage(S1, AddFrags(1)) == S1
|
||||
)
|
||||
|
||||
val Root0 = RootState()
|
||||
val Root1 = ReduceRoot(Root0, LoadClanIntoStorage(Clan()))
|
||||
Check(
|
||||
"根:ClanLoaded 只动 clansStorage",
|
||||
Root1.ClansStorage.Clans.keys == setOf<Any?>(1L) &&
|
||||
Root1.Challenges == Root0.Challenges
|
||||
)
|
||||
val W = RootWiring.ByField("clansStorage")!!
|
||||
Check(
|
||||
"根:clansStorage 委派到 lO / me.vr1 且 13 个动作",
|
||||
W.SliceClass == "lO" && W.Reducer == "me.vr1" && W.Actions.size == 13
|
||||
)
|
||||
}
|
||||
|
||||
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