feat(state): 迁移 battleMessage 并补战斗消息协议模型

This commit is contained in:
TermiNexus committed 2026-10-24 19:46:20 +08:00
1 parent 0cd2a23c9e
commit 9cf760b354
8 files changed
+261 -22

No files matched your search

+11 -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` 动作名。
- 已迁移切片 **75/89**:
- 已迁移切片 **76/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)
@@ -379,12 +379,17 @@
`SetParts` 把零件表拆成 `hulls`(hullId 列表)与 `weapons`(index → weaponIds)、并写 `paints`;
`SetPartsInfo` 按零件 / 涂装各自的 id 建索引;零件模型跨模块,收口为
`TankBuilderPart` / `TankBuilderPartInfo` / `TankBuilderPaintInfo`;`Trt`→`ResetTankBuilder` 整片复位。
- `battleMessage` → `FE` / `vT().rqb`(`BattleMessageReducer.kt`):1 字段,7 个分支。
四种消息模型按源码 `toString` 原样收口:`TextMessage(type, hideTimeMs, text, isHidden)`、
`LocalizedMessage(…, content, arguments, isHidden)`、`LocalizedUserMessage(…, content, userTeamRelation, userUid)`、
`LocalizedTeamMessage(…, content, teamRelation)`;`HideMessage` 只把 `hideTimeMs` 匹配的消息标隐藏,
`RemoveMessage` 删掉匹配项,`sT`→`ResetBattleMessages` 整片复位。
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 74 个有 reducer 的切片,
`launchParams` 与其余 **14 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 75/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **823 项全绿**,含 89 槽位完整性、74 个归约切片一次归约互不串片、未迁移字段透传,
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 75 个有 reducer 的切片,
`launchParams` 与其余 **13 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 76/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **832 项全绿**,含 89 槽位完整性、75 个归约切片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
@@ -411,6 +416,7 @@
(`Bet`/`v2`/`B2`/`u2`/`K0`/`C7` 共 6 个)。
contractState 的 `Fnt`/`Hnt` 也已登记(`Fnt` 因与任务切片同名改名为 `ClaimContractReward`);
tankBuilderState 的 `Trt`(`ResetTankBuilder`)同样按效果定名。
battleMessage 的 `sT`(`ResetBattleMessages`)也已登记。
### S4 引擎核心
- 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入)
+12 -5
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-24T09:32:15.650Z",
"generatedAt": "2026-10-24T11:41:22.721Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -81,7 +81,8 @@
"controlSettings",
"battleSettings",
"contractState",
"tankBuilderState"
"tankBuilderState",
"battleMessage"
],
"AnonymousActions": [
{
@@ -1374,6 +1375,12 @@
"Name": "ResetTankBuilder",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new brt() 整片复位"
},
{
"Local": "sT",
"Name": "ResetBattleMessages",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new FE() 整片复位"
}
],
"Markers": [
@@ -2464,16 +2471,16 @@
"Reducer": "vT().rqb",
"Delegate": "vT().rqb(t, n.qpw_1)",
"Actions": [
"?sT",
"DoAddLocalizedMessage",
"DoAddLocalizedTeamMessage",
"DoAddLocalizedUserMessage",
"DoAddTextMessage",
"HideMessage",
"RemoveMessage"
"RemoveMessage",
"ResetBattleMessages"
],
"Default": "new FE()",
"Migrated": false
"Migrated": true
},
{
"Field": "battleChat",
+4
View File
@@ -316,6 +316,8 @@ const AnonymousActionNames = new Map([
['Hnt', 'RemoveCompletedContracts'],
// tankBuilderState 切片(brt / Irt.ati):Trt 没有 toString。
['Trt', 'ResetTankBuilder'],
// battleMessage 切片(FE / fT.rqb):sT 没有 toString。
['sT', 'ResetBattleMessages'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
@@ -792,6 +794,7 @@ const Migrated = {
battleSettings: { Type: 'BattleSettingsStateSlice', Reduce: 'ReduceBattleSettings' },
contractState: { Type: 'ContractStateSlice', Reduce: 'ReduceContractState' },
tankBuilderState: { Type: 'TankBuilderStateSlice', Reduce: 'ReduceTankBuilder' },
battleMessage: { Type: 'BattleMessageStateSlice', Reduce: 'ReduceBattleMessage' },
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -1198,6 +1201,7 @@ const AppSettingsAnonymousEffects = {
Fnt: '复制该合同并把 rewards 清空(原名 GetReward,与任务切片同名)',
Hnt: '只保留 rewards 非空的合同',
Trt: 'new brt() 整片复位',
sT: 'new FE() 整片复位',
};
fs.writeFileSync(
@@ -0,0 +1,142 @@
// BattleMessageReducer.kt —— 战斗消息切片(对应 module_10032 的 FE 状态 + fT.rqb)
//
// 证据链:
// 状态类 FE:1 个字段,toString 名 "BattleMessageState(messages=…)"。
// 默认:messages = emptyList()。
// 根委派:`vT().rqb(t, n.qpw_1)`。
// 消息模型(四种,toString 给出完整字段名):
// HE TextMessage(type, hideTimeMs, text, isHidden)
// GE LocalizedMessage(type, hideTimeMs, content, arguments, isHidden)
// WE LocalizedUserMessage(type, hideTimeMs, isHidden, content, userTeamRelation, userUid)
// VE LocalizedTeamMessage(type, hideTimeMs, isHidden, content, teamRelation)
// reducer(6 个实例臂):
// aT(DoAddTextMessage) → 追加 TextMessage
// oT(DoAddLocalizedMessage) → 追加 LocalizedMessage
// _T(DoAddLocalizedUserMessage) → 追加 LocalizedUserMessage
// hT(DoAddLocalizedTeamMessage) → 追加 LocalizedTeamMessage
// eT(HideMessage) → 把 hideTimeMs 匹配的消息标成 isHidden=true
// uT(RemoveMessage) → 删掉 hideTimeMs 匹配的消息
// sT(无 toString) → `new FE()` 整片复位
package App.State.Reducers
import App.State.DoAddLocalizedMessage
import App.State.DoAddLocalizedTeamMessage
import App.State.DoAddLocalizedUserMessage
import App.State.DoAddTextMessage
import App.State.HideMessage
import App.State.RemoveMessage
/* ------------------------------------------------------------------ 消息模型 */
sealed interface BattleMessageEntry {
val Type: Any?
val HideTimeMs: Any?
val IsHidden: Boolean
fun WithHidden(Value: Boolean): BattleMessageEntry
}
data class TextMessage(
override val Type: Any?,
override val HideTimeMs: Any?,
val Text: String,
override val IsHidden: Boolean = false,
) : BattleMessageEntry {
override fun WithHidden(Value: Boolean) = copy(IsHidden = Value)
}
data class LocalizedMessage(
override val Type: Any?,
override val HideTimeMs: Any?,
val Content: Any?,
val Arguments: List<Any?> = emptyList(),
override val IsHidden: Boolean = false,
) : BattleMessageEntry {
override fun WithHidden(Value: Boolean) = copy(IsHidden = Value)
}
data class LocalizedUserMessage(
override val Type: Any?,
override val HideTimeMs: Any?,
override val IsHidden: Boolean = false,
val Content: Any?,
val UserTeamRelation: Any?,
val UserUid: Any?,
) : BattleMessageEntry {
override fun WithHidden(Value: Boolean) = copy(IsHidden = Value)
}
data class LocalizedTeamMessage(
override val Type: Any?,
override val HideTimeMs: Any?,
override val IsHidden: Boolean = false,
val Content: Any?,
val TeamRelation: Any?,
) : BattleMessageEntry {
override fun WithHidden(Value: Boolean) = copy(IsHidden = Value)
}
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `sT`:整片复位(`new FE()`)。 */
object ResetBattleMessages
/* ------------------------------------------------------------------ 状态 */
data class BattleMessageStateSlice(
val Messages: List<Any?> = emptyList(),
)
/* ------------------------------------------------------------------ reducer */
private fun HideByTime(Messages: List<Any?>, HideTimeMs: Any?): List<Any?> =
Messages.map {
val Entry = it as? BattleMessageEntry
if (Entry != null && Entry.HideTimeMs == HideTimeMs) Entry.WithHidden(true) else it
}
fun ReduceBattleMessage(
State0: BattleMessageStateSlice,
Action: Any,
): BattleMessageStateSlice = when (Action) {
is DoAddTextMessage -> State0.copy(
Messages = State0.Messages + TextMessage(
Type = Action.type,
HideTimeMs = Action.hideTimeMs,
Text = Action.text?.toString() ?: "",
),
)
is DoAddLocalizedMessage -> State0.copy(
Messages = State0.Messages + LocalizedMessage(
Type = Action.type,
HideTimeMs = Action.hideTimeMs,
Content = Action.message,
Arguments = (Action.args as? List<Any?>) ?: emptyList(),
),
)
is DoAddLocalizedUserMessage -> State0.copy(
Messages = State0.Messages + LocalizedUserMessage(
Type = Action.type,
HideTimeMs = Action.hideTimeMs,
Content = Action.message,
UserTeamRelation = Action.userTeamRelation,
UserUid = Action.userUid,
),
)
is DoAddLocalizedTeamMessage -> State0.copy(
Messages = State0.Messages + LocalizedTeamMessage(
Type = Action.type,
HideTimeMs = Action.hideTimeMs,
Content = Action.message,
TeamRelation = Action.teamRelation,
),
)
is HideMessage -> State0.copy(Messages = HideByTime(State0.Messages, Action.hideTimeMs))
is RemoveMessage -> State0.copy(
Messages = State0.Messages.filterNot {
(it as? BattleMessageEntry)?.HideTimeMs == Action.hideTimeMs
},
)
is ResetBattleMessages -> BattleMessageStateSlice()
else -> State0
}
@@ -1,7 +1,7 @@
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
//
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
// 这里采用同一形状。已迁移的 75 个切片里,74 个真正调用 reducer,launchParams 只做类型收口:
// 这里采用同一形状。已迁移的 76 个切片里,75 个真正调用 reducer,launchParams 只做类型收口:
//
// inviteState → ReduceInvite(aD / pD.er5)
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
@@ -77,9 +77,10 @@
// battleSettings → ReduceBattleSettings(W0 / X0().esq,根委派 `X0().esq(t, n.xpw_1)`)
// contractState → ReduceContractState(Ont / xv.bti,根委派 `xv.bti(t, n.fpy_1)`)
// tankBuilderState → ReduceTankBuilder(brt / wp.ati,根委派 `wp.ati(t, n.epy_1)`)
// battleMessage → ReduceBattleMessage(FE / vT().rqb,根委派 `vT().rqb(t, n.qpw_1)`)
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
//
// 其余 14 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 13 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
@@ -88,6 +89,7 @@ import App.State.Reducers.ReduceAccountSettings
import App.State.Reducers.ReduceAnnouncement
import App.State.Reducers.ReduceAntiAddiction
import App.State.Reducers.ReduceBattleResult
import App.State.Reducers.ReduceBattleMessage
import App.State.Reducers.ReduceBattlePause
import App.State.Reducers.ReduceBattleSearch
import App.State.Reducers.ReduceBattleSettings
@@ -246,6 +248,7 @@ object RootReducer {
BattleSettings = ReduceBattleSettings(State0.BattleSettings, Action),
ContractState = ReduceContractState(State0.ContractState, Action),
TankBuilderState = ReduceTankBuilder(State0.TankBuilderState, Action),
BattleMessage = ReduceBattleMessage(State0.BattleMessage, Action),
)
}
@@ -9,6 +9,7 @@ import App.State.Reducers.AnnouncementStateSlice
import App.State.Reducers.AntiAddictionStateSlice
import App.State.Reducers.AvailabilityByRankStateSlice
import App.State.Reducers.BattleInviteStateSlice
import App.State.Reducers.BattleMessageStateSlice
import App.State.Reducers.BattlePauseStateSlice
import App.State.Reducers.BattleResultStateSlice
import App.State.Reducers.BattleSearchStateSlice
@@ -171,8 +172,8 @@ data class RootState(
val ClanUserInfo: ClanUserInfoStateSlice = ClanUserInfoStateSlice(),
/** 原 tankActionsLogState(vI / wi.gqe)。已迁移。 */
val TankActionsLogState: TankActionsLogStateSlice = TankActionsLogStateSlice(),
/** 原 battleMessage(FE / vT().rqb)。未迁移:原样透传。 */
val BattleMessage: Any? = null,
/** 原 battleMessage(FE / vT().rqb)。已迁移。 */
val BattleMessage: BattleMessageStateSlice = BattleMessageStateSlice(),
/** 原 battleChat(Q6 / Hf.wtg)。未迁移:原样透传。 */
val BattleChat: Any? = null,
/** 原 droppableGold(Vet / Wp.kth)。已迁移。 */
@@ -58,7 +58,7 @@ object RootWiring {
SliceWiring("rankUpDialogState", "npw_1", "LO", "Se.br3", "Se.br3(t, n.npw_1)", listOf("ResetRankUpDialog", "ShowAvailableItems")),
SliceWiring("clanUserInfo", "opw_1", "hO", "de.gr1", "de.gr1(t, n.opw_1)", listOf("Loaded")),
SliceWiring("tankActionsLogState", "ppw_1", "vI", "wi.gqe", "wi.gqe(t, n.ppw_1)", listOf("OnUserJoinTheBattle", "OnUserLeaveTheBattle", "ResetTankActionsLog", "UpdateLog")),
SliceWiring("battleMessage", "qpw_1", "FE", "vT().rqb", "vT().rqb(t, n.qpw_1)", listOf("?sT", "DoAddLocalizedMessage", "DoAddLocalizedTeamMessage", "DoAddLocalizedUserMessage", "DoAddTextMessage", "HideMessage", "RemoveMessage")),
SliceWiring("battleMessage", "qpw_1", "FE", "vT().rqb", "vT().rqb(t, n.qpw_1)", listOf("DoAddLocalizedMessage", "DoAddLocalizedTeamMessage", "DoAddLocalizedUserMessage", "DoAddTextMessage", "HideMessage", "RemoveMessage", "ResetBattleMessages")),
SliceWiring("battleChat", "rpw_1", "Q6", "Hf.wtg", "Hf.wtg(t, n.rpw_1)", listOf("?ett", "?rtt", "?stt", "?utt", "AddMessage", "ChatLoaded")),
SliceWiring("droppableGold", "spw_1", "Vet", "Wp.kth", "Wp.kth(t, n.spw_1)", listOf("HideDroppableGoldAuthor", "ShowDroppableGoldAuthor")),
SliceWiring("home", "tpw_1", "s1", "(inline@home)", "(function (t, n) { return t instanceof o1 ? n.f7j(!0) : n; })(t, n.tpw_1)", listOf("ShowShopDiscountNotification")),
@@ -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")
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")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
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;
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial / ClanChat / BattleSearch / BattleStatistics / SoundSettings / GameSettings / KeyboardSettings / TouchscreenHudSettings / GraphicsSettings / ControlSettings / BattleSettings / ContractState / TankBuilder / BattleMessage;
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
@@ -716,6 +716,20 @@ import App.State.Reducers.TankBuilderPaintInfo
import App.State.Reducers.TankBuilderPart
import App.State.Reducers.TankBuilderPartInfo
import App.State.Reducers.TankBuilderStateSlice
import App.State.DoAddLocalizedMessage
import App.State.DoAddLocalizedTeamMessage
import App.State.DoAddLocalizedUserMessage
import App.State.DoAddTextMessage
import App.State.HideMessage
import App.State.RemoveMessage
import App.State.Reducers.BattleMessageEntry
import App.State.Reducers.BattleMessageStateSlice
import App.State.Reducers.LocalizedMessage
import App.State.Reducers.LocalizedTeamMessage
import App.State.Reducers.LocalizedUserMessage
import App.State.Reducers.ReduceBattleMessage
import App.State.Reducers.ResetBattleMessages
import App.State.Reducers.TextMessage
import App.State.Reducers.UserRankBounds
import App.State.Reducers.UserStateSlice
import App.State.Reducers.ClearHullData
@@ -1143,7 +1157,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 75 个已迁移切片(74 归约 + launchParams 透传)
// ⑭ 根 reducer:89 个槽位的委派表 + 76 个已迁移切片(75 归约 + launchParams 透传)
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -1207,7 +1221,7 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 75 个已迁移字段,其余 14 个待迁",
"根:迁移表 = 76 个已迁移字段,其余 13 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
@@ -1230,9 +1244,9 @@ object StateCheck {
"clanChatState", "battleSearchState", "battleStatistics",
"soundSettings", "gameSettings", "keyboardSettings", "touchscreenHudSettings",
"graphicsSettings", "controlSettings", "battleSettings", "contractState",
"tankBuilderState",
"tankBuilderState", "battleMessage",
) &&
RootWiring.Unmigrated.size == 14
RootWiring.Unmigrated.size == 13
)
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
@@ -1288,7 +1302,7 @@ object StateCheck {
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 75/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 76/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -6273,6 +6287,68 @@ object StateCheck {
)
}
// 86 切片 reducer:BattleMessage(FE / vT().rqb,1 字段;7 个动作)
run {
val S0 = BattleMessageStateSlice()
Check("切片 BattleMessage:默认空消息表", S0.Messages.isEmpty())
val S1 = ReduceBattleMessage(S0, DoAddTextMessage("info", 100L, "hello"))
Check(
"切片 BattleMessage:DoAddTextMessage 追加 TextMessage",
S1.Messages.size == 1 &&
(S1.Messages[0] as TextMessage).Text == "hello" &&
(S1.Messages[0] as TextMessage).HideTimeMs == 100L
)
val S2 = ReduceBattleMessage(
ReduceBattleMessage(
ReduceBattleMessage(S1, DoAddLocalizedMessage("sys", 200L, "KILL", listOf("a"))),
DoAddLocalizedUserMessage("enemy", "user", 300L, "MSG", "uid1"),
),
DoAddLocalizedTeamMessage("team", 400L, "TEAM", "red"),
)
Check(
"切片 BattleMessage:四种消息按各自字段入表",
S2.Messages.size == 4 &&
(S2.Messages[1] as LocalizedMessage).Content == "KILL" &&
(S2.Messages[1] as LocalizedMessage).Arguments == listOf<Any?>("a") &&
(S2.Messages[2] as LocalizedUserMessage).UserTeamRelation == "enemy" &&
(S2.Messages[2] as LocalizedUserMessage).UserUid == "uid1" &&
(S2.Messages[3] as LocalizedTeamMessage).TeamRelation == "red"
)
val S3 = ReduceBattleMessage(S2, HideMessage(100L))
Check(
"切片 BattleMessage:HideMessage 只把匹配 hideTimeMs 的标隐藏",
(S3.Messages[0] as TextMessage).IsHidden &&
!(S3.Messages[1] as LocalizedMessage).IsHidden
)
val S4 = ReduceBattleMessage(S3, RemoveMessage(200L))
Check(
"切片 BattleMessage:RemoveMessage 删掉匹配的那条",
S4.Messages.size == 3 &&
S4.Messages.none { (it as BattleMessageEntry).HideTimeMs == 200L }
)
Check(
"切片 BattleMessage:ResetBattleMessages 整片复位",
ReduceBattleMessage(S4, ResetBattleMessages) == BattleMessageStateSlice()
)
Check(
"切片 BattleMessage:无关动作不改状态",
ReduceBattleMessage(S4, AddFrags(1)) == S4
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, DoAddTextMessage("info", 1L, "x"))
Check(
"根:DoAddTextMessage 只动 battleMessage",
Root1.BattleMessage.Messages.size == 1 &&
Root1.TankBuilderState == Root0.TankBuilderState
)
val W = RootWiring.ByField("battleMessage")!!
Check(
"根:battleMessage 委派到 FE / vT().rqb 且 7 个动作",
W.SliceClass == "FE" && W.Reducer == "vT().rqb" && W.Actions.size == 7
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)