feat(state): 迁移屏幕导航切片(13 个动作 + ScreenId)

This commit is contained in:
TermiNexus committed 2026-10-23 03:28:57 +08:00
1 parent b7afe07894
commit 974a9f49ad
12 files changed
+446 -36

No files matched your search

@@ -422,7 +422,7 @@ object ActionCatalog {
ActionMeta("ReferralEventEnterPromoCodeStatus", ReferralEventEnterPromoCodeStatus::class, listOf(ActionFieldMeta("referralEventEnterCodeStatus", "Any?"))),
ActionMeta("TemporaryCashExpiredInfo", TemporaryCashExpiredInfo::class, listOf(ActionFieldMeta("temporaryCashData", "Any?"))),
ActionMeta("Screen", Screen::class, listOf(ActionFieldMeta("current", "Any?"), ActionFieldMeta("loaderBackgroundImage", "Any?"), ActionFieldMeta("foreground", "Any?"), ActionFieldMeta("screenStack", "Any?"), ActionFieldMeta("enableBack", "Any?"), ActionFieldMeta("isResetBackStackOnNextGoTo", "Any?"), ActionFieldMeta("isDelayedGoto", "Any?"), ActionFieldMeta("pressedAnyButton", "Any?"), ActionFieldMeta("displayMainEntranceScreen", "Any?"), ActionFieldMeta("needReauthorization", "Any?"))),
ActionMeta("GoTo", GoTo::class, listOf(ActionFieldMeta("screen", "Any?"), ActionFieldMeta("history", "Boolean"))),
ActionMeta("GoTo", GoTo::class, listOf(ActionFieldMeta("screen", "ScreenId"), ActionFieldMeta("history", "Boolean"))),
ActionMeta("DisplayMainEntranceScreen", DisplayMainEntranceScreen::class, listOf(ActionFieldMeta("isDisplayed", "Boolean"))),
ActionMeta("PressedAnyButton", PressedAnyButton::class, listOf(ActionFieldMeta("isPressed", "Boolean"))),
ActionMeta("DisplayReauthorizationMessage", DisplayReauthorizationMessage::class, listOf(ActionFieldMeta("isDisplay", "Boolean"))),
@@ -5,7 +5,7 @@ package App.State
import Core.Vector3
// 动作/状态类共 816 个,字段 2150 个(已定类型 508 个)
// 动作/状态类共 816 个,字段 2150 个(已定类型 509 个)
data class AnnouncementState(
val isEnabled: Any?, // 仅 copy 转发(无类型信息)
val modelLoaded: Any?, // 仅 copy 转发(无类型信息)
@@ -2408,7 +2408,7 @@ data class Screen(
)
data class GoTo(
val screen: Any?, // 未收录类型: Any?(runtime)
val screen: ScreenId, // override
val history: Boolean, // runtime
)
@@ -0,0 +1,112 @@
// ScreenReducer.kt —— 屏幕导航切片(对应 module_10032 的 WK 状态 + wY.ks5 reducer)
//
// 证据链:
// 状态类 WK:`function WK(t, n, i, r, e, u, s, o, _, h)`,10 个字段,toString 名为 `Screen(...)`。
// 默认值取自构造:current = PX()(LoaderScreen)、loaderBackgroundImage = null、
// foreground = false、screenStack = md()(emptyList)、enableBack = true(`e === cd || e`)、
// isResetBackStackOnNextGoTo / isDelayedGoto / pressedAnyButton / displayMainEntranceScreen /
// needReauthorization 均 false。
// reducer:`ld(wY).ks5`(module_10032 第 34950 行)13 个 instanceof 臂。
//
// sC → foreground = false oC → foreground = true
// eY → screenStack = [] uY → isResetBackStackOnNextGoTo = true
// sY → isDelayedGoto = true lY → enableBack = true
// cY → enableBack = false fY → screenStack = dropLast(1)(原 `f$(stack, 1)`)
// oY → screenStack = stack.filter(predicate)(原 `e(o) && u.y(o)`,kd() 是 ArrayList)
// QK → GoTo(见下) hY → pressedAnyButton = action.isPressed
// _Y → displayMainEntranceScreen = action.isDisplayed
// aY → needReauthorization = action.isDisplay
//
// GoTo 分支(原 IIFE,`ks5` 内调用参数为 (0, t, n)):
// 新栈 = isResetBackStackOnNextGoTo ? [] : history ? stack + current : stack
// current = 目标;enableBack 不变;isResetBackStackOnNextGoTo / isDelayedGoto 归 false。
// 原代码还会用 `rk().boz(u)` 发一条 screen_show analytics(目标等于当前时不发,
// Entrance 带 isModal=true,System.Loader 不发);这是副作用,纯 reducer 只保留状态变化。
//
// 无 toString 的 9 个类按 reducer 效果定名,原名写在注释里;名字同时登记在
// 3DTank/tools/gen_root_wiring.js 的 AnonymousActionNames,重新生成 RootWiring 后显示。
package App.State.Reducers
import App.State.DisplayMainEntranceScreen
import App.State.DisplayReauthorizationMessage
import App.State.GoTo
import App.State.LoaderScreen
import App.State.PressedAnyButton
import App.State.ScreenId
/** 原 `sC`:无字段;`foreground = false`。 */
object DisableForeground
/** 原 `oC`:无字段;`foreground = true`。 */
object EnableForeground
/** 原 `eY`:无字段;`screenStack = emptyList()`。 */
object ClearScreenStack
/** 原 `uY`:无字段;`isResetBackStackOnNextGoTo = true`。 */
object ResetScreenStackOnNextGoTo
/** 原 `sY`:无字段;`isDelayedGoto = true`。 */
object DelayNextGoTo
/**
* 原 `oY(predicate)`:只保留 predicate 为 true 的屏幕(`for (…) e(o) && u.y(o)`)。
* 默认谓词是原 `YK`(恒 false),所以缺省调用会清空历史栈。
*/
data class FilterScreenStack(val predicate: (ScreenId) -> Boolean = { false })
/** 原 `lY`:无字段;`enableBack = true`。 */
object EnableBack
/** 原 `cY`:无字段;`enableBack = false`。 */
object DisableBack
/** 原 `fY`:无字段;`screenStack = dropLast(1)`。 */
object PopScreen
/**
* 原 `WK` / `Screen`:`Screen(current, loaderBackgroundImage, foreground, screenStack, enableBack,
* isResetBackStackOnNextGoTo, isDelayedGoto, pressedAnyButton, displayMainEntranceScreen, needReauthorization)`。
*/
data class ScreenStateSlice(
val Current: ScreenId = LoaderScreen,
/** 本 reducer 只 copy 转发、从不写;源码没有给出元素类型,保留 Any?。 */
val LoaderBackgroundImage: Any? = null,
val Foreground: Boolean = false,
val ScreenStack: List<ScreenId> = emptyList(),
val EnableBack: Boolean = true,
val IsResetBackStackOnNextGoTo: Boolean = false,
val IsDelayedGoto: Boolean = false,
val PressedAnyButton: Boolean = false,
val DisplayMainEntranceScreen: Boolean = false,
val NeedReauthorization: Boolean = false,
)
/** 根 reducer 的 screen 分支(原 `dY().ks5(t, n.zpu_1)`)。 */
fun ReduceScreen(State0: ScreenStateSlice, A: Any): ScreenStateSlice = when (A) {
is DisableForeground -> State0.copy(Foreground = false)
is EnableForeground -> State0.copy(Foreground = true)
is ClearScreenStack -> State0.copy(ScreenStack = emptyList())
is ResetScreenStackOnNextGoTo -> State0.copy(IsResetBackStackOnNextGoTo = true)
is DelayNextGoTo -> State0.copy(IsDelayedGoto = true)
is GoTo -> State0.copy(
Current = A.screen,
ScreenStack = when {
State0.IsResetBackStackOnNextGoTo -> emptyList()
A.history -> State0.ScreenStack + State0.Current
else -> State0.ScreenStack
},
IsResetBackStackOnNextGoTo = false,
IsDelayedGoto = false,
)
is FilterScreenStack -> State0.copy(ScreenStack = State0.ScreenStack.filter(A.predicate))
is EnableBack -> State0.copy(EnableBack = true)
is DisableBack -> State0.copy(EnableBack = false)
is PopScreen -> State0.copy(ScreenStack = State0.ScreenStack.dropLast(1))
is PressedAnyButton -> State0.copy(PressedAnyButton = A.isPressed)
is DisplayMainEntranceScreen -> State0.copy(DisplayMainEntranceScreen = A.isDisplayed)
is DisplayReauthorizationMessage -> State0.copy(NeedReauthorization = A.isDisplay)
else -> State0
}
@@ -9,8 +9,9 @@
// home → ReduceHome(s1 / 根 reducer 的 `(inline@home)` 分支)
// localBattleUserState → ReduceLocalBattleUser(lE / `(inline@localBattleUserState)` 分支)
// shop → ReduceShop(j4 / `(inline@shop)` 分支)
// screen → ReduceScreen(WK / dY().ks5)
//
// 其余 83 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 82 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
@@ -19,6 +20,7 @@ import App.State.Reducers.ReduceHome
import App.State.Reducers.ReduceInvite
import App.State.Reducers.ReduceLocalBattleUser
import App.State.Reducers.ReduceLoginByPassword
import App.State.Reducers.ReduceScreen
import App.State.Reducers.ReduceShop
import App.State.Reducers.ReduceTwitch
@@ -41,6 +43,7 @@ object RootReducer {
Home = ReduceHome(State0.Home, Action),
LocalBattleUserState = ReduceLocalBattleUser(State0.LocalBattleUserState, Action),
Shop = ReduceShop(State0.Shop, Action),
Screen = ReduceScreen(State0.Screen, Action),
)
}
@@ -8,6 +8,7 @@ import App.State.Reducers.HomeStateSlice
import App.State.Reducers.InviteStateSlice
import App.State.Reducers.LocalBattleUserStateSlice
import App.State.Reducers.LoginByPasswordStateSlice
import App.State.Reducers.ScreenStateSlice
import App.State.Reducers.ShopStateSlice
import App.State.Reducers.TwitchStateSlice
@@ -18,8 +19,8 @@ import App.State.Reducers.TwitchStateSlice
data class RootState(
/** 原 user(t8 / M8().qtd)。未迁移:原样透传。 */
val User: Any? = null,
/** 原 screen(WK / dY().ks5)。未迁移:原样透传。 */
val Screen: Any? = null,
/** 原 screen(WK / dY().ks5)。已迁移。 */
val Screen: ScreenStateSlice = ScreenStateSlice(),
/** 原 modalScreen(nV / Fs.js1)。未迁移:原样透传。 */
val ModalScreen: Any? = null,
/** 原 battleStatistics(jR / rr.fqr)。未迁移:原样透传。 */
@@ -15,7 +15,7 @@ data class SliceWiring(
object RootWiring {
val All: List<SliceWiring> = listOf(
SliceWiring("user", "ypu_1", "t8", "M8().qtd", "M8().qtd(t, n.ypu_1)", listOf("?R8", "?p8", "?v8", "AddCoins", "AddRubies", "SetBatteryLevel", "SetClanInfo", "SetCoins", "SetDisabledWithoutPremiumLocations", "SetHasTemporaryCash", "SetPremiumExpirationTime", "SetRankNames", "SetRubies", "SetUserId", "SetUserPropertiesAction", "SpendCoins", "SpendCrystals", "SpendRubies", "UpdateClanTag", "UpdateCrystals", "UpdateEventPass", "UpdateGearScore", "UpdatePremium", "UpdateRank", "UpdateScore", "UpdateUid", "UpdateUserRating")),
SliceWiring("screen", "zpu_1", "WK", "dY().ks5", "dY().ks5(t, n.zpu_1)", listOf("?LX", "?RX", "?cY", "?eY", "?fY", "?lY", "?oC", "?oY", "?sC", "?sY", "?uY", "DisplayMainEntranceScreen", "DisplayReauthorizationMessage", "GoTo", "PressedAnyButton")),
SliceWiring("screen", "zpu_1", "WK", "dY().ks5", "dY().ks5(t, n.zpu_1)", listOf("ClearScreenStack", "DelayNextGoTo", "DisableBack", "DisableForeground", "DisplayMainEntranceScreen", "DisplayReauthorizationMessage", "EnableBack", "EnableForeground", "FilterScreenStack", "GoTo", "PopScreen", "PressedAnyButton", "ResetScreenStackOnNextGoTo")),
SliceWiring("modalScreen", "apv_1", "nV", "Fs.js1", "Fs.js1(t, n.apv_1)", listOf("?XW", "AddModal", "RemoveCurrentByName", "RemoveModal")),
SliceWiring("battleStatistics", "bpv_1", "jR", "rr.fqr", "rr.fqr(t, n.bpv_1)", listOf("?DR", "?HR", "?KR", "?PR", "?QR", "?UR", "?XR", "?YR", "?ZR", "?nP", "?tP", "Init", "InitBattleModeWithLevels", "SetBattleMode", "SetDisplayClientInfo", "SetFlagOrBallOwner", "SetRankRange", "SetReArmor", "SetRemainingTime", "SetRoundRestartTime", "UpdateFund", "UpdateScore")),
SliceWiring("battleTutorial", "cpv_1", "rE", "Q.sq8", "Q.sq8(t, n.cpv_1)", listOf("?oE", "?sE", "SetFragsCount", "ShowMessage")),
@@ -106,7 +106,7 @@ object RootWiring {
)
/** 已迁移(由 RootReducer 真正归约)的字段。 */
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop")
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
@@ -0,0 +1,30 @@
// ScreenId.kt —— 屏幕标识(原 module_10032 的 XX 体系里,screen 切片真正用到的部分)
//
// 证据链:
// · WK.current 默认 `PX()` → `RX` 单例,`ld(RX).m()` 返回 "System.Loader"。
// · 根 reducer 的 GoTo 分支只对目标做 `instanceof LX`(Entrance,`m()` = "Entrance")
// 与 `instanceof RX`(System.Loader)判别,其余屏幕只取 `m()` 当 analytics 的 screen 名。
// · 其余屏幕类(UX=Shop、FX=Garage、eX=Lobby.Home …)的字段只在各自切片里使用;
// 在那些切片迁移之前,这里先用「名字」承载身份,避免凭空补一层用不到的字段。
//
// 因此 ScreenId 只保留该 reducer 需要的三种能力:名字(analytics)、Entrance/Loader 判别、相等性。
// 注意:同名的两个不同 Shop 类别在真实包里有不同字段,这里会折叠成同一个 NamedScreen;
// 等 shop 屏幕对象(UX)本身成为状态切片时再替换成带字段的类。
package App.State
sealed interface ScreenId {
val name: String
}
/** 原 `LX`:`m() = "Entrance"`。 */
object EntranceScreen : ScreenId {
override val name: String = "Entrance"
}
/** 原 `RX`:`m() = "System.Loader"`,也是 `WK` 的默认 current(`PX()`)。 */
object LoaderScreen : ScreenId {
override val name: String = "System.Loader"
}
/** 其余屏幕:`m()` 返回带层级的路径,例如 "Lobby.Home" / "Lobby.Quests" / "Shop" / "Garage"。 */
data class NamedScreen(override val name: String) : ScreenId
@@ -9,8 +9,8 @@
// ⑥ 动作目录:816 个类与 138 个真实派发名对齐
// ⑦ 回放:真实 toString 样例的类/字段/类型校验
// ⑧ 全目录动作类都能按 KClass 路由
// ⑨~⑮ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
// Shop(含 U7 类别表与 w3 物品表两个子 reducer);⑭ 根 reducer 的 89 槽位委派
// ⑨~⑰ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
package App
@@ -126,6 +126,24 @@ import App.State.SetReArmorTemporaryDisabled
import App.State.SetRememberMe
import App.State.SetStatus
import App.State.SetTwitchEnabled
import App.State.DisplayMainEntranceScreen
import App.State.DisplayReauthorizationMessage
import App.State.EntranceScreen
import App.State.GoTo
import App.State.LoaderScreen
import App.State.NamedScreen
import App.State.PressedAnyButton
import App.State.Reducers.ClearScreenStack
import App.State.Reducers.DelayNextGoTo
import App.State.Reducers.DisableBack
import App.State.Reducers.DisableForeground
import App.State.Reducers.EnableBack
import App.State.Reducers.EnableForeground
import App.State.Reducers.FilterScreenStack
import App.State.Reducers.PopScreen
import App.State.Reducers.ReduceScreen
import App.State.Reducers.ResetScreenStackOnNextGoTo
import App.State.Reducers.ScreenStateSlice
private data class AddFrags(val fragsCount: Int)
private data class SetScreen(val screen: String)
@@ -563,14 +581,14 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 六个已迁移字段,其余 83 个待迁",
"根:迁移表 = 七个已迁移字段,其余 82 个待迁",
RootReducer.Migrated ==
setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop") &&
RootWiring.Unmigrated.size == 83
setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen") &&
RootWiring.Unmigrated.size == 82
)
// 一次 ReduceRoot 里五个切片各归约自己的动作,互不串片
val S0 = RootState(User = "user-1", Screen = "Preloader")
// 一次 ReduceRoot 里六个切片各归约自己的动作,互不串片
val S0 = RootState(User = "user-1", Screen = ScreenStateSlice(Current = NamedScreen("Preloader")))
val S1 = ReduceRoot(S0, ApplyInvite("uid-1"))
Check(
"根:ApplyInvite 只动 inviteState",
@@ -619,10 +637,10 @@ object StateCheck {
// 未迁移字段原样透传;无关动作不改变任何字段
Check(
"根:未迁移字段原样透传",
S5.User == "user-1" && S5.Screen == "Preloader"
S5.User == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 6/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 7/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -924,6 +942,131 @@ object StateCheck {
// 69 个动作里只有 x4 是标记接口(没有独立效果),其余都已有名字
val ShopUnknown = RootWiring.ByField("shop")!!.Actions.filter { it.startsWith("?") }
Check("shop 回放:唯一未具名的是标记 x4", ShopUnknown == listOf("?x4"))
// ⑰ 切片 reducer:Screen(原 module_10032 的 WK / wY.ks5,13 个 instanceof 臂)
run {
val Lobby = NamedScreen("Lobby.Home")
val Shop = NamedScreen("Shop")
val S0 = ScreenStateSlice()
Check(
"切片 Screen:默认值与原构造一致(current=System.Loader、enableBack=true)",
S0.Current == LoaderScreen && S0.LoaderBackgroundImage == null && !S0.Foreground &&
S0.ScreenStack.isEmpty() && S0.EnableBack && !S0.IsResetBackStackOnNextGoTo &&
!S0.IsDelayedGoto && !S0.PressedAnyButton && !S0.DisplayMainEntranceScreen &&
!S0.NeedReauthorization
)
Check(
"切片 Screen:前景开关只改 foreground",
!ReduceScreen(S0, DisableForeground).Foreground &&
ReduceScreen(S0, EnableForeground).Foreground &&
ReduceScreen(S0, EnableForeground).Current == LoaderScreen
)
val S1 = S0.copy(Current = Lobby, ScreenStack = listOf(NamedScreen("Preloader")))
Check(
"切片 Screen:ClearScreenStack 清历史但保留 current",
ReduceScreen(S1, ClearScreenStack).let { it.ScreenStack.isEmpty() && it.Current == Lobby }
)
val S2 = ReduceScreen(S1, GoTo(Shop, false))
Check(
"切片 Screen:GoTo 无 history 只换 current",
S2.Current == Shop && S2.ScreenStack == S1.ScreenStack
)
val S3 = ReduceScreen(S1, GoTo(Shop, true))
Check(
"切片 Screen:GoTo history 把原 current 压栈",
S3.Current == Shop && S3.ScreenStack == S1.ScreenStack + Lobby
)
Check(
"切片 Screen:GoTo 到当前屏仍按 history 压栈(原包只在 analytics 里去重)",
ReduceScreen(S1, GoTo(Lobby, true)).ScreenStack == listOf(NamedScreen("Preloader"), Lobby)
)
val S4 = ReduceScreen(S3.copy(IsResetBackStackOnNextGoTo = true), GoTo(Lobby, true))
Check(
"切片 Screen:复位标志优先于 history,且 GoTo 后归 false",
S4.Current == Lobby && S4.ScreenStack.isEmpty() && !S4.IsResetBackStackOnNextGoTo
)
Check(
"切片 Screen:GoTo 清 isDelayedGoto",
!ReduceScreen(S3.copy(IsDelayedGoto = true), GoTo(Shop, false)).IsDelayedGoto
)
Check(
"切片 Screen:ResetScreenStackOnNextGoTo / DelayNextGoTo 各置本标志",
ReduceScreen(S0, ResetScreenStackOnNextGoTo).IsResetBackStackOnNextGoTo &&
ReduceScreen(S0, DelayNextGoTo).IsDelayedGoto
)
val S5 = S1.copy(ScreenStack = listOf(NamedScreen("Battle"), Lobby, Shop))
Check(
"切片 Screen:FilterScreenStack 只保留 predicate 命中项",
ReduceScreen(S5, FilterScreenStack { it.name == "Battle" }).ScreenStack ==
listOf(NamedScreen("Battle"))
)
Check(
"切片 Screen:FilterScreenStack 缺省谓词(原 YK)清空历史",
ReduceScreen(S5, FilterScreenStack()).ScreenStack.isEmpty()
)
Check(
"切片 Screen:EnableBack / DisableBack 切返回键",
ReduceScreen(S0.copy(EnableBack = false), EnableBack).EnableBack &&
!ReduceScreen(S0, DisableBack).EnableBack
)
Check(
"切片 Screen:PopScreen 丢最后一个,空栈安全",
ReduceScreen(S1, PopScreen).ScreenStack.isEmpty() &&
ReduceScreen(S0, PopScreen).ScreenStack.isEmpty()
)
Check(
"切片 Screen:PressedAnyButton 写 action.isPressed(false 也生效)",
!ReduceScreen(S0.copy(PressedAnyButton = true), PressedAnyButton(false)).PressedAnyButton &&
ReduceScreen(S0, PressedAnyButton(true)).PressedAnyButton
)
Check(
"切片 Screen:DisplayMainEntranceScreen / DisplayReauthorizationMessage 各写本字段",
ReduceScreen(S0, DisplayMainEntranceScreen(true)).DisplayMainEntranceScreen &&
ReduceScreen(S0, DisplayReauthorizationMessage(true)).NeedReauthorization
)
Check("切片 Screen:无关动作不改状态", ReduceScreen(S1, AddFrags(1)) == S1)
// 根:screen 动作只动 screen,且与已迁移的其余六片共存
val R0 = RootState()
val R1 = ReduceRoot(R0, GoTo(Shop, false))
Check(
"根:GoTo 只动 screen",
R1.Screen.Current == Shop && R1.User == null && R1.Shop == R0.Shop &&
R1.InviteState == R0.InviteState && R1.Home == R0.Home
)
val R2 = ReduceRoot(R1, EnableForeground)
Check(
"根:EnableForeground 只动 screen 前景位",
R2.Screen.Foreground && R2.Screen.Current == Shop &&
R2.Screen.ScreenStack == R1.Screen.ScreenStack && R2.Shop == R1.Shop
)
Check("根:screen 迁移后无关动作仍返回等值状态", ReduceRoot(R2, AddFrags(1)) == R2)
val W = RootWiring.ByField("screen")!!
Check(
"根:screen 委派到 WK / dY().ks5 且 13 个动作全部具名",
W.SliceClass == "WK" && W.Reducer == "dY().ks5" && W.Actions.size == 13 &&
W.Actions.none { it.startsWith("?") }
)
Check(
"根:screen 的动作名覆盖 13 个 instanceof 臂",
listOf(
"DisableForeground", "EnableForeground", "ClearScreenStack",
"ResetScreenStackOnNextGoTo", "DelayNextGoTo", "GoTo", "FilterScreenStack",
"EnableBack", "DisableBack", "PopScreen", "PressedAnyButton",
"DisplayMainEntranceScreen", "DisplayReauthorizationMessage",
).all { it in W.Actions }
)
}
}
val Failed = Results.filter { !it.second }