feat(state): 迁移 clan(36 字段 / 43 动作)

This commit is contained in:
TermiNexus committed 2026-10-24 22:05:19 +08:00
1 parent ad5686241d
commit 2f38609a98
8 files changed
+448 -28

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` 动作名。
- 已迁移切片 **84/89**:
- 已迁移切片 **85/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)
@@ -425,12 +425,16 @@
`vO`(原名 `ClanLoaded`,与 clan 切片的 `isLoaded` 同名)改名 `LoadClanIntoStorage`;
`dO`/`gO`(原名 `AddUser`/`RemoveUser`,与战斗列表同名)改名 `AddClanUser`/`RemoveClanUser`;
查找时兼容 Int/Long 两种 clanId。
- `clan` → `jN` / `we.hr0`(`ClanReducer.kt`):36 字段,43 个分支。
成员 / 待审用户 / 人员 / 外战队 / 新申请各有增删两臂;校验状态 4 个 setter + 2 个复位;
`ClanInfoLoaded` 写 6 个身份字段(复用 `ClanInfo`);`UpdateHasClan(false)` 整片复位;
通知计数 3 个直接写 + 2 个已读 + 计数 ±1;其余为中间态三元组、封禁、弹窗与改名价格。
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 83 个有 reducer 的切片,
`launchParams` 与其余 **5 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 84/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **924 项全绿**,含 89 槽位完整性、83 个归约切片一次归约互不串片、未迁移字段透传,
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 84 个有 reducer 的切片,
`launchParams` 与其余 **4 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 85/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **939 项全绿**,含 89 槽位完整性、84 个归约切片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
+60 -11
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-24T13:56:08.668Z",
"generatedAt": "2026-10-24T14:00:39.799Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -90,7 +90,8 @@
"battlesSelect",
"battleUsers",
"challenges",
"clansStorage"
"clansStorage",
"clan"
],
"AnonymousActions": [
{
@@ -1514,6 +1515,54 @@
"Name": "RemoveClanUser",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "clan.users -= userId(原名 RemoveUser,同上改名 RemoveClanUser)"
},
{
"Local": "iM",
"Name": "ResetClanUserValidation",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "clanUserValidation 复位(`n.iqx(…, sO())`)"
},
{
"Local": "FM",
"Name": "ResetClanNameTagValidation",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "clanNameNameValidation / clanTagNameValidation 复位(`oO()`)"
},
{
"Local": "IM",
"Name": "UpdateIntermediateMinRank",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "intermediateMinRankIndex(`n.iqx(cd, cd, cd, cd, cd, cd, cd, t.lr0_1)`)"
},
{
"Local": "RM",
"Name": "UpdateIntermediateRequests",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "intermediateRequestsEnabled(`n.iqx(cd, cd, cd, cd, cd, cd, t.kr0_1)`)"
},
{
"Local": "PM",
"Name": "UpdateIntermediateDescription",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "intermediateDescription(`n.iqx(cd, cd, cd, cd, cd, cd, cd, cd, t.jr0_1)`)"
},
{
"Local": "VM",
"Name": "SetClanNotificationsCount",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "notificationsCount(`t.ir0_1`)"
},
{
"Local": "KM",
"Name": "IncrementClanNotifications",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "notificationsCount + 1(`(n.bqx_1 + 1) | 0`)"
},
{
"Local": "YM",
"Name": "DecrementClanNotifications",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "notificationsCount - 1,下限 0"
}
],
"Markers": [
@@ -2008,14 +2057,6 @@
"Reducer": "we.hr0",
"Delegate": "we.hr0(t, n.qpv_1)",
"Actions": [
"?FM",
"?IM",
"?KM",
"?PM",
"?RM",
"?VM",
"?YM",
"?iM",
"AcceptedUserViewed",
"AddIncomingClan",
"AddNewIncoming",
@@ -2027,9 +2068,11 @@
"ClanInfoLoaded",
"ClanLoaded",
"ClanModalVisible",
"DecrementClanNotifications",
"IncomingAdded",
"IncomingRemoved",
"IncomingUserViewed",
"IncrementClanNotifications",
"MembersFetched",
"NewAcceptedUserNotification",
"NewClanInviteNotification",
@@ -2042,18 +2085,24 @@
"RemoveOutgoingClan",
"RemoveUserFromAccepted",
"RemoveUsersFromNewAccepted",
"ResetClanNameTagValidation",
"ResetClanUserValidation",
"SetClanEditPrice",
"SetClanNotificationsCount",
"SetMinRankForCreate",
"UpdateClanLicense",
"UpdateClanNameValidation",
"UpdateClanSearchValidation",
"UpdateClanTagValidation",
"UpdateHasClan",
"UpdateIntermediateDescription",
"UpdateIntermediateMinRank",
"UpdateIntermediateRequests",
"UpdateTimeToJoinRestriction",
"UpdateValidation"
],
"Default": "new jN()",
"Migrated": false
"Migrated": true
},
{
"Field": "clansStorage",
+18
View File
@@ -345,6 +345,15 @@ const AnonymousActionNames = new Map([
['gO', 'RemoveClanUser'],
// vO 的 toString 名 ClanLoaded 与 clan 切片的 isLoaded 动作同名冲突,改名避免字段错配。
['vO', 'LoadClanIntoStorage'],
// clan 切片(jN / we.hr0):8 个匿名类。
['iM', 'ResetClanUserValidation'],
['FM', 'ResetClanNameTagValidation'],
['IM', 'UpdateIntermediateMinRank'],
['RM', 'UpdateIntermediateRequests'],
['PM', 'UpdateIntermediateDescription'],
['VM', 'SetClanNotificationsCount'],
['KM', 'IncrementClanNotifications'],
['YM', 'DecrementClanNotifications'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
@@ -833,6 +842,7 @@ const Migrated = {
battleUsers: { Type: 'BattleUsersStateSlice', Reduce: 'ReduceBattleUsers' },
challenges: { Type: 'ChallengesStateSlice', Reduce: 'ReduceChallenges' },
clansStorage: { Type: 'ClanStorageStateSlice', Reduce: 'ReduceClanStorage' },
clan: { Type: 'ClanStateSlice', Reduce: 'ReduceClan' },
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -1261,6 +1271,14 @@ const AppSettingsAnonymousEffects = {
vO: 'clans[clan.id]=clan(原名 ClanLoaded,与 clan 切片同名类冲突,改名 LoadClanIntoStorage)',
dO: 'clan.users += userId(原名 AddUser,与战斗列表同名类冲突,改名 AddClanUser)',
gO: 'clan.users -= userId(原名 RemoveUser,同上改名 RemoveClanUser)',
iM: 'clanUserValidation 复位(`n.iqx(…, sO())`)',
FM: 'clanNameNameValidation / clanTagNameValidation 复位(`oO()`)',
IM: 'intermediateMinRankIndex(`n.iqx(cd, cd, cd, cd, cd, cd, cd, t.lr0_1)`)',
RM: 'intermediateRequestsEnabled(`n.iqx(cd, cd, cd, cd, cd, cd, t.kr0_1)`)',
PM: 'intermediateDescription(`n.iqx(cd, cd, cd, cd, cd, cd, cd, cd, t.jr0_1)`)',
VM: 'notificationsCount(`t.ir0_1`)',
KM: 'notificationsCount + 1(`(n.bqx_1 + 1) | 0`)',
YM: 'notificationsCount - 1,下限 0',
};
fs.writeFileSync(
path.join(P, 'names', 'root_wiring.json'),
@@ -0,0 +1,199 @@
// ClanReducer.kt —— 战队切片(对应 module_10032 的 jN 状态 + we.hr0)
//
// 证据链:
// 状态类 jN:36 个字段,toString 名 "Clan(…)";copy 是 iqx(36 字段 + 回调)。
// 根委派:`we.hr0(t, n.qpv_1)`。
// reducer(43 个实例臂)——绝大多数是 `iqx` 的单个位置参数:
// 成员/待审:DN/LN(accepted)BN/FN(acceptedNew)
// 校验:iM 复位、rM/uM/sM/oM 直接写、FM 复位 name/tag
// 载入:HN(ClanInfoLoaded 写 id/creatorId/name/createTime/description/tag)、fM(isLoaded)
// 人员:WN/VN/KN/YN(incoming/outgoing)
// 外战队:vM/pM/wM/dM/gM/mM
// 中间态:IM/RM/PM(minRank / requests / description)
// 通知计数:VM/KM/YM/UN/WM/XM/JM/ZM
// 其他:GN 最小创建等级、ON 成员统计合并、CM 入队限制时间、
// lM hasClan(false 时整片复位)、NM 封禁、GM/HM 新申请、QM 弹窗、tO 改名价格
//
// `ClanInfoLoaded.info` 复用生成物 `ClanInfo`;`ON` 的 `jm` 是 putAll(合并)。
package App.State.Reducers
import App.State.AddIncomingClan
import App.State.AddNewIncoming
import App.State.AddNewIncomingClan
import App.State.AddOutgoingClan
import App.State.AddUsersToAccepted
import App.State.AddUsersToNewAccepted
import App.State.AcceptedUserViewed
import App.State.ClanBanned
import App.State.ClanInfo
import App.State.ClanInfoLoaded
import App.State.ClanLoaded
import App.State.ClanModalVisible
import App.State.IncomingAdded
import App.State.IncomingRemoved
import App.State.IncomingUserViewed
import App.State.MembersFetched
import App.State.NewAcceptedUserNotification
import App.State.NewClanInviteNotification
import App.State.NewIncomingUserNotification
import App.State.OutgoingAdded
import App.State.OutgoingRemoved
import App.State.RemoveIncomingClan
import App.State.RemoveNewIncoming
import App.State.RemoveNewIncomingClan
import App.State.RemoveOutgoingClan
import App.State.RemoveUserFromAccepted
import App.State.RemoveUsersFromNewAccepted
import App.State.SetClanEditPrice
import App.State.SetMinRankForCreate
import App.State.UpdateClanLicense
import App.State.UpdateClanNameValidation
import App.State.UpdateClanSearchValidation
import App.State.UpdateClanTagValidation
import App.State.UpdateHasClan
import App.State.UpdateTimeToJoinRestriction
import App.State.UpdateValidation
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `iM`:复位用户名校验状态。 */
object ResetClanUserValidation
/** 原 `FM`:复位战队名 / 标签校验状态。 */
object ResetClanNameTagValidation
/** 原 `IM`:写中间态最小等级。 */
data class UpdateIntermediateMinRank(val rankIndex: Any?)
/** 原 `RM`:写「可自由加入」开关。 */
data class UpdateIntermediateRequests(val enabled: Any?)
/** 原 `PM`:写中间态说明。 */
data class UpdateIntermediateDescription(val description: Any?)
/** 原 `VM`:直接写通知总数。 */
data class SetClanNotificationsCount(val count: Int)
/** 原 `KM` / `YM`:通知总数 +1 / -1。 */
object IncrementClanNotifications
object DecrementClanNotifications
/* ------------------------------------------------------------------ 状态 */
data class ClanStateSlice(
val CurrentClanId: Any? = null,
val CreatorId: Any? = null,
val Name: Any? = null,
val CreateTime: Any? = null,
val Description: Any? = null,
val Tag: Any? = null,
val IntermediateRequestsEnabled: Any? = null,
val IntermediateMinRankIndex: Any? = null,
val IntermediateDescription: Any? = null,
val RulesConfirmed: Boolean = false,
val HasLicense: Boolean = false,
val HasClan: Boolean = false,
val IsLoaded: Any? = null,
val IncomingUsers: Set<Any?> = emptySet(),
val OutgoingUsers: Set<Any?> = emptySet(),
val ClanUserValidation: Any? = null,
val ClanNameNameValidation: Any? = null,
val ClanTagNameValidation: Any? = null,
val ClanSearchValidation: Any? = null,
val ClanAcceptedMembers: Set<Any?> = emptySet(),
val ClanAcceptedMembersNew: Set<Any?> = emptySet(),
val MinRankForCreateClan: Int = 0,
val ClanMembersStatistics: Map<Any?, Any?> = emptyMap(),
val OutgoingClans: Set<Any?> = emptySet(),
val IncomingClans: Set<Any?> = emptySet(),
val RestrictionToJoinInClan: Any? = null,
val Banned: Boolean = false,
val BanReason: Any? = null,
val NewIncomingClans: Set<Any?> = emptySet(),
val NewIncoming: Set<Any?> = emptySet(),
val NotificationsCount: Int = 0,
val IncomingUserNotificationCount: Int = 0,
val AcceptedUserNotificationCount: Int = 0,
val ClanInviteNotificationCount: Int = 0,
val ClanModalVisible: Boolean = false,
val ClanEditPrice: Any? = null,
)
/* ------------------------------------------------------------------ reducer */
private fun Plus(Target: Set<Any?>, Value: Any?): Set<Any?> =
if (Value is Iterable<*>) Target + Value.toSet() else Target + Value
private fun Minus(Target: Set<Any?>, Value: Any?): Set<Any?> =
if (Value is Iterable<*>) Target - Value.toSet() else Target - Value
fun ReduceClan(
State0: ClanStateSlice,
Action: Any,
): ClanStateSlice = when (Action) {
is AddUsersToAccepted -> State0.copy(ClanAcceptedMembers = Plus(State0.ClanAcceptedMembers, Action.usersId))
is RemoveUserFromAccepted -> State0.copy(ClanAcceptedMembers = Minus(State0.ClanAcceptedMembers, Action.userId))
is AddUsersToNewAccepted -> State0.copy(ClanAcceptedMembersNew = Plus(State0.ClanAcceptedMembersNew, Action.userIds))
is RemoveUsersFromNewAccepted -> State0.copy(ClanAcceptedMembersNew = Minus(State0.ClanAcceptedMembersNew, Action.userId))
is SetMinRankForCreate -> State0.copy(MinRankForCreateClan = (Action.rankIndex as? Number)?.toInt() ?: State0.MinRankForCreateClan)
is MembersFetched -> State0.copy(
ClanMembersStatistics =
State0.ClanMembersStatistics + ((Action.users as? Map<Any?, Any?>) ?: emptyMap()),
)
is ClanInfoLoaded -> {
val Info = Action.info as? ClanInfo
if (Info == null) State0
else State0.copy(
CurrentClanId = Info.id,
CreatorId = Info.creatorId,
Name = Info.name,
CreateTime = Info.createTime,
Description = Info.description,
Tag = Info.tag,
)
}
is IncomingAdded -> State0.copy(IncomingUsers = Plus(State0.IncomingUsers, Action.incomingUsers))
is IncomingRemoved -> State0.copy(IncomingUsers = Minus(State0.IncomingUsers, Action.incomingUsers))
is OutgoingAdded -> State0.copy(OutgoingUsers = Plus(State0.OutgoingUsers, Action.outgoingUsers))
is OutgoingRemoved -> State0.copy(OutgoingUsers = Minus(State0.OutgoingUsers, Action.outgoingUser))
is ResetClanUserValidation -> State0.copy(ClanUserValidation = null)
is UpdateValidation -> State0.copy(ClanUserValidation = Action.state)
is UpdateClanNameValidation -> State0.copy(ClanNameNameValidation = Action.state)
is UpdateClanTagValidation -> State0.copy(ClanTagNameValidation = Action.state)
is UpdateClanSearchValidation -> State0.copy(ClanSearchValidation = Action.state)
is UpdateClanLicense -> State0.copy(HasLicense = Action.hasLicense)
is UpdateHasClan -> if (Action.hasClan) State0.copy(HasClan = true) else ClanStateSlice()
is ClanLoaded -> State0.copy(IsLoaded = Action.isLoaded)
is AddOutgoingClan -> State0.copy(OutgoingClans = Plus(State0.OutgoingClans, Action.clanId))
is RemoveOutgoingClan -> State0.copy(OutgoingClans = Minus(State0.OutgoingClans, Action.clanId))
is AddIncomingClan -> State0.copy(IncomingClans = Plus(State0.IncomingClans, Action.clanIds))
is RemoveIncomingClan -> State0.copy(IncomingClans = Minus(State0.IncomingClans, Action.clanId))
is AddNewIncomingClan -> State0.copy(NewIncomingClans = Plus(State0.NewIncomingClans, Action.clanIds))
is RemoveNewIncomingClan -> State0.copy(NewIncomingClans = Minus(State0.NewIncomingClans, Action.clanId))
is UpdateTimeToJoinRestriction ->
State0.copy(RestrictionToJoinInClan = Action.restrictionTimeJoinClanInSec)
is UpdateIntermediateMinRank -> State0.copy(IntermediateMinRankIndex = Action.rankIndex)
is UpdateIntermediateRequests -> State0.copy(IntermediateRequestsEnabled = Action.enabled)
is UpdateIntermediateDescription -> State0.copy(IntermediateDescription = Action.description)
is ClanBanned -> State0.copy(Banned = true, BanReason = Action.reason)
is ResetClanNameTagValidation -> State0.copy(
ClanNameNameValidation = null,
ClanTagNameValidation = null,
)
is AddNewIncoming -> State0.copy(NewIncoming = Plus(State0.NewIncoming, Action.newIncoming))
is RemoveNewIncoming -> State0.copy(NewIncoming = Minus(State0.NewIncoming, Action.newIncoming))
is SetClanNotificationsCount -> State0.copy(NotificationsCount = Action.count)
is IncrementClanNotifications -> State0.copy(NotificationsCount = State0.NotificationsCount + 1)
is DecrementClanNotifications ->
State0.copy(NotificationsCount = (State0.NotificationsCount - 1).coerceAtLeast(0))
is AcceptedUserViewed ->
State0.copy(AcceptedUserNotificationCount = State0.AcceptedUserNotificationCount - 1)
is IncomingUserViewed ->
State0.copy(IncomingUserNotificationCount = State0.IncomingUserNotificationCount - 1)
is NewIncomingUserNotification -> State0.copy(IncomingUserNotificationCount = Action.count)
is NewAcceptedUserNotification -> State0.copy(AcceptedUserNotificationCount = Action.count)
is NewClanInviteNotification -> State0.copy(ClanInviteNotificationCount = Action.count)
is ClanModalVisible -> State0.copy(ClanModalVisible = Action.visible)
is SetClanEditPrice -> State0.copy(ClanEditPrice = Action.clanEditPrice)
else -> State0
}
@@ -1,7 +1,7 @@
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
//
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
// 这里采用同一形状。已迁移的 84 个切片里,83 个真正调用 reducer,launchParams 只做类型收口:
// 这里采用同一形状。已迁移的 85 个切片里,84 个真正调用 reducer,launchParams 只做类型收口:
//
// inviteState → ReduceInvite(aD / pD.er5)
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
@@ -86,9 +86,10 @@
// 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)`)
// clan → ReduceClan(jN / we.hr0,根委派 `we.hr0(t, n.qpv_1)`)
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
//
// 其余 5 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 4 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
@@ -110,6 +111,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.ReduceClan
import App.State.Reducers.ReduceClanStorage
import App.State.Reducers.ReduceClanUserInfo
import App.State.Reducers.ReduceClanChat
@@ -273,6 +275,7 @@ object RootReducer {
BattleUsers = ReduceBattleUsers(State0.BattleUsers, Action),
Challenges = ReduceChallenges(State0.Challenges, Action),
ClansStorage = ReduceClanStorage(State0.ClansStorage, Action),
Clan = ReduceClan(State0.Clan, Action),
)
}
@@ -22,6 +22,7 @@ import App.State.Reducers.BattleViewStateSlice
import App.State.Reducers.BattlesSelectStateSlice
import App.State.Reducers.CaptchaStateSlice
import App.State.Reducers.ChallengesStateSlice
import App.State.Reducers.ClanStateSlice
import App.State.Reducers.ClanChatStateSlice
import App.State.Reducers.ClanUserInfoStateSlice
import App.State.Reducers.ClanStorageStateSlice
@@ -128,8 +129,8 @@ data class RootState(
val ChatState: Any? = null,
/** 原 clanChatState(qnt / yv.qtg)。已迁移。 */
val ClanChatState: ClanChatStateSlice = ClanChatStateSlice(),
/** 原 clan(jN / we.hr0)。未迁移:原样透传。 */
val Clan: Any? = null,
/** 原 clan(jN / we.hr0)。已迁移。 */
val Clan: ClanStateSlice = ClanStateSlice(),
/** 原 clansStorage(lO / me.vr1)。已迁移。 */
val ClansStorage: ClanStorageStateSlice = ClanStorageStateSlice(),
/** 原 tipsState(dst / Hw.vth)。已迁移。 */
@@ -32,7 +32,7 @@ object RootWiring {
SliceWiring("quests", "npv_1", "Frt", "Yrt.otg", "(null == zp && new Yrt(), zp).otg(t, n.npv_1)", listOf("GoTo", "SetCurrentCategory", "SetNotCompletedQuests")),
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("clan", "qpv_1", "jN", "we.hr0", "we.hr0(t, n.qpv_1)", listOf("AcceptedUserViewed", "AddIncomingClan", "AddNewIncoming", "AddNewIncomingClan", "AddOutgoingClan", "AddUsersToAccepted", "AddUsersToNewAccepted", "ClanBanned", "ClanInfoLoaded", "ClanLoaded", "ClanModalVisible", "DecrementClanNotifications", "IncomingAdded", "IncomingRemoved", "IncomingUserViewed", "IncrementClanNotifications", "MembersFetched", "NewAcceptedUserNotification", "NewClanInviteNotification", "NewIncomingUserNotification", "OutgoingAdded", "OutgoingRemoved", "RemoveIncomingClan", "RemoveNewIncoming", "RemoveNewIncomingClan", "RemoveOutgoingClan", "RemoveUserFromAccepted", "RemoveUsersFromNewAccepted", "ResetClanNameTagValidation", "ResetClanUserValidation", "SetClanEditPrice", "SetClanNotificationsCount", "SetMinRankForCreate", "UpdateClanLicense", "UpdateClanNameValidation", "UpdateClanSearchValidation", "UpdateClanTagValidation", "UpdateHasClan", "UpdateIntermediateDescription", "UpdateIntermediateMinRank", "UpdateIntermediateRequests", "UpdateTimeToJoinRestriction", "UpdateValidation")),
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")),
@@ -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", "clansStorage")
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", "clan")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
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 / ClansStorage;
// 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 / Clan;
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
@@ -826,6 +826,7 @@ import App.State.UpdateStars
import App.State.Reducers.ChallengesStateSlice
import App.State.Reducers.ReduceChallenges
import App.State.Reducers.RefreshBattlePasses
import App.State.ClanEditPriceCC
import App.State.ClanInfo
import App.State.ClanUnloaded
import App.State.BuyAdditionalPlace
@@ -840,6 +841,44 @@ import App.State.Reducers.ClanStorageStateSlice
import App.State.Reducers.ClearForeignClan
import App.State.Reducers.ReduceClanStorage
import App.State.Reducers.RemoveClanUser
import App.State.AddIncomingClan
import App.State.AddNewIncoming
import App.State.AddNewIncomingClan
import App.State.AddOutgoingClan
import App.State.AcceptedUserViewed
import App.State.ClanBanned
import App.State.ClanModalVisible
import App.State.IncomingAdded
import App.State.IncomingRemoved
import App.State.IncomingUserViewed
import App.State.MembersFetched
import App.State.NewAcceptedUserNotification
import App.State.NewClanInviteNotification
import App.State.NewIncomingUserNotification
import App.State.OutgoingAdded
import App.State.OutgoingRemoved
import App.State.ClanInfoLoaded
import App.State.RemoveIncomingClan
import App.State.RemoveNewIncoming
import App.State.RemoveNewIncomingClan
import App.State.RemoveOutgoingClan
import App.State.SetClanEditPrice
import App.State.SetMinRankForCreate
import App.State.UpdateClanLicense
import App.State.UpdateClanNameValidation
import App.State.UpdateHasClan
import App.State.UpdateTimeToJoinRestriction
import App.State.UpdateValidation
import App.State.Reducers.ClanStateSlice
import App.State.Reducers.DecrementClanNotifications
import App.State.Reducers.IncrementClanNotifications
import App.State.Reducers.ReduceClan
import App.State.Reducers.ResetClanNameTagValidation
import App.State.Reducers.ResetClanUserValidation
import App.State.Reducers.SetClanNotificationsCount
import App.State.Reducers.UpdateIntermediateDescription
import App.State.Reducers.UpdateIntermediateMinRank
import App.State.Reducers.UpdateIntermediateRequests
import App.State.Reducers.UserRankBounds
import App.State.Reducers.UserStateSlice
import App.State.Reducers.ClearHullData
@@ -1267,7 +1306,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 84 个已迁移切片(83 归约 + launchParams 透传)
// ⑭ 根 reducer:89 个槽位的委派表 + 85 个已迁移切片(84 归约 + launchParams 透传)
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -1331,7 +1370,7 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 84 个已迁移字段,其余 5 个待迁",
"根:迁移表 = 85 个已迁移字段,其余 4 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
@@ -1355,9 +1394,9 @@ object StateCheck {
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
"tankBuilderState", "battleMessage", "maps", "battleChat",
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges", "clansStorage",
"remoteUserDataStorage", "matchmaking", "battlesSelect", "battleUsers", "challenges", "clansStorage", "clan",
) &&
RootWiring.Unmigrated.size == 5
RootWiring.Unmigrated.size == 4
)
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
@@ -1413,7 +1452,7 @@ object StateCheck {
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 84/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 85/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -7082,6 +7121,113 @@ object StateCheck {
)
}
// 95 切片 reducer:Clan(jN / we.hr0,36 字段;43 个动作)
run {
val S0 = ClanStateSlice()
Check(
"切片 Clan:默认值完整",
S0.CurrentClanId == null && !S0.HasClan && !S0.HasLicense &&
S0.ClanAcceptedMembers.isEmpty()
)
val S1 = ReduceClan(S0, SetMinRankForCreate(5))
Check("切片 Clan:SetMinRankForCreate 写最小等级", S1.MinRankForCreateClan == 5)
val S2 = ReduceClan(S1, AddUsersToAccepted(listOf(1L, 2L)))
Check(
"切片 Clan:AddUsersToAccepted 批量入集合",
S2.ClanAcceptedMembers == setOf<Any?>(1L, 2L)
)
Check(
"切片 Clan:RemoveUserFromAccepted 删单值",
ReduceClan(S2, RemoveUserFromAccepted(1L))
.ClanAcceptedMembers == setOf<Any?>(2L)
)
val Info = ClanInfo(
id = 7L, 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 S3 = ReduceClan(S2, ClanInfoLoaded(Info))
Check(
"切片 Clan:ClanInfoLoaded 写 6 个身份字段",
S3.CurrentClanId == 7L && S3.CreatorId == 9L && S3.Name == "n" &&
S3.Tag == "t" && S3.Description == "d"
)
Check(
"切片 Clan:人员 / 战队列表四臂",
ReduceClan(S3, IncomingAdded(listOf(3L))).IncomingUsers == setOf<Any?>(3L) &&
ReduceClan(S3, OutgoingAdded(listOf(4L))).OutgoingUsers == setOf<Any?>(4L) &&
ReduceClan(S3, AddOutgoingClan(11L)).OutgoingClans == setOf<Any?>(11L) &&
ReduceClan(S3, AddNewIncomingClan(listOf(12L)))
.NewIncomingClans == setOf<Any?>(12L)
)
Check(
"切片 Clan:中间态三臂",
ReduceClan(S3, UpdateIntermediateMinRank(2)).IntermediateMinRankIndex == 2 &&
ReduceClan(S3, UpdateIntermediateRequests(true))
.IntermediateRequestsEnabled == true &&
ReduceClan(S3, UpdateIntermediateDescription("x"))
.IntermediateDescription == "x"
)
Check(
"切片 Clan:校验状态写 / 复位",
ReduceClan(S3, UpdateValidation("bad")).ClanUserValidation == "bad" &&
ReduceClan(ReduceClan(S3, UpdateValidation("bad")), ResetClanUserValidation)
.ClanUserValidation == null &&
ReduceClan(ReduceClan(S3, UpdateClanNameValidation("bad")),
ResetClanNameTagValidation).ClanNameNameValidation == null
)
Check(
"切片 Clan:HasClan=false 整片复位、true 只置位",
ReduceClan(
ReduceClan(S3, UpdateClanLicense(true)),
UpdateHasClan(false),
) == ClanStateSlice() &&
ReduceClan(S3, UpdateHasClan(true)).HasClan
)
Check(
"切片 Clan:封禁 / 弹窗 / 改名价格 / 入队限制",
ReduceClan(S3, ClanBanned("why")).let { it.Banned && it.BanReason == "why" } &&
ReduceClan(S3, ClanModalVisible(true)).ClanModalVisible &&
ReduceClan(S3, SetClanEditPrice(ClanEditPriceCC(1, 2, null, 3, 4, 5)))
.ClanEditPrice == ClanEditPriceCC(1, 2, null, 3, 4, 5) &&
ReduceClan(S3, UpdateTimeToJoinRestriction(60))
.RestrictionToJoinInClan == 60
)
Check(
"切片 Clan:通知计数 +1 / -1 / 直接写",
ReduceClan(S3, IncrementClanNotifications).NotificationsCount == 1 &&
ReduceClan(ReduceClan(S3, IncrementClanNotifications),
DecrementClanNotifications).NotificationsCount == 0 &&
ReduceClan(S3, SetClanNotificationsCount(4)).NotificationsCount == 4
)
Check(
"切片 Clan:三个通知徽标与两个已读动作",
ReduceClan(S3, NewIncomingUserNotification(2))
.IncomingUserNotificationCount == 2 &&
ReduceClan(ReduceClan(S3, NewAcceptedUserNotification(3)),
AcceptedUserViewed(1L)).AcceptedUserNotificationCount == 2 &&
ReduceClan(ReduceClan(S3, NewIncomingUserNotification(2)),
IncomingUserViewed(1L)).IncomingUserNotificationCount == 1 &&
ReduceClan(S3, NewClanInviteNotification(1)).ClanInviteNotificationCount == 1
)
Check("切片 Clan:无关动作不改状态", ReduceClan(S3, AddFrags(1)) == S3)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, SetMinRankForCreate(3))
Check(
"根:SetMinRankForCreate 只动 clan",
Root1.Clan.MinRankForCreateClan == 3 && Root1.ClansStorage == Root0.ClansStorage
)
val W = RootWiring.ByField("clan")!!
Check(
"根:clan 委派到 jN / we.hr0 且 43 个动作",
W.SliceClass == "jN" && W.Reducer == "we.hr0" && W.Actions.size == 43
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)