feat(state): 迁移 battlePauseState/battleViewState/tankActionsLogState/suppliesTutorial

This commit is contained in:
TermiNexus committed 2026-10-24 16:33:38 +08:00
1 parent 0183f191d3
commit 38ec7fa28e
11 files changed
+719 -49

No files matched your search

+18 -6
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` 动作名。
- 已迁移切片 **59/89**:
- 已迁移切片 **63/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)
@@ -330,12 +330,23 @@
`SetValidationStatus` 按 ordinal 映射输入状态(EMPTY→SHORT、SHORT→NOT_VALID、RESERVED→RESERVED、
FORBIDDEN/NOT_VALID→FORBIDDEN、FREE→FREE)并把 `isResponseDelivered` 置 true;
`G8`→`ResetRenameDialog` 复位弹窗字段但保留 rubies / hash / email。
- `battlePauseState` → `dT` / `NT().mqc`(`BattlePauseStateReducer.kt`):3 字段,4 个分支。
`OnPauseMode` 置 `inAutoPauseMode=true` 并写剩余时长;`ResetBattlePause` 只保留
`idleKickPeriodInMsec`;`timeForAutoLeave` 原值是「当前时间 + 时长」,时钟换算留给中间件层。
- `battleViewState` → `DT` / `sI.jqd`(`BattleViewStateReducer.kt`):7 字段,14 个分支。
`SetVisibility` / `SetElementsVisibility` 按 `visible` 增删 `invisibleElements`;
其余 12 个无 `toString` 的动作两两成对,分别切换输入 / 统计 / 菜单 / 隐藏 / 后台位。
- `tankActionsLogState` → `vI` / `wi.gqe`(`TankActionsLogReducer.kt`):2 字段,4 个分支。
`UpdateLog` 在超过 10 条时先丢弃最早一条再追加(源码 `(Lf = 10)`);
`OnUserJoinTheBattle` / `OnUserLeaveTheBattle` 增删 `noticed`;`dI`→`ResetTankActionsLog` 整片复位。
- `suppliesTutorial` → `ust` / `Uw.uth`(`SuppliesTutorialReducer.kt`):2 字段,2 个分支。
生成器认不出切片类(根构造默认值不是 `new ust()`),在迁移表里显式补 `SliceClass: 'ust'`。
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 58 个有 reducer 的切片,
`launchParams` 与其余 **30 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 59/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **685 项全绿**,含 89 槽位完整性、58 个归约切片一次归约互不串片、未迁移字段透传,
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 62 个有 reducer 的切片,
`launchParams` 与其余 **26 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 63/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **719 项全绿**,含 89 槽位完整性、62 个归约切片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
@@ -353,7 +364,8 @@
battleTutorial 的 2 个、spectator 的 4 个、antiAddiction 的 3 个、moderation 的 13 个、
迁移 / 注册共用的 10 个(`Mit`…`_rt`)匿名类 / 单例同样按效果定名并登记。
friends 的 2 个、suppliesState 的 1 个、battleInviteState 的 2 个、partner 的 4 个、
userRenameState 的 5 个也已登记(其中 `mtt` 因同名类冲突改名为 `AddInvitedUser`)。
userRenameState 的 5 个也已登记(其中 `mtt` 因同名类冲突改名为 `AddInvitedUser`);
battlePauseState 的 2 个、battleViewState 的 12 个、tankActionsLogState 的 1 个同样按效果定名。
### S4 引擎核心
- 模块:32088(3D 数学)、92980(渲染)、61378(ECS)、55088(物理)、36659 + 71105(资源)、36734(音频)、3569(输入)
+117 -23
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-24T07:48:13.464Z",
"generatedAt": "2026-10-24T08:27:50.811Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -65,7 +65,11 @@
"clanUserInfo",
"battleInviteState",
"partner",
"userRenameState"
"userRenameState",
"battlePauseState",
"battleViewState",
"tankActionsLogState",
"suppliesTutorial"
],
"AnonymousActions": [
{
@@ -1088,6 +1092,96 @@
"Name": "HideLinkErrorWarning",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "showLinkErrorWarning=false(`n.cte(…, !1)`)"
},
{
"Local": "xT",
"Name": "ResetBattlePause",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "inAutoPauseMode=false、timeForAutoLeave 复位,保留 idleKickPeriodInMsec(`new dT(cd, cd, n.uqb_1)`)"
},
{
"Local": "TT",
"Name": "CancelAutoPause",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "inAutoPauseMode=false(`n.wqb(!1)`)"
},
{
"Local": "WT",
"Name": "ResetBattleView",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new DT() 整片复位"
},
{
"Local": "YT",
"Name": "ClearInvisibleElements",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "invisibleElements=emptySet()(`n.vqc(cd, cd, Yg())`)"
},
{
"Local": "JT",
"Name": "EnableBattleInput",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "inputEnabled=true(`n.vqc(cd, cd, cd, !0)`)"
},
{
"Local": "ZT",
"Name": "DisableBattleInput",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "inputEnabled=false(`n.vqc(cd, cd, cd, !1)`)"
},
{
"Local": "QT",
"Name": "EnableBattleStatistics",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "battleStatisticsActive=true(`n.vqc(cd, cd, cd, cd, !0)`)"
},
{
"Local": "tI",
"Name": "DisableBattleStatistics",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "battleStatisticsActive=false(`n.vqc(cd, cd, cd, cd, !1)`)"
},
{
"Local": "nI",
"Name": "EnableBattleMenu",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "battleMenuActive=true(`n.vqc(cd, cd, cd, cd, cd, !0)`)"
},
{
"Local": "iI",
"Name": "DisableBattleMenu",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "battleMenuActive=false(`n.vqc(cd, cd, cd, cd, cd, !1)`)"
},
{
"Local": "nE",
"Name": "HideBattleView",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "isHidden=true(`n.vqc(cd, !0)`)"
},
{
"Local": "tE",
"Name": "ShowBattleView",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "isHidden=false(`n.vqc(cd, !1)`)"
},
{
"Local": "rI",
"Name": "EnterBackground",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "inBackgroundState=true(`n.vqc(!0)`)"
},
{
"Local": "eI",
"Name": "LeaveBackground",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "inBackgroundState=false(`n.vqc(!1)`)"
},
{
"Local": "dI",
"Name": "ResetTankActionsLog",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "new vI() 整片复位"
}
],
"Markers": [
@@ -2098,13 +2192,13 @@
"Reducer": "NT().mqc",
"Delegate": "NT().mqc(t, n.lpw_1)",
"Actions": [
"?TT",
"?xT",
"CancelAutoPause",
"OnPauseMode",
"ResetBattlePause",
"SetIdleKickPeriod"
],
"Default": "new dT()",
"Migrated": false
"Migrated": true
},
{
"Field": "battleViewState",
@@ -2113,23 +2207,23 @@
"Reducer": "sI.jqd",
"Delegate": "(null == $n && new sI(), $n).jqd(t, n.mpw_1)",
"Actions": [
"?JT",
"?QT",
"?WT",
"?YT",
"?ZT",
"?eI",
"?iI",
"?nE",
"?nI",
"?rI",
"?tE",
"?tI",
"ClearInvisibleElements",
"DisableBattleInput",
"DisableBattleMenu",
"DisableBattleStatistics",
"EnableBattleInput",
"EnableBattleMenu",
"EnableBattleStatistics",
"EnterBackground",
"HideBattleView",
"LeaveBackground",
"ResetBattleView",
"SetElementsVisibility",
"SetVisibility"
"SetVisibility",
"ShowBattleView"
],
"Default": "new DT()",
"Migrated": false
"Migrated": true
},
{
"Field": "rankUpDialogState",
@@ -2163,13 +2257,13 @@
"Reducer": "wi.gqe",
"Delegate": "wi.gqe(t, n.ppw_1)",
"Actions": [
"?dI",
"OnUserJoinTheBattle",
"OnUserLeaveTheBattle",
"ResetTankActionsLog",
"UpdateLog"
],
"Default": "new vI()",
"Migrated": false
"Migrated": true
},
{
"Field": "battleMessage",
@@ -2699,7 +2793,7 @@
{
"Field": "suppliesTutorial",
"Slot": "zpx_1",
"SliceClass": null,
"SliceClass": "ust",
"Reducer": "Uw.uth",
"Delegate": "Uw.uth(t, n.zpx_1)",
"Actions": [
@@ -2707,7 +2801,7 @@
"SetLoadedElement"
],
"Default": "",
"Migrated": false
"Migrated": true
},
{
"Field": "questCategories",
+72
View File
@@ -257,6 +257,24 @@ const AnonymousActionNames = new Map([
['U8', 'MarkRenameFailed'],
['G8', 'ResetRenameDialog'],
['Q8', 'HideLinkErrorWarning'],
// battlePauseState 切片(dT / PT.mqc):xT / TT 没有 toString。
['xT', 'ResetBattlePause'],
['TT', 'CancelAutoPause'],
// battleViewState 切片(DT / sI.jqd):WT/YT/JT/ZT/QT/tI/nI/iI/nE/tE/rI/eI 没有 toString。
['WT', 'ResetBattleView'],
['YT', 'ClearInvisibleElements'],
['JT', 'EnableBattleInput'],
['ZT', 'DisableBattleInput'],
['QT', 'EnableBattleStatistics'],
['tI', 'DisableBattleStatistics'],
['nI', 'EnableBattleMenu'],
['iI', 'DisableBattleMenu'],
['nE', 'HideBattleView'],
['tE', 'ShowBattleView'],
['rI', 'EnterBackground'],
['eI', 'LeaveBackground'],
// tankActionsLogState 切片(vI / yI.gqe):dI 没有 toString。
['dI', 'ResetTankActionsLog'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
@@ -704,6 +722,15 @@ const Migrated = {
battleInviteState: { Type: 'BattleInviteStateSlice', Reduce: 'ReduceBattleInvite' },
partner: { Type: 'PartnerStateSlice', Reduce: 'ReducePartner' },
userRenameState: { Type: 'UserRenameStateSlice', Reduce: 'ReduceUserRename' },
battlePauseState: { Type: 'BattlePauseStateSlice', Reduce: 'ReduceBattlePause' },
battleViewState: { Type: 'BattleViewStateSlice', Reduce: 'ReduceBattleView' },
tankActionsLogState: { Type: 'TankActionsLogStateSlice', Reduce: 'ReduceTankActionsLog' },
// 根构造里 suppliesTutorial 的默认值不是 `new <Class>()`,生成器认不出切片类,这里显式补。
suppliesTutorial: {
Type: 'SuppliesTutorialStateSlice',
SliceClass: 'ust',
Reduce: 'ReduceSuppliesTutorial',
},
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -1039,6 +1066,33 @@ const UserRenameAnonymousEffects = {
Q8: 'showLinkErrorWarning=false(`n.cte(…, !1)`)',
};
// battlePauseState 切片(dT / PT.mqc):xT / TT 没有 toString。
const BattlePauseAnonymousEffects = {
xT: 'inAutoPauseMode=false、timeForAutoLeave 复位,保留 idleKickPeriodInMsec(`new dT(cd, cd, n.uqb_1)`)',
TT: 'inAutoPauseMode=false(`n.wqb(!1)`)',
};
// battleViewState 切片(DT / sI.jqd):12 个类没有 toString。
const BattleViewAnonymousEffects = {
WT: 'new DT() 整片复位',
YT: 'invisibleElements=emptySet()(`n.vqc(cd, cd, Yg())`)',
JT: 'inputEnabled=true(`n.vqc(cd, cd, cd, !0)`)',
ZT: 'inputEnabled=false(`n.vqc(cd, cd, cd, !1)`)',
QT: 'battleStatisticsActive=true(`n.vqc(cd, cd, cd, cd, !0)`)',
tI: 'battleStatisticsActive=false(`n.vqc(cd, cd, cd, cd, !1)`)',
nI: 'battleMenuActive=true(`n.vqc(cd, cd, cd, cd, cd, !0)`)',
iI: 'battleMenuActive=false(`n.vqc(cd, cd, cd, cd, cd, !1)`)',
nE: 'isHidden=true(`n.vqc(cd, !0)`)',
tE: 'isHidden=false(`n.vqc(cd, !1)`)',
rI: 'inBackgroundState=true(`n.vqc(!0)`)',
eI: 'inBackgroundState=false(`n.vqc(!1)`)',
};
// tankActionsLogState 切片(vI / yI.gqe):dI 没有 toString。
const TankActionsLogAnonymousEffects = {
dI: 'new vI() 整片复位',
};
fs.writeFileSync(
path.join(P, 'names', 'root_wiring.json'),
JSON.stringify(
@@ -1265,6 +1319,24 @@ fs.writeFileSync(
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
...Object.entries(BattlePauseAnonymousEffects).map(([Local, Effect]) => ({
Local,
Name: AnonymousActionNames.get(Local),
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
...Object.entries(BattleViewAnonymousEffects).map(([Local, Effect]) => ({
Local,
Name: AnonymousActionNames.get(Local),
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
...Object.entries(TankActionsLogAnonymousEffects).map(([Local, Effect]) => ({
Local,
Name: AnonymousActionNames.get(Local),
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
],
Markers: [
{
@@ -0,0 +1,50 @@
// BattlePauseStateReducer.kt —— 战斗自动暂停切片(对应 module_10032 的 dT 状态 + PT.mqc)
//
// 证据链:
// 状态类 dT:3 个字段,toString 名 "BattlePauseState(…)"。
// 默认:inAutoPauseMode=false、timeForAutoLeave=new Dd(0,0)、idleKickPeriodInMsec=0。
// 根委派:`NT().mqc(t, n.lpw_1)`。
// reducer:
// xT(无 toString) → `new dT(cd, cd, n.uqb_1)`:只保留 idleKickPeriodInMsec
// jT(OnPauseMode) → inAutoPauseMode=true、timeForAutoLeave=剩余时长
// TT(无 toString) → inAutoPauseMode=false
// CT(SetIdleKickPeriod)→ idleKickPeriodInMsec
//
// `timeForAutoLeave` 原值是 `Fg(Md(), Wg(remaining))`(当前时间 + 时长),需要时钟服务;
// 离线重建把动作给的值原样收口在 `Any?` 里,时间换算留给中间件层。
package App.State.Reducers
import App.State.OnPauseMode
import App.State.SetIdleKickPeriod
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `xT`:退出战斗时复位暂停状态,只保留挂机踢出周期。 */
object ResetBattlePause
/** 原 `TT`:取消自动暂停。 */
object CancelAutoPause
/* ------------------------------------------------------------------ 状态 */
data class BattlePauseStateSlice(
val InAutoPauseMode: Boolean = false,
val TimeForAutoLeave: Any? = null,
val IdleKickPeriodInMsec: Any? = 0,
)
/* ------------------------------------------------------------------ reducer */
fun ReduceBattlePause(
State0: BattlePauseStateSlice,
Action: Any,
): BattlePauseStateSlice = when (Action) {
is ResetBattlePause -> BattlePauseStateSlice(IdleKickPeriodInMsec = State0.IdleKickPeriodInMsec)
is OnPauseMode -> State0.copy(
InAutoPauseMode = true,
TimeForAutoLeave = Action.remainingTimeForAutoLeave,
)
is CancelAutoPause -> State0.copy(InAutoPauseMode = false)
is SetIdleKickPeriod -> State0.copy(IdleKickPeriodInMsec = Action.idleKickPeriodMsec)
else -> State0
}
@@ -0,0 +1,100 @@
// BattleViewStateReducer.kt —— 战斗视图切片(对应 module_10032 的 DT 状态 + sI.jqd)
//
// 证据链:
// 状态类 DT:7 个字段,toString 名 "BattleViewState(…)"。
// 默认:inBackgroundState=true、isHidden=true、invisibleElements=emptySet()、inputEnabled=true、
// battleStatisticsActive=false、battleMenuActive=false、joystickPosition=null。
// 根委派:`(null == $n && new sI(), $n).jqd(t, n.mpw_1)`。
// reducer(13 个实例臂):
// WT(无 toString) → `new DT()` 整片复位
// VT(SetVisibility) → invisibleElements:visible=true 删元素、false 加元素
// KT(SetElementsVisibility)→ 同上,但按集合批量增删
// YT(无 toString) → invisibleElements=emptySet()
// JT / ZT(无 toString) → inputEnabled=true / false
// QT / tI(无 toString) → battleStatisticsActive=true / false
// nI / iI(无 toString) → battleMenuActive=true / false
// nE / tE(无 toString) → isHidden=true / false
// rI / eI(无 toString) → inBackgroundState=true / false
package App.State.Reducers
import App.State.SetElementsVisibility
import App.State.SetVisibility
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `WT`:整片复位(`new DT()`)。 */
object ResetBattleView
/** 原 `YT`:清空隐藏元素集合。 */
object ClearInvisibleElements
/** 原 `JT` / `ZT`:开 / 关输入。 */
object EnableBattleInput
object DisableBattleInput
/** 原 `QT` / `tI`:开 / 关战斗统计。 */
object EnableBattleStatistics
object DisableBattleStatistics
/** 原 `nI` / `iI`:开 / 关战斗菜单。 */
object EnableBattleMenu
object DisableBattleMenu
/** 原 `nE` / `tE`:隐藏 / 显示战斗界面。 */
object HideBattleView
object ShowBattleView
/** 原 `rI` / `eI`:切到后台 / 前台。 */
object EnterBackground
object LeaveBackground
/* ------------------------------------------------------------------ 状态 */
data class BattleViewStateSlice(
val InBackgroundState: Boolean = true,
val IsHidden: Boolean = true,
val InvisibleElements: Set<Any?> = emptySet(),
val InputEnabled: Boolean = true,
val BattleStatisticsActive: Boolean = false,
val BattleMenuActive: Boolean = false,
val JoystickPosition: Any? = null,
)
/* ------------------------------------------------------------------ reducer */
/** 原 `Qg`:集合 -= 集合 / 单值。 */
private fun Minus(Target: Set<Any?>, Value: Any?): Set<Any?> =
if (Value is Iterable<*>) Target - Value.toSet() else Target - Value
/** 原 `Zg`:集合 += 集合 / 单值。 */
private fun Plus(Target: Set<Any?>, Value: Any?): Set<Any?> =
if (Value is Iterable<*>) Target + Value.toSet() else Target + Value
fun ReduceBattleView(
State0: BattleViewStateSlice,
Action: Any,
): BattleViewStateSlice = when (Action) {
is ResetBattleView -> BattleViewStateSlice()
is SetVisibility -> State0.copy(
InvisibleElements =
if (Action.visible) State0.InvisibleElements - Action.element
else State0.InvisibleElements + Action.element,
)
is SetElementsVisibility -> State0.copy(
InvisibleElements =
if (Action.visible) Minus(State0.InvisibleElements, Action.elements)
else Plus(State0.InvisibleElements, Action.elements),
)
is ClearInvisibleElements -> State0.copy(InvisibleElements = emptySet())
is EnableBattleInput -> State0.copy(InputEnabled = true)
is DisableBattleInput -> State0.copy(InputEnabled = false)
is EnableBattleStatistics -> State0.copy(BattleStatisticsActive = true)
is DisableBattleStatistics -> State0.copy(BattleStatisticsActive = false)
is EnableBattleMenu -> State0.copy(BattleMenuActive = true)
is DisableBattleMenu -> State0.copy(BattleMenuActive = false)
is HideBattleView -> State0.copy(IsHidden = true)
is ShowBattleView -> State0.copy(IsHidden = false)
is EnterBackground -> State0.copy(InBackgroundState = true)
is LeaveBackground -> State0.copy(InBackgroundState = false)
else -> State0
}
@@ -0,0 +1,32 @@
// SuppliesTutorialReducer.kt —— 补给上手指引切片(对应 module_10032 的 ust 状态 + fst.uth)
//
// 证据链:
// 状态类 ust:2 个字段,toString 名 "SuppliesTutorialState(loadedElement=…, activatedInCurrentBattle=…)"。
// 默认:loadedElement=null、activatedInCurrentBattle=false。
// 根委派:`Uw.uth(t, n.zpx_1)`。
// reducer:
// hst(ActivatedInCurrentBattle)→ activatedInCurrentBattle
// ast(SetLoadedElement) → loadedElement
package App.State.Reducers
import App.State.ActivatedInCurrentBattle
import App.State.SetLoadedElement
/* ------------------------------------------------------------------ 状态 */
data class SuppliesTutorialStateSlice(
val LoadedElement: Any? = null,
val ActivatedInCurrentBattle: Boolean = false,
)
/* ------------------------------------------------------------------ reducer */
fun ReduceSuppliesTutorial(
State0: SuppliesTutorialStateSlice,
Action: Any,
): SuppliesTutorialStateSlice = when (Action) {
is ActivatedInCurrentBattle ->
State0.copy(ActivatedInCurrentBattle = Action.effectActivated)
is SetLoadedElement -> State0.copy(LoadedElement = Action.loadedElement)
else -> State0
}
@@ -0,0 +1,51 @@
// TankActionsLogReducer.kt —— 战斗日志切片(对应 module_10032 的 vI 状态 + yI.gqe)
//
// 证据链:
// 状态类 vI:2 个字段,toString 名 "TankActionsLogState(…)"。
// 默认:logs=emptyList()、noticed=emptySet()。
// 根委派:`wi.gqe(t, n.ppw_1)`。
// reducer:
// dI(无 toString) → `new vI()` 整片复位
// zI(OnUserJoinTheBattle)→ noticed += userId
// bI(OnUserLeaveTheBattle)→ noticed -= userId
// qI(UpdateLog) → logs 先按上限截断,再追加一条
//
// 上限 `Lf = 10`(module_10032 初始化:`(Lf = 10)`);原逻辑是
// 「size >= 10 时从第 2 条开始拷贝,再 append 新日志」。
package App.State.Reducers
import App.State.OnUserJoinTheBattle
import App.State.OnUserLeaveTheBattle
import App.State.UpdateLog
/* ------------------------------------------------------------------ 常量 */
private const val MaxLogs = 10
/* ------------------------------------------------------------------ 匿名动作 */
/** 原 `dI`:整片复位(`new vI()`)。 */
object ResetTankActionsLog
/* ------------------------------------------------------------------ 状态 */
data class TankActionsLogStateSlice(
val Logs: List<Any?> = emptyList(),
val Noticed: Set<Any?> = emptySet(),
)
/* ------------------------------------------------------------------ reducer */
fun ReduceTankActionsLog(
State0: TankActionsLogStateSlice,
Action: Any,
): TankActionsLogStateSlice = when (Action) {
is ResetTankActionsLog -> TankActionsLogStateSlice()
is OnUserJoinTheBattle -> State0.copy(Noticed = State0.Noticed + Action.userId)
is OnUserLeaveTheBattle -> State0.copy(Noticed = State0.Noticed - Action.userId)
is UpdateLog -> {
val Kept = if (State0.Logs.size >= MaxLogs) State0.Logs.drop(1) else State0.Logs
State0.copy(Logs = Kept + Action.tankActionLog)
}
else -> State0
}
@@ -1,7 +1,7 @@
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
//
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
// 这里采用同一形状。已迁移的 59 个切片里,58 个真正调用 reducer,launchParams 只做类型收口:
// 这里采用同一形状。已迁移的 63 个切片里,62 个真正调用 reducer,launchParams 只做类型收口:
//
// inviteState → ReduceInvite(aD / pD.er5)
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
@@ -61,9 +61,13 @@
// battleInviteState → ReduceBattleInvite(att / Ttt().nth,根委派 `Ttt().nth(t, n.cpx_1)`)
// partner → ReducePartner(UJ / `(inline@partner)`,根委派内联 IIFE)
// userRenameState → ReduceUserRename(O8 / Rf.pte,根委派 `Rf.pte(t, n.qpx_1)`)
// battlePauseState → ReduceBattlePause(dT / NT().mqc,根委派 `NT().mqc(t, n.lpw_1)`)
// 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)`)
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
//
// 其余 30 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 26 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
@@ -72,7 +76,9 @@ import App.State.Reducers.ReduceAccountSettings
import App.State.Reducers.ReduceAnnouncement
import App.State.Reducers.ReduceAntiAddiction
import App.State.Reducers.ReduceBattleResult
import App.State.Reducers.ReduceBattlePause
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
@@ -119,7 +125,9 @@ import App.State.Reducers.ReduceSupportUrl
import App.State.Reducers.ReduceSettings
import App.State.Reducers.ReduceShop
import App.State.Reducers.ReduceSupplies
import App.State.Reducers.ReduceSuppliesTutorial
import App.State.Reducers.ReduceSystem
import App.State.Reducers.ReduceTankActionsLog
import App.State.Reducers.ReduceTips
import App.State.Reducers.ReduceTwitch
import App.State.Reducers.ReduceTwoStepVerification
@@ -198,6 +206,10 @@ object RootReducer {
BattleInviteState = ReduceBattleInvite(State0.BattleInviteState, Action),
Partner = ReducePartner(State0.Partner, Action),
UserRenameState = ReduceUserRename(State0.UserRenameState, Action),
BattlePauseState = ReduceBattlePause(State0.BattlePauseState, Action),
BattleViewState = ReduceBattleView(State0.BattleViewState, Action),
TankActionsLogState = ReduceTankActionsLog(State0.TankActionsLogState, Action),
SuppliesTutorial = ReduceSuppliesTutorial(State0.SuppliesTutorial, Action),
)
}
@@ -9,8 +9,10 @@ import App.State.Reducers.AnnouncementStateSlice
import App.State.Reducers.AntiAddictionStateSlice
import App.State.Reducers.AvailabilityByRankStateSlice
import App.State.Reducers.BattleInviteStateSlice
import App.State.Reducers.BattlePauseStateSlice
import App.State.Reducers.BattleResultStateSlice
import App.State.Reducers.BattleTutorialStateSlice
import App.State.Reducers.BattleViewStateSlice
import App.State.Reducers.CaptchaStateSlice
import App.State.Reducers.ClanUserInfoStateSlice
import App.State.Reducers.ContextTutorialStateSlice
@@ -53,8 +55,10 @@ import App.State.Reducers.SettingsStateSlice
import App.State.Reducers.ShopStateSlice
import App.State.Reducers.SpectatorStateSlice
import App.State.Reducers.SuppliesStateSlice
import App.State.Reducers.SuppliesTutorialStateSlice
import App.State.Reducers.SupportUrlStateSlice
import App.State.Reducers.SystemStateSlice
import App.State.Reducers.TankActionsLogStateSlice
import App.State.Reducers.TipsStateSlice
import App.State.Reducers.TwitchStateSlice
import App.State.Reducers.TwoStepVerificationStateSlice
@@ -145,16 +149,16 @@ data class RootState(
val MatchmakingGroup: Any? = null,
/** 原 shop(j4 / (inline@shop))。已迁移。 */
val Shop: ShopStateSlice = ShopStateSlice(),
/** 原 battlePauseState(dT / NT().mqc)。未迁移:原样透传。 */
val BattlePauseState: Any? = null,
/** 原 battleViewState(DT / sI.jqd)。未迁移:原样透传。 */
val BattleViewState: Any? = null,
/** 原 battlePauseState(dT / NT().mqc)。已迁移。 */
val BattlePauseState: BattlePauseStateSlice = BattlePauseStateSlice(),
/** 原 battleViewState(DT / sI.jqd)。已迁移。 */
val BattleViewState: BattleViewStateSlice = BattleViewStateSlice(),
/** 原 rankUpDialogState(LO / Se.br3)。已迁移。 */
val RankUpDialogState: RankUpDialogStateSlice = RankUpDialogStateSlice(),
/** 原 clanUserInfo(hO / de.gr1)。已迁移。 */
val ClanUserInfo: ClanUserInfoStateSlice = ClanUserInfoStateSlice(),
/** 原 tankActionsLogState(vI / wi.gqe)。未迁移:原样透传。 */
val TankActionsLogState: Any? = null,
/** 原 tankActionsLogState(vI / wi.gqe)。已迁移。 */
val TankActionsLogState: TankActionsLogStateSlice = TankActionsLogStateSlice(),
/** 原 battleMessage(FE / vT().rqb)。未迁移:原样透传。 */
val BattleMessage: Any? = null,
/** 原 battleChat(Q6 / Hf.wtg)。未迁移:原样透传。 */
@@ -225,8 +229,8 @@ data class RootState(
val ItemNotifications: Any? = null,
/** 原 contextTutorial(Mst / Jst().tth)。已迁移。 */
val ContextTutorial: ContextTutorialStateSlice = ContextTutorialStateSlice(),
/** 原 suppliesTutorial(? / Uw.uth)。未迁移:原样透传。 */
val SuppliesTutorial: Any? = null,
/** 原 suppliesTutorial(ust / Uw.uth)。已迁移。 */
val SuppliesTutorial: SuppliesTutorialStateSlice = SuppliesTutorialStateSlice(),
/** 原 questCategories(zZ / th.lsh)。已迁移。 */
val QuestCategories: QuestCategoriesStateSlice = QuestCategoriesStateSlice(),
/** 原 proBattle(Wtt / uv.wth)。已迁移。 */
@@ -53,11 +53,11 @@ object RootWiring {
SliceWiring("suppliesState", "ipw_1", "wE", "\$t.fq9", "\$t.fq9(t, n.ipw_1)", listOf("SetSupplyCount")),
SliceWiring("matchmakingGroup", "jpw_1", "rW", "NW().is0", "NW().is0(t, n.jpw_1)", listOf("?zW", "AcceptedInviteToGroup", "AddUser", "AddUserToSelected", "InitMatchmakingGroup", "Invite", "RejectedInviteToGroup", "RemoveUserToSelected", "SetAllowedFromFriends", "SetSelectedUsers", "UpdateMountItems", "UpdateUserReadiness", "UserRemoved")),
SliceWiring("shop", "kpw_1", "j4", "(inline@shop)", "(function (t, n) { var i; if (t instanceof w4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, t.lt6_1); else if (t instanceof Yut) i = n.qt6(cd, cd, cd, cd, !0); else if (t instanceof F5) i = new j4(cd, cd, cd, cd, cd, cd, cd, n.qq3_1); else if (t instanceof h4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.st5_1); else if (t instanceof c4) i = n.qt6(cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, cd, t.ut5_1); else if (t instanceof u4) i = n.qt6( cd, cd, cd, cd, cd, cd, …", listOf("?x4", "AddCategoriesWithBonus", "AddItemToOrderProcessing", "BlockBackButton", "BraintreeLoadingInterrupt", "ClearPreviousScreen", "ClearTargetCategory", "ClearTargetProductId", "CurrentPayMode", "DeleteOneTimePurchaseItem", "InitAdditionalDescription", "InitAmountString", "InitCategory", "InitCoinPackage", "InitCompensation", "InitCrystalKitPackage", "InitFeatureItem", "InitGroup", "InitItemViewType", "InitKeysPackage", "InitKitBundle", "InitKitPackage", "InitLootBoxAndPaint", "InitOneTimePurchaseItem", "InitPayModeAction", "InitPaymentModeAsShopItem", "InitPaymentProviderItems", "InitPaymentViewType", "InitRestrictionByPayMode", "InitRewardedVideosBlocks", "InitShopCard", "InitShopCardDiscount", "InitShopCardView", "InitShopItem", "InitShopItemName", "InitSingleItemKit", "InitSpecialKitPackage", "MobilePaymentStateChanged", "PaymentProviderFinishFail", "PaymentProviderFinishSuccess", "PaymentProviderInProgress", "PaymentUrlReceiveFailed", "PaymentUrlReceived", "RemoveDiscountItemForItem", "RemoveItemFromOrderProcessing", "ResetPaymentState", "ResetShop", "SavePreviousScreen", "SaveTargetProductId", "SaveTargetVisitedCategory", "SelectCategory", "SelectCategoryId", "SelectItem", "SetBraintreeToken", "SetCoinPaymentProviderUrl", "SetGoToHomeAfterBuy", "SetInitShopItemId", "SetPurchaseCompleted", "SetStripeIntentParams", "SetupDiscountCategoryByItem", "SetupDiscountItem", "SetupDiscountItemForItem", "ShopCardExpired", "ShopCardRemoved", "ShopCardSold", "ShopLoaded", "ToggleShopActivity", "TryToBuyOneTimePurchase", "UnblockBackButton")),
SliceWiring("battlePauseState", "lpw_1", "dT", "NT().mqc", "NT().mqc(t, n.lpw_1)", listOf("?TT", "?xT", "OnPauseMode", "SetIdleKickPeriod")),
SliceWiring("battleViewState", "mpw_1", "DT", "sI.jqd", "(null == \$n && new sI(), \$n).jqd(t, n.mpw_1)", listOf("?JT", "?QT", "?WT", "?YT", "?ZT", "?eI", "?iI", "?nE", "?nI", "?rI", "?tE", "?tI", "SetElementsVisibility", "SetVisibility")),
SliceWiring("battlePauseState", "lpw_1", "dT", "NT().mqc", "NT().mqc(t, n.lpw_1)", listOf("CancelAutoPause", "OnPauseMode", "ResetBattlePause", "SetIdleKickPeriod")),
SliceWiring("battleViewState", "mpw_1", "DT", "sI.jqd", "(null == \$n && new sI(), \$n).jqd(t, n.mpw_1)", listOf("ClearInvisibleElements", "DisableBattleInput", "DisableBattleMenu", "DisableBattleStatistics", "EnableBattleInput", "EnableBattleMenu", "EnableBattleStatistics", "EnterBackground", "HideBattleView", "LeaveBackground", "ResetBattleView", "SetElementsVisibility", "SetVisibility", "ShowBattleView")),
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("?dI", "OnUserJoinTheBattle", "OnUserLeaveTheBattle", "UpdateLog")),
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("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")),
@@ -93,7 +93,7 @@ object RootWiring {
SliceWiring("icons", "wpx_1", "oH", "vs.kru", "vs.kru(t, n.wpx_1)", listOf("SetIcons")),
SliceWiring("itemNotifications", "xpx_1", "_1", "A1().dso", "A1().dso(t, n.xpx_1)", listOf("?S1", "?_C", "AddItemNotification", "AddItemNotifications", "RemoveItemNotification", "UpdateItemNotification")),
SliceWiring("contextTutorial", "ypx_1", "Mst", "Jst().tth", "Jst().tth(t, n.ypx_1)", listOf("LoadCurrentElement", "MarkElementAsViewed", "SetLoadedElements", "SetMissionToElement", "SetUnViewedElements")),
SliceWiring("suppliesTutorial", "zpx_1", "?", "Uw.uth", "Uw.uth(t, n.zpx_1)", listOf("ActivatedInCurrentBattle", "SetLoadedElement")),
SliceWiring("suppliesTutorial", "zpx_1", "ust", "Uw.uth", "Uw.uth(t, n.zpx_1)", listOf("ActivatedInCurrentBattle", "SetLoadedElement")),
SliceWiring("questCategories", "apy_1", "zZ", "th.lsh", "th.lsh(t, n.apy_1)", listOf("RemoveQuestCategory", "RequestQuestCategoriesStatistic", "SetQuestCategories", "SetQuestCategoriesStatistic")),
SliceWiring("proBattle", "bpy_1", "Wtt", "uv.wth", "uv.wth(t, n.bpy_1)", listOf("Fight", "SetPassword")),
SliceWiring("availabilityByRank", "cpy_1", "tet", "\$p.xth", "\$p.xth(t, n.cpy_1)", listOf("SetLoadedData")),
@@ -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")
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 Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
@@ -18,7 +18,8 @@
// CreatorTagState / RewardedVideosState / PasswordChangeState /
// System / ContextTutorial / BattleTutorial / Spectator / TwoStepVerification / AntiAddiction /
// Moderation / MigrationState / Register / UserReports /
// Friends / Supplies / ClanUserInfo / BattleInvite / Partner / UserRename;
// Friends / Supplies / ClanUserInfo / BattleInvite / Partner / UserRename /
// BattlePause / BattleView / TankActionsLog / SuppliesTutorial;
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
@@ -592,6 +593,38 @@ import App.State.Reducers.ReduceUserRename
import App.State.Reducers.ResetRenameDialog
import App.State.Reducers.RenameUidState
import App.State.Reducers.UserRenameStateSlice
import App.State.OnPauseMode
import App.State.SetIdleKickPeriod
import App.State.Reducers.BattlePauseStateSlice
import App.State.Reducers.CancelAutoPause
import App.State.Reducers.ReduceBattlePause
import App.State.Reducers.ResetBattlePause
import App.State.SetElementsVisibility
import App.State.SetVisibility
import App.State.Reducers.BattleViewStateSlice
import App.State.Reducers.ClearInvisibleElements
import App.State.Reducers.DisableBattleInput
import App.State.Reducers.DisableBattleMenu
import App.State.Reducers.DisableBattleStatistics
import App.State.Reducers.EnableBattleInput
import App.State.Reducers.EnableBattleMenu
import App.State.Reducers.EnableBattleStatistics
import App.State.Reducers.EnterBackground
import App.State.Reducers.HideBattleView
import App.State.Reducers.LeaveBackground
import App.State.Reducers.ReduceBattleView
import App.State.Reducers.ResetBattleView
import App.State.Reducers.ShowBattleView
import App.State.OnUserJoinTheBattle
import App.State.OnUserLeaveTheBattle
import App.State.UpdateLog
import App.State.Reducers.ReduceTankActionsLog
import App.State.Reducers.ResetTankActionsLog
import App.State.Reducers.TankActionsLogStateSlice
import App.State.ActivatedInCurrentBattle
import App.State.SetLoadedElement
import App.State.Reducers.ReduceSuppliesTutorial
import App.State.Reducers.SuppliesTutorialStateSlice
import App.State.Reducers.UserRankBounds
import App.State.Reducers.UserStateSlice
import App.State.Reducers.ClearHullData
@@ -1019,7 +1052,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 59 个已迁移切片(58 归约 + launchParams 透传)
// ⑭ 根 reducer:89 个槽位的委派表 + 63 个已迁移切片(62 归约 + launchParams 透传)
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -1083,7 +1116,7 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 59 个已迁移字段,其余 30 个待迁",
"根:迁移表 = 63 个已迁移字段,其余 26 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
@@ -1102,8 +1135,9 @@ object StateCheck {
"migrationState", "register", "userReportsState",
"friends", "suppliesState", "clanUserInfo", "battleInviteState",
"partner", "userRenameState",
"battlePauseState", "battleViewState", "tankActionsLogState", "suppliesTutorial",
) &&
RootWiring.Unmigrated.size == 30
RootWiring.Unmigrated.size == 26
)
// 一次 ReduceRoot 里多个切片各归约自己的动作,互不串片
@@ -1159,7 +1193,7 @@ object StateCheck {
S5.User.Uid == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 59/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 63/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -5233,6 +5267,215 @@ object StateCheck {
)
}
// 70 切片 reducer:BattlePause(dT / NT().mqc,3 字段;4 个动作)
run {
val S0 = BattlePauseStateSlice()
Check(
"切片 BattlePause:默认不暂停、无剩余时间",
!S0.InAutoPauseMode && S0.TimeForAutoLeave == null && S0.IdleKickPeriodInMsec == 0
)
val S1 = ReduceBattlePause(S0, OnPauseMode("30s"))
Check(
"切片 BattlePause:OnPauseMode 置暂停并写剩余时间",
S1.InAutoPauseMode && S1.TimeForAutoLeave == "30s"
)
Check(
"切片 BattlePause:CancelAutoPause 只清暂停位",
ReduceBattlePause(S1, CancelAutoPause).let {
!it.InAutoPauseMode && it.TimeForAutoLeave == "30s"
}
)
Check(
"切片 BattlePause:SetIdleKickPeriod 写周期",
ReduceBattlePause(S1, SetIdleKickPeriod(120)).IdleKickPeriodInMsec == 120
)
Check(
"切片 BattlePause:ResetBattlePause 保留挂机周期",
ReduceBattlePause(
ReduceBattlePause(S1, SetIdleKickPeriod(120)),
ResetBattlePause,
).let { !it.InAutoPauseMode && it.IdleKickPeriodInMsec == 120 }
)
Check(
"切片 BattlePause:无关动作不改状态",
ReduceBattlePause(S1, AddFrags(1)) == S1
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, CancelAutoPause)
Check(
"根:CancelAutoPause 只动 battlePauseState",
!Root1.BattlePauseState.InAutoPauseMode &&
Root1.BattleViewState == Root0.BattleViewState
)
val W = RootWiring.ByField("battlePauseState")!!
Check(
"根:battlePauseState 委派到 dT / NT().mqc 且 4 个动作",
W.SliceClass == "dT" && W.Reducer == "NT().mqc" &&
W.Actions.toSet() == setOf(
"ResetBattlePause", "OnPauseMode", "CancelAutoPause", "SetIdleKickPeriod",
)
)
}
// 71 切片 reducer:BattleView(DT / sI.jqd,7 字段;14 个动作)
run {
val S0 = BattleViewStateSlice()
Check(
"切片 BattleView:默认在后台、隐藏、输入开",
S0.InBackgroundState && S0.IsHidden && S0.InvisibleElements.isEmpty() &&
S0.InputEnabled && !S0.BattleStatisticsActive && !S0.BattleMenuActive &&
S0.JoystickPosition == null
)
val S1 = ReduceBattleView(S0, SetVisibility(true, "e1"))
Check(
"切片 BattleView:SetVisibility(true) 从隐藏集合删元素",
S1.InvisibleElements.isEmpty()
)
val S2 = ReduceBattleView(S1, SetVisibility(false, "e1"))
Check(
"切片 BattleView:SetVisibility(false) 加入隐藏集合",
S2.InvisibleElements == setOf<Any?>("e1")
)
val S3 = ReduceBattleView(
S2,
SetElementsVisibility(true, listOf("e1", "e2")),
)
Check(
"切片 BattleView:SetElementsVisibility 按集合增删",
S3.InvisibleElements.isEmpty() &&
ReduceBattleView(S3, SetElementsVisibility(false, listOf("x", "y")))
.InvisibleElements == setOf<Any?>("x", "y")
)
Check(
"切片 BattleView:ClearInvisibleElements 清空",
ReduceBattleView(S2, ClearInvisibleElements).InvisibleElements.isEmpty()
)
Check(
"切片 BattleView:输入开关两态",
ReduceBattleView(S2, DisableBattleInput).let { !it.InputEnabled } &&
ReduceBattleView(S2, EnableBattleInput).InputEnabled
)
Check(
"切片 BattleView:统计与菜单开关两态",
ReduceBattleView(S2, EnableBattleStatistics).BattleStatisticsActive &&
!ReduceBattleView(S2, DisableBattleStatistics).BattleStatisticsActive &&
ReduceBattleView(S2, EnableBattleMenu).BattleMenuActive &&
!ReduceBattleView(S2, DisableBattleMenu).BattleMenuActive
)
Check(
"切片 BattleView:显示 / 后台开关两态",
!ReduceBattleView(S2, ShowBattleView).IsHidden &&
ReduceBattleView(S2, HideBattleView).IsHidden &&
!ReduceBattleView(S2, LeaveBackground).InBackgroundState &&
ReduceBattleView(S2, EnterBackground).InBackgroundState
)
Check(
"切片 BattleView:ResetBattleView 整片复位",
ReduceBattleView(S2, ResetBattleView) == BattleViewStateSlice()
)
Check(
"切片 BattleView:无关动作不改状态",
ReduceBattleView(S2, AddFrags(1)) == S2
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, DisableBattleInput)
Check(
"根:DisableBattleInput 只动 battleViewState",
!Root1.BattleViewState.InputEnabled &&
Root1.BattlePauseState == Root0.BattlePauseState
)
val W = RootWiring.ByField("battleViewState")!!
Check(
"根:battleViewState 委派到 DT / sI.jqd 且 14 个动作",
W.SliceClass == "DT" && W.Reducer == "sI.jqd" && W.Actions.size == 14
)
}
// 72 切片 reducer:TankActionsLog(vI / wi.gqe,2 字段;4 个动作)
run {
val S0 = TankActionsLogStateSlice()
Check(
"切片 TankActionsLog:默认两个集合为空",
S0.Logs.isEmpty() && S0.Noticed.isEmpty()
)
val S1 = ReduceTankActionsLog(S0, OnUserJoinTheBattle(1L))
Check(
"切片 TankActionsLog:OnUserJoinTheBattle 记入 noticed",
S1.Noticed == setOf<Any?>(1L)
)
Check(
"切片 TankActionsLog:OnUserLeaveTheBattle 移除 noticed",
ReduceTankActionsLog(S1, OnUserLeaveTheBattle(1L)).Noticed.isEmpty()
)
var S2 = S1
for (I in 1..12) S2 = ReduceTankActionsLog(S2, UpdateLog("log$I"))
Check(
"切片 TankActionsLog:超过 10 条从头截断",
S2.Logs.size == 10 && S2.Logs.first() == "log3" && S2.Logs.last() == "log12"
)
Check(
"切片 TankActionsLog:ResetTankActionsLog 整片复位",
ReduceTankActionsLog(S2, ResetTankActionsLog) == TankActionsLogStateSlice()
)
Check(
"切片 TankActionsLog:无关动作不改状态",
ReduceTankActionsLog(S2, AddFrags(1)) == S2
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, UpdateLog("x"))
Check(
"根:UpdateLog 只动 tankActionsLogState",
Root1.TankActionsLogState.Logs == listOf<Any?>("x") &&
Root1.SuppliesTutorial == Root0.SuppliesTutorial
)
val W = RootWiring.ByField("tankActionsLogState")!!
Check(
"根:tankActionsLogState 委派到 vI / wi.gqe 且 4 个动作",
W.SliceClass == "vI" && W.Reducer == "wi.gqe" &&
W.Actions.toSet() == setOf(
"ResetTankActionsLog", "OnUserJoinTheBattle", "OnUserLeaveTheBattle",
"UpdateLog",
)
)
}
// 73 切片 reducer:SuppliesTutorial(ust / Uw.uth,2 字段;2 个动作)
run {
val S0 = SuppliesTutorialStateSlice()
Check(
"切片 SuppliesTutorial:默认未加载、未激活",
S0.LoadedElement == null && !S0.ActivatedInCurrentBattle
)
val S1 = ReduceSuppliesTutorial(S0, SetLoadedElement("hull"))
Check("切片 SuppliesTutorial:SetLoadedElement 写元素", S1.LoadedElement == "hull")
Check(
"切片 SuppliesTutorial:ActivatedInCurrentBattle 写激活位",
ReduceSuppliesTutorial(S1, ActivatedInCurrentBattle(true))
.ActivatedInCurrentBattle
)
Check(
"切片 SuppliesTutorial:无关动作不改状态",
ReduceSuppliesTutorial(S1, AddFrags(1)) == S1
)
val Root0 = RootState()
val Root1 = ReduceRoot(Root0, SetLoadedElement("x"))
Check(
"根:SetLoadedElement 只动 suppliesTutorial",
Root1.SuppliesTutorial.LoadedElement == "x" &&
Root1.TankActionsLogState == Root0.TankActionsLogState
)
val W = RootWiring.ByField("suppliesTutorial")!!
Check(
"根:suppliesTutorial 委派到 ust / Uw.uth",
W.SliceClass == "ust" && W.Reducer == "Uw.uth" &&
W.Actions.toSet() == setOf("ActivatedInCurrentBattle", "SetLoadedElement")
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)