feat(state): 迁移 clanChatState/battleSearchState

This commit is contained in:
TermiNexus committed 2026-10-24 16:45:20 +08:00
1 parent 4c11c3e530
commit 437c71ccdb
9 files changed
+360 -28

No files matched your search

+12 -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` 动作名。
- 已迁移切片 **63/89**:
- 已迁移切片 **65/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)
@@ -341,12 +341,18 @@
`OnUserJoinTheBattle` / `OnUserLeaveTheBattle` 增删 `noticed`;`dI`→`ResetTankActionsLog` 整片复位。
- `suppliesTutorial` → `ust` / `Uw.uth`(`SuppliesTutorialReducer.kt`):2 字段,2 个分支。
生成器认不出切片类(根构造默认值不是 `new ust()`),在迁移表里显式补 `SliceClass: 'ust'`。
- `clanChatState` → `qnt` / `yv.qtg`(`ClanChatStateReducer.kt`):3 字段,5 个分支。
`$nt`/`xnt` 开关面板,`Configure` 写配置(`ynt` 的 `config` 与 clan 切片同名动作冲突,
整条动作对象按配置载荷收口),`DoAddMessages` 追加后只保留最后 100 条,`Tnt` 整片复位。
- `battleSearchState` → `Itt` / `Htt().vtg`(`BattleSearchStateReducer.kt`):5 字段,9 个分支。
`Fight` 只发副作用、状态不变;`RemoveBattle` 仅当选中 id 匹配时清空;
`selectedFormat` 默认值来自服务定位器,按 `null` 收口。
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 62 个有 reducer 的切片,
`launchParams` 与其余 **26 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 63/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **719 项全绿**,含 89 槽位完整性、62 个归约切片一次归约互不串片、未迁移字段透传,
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 64 个有 reducer 的切片,
`launchParams` 与其余 **24 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 65/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **741 项全绿**,含 89 槽位完整性、64 个归约切片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
@@ -366,6 +372,7 @@
friends 的 2 个、suppliesState 的 1 个、battleInviteState 的 2 个、partner 的 4 个、
userRenameState 的 5 个也已登记(其中 `mtt` 因同名类冲突改名为 `AddInvitedUser`);
battlePauseState 的 2 个、battleViewState 的 12 个、tankActionsLogState 的 1 个同样按效果定名。
clanChatState 的 3 个(`$nt`/`xnt`/`Tnt`)、battleSearchState 的 1 个也已登记。
### S4 引擎核心
- 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入)
+35 -9
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-24T08:27:50.811Z",
"generatedAt": "2026-10-24T08:38:53.378Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -69,7 +69,9 @@
"battlePauseState",
"battleViewState",
"tankActionsLogState",
"suppliesTutorial"
"suppliesTutorial",
"clanChatState",
"battleSearchState"
],
"AnonymousActions": [
{
@@ -1182,6 +1184,30 @@
"Name": "ResetTankActionsLog",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new vI() 整片复位"
},
{
"Local": "$nt",
"Name": "OpenClanChat",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "open=true(`n.qtp(!0)`)"
},
{
"Local": "xnt",
"Name": "CloseClanChat",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "open=false(`n.qtp(!1)`)"
},
{
"Local": "Tnt",
"Name": "ResetClanChat",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new qnt() 整片复位"
},
{
"Local": "Dtt",
"Name": "ClearSelectedBattle",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "selectedBattleId=null(`n.vtn(cd, cd, cd, cd, null)`)"
}
],
"Markers": [
@@ -1660,14 +1686,14 @@
"Reducer": "yv.qtg",
"Delegate": "yv.qtg(t, n.ppv_1)",
"Actions": [
"?$nt",
"?Tnt",
"?xnt",
"CloseClanChat",
"Configure",
"DoAddMessages"
"DoAddMessages",
"OpenClanChat",
"ResetClanChat"
],
"Default": "new qnt()",
"Migrated": false
"Migrated": true
},
{
"Field": "clan",
@@ -1997,7 +2023,7 @@
"Reducer": "Htt().vtg",
"Delegate": "Htt().vtg(t, n.epw_1)",
"Actions": [
"?Dtt",
"ClearSelectedBattle",
"EnterToBattle",
"Fight",
"RemoveBattle",
@@ -2008,7 +2034,7 @@
"SetSuspiciousFilter"
],
"Default": "new Itt()",
"Migrated": false
"Migrated": true
},
{
"Field": "lobbyLoader",
+32
View File
@@ -275,6 +275,12 @@ const AnonymousActionNames = new Map([
['eI', 'LeaveBackground'],
// tankActionsLogState 切片(vI / yI.gqe):dI 没有 toString。
['dI', 'ResetTankActionsLog'],
// clanChatState 切片(qnt / Int.qtg):$nt/xnt/Tnt 没有 toString。
['$nt', 'OpenClanChat'],
['xnt', 'CloseClanChat'],
['Tnt', 'ResetClanChat'],
// battleSearchState 切片(Itt / Gtt.vtg):Dtt 没有 toString。
['Dtt', 'ClearSelectedBattle'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
@@ -731,6 +737,8 @@ const Migrated = {
SliceClass: 'ust',
Reduce: 'ReduceSuppliesTutorial',
},
clanChatState: { Type: 'ClanChatStateSlice', Reduce: 'ReduceClanChat' },
battleSearchState: { Type: 'BattleSearchStateSlice', Reduce: 'ReduceBattleSearch' },
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -1093,6 +1101,18 @@ const TankActionsLogAnonymousEffects = {
dI: 'new vI() 整片复位',
};
// clanChatState 切片(qnt / Int.qtg):$nt/xnt/Tnt 没有 toString。
const ClanChatAnonymousEffects = {
$nt: 'open=true(`n.qtp(!0)`)',
xnt: 'open=false(`n.qtp(!1)`)',
Tnt: 'new qnt() 整片复位',
};
// battleSearchState 切片(Itt / Gtt.vtg):Dtt 没有 toString。
const BattleSearchAnonymousEffects = {
Dtt: 'selectedBattleId=null(`n.vtn(cd, cd, cd, cd, null)`)',
};
fs.writeFileSync(
path.join(P, 'names', 'root_wiring.json'),
JSON.stringify(
@@ -1337,6 +1357,18 @@ fs.writeFileSync(
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
...Object.entries(ClanChatAnonymousEffects).map(([Local, Effect]) => ({
Local,
Name: AnonymousActionNames.get(Local),
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
...Object.entries(BattleSearchAnonymousEffects).map(([Local, Effect]) => ({
Local,
Name: AnonymousActionNames.get(Local),
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
],
Markers: [
{
@@ -0,0 +1,67 @@
// BattleSearchStateReducer.kt —— 战斗搜索切片(对应 module_10032 的 Itt 状态 + Gtt.vtg)
//
// 证据链:
// 状态类 Itt:5 个字段,toString 名 "BattleSearchState(…)"。
// 默认:filter=null、selectedFormat=cA().xqh()、search=null、suspiciousBattlesOnly=false、
// selectedBattleId=null。
// 根委派:`Htt().vtg(t, n.epw_1)`。
// reducer(9 个实例臂):
// Ntt(SetFilter) → filter
// Mtt(SetSearch) → search
// Ptt(SetSuspiciousFilter)→ suspiciousBattlesOnly
// Vtt(Fight) → 状态不变(原分支只发 `Fy().ven()` 副作用)
// FH(EnterToBattle) → search=null
// Ott(SelectBattle) → selectedBattleId
// Ltt(SelectBattleFormat)→ selectedFormat
// kA(RemoveBattle) → 选中的正是该战斗时清空 selectedBattleId
// Dtt(无 toString) → selectedBattleId=null
//
// `selectedFormat` 的默认值 `cA().xqh()` 来自服务定位器,离线重建按 `null` 收口。
package App.State.Reducers
import App.State.BattleFormat
import App.State.EnterToBattle
import App.State.Fight
import App.State.RemoveBattle
import App.State.SelectBattle
import App.State.SelectBattleFormat
import App.State.SetFilter
import App.State.SetSearch
import App.State.SetSuspiciousFilter
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `Dtt`:清空当前选中的战斗。 */
object ClearSelectedBattle
/* ------------------------------------------------------------------ 状态 */
data class BattleSearchStateSlice(
val Filter: Boolean? = null,
val SelectedFormat: BattleFormat? = null,
val Search: Any? = null,
val SuspiciousBattlesOnly: Boolean = false,
val SelectedBattleId: Any? = null,
)
/* ------------------------------------------------------------------ reducer */
fun ReduceBattleSearch(
State0: BattleSearchStateSlice,
Action: Any,
): BattleSearchStateSlice = when (Action) {
is SetFilter -> State0.copy(Filter = Action.filter)
is SetSearch -> State0.copy(Search = Action.search)
is SetSuspiciousFilter ->
State0.copy(SuspiciousBattlesOnly = Action.suspiciousBattlesOnly)
// 原分支只发 `Fy().ven()`(特效/音效副作用),不改状态。
is Fight -> State0
is EnterToBattle -> State0.copy(Search = null)
is SelectBattle -> State0.copy(SelectedBattleId = Action.battleId)
is SelectBattleFormat -> State0.copy(SelectedFormat = Action.format)
is RemoveBattle ->
if (State0.SelectedBattleId == Action.battleId) State0.copy(SelectedBattleId = null)
else State0
is ClearSelectedBattle -> State0.copy(SelectedBattleId = null)
else -> State0
}
@@ -0,0 +1,58 @@
// ClanChatStateReducer.kt —— 战队聊天切片(对应 module_10032 的 qnt 状态 + Int.qtg)
//
// 证据链:
// 状态类 qnt:3 个字段,toString 名 "ClanChatState(…)"。
// 默认:open=false、messages=emptyList()、config=null。
// 根委派:`yv.qtg(t, n.ppv_1)`。
// reducer(5 个实例臂):
// $nt(无 toString) → open=true
// xnt(无 toString) → open=false
// ynt(Configure) → config
// Cnt(DoAddMessages) → messages 追加后只保留最后 100 条
// Tnt(无 toString) → `new qnt()` 整片复位
package App.State.Reducers
import App.State.Configure
import App.State.DoAddMessages
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `$nt`:展开战队聊天。 */
object OpenClanChat
/** 原 `xnt`:收起战队聊天。 */
object CloseClanChat
/** 原 `Tnt`:整片复位(`new qnt()`)。 */
object ResetClanChat
/* ------------------------------------------------------------------ 状态 */
data class ClanChatStateSlice(
val Open: Boolean = false,
val Messages: List<Any?> = emptyList(),
val Config: Any? = null,
)
/* ------------------------------------------------------------------ reducer */
/** 原 `e.q1(max(0, size - 100), size)`:只保留最后 100 条。 */
private const val MaxMessages = 100
private fun AppendMessages(Current: List<Any?>, Incoming: Any?): List<Any?> {
val Added = if (Incoming is Iterable<*>) Current + Incoming else Current + Incoming
return if (Added.size <= MaxMessages) Added
else Added.subList(Added.size - MaxMessages, Added.size)
}
fun ReduceClanChat(
State0: ClanChatStateSlice,
Action: Any,
): ClanChatStateSlice = when (Action) {
is OpenClanChat -> State0.copy(Open = true)
is CloseClanChat -> State0.copy(Open = false)
is Configure -> State0.copy(Config = Action)
is DoAddMessages -> State0.copy(Messages = AppendMessages(State0.Messages, Action.messages))
is ResetClanChat -> ClanChatStateSlice()
else -> State0
}
@@ -1,7 +1,7 @@
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
//
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
// 这里采用同一形状。已迁移的 63 个切片里,62 个真正调用 reducer,launchParams 只做类型收口:
// 这里采用同一形状。已迁移的 65 个切片里,64 个真正调用 reducer,launchParams 只做类型收口:
//
// inviteState → ReduceInvite(aD / pD.er5)
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
@@ -65,9 +65,11 @@
// battleViewState → ReduceBattleView(DT / sI.jqd,根委派 `(null == $n && new sI(), $n).jqd(t, n.mpw_1)`)
// tankActionsLogState → ReduceTankActionsLog(vI / wi.gqe,根委派 `wi.gqe(t, n.ppw_1)`)
// suppliesTutorial → ReduceSuppliesTutorial(ust / Uw.uth,根委派 `Uw.uth(t, n.zpx_1)`)
// clanChatState → ReduceClanChat(qnt / yv.qtg,根委派 `yv.qtg(t, n.ppv_1)`)
// battleSearchState → ReduceBattleSearch(Itt / Htt().vtg,根委派 `Htt().vtg(t, n.epw_1)`)
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
//
// 其余 26 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 24 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
@@ -77,11 +79,13 @@ import App.State.Reducers.ReduceAnnouncement
import App.State.Reducers.ReduceAntiAddiction
import App.State.Reducers.ReduceBattleResult
import App.State.Reducers.ReduceBattlePause
import App.State.Reducers.ReduceBattleSearch
import App.State.Reducers.ReduceBattleTutorial
import App.State.Reducers.ReduceBattleView
import App.State.Reducers.ReduceBattleInvite
import App.State.Reducers.ReduceCaptcha
import App.State.Reducers.ReduceClanUserInfo
import App.State.Reducers.ReduceClanChat
import App.State.Reducers.ReduceContextTutorial
import App.State.Reducers.ReduceCreatorTag
import App.State.Reducers.ReduceDroppableGold
@@ -210,6 +214,8 @@ object RootReducer {
BattleViewState = ReduceBattleView(State0.BattleViewState, Action),
TankActionsLogState = ReduceTankActionsLog(State0.TankActionsLogState, Action),
SuppliesTutorial = ReduceSuppliesTutorial(State0.SuppliesTutorial, Action),
ClanChatState = ReduceClanChat(State0.ClanChatState, Action),
BattleSearchState = ReduceBattleSearch(State0.BattleSearchState, Action),
)
}
@@ -11,9 +11,11 @@ import App.State.Reducers.AvailabilityByRankStateSlice
import App.State.Reducers.BattleInviteStateSlice
import App.State.Reducers.BattlePauseStateSlice
import App.State.Reducers.BattleResultStateSlice
import App.State.Reducers.BattleSearchStateSlice
import App.State.Reducers.BattleTutorialStateSlice
import App.State.Reducers.BattleViewStateSlice
import App.State.Reducers.CaptchaStateSlice
import App.State.Reducers.ClanChatStateSlice
import App.State.Reducers.ClanUserInfoStateSlice
import App.State.Reducers.ContextTutorialStateSlice
import App.State.Reducers.CreatorTagStateSlice
@@ -105,8 +107,8 @@ data class RootState(
val Quests: QuestsStateSlice = QuestsStateSlice(),
/** 原 chatState(LP / $r.wqv)。未迁移:原样透传。 */
val ChatState: Any? = null,
/** 原 clanChatState(qnt / yv.qtg)。未迁移:原样透传。 */
val ClanChatState: Any? = null,
/** 原 clanChatState(qnt / yv.qtg)。已迁移。 */
val ClanChatState: ClanChatStateSlice = ClanChatStateSlice(),
/** 原 clan(jN / we.hr0)。未迁移:原样透传。 */
val Clan: Any? = null,
/** 原 clansStorage(lO / me.vr1)。未迁移:原样透传。 */
@@ -135,8 +137,8 @@ data class RootState(
val Matchmaking: Any? = null,
/** 原 battleCreateState($6 / Df.ttg)。未迁移:原样透传。 */
val BattleCreateState: Any? = null,
/** 原 battleSearchState(Itt / Htt().vtg)。未迁移:原样透传。 */
val BattleSearchState: Any? = null,
/** 原 battleSearchState(Itt / Htt().vtg)。已迁移。 */
val BattleSearchState: BattleSearchStateSlice = BattleSearchStateSlice(),
/** 原 lobbyLoader(KW / Bs.es1)。已迁移。 */
val LobbyLoader: LobbyLoaderStateSlice = LobbyLoaderStateSlice(),
/** 原 battleResult(sR / Fi.aqp)。已迁移。 */
@@ -31,7 +31,7 @@ object RootWiring {
SliceWiring("system", "mpv_1", "_9", "w9().xt9", "w9().xt9(t, n.mpv_1)", listOf("DoShowMessage", "SetFatalError")),
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", "?_C", "?bN", "?fN", "?lN", "?nN", "?pN", "?qN", "?sN", "?vN", "?wN", "ChangeChannel", "CleanUsersMessage", "Configure", "IgnoreUserMessages", "SaveChatMessage", "SendMessageToServer", "SetMessages", "SetSelectedUid", "ShowContextMenu", "ShowUserMessages", "UpdateTypingSpeedAntifloodParams", "UpdateUsers")),
SliceWiring("clanChatState", "ppv_1", "qnt", "yv.qtg", "yv.qtg(t, n.ppv_1)", listOf("?\$nt", "?Tnt", "?xnt", "Configure", "DoAddMessages")),
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("tipsState", "spv_1", "dst", "Hw.vth", "Hw.vth(t, n.spv_1)", listOf("UpdateTips")),
@@ -46,7 +46,7 @@ object RootWiring {
SliceWiring("maps", "bpw_1", "OA", "Oi.sql", "Oi.sql(t, n.bpw_1)", listOf("AddMap")),
SliceWiring("matchmaking", "cpw_1", "TH", "JH().qrx", "JH().qrx(t, n.cpw_1)", listOf("?WH", "?nW", "?tW", "Configure", "EnterToBattleApply", "Play", "SelectMatchmakingMode", "SetRankIndexToMinGS", "WasRegistered")),
SliceWiring("battleCreateState", "dpw_1", "\$6", "Df.ttg", "Df.ttg(t, n.dpw_1, n.bpw_1, n.ypu_1)", listOf("?A6", "?B6", "?C6", "?D6", "?E6", "?F6", "?I6", "?L6", "?M6", "?N6", "?P6", "?R6", "?T6", "?U6", "Configure", "CreateBattle", "SetRestrictions")),
SliceWiring("battleSearchState", "epw_1", "Itt", "Htt().vtg", "Htt().vtg(t, n.epw_1)", listOf("?Dtt", "EnterToBattle", "Fight", "RemoveBattle", "SelectBattle", "SelectBattleFormat", "SetFilter", "SetSearch", "SetSuspiciousFilter")),
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")),
@@ -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")
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")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
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;
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch;
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
@@ -625,6 +625,24 @@ import App.State.ActivatedInCurrentBattle
import App.State.SetLoadedElement
import App.State.Reducers.ReduceSuppliesTutorial
import App.State.Reducers.SuppliesTutorialStateSlice
import App.State.Configure
import App.State.DoAddMessages
import App.State.Reducers.ClanChatStateSlice
import App.State.Reducers.CloseClanChat
import App.State.Reducers.OpenClanChat
import App.State.Reducers.ReduceClanChat
import App.State.Reducers.ResetClanChat
import App.State.BattleFormat
import App.State.EnterToBattle
import App.State.RemoveBattle
import App.State.SelectBattle
import App.State.SelectBattleFormat
import App.State.SetFilter
import App.State.SetSearch
import App.State.SetSuspiciousFilter
import App.State.Reducers.BattleSearchStateSlice
import App.State.Reducers.ClearSelectedBattle
import App.State.Reducers.ReduceBattleSearch
import App.State.Reducers.UserRankBounds
import App.State.Reducers.UserStateSlice
import App.State.Reducers.ClearHullData
@@ -1052,7 +1070,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 63 个已迁移切片(62 归约 + launchParams 透传)
// ⑭ 根 reducer:89 个槽位的委派表 + 65 个已迁移切片(64 归约 + launchParams 透传)
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -1116,7 +1134,7 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 63 个已迁移字段,其余 26 个待迁",
"根:迁移表 = 65 个已迁移字段,其余 24 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
@@ -1136,8 +1154,9 @@ object StateCheck {
"friends", "suppliesState", "clanUserInfo", "battleInviteState",
"partner", "userRenameState",
"battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial",
"clanChatState", "battleSearchState",
) &&
RootWiring.Unmigrated.size == 26
RootWiring.Unmigrated.size == 24
)
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
@@ -1193,7 +1212,7 @@ object StateCheck {
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 63/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 65/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -5476,6 +5495,121 @@ object StateCheck {
)
}
// 74 切片 reducer:ClanChat(qnt / yv.qtg,3 字段;5 个动作)
run {
val S0 = ClanChatStateSlice()
Check(
"切片 ClanChat:默认关闭、无消息、无配置",
!S0.Open && S0.Messages.isEmpty() && S0.Config == null
)
val S1 = ReduceClanChat(S0, OpenClanChat)
Check("切片 ClanChat:OpenClanChat 打开", S1.Open)
Check(
"切片 ClanChat:CloseClanChat 关闭",
!ReduceClanChat(S1, CloseClanChat).Open
)
val S2 = ReduceClanChat(S1, DoAddMessages(listOf("m1", "m2")))
Check(
"切片 ClanChat:DoAddMessages 追加消息",
S2.Messages == listOf<Any?>("m1", "m2")
)
var S3 = S2
for (I in 1..120) S3 = ReduceClanChat(S3, DoAddMessages(listOf("x$I")))
Check(
"切片 ClanChat:超过 100 条只保留最后 100",
S3.Messages.size == 100 && S3.Messages.last() == "x120"
)
Check(
"切片 ClanChat:Configure 写配置",
ReduceClanChat(S1, Configure(emptyList(), emptyList(), false, "", null, null, "", 0, 0, false, null, ""))
.Config != null
)
Check(
"切片 ClanChat:ResetClanChat 整片复位",
ReduceClanChat(S3, ResetClanChat) == ClanChatStateSlice()
)
Check(
"切片 ClanChat:无关动作不改状态",
ReduceClanChat(S3, AddFrags(1)) == S3
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, OpenClanChat)
Check(
"根:OpenClanChat 只动 clanChatState",
Root1.ClanChatState.Open &&
Root1.BattleSearchState == Root0.BattleSearchState
)
val W = RootWiring.ByField("clanChatState")!!
Check(
"根:clanChatState 委派到 qnt / yv.qtg 且 5 个动作",
W.SliceClass == "qnt" && W.Reducer == "yv.qtg" &&
W.Actions.toSet() == setOf(
"OpenClanChat", "CloseClanChat", "Configure", "DoAddMessages",
"ResetClanChat",
)
)
}
// 75 切片 reducer:BattleSearch(Itt / Htt().vtg,5 字段;9 个动作)
run {
val Format = BattleFormat("fmt", "name", "desc", null, null)
val S0 = BattleSearchStateSlice()
Check(
"切片 BattleSearch:默认值完整",
S0.Filter == null && S0.SelectedFormat == null && S0.Search == null &&
!S0.SuspiciousBattlesOnly && S0.SelectedBattleId == null
)
val S1 = ReduceBattleSearch(S0, SetFilter(true))
Check("切片 BattleSearch:SetFilter 写过滤位", S1.Filter == true)
val S2 = ReduceBattleSearch(S1, SetSearch("abc"))
Check("切片 BattleSearch:SetSearch 写搜索串", S2.Search == "abc")
Check(
"切片 BattleSearch:SetSuspiciousFilter 写布尔",
ReduceBattleSearch(S2, SetSuspiciousFilter(true)).SuspiciousBattlesOnly
)
Check(
"切片 BattleSearch:EnterToBattle 清空搜索串",
ReduceBattleSearch(S2, EnterToBattle(7L)).Search == null
)
Check(
"切片 BattleSearch:SelectBattle / SelectBattleFormat",
ReduceBattleSearch(S2, SelectBattle(7L)).SelectedBattleId == 7L &&
ReduceBattleSearch(S2, SelectBattleFormat(Format)).SelectedFormat == Format
)
Check(
"切片 BattleSearch:RemoveBattle 只清匹配的那条",
ReduceBattleSearch(ReduceBattleSearch(S2, SelectBattle(7L)), RemoveBattle(7L))
.SelectedBattleId == null &&
ReduceBattleSearch(
ReduceBattleSearch(S2, SelectBattle(7L)),
RemoveBattle(8L),
).SelectedBattleId == 7L
)
Check(
"切片 BattleSearch:ClearSelectedBattle 清空选中",
ReduceBattleSearch(S2, ClearSelectedBattle).SelectedBattleId == null
)
Check("切片 BattleSearch:Fight 不改状态", ReduceBattleSearch(S2, Fight(1L, BattleTeam.NONE)) == S2)
Check(
"切片 BattleSearch:无关动作不改状态",
ReduceBattleSearch(S2, AddFrags(1)) == S2
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, SetSearch("q"))
Check(
"根:SetSearch 只动 battleSearchState",
Root1.BattleSearchState.Search == "q" &&
Root1.ClanChatState == Root0.ClanChatState
)
val W = RootWiring.ByField("battleSearchState")!!
Check(
"根:battleSearchState 委派到 Itt / Htt().vtg 且 9 个动作",
W.SliceClass == "Itt" && W.Reducer == "Htt().vtg" && W.Actions.size == 9
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)