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

+9 -4
View File
@@ -143,7 +143,7 @@
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。 `names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。 - 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。 - 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
- 已迁移切片 **6/89**: - 已迁移切片 **7/89**:
- `inviteState` → `aD` / `pD.er5`(`InviteReducer.kt`) - `inviteState` → `aD` / `pD.er5`(`InviteReducer.kt`)
- `loginByPassword` → `gD` / `MD.cr6`(`LoginReducer.kt`,含 `SetLogin`/`SetPassword` 把 status 打回 `NONE` 的原语义) - `loginByPassword` → `gD` / `MD.cr6`(`LoginReducer.kt`,含 `SetLogin`/`SetPassword` 把 status 打回 `NONE` 的原语义)
- `twitchState` → `g9` / `y9.gta`(`TwitchReducer.kt`;`SetAccount` 是 Twitch 账号,不是 UID) - `twitchState` → `g9` / `y9.gta`(`TwitchReducer.kt`;`SetAccount` 是 Twitch 账号,不是 UID)
@@ -153,9 +153,14 @@
嵌套 `U7`(`Categories`,7 字段)5 臂、`w3`(`ShopItems`,23 字段)32 臂,共 **69 个 instanceof 动作** 全量翻译。 嵌套 `U7`(`Categories`,7 字段)5 臂、`w3`(`ShopItems`,23 字段)32 臂,共 **69 个 instanceof 动作** 全量翻译。
无 `toString` 的 `Yut/F5/g4/X5/w5/o4/m4/k4/i4/t4/f5/v5/p5/H7/s5/o5/_5` 按效果定名 无 `toString` 的 `Yut/F5/g4/X5/w5/o4/m4/k4/i4/t4/f5/v5/p5/H7/s5/o5/_5` 按效果定名
(`ShopLoaded`/`ResetShop`/…/`AddCategoriesWithBonus`/…),原名保留在 `ShopReducer.kt` 注释里。 (`ShopLoaded`/`ResetShop`/…/`AddCategoriesWithBonus`/…),原名保留在 `ShopReducer.kt` 注释里。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述六片,其余 **83 个字段原样透传**; - `screen` → `WK` / `dY().ks5`(`ScreenReducer.kt`):10 字段,13 个 `instanceof` 臂全量翻译。
迁移进度用 `RootReducer.Coverage`(`已迁移 6/89`)与 `RootWiring.Unmigrated` 暴露。 `GoTo` 的新栈规则 = `isResetBackStackOnNextGoTo ? [] : history ? stack + current : stack`,
- 自检:`TankiOffline/tools/RunStateTest.ps1` **120 项全绿**,含 89 槽位完整性、六片一次归约互不串片、未迁移字段透传, `current` 换成目标屏并清 `isResetBackStackOnNextGoTo` / `isDelayedGoto`;`screen_show` analytics 是副作用,纯 reducer 只保留状态。
屏幕身份用 `App.State.ScreenId`(`EntranceScreen` / `LoaderScreen` / `NamedScreen(name)`);
`LX` / `RX` 是屏幕标识类,已从动作表剔除并记入 `root_wiring.json` 的 `Markers`(`screen-id-class`)。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述七片,其余 **82 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 7/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **141 项全绿**,含 89 槽位完整性、七片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。 以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); 已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
+78 -15
View File
@@ -1,5 +1,5 @@
{ {
"generatedAt": "2026-10-22T18:11:10.717Z", "generatedAt": "2026-10-22T19:22:01.390Z",
"module": "module_10032.js", "module": "module_10032.js",
"RootState": { "RootState": {
"Class": "z6", "Class": "z6",
@@ -12,7 +12,8 @@
"twitchState", "twitchState",
"home", "home",
"localBattleUserState", "localBattleUserState",
"shop" "shop",
"screen"
], ],
"AnonymousActions": [ "AnonymousActions": [
{ {
@@ -123,6 +124,60 @@
"Name": "RemoveDiscountItemForItem", "Name": "RemoveDiscountItemForItem",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名", "DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "U7/w3.discountItems 删除键 ct3_1" "Effect": "U7/w3.discountItems 删除键 ct3_1"
},
{
"Local": "sC",
"Name": "DisableForeground",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "foreground = false(`n.vs4(cd, cd, !1)`)"
},
{
"Local": "oC",
"Name": "EnableForeground",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "foreground = true(`n.vs4(cd, cd, !0)`)"
},
{
"Local": "eY",
"Name": "ClearScreenStack",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "screenStack = emptyList()(`n.vs4(cd, cd, cd, md())`)"
},
{
"Local": "uY",
"Name": "ResetScreenStackOnNextGoTo",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "isResetBackStackOnNextGoTo = true(第 6 个构造参数 !0)"
},
{
"Local": "sY",
"Name": "DelayNextGoTo",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "isDelayedGoto = true(第 7 个构造参数 !0)"
},
{
"Local": "oY",
"Name": "FilterScreenStack",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "screenStack = stack.filter(predicate)(`for (…) e(o) && u.y(o)`,默认谓词 YK 恒 false)"
},
{
"Local": "lY",
"Name": "EnableBack",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "enableBack = true(第 5 个构造参数 !0)"
},
{
"Local": "cY",
"Name": "DisableBack",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "enableBack = false(第 5 个构造参数 !1)"
},
{
"Local": "fY",
"Name": "PopScreen",
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
"Effect": "screenStack = dropLast(1)(`f$(stack, 1)` 是 Kotlin dropLast)"
} }
], ],
"Markers": [ "Markers": [
@@ -135,6 +190,16 @@
"Local": "x4", "Local": "x4",
"Kind": "marker-action", "Kind": "marker-action",
"Evidence": "`fd(x4, \"\", cd, Wm)`;根 reducer 的 `Xd(t, $4) || t instanceof x4` 同样走 U7+w3 快路径,本身无字段、无独立效果。" "Evidence": "`fd(x4, \"\", cd, Wm)`;根 reducer 的 `Xd(t, $4) || t instanceof x4` 同样走 U7+w3 快路径,本身无字段、无独立效果。"
},
{
"Local": "LX",
"Kind": "screen-id-class",
"Evidence": "屏幕标识类 Entrance:`function LX(){ this.gs5_1=\"Entrance\" }`,`ld(LX).m()` 返回 \"Entrance\";GoTo 分支用它判断 analytics 事件是否 isModal。"
},
{
"Local": "RX",
"Kind": "screen-id-class",
"Evidence": "屏幕标识类 System.Loader:`function RX(){ this.ysb_1=\"System.Loader\" }`,`PX()` 返回该单例,也是 WK.current 的默认值;GoTo 到它时不发 screen_show。"
} }
], ],
"Rows": [ "Rows": [
@@ -183,24 +248,22 @@
"Reducer": "dY().ks5", "Reducer": "dY().ks5",
"Delegate": "dY().ks5(t, n.zpu_1)", "Delegate": "dY().ks5(t, n.zpu_1)",
"Actions": [ "Actions": [
"?LX", "ClearScreenStack",
"?RX", "DelayNextGoTo",
"?cY", "DisableBack",
"?eY", "DisableForeground",
"?fY",
"?lY",
"?oC",
"?oY",
"?sC",
"?sY",
"?uY",
"DisplayMainEntranceScreen", "DisplayMainEntranceScreen",
"DisplayReauthorizationMessage", "DisplayReauthorizationMessage",
"EnableBack",
"EnableForeground",
"FilterScreenStack",
"GoTo", "GoTo",
"PressedAnyButton" "PopScreen",
"PressedAnyButton",
"ResetScreenStackOnNextGoTo"
], ],
"Default": "new WK(PX())", "Default": "new WK(PX())",
"Migrated": false "Migrated": true
}, },
{ {
"Field": "modalScreen", "Field": "modalScreen",
+10
View File
@@ -563,6 +563,14 @@ function InferDeep(Expr, Mod, Off, Depth) {
return null; return null;
} }
// 个别字段的调用点/运行时证据只能是 Any?(例如 GoTo 样例里的 screen=[object Object]),
// 但切片 reducer 已经能给出强类型时在这里显式指定。键:`类名.字段`。
// GoTo.screen:QK.grm_1 在 screen 切片里只按 ScreenId 使用(instanceof LX/RX + m()/equals),
// 类型定义见 TankiOffline/.../App/State/ScreenId.kt。
const FieldTypeOverrides = new Map([
['GoTo.screen', 'ScreenId'],
]);
const Emitted = []; const Emitted = [];
// 动作目录:类名 + 字段(名, 类型),用于生成 ActionCatalog.kt(运行时按名字查类) // 动作目录:类名 + 字段(名, 类型),用于生成 ActionCatalog.kt(运行时按名字查类)
const Catalog = []; const Catalog = [];
@@ -639,6 +647,8 @@ for (const R of Inventory.Rows) {
if (C.Ok) Type = C.Type; if (C.Ok) Type = C.Type;
else { Note = '// 未收录类型: ' + C.Bad + '(' + Source + ')'; Type = null; } else { Note = '// 未收录类型: ' + C.Bad + '(' + Source + ')'; Type = null; }
} }
const Override = FieldTypeOverrides.get(R.Name + '.' + F);
if (Override) { Type = Override; Source = 'override'; Note = ''; }
if (Type) TypedFields++; if (Type) TypedFields++;
if (Stats && !Type) { if (Stats && !Type) {
for (const S of Sites) { for (const S of Sites) {
+43
View File
@@ -58,6 +58,23 @@ const AnonymousActionNames = new Map([
['s5', 'InitPaymentModeAsShopItem'], ['s5', 'InitPaymentModeAsShopItem'],
['o5', 'SetupDiscountItemForItem'], ['o5', 'SetupDiscountItemForItem'],
['_5', 'RemoveDiscountItemForItem'], ['_5', 'RemoveDiscountItemForItem'],
// 屏幕切片(WK / wY.ks5 第 34950 行):没有 toString / 不在 action_inventory 的 9 个类。
['sC', 'DisableForeground'],
['oC', 'EnableForeground'],
['eY', 'ClearScreenStack'],
['uY', 'ResetScreenStackOnNextGoTo'],
['sY', 'DelayNextGoTo'],
['oY', 'FilterScreenStack'],
['lY', 'EnableBack'],
['cY', 'DisableBack'],
['fY', 'PopScreen'],
]);
// 非动作的 `instanceof`:屏幕标识类,用于 GoTo 分支判断(不是 dispatch 的动作)。
// 它们从 Actions 列表里剔除,改记到 root_wiring.json 的 Markers。
const NonActionInstanceof = new Map([
['LX', '屏幕标识类 Entrance:`function LX(){ this.gs5_1="Entrance" }`,`ld(LX).m()` 返回 "Entrance";GoTo 分支用它判断 analytics 事件是否 isModal。'],
['RX', '屏幕标识类 System.Loader:`function RX(){ this.ysb_1="System.Loader" }`,`PX()` 返回该单例,也是 WK.current 的默认值;GoTo 到它时不发 screen_show。'],
]); ]);
/** 动作局部类名 → 动作名;未收录的返回 null。 */ /** 动作局部类名 → 动作名;未收录的返回 null。 */
@@ -296,6 +313,7 @@ function BodyOfRef(Ref) {
function NamesInMasked(Masked) { function NamesInMasked(Masked) {
const Names = new Set(); const Names = new Set();
for (const M of Masked.matchAll(/instanceof ([A-Za-z0-9_$]+)/g)) { for (const M of Masked.matchAll(/instanceof ([A-Za-z0-9_$]+)/g)) {
if (NonActionInstanceof.has(M[1])) continue;
const N = ActionNameOf(M[1]); const N = ActionNameOf(M[1]);
Names.add(N || '?' + M[1]); Names.add(N || '?' + M[1]);
} }
@@ -414,6 +432,7 @@ const Migrated = {
home: { Type: 'HomeStateSlice', Reduce: 'ReduceHome' }, home: { Type: 'HomeStateSlice', Reduce: 'ReduceHome' },
localBattleUserState: { Type: 'LocalBattleUserStateSlice', Reduce: 'ReduceLocalBattleUser' }, localBattleUserState: { Type: 'LocalBattleUserStateSlice', Reduce: 'ReduceLocalBattleUser' },
shop: { Type: 'ShopStateSlice', Reduce: 'ReduceShop' }, shop: { Type: 'ShopStateSlice', Reduce: 'ReduceShop' },
screen: { Type: 'ScreenStateSlice', Reduce: 'ReduceScreen' },
}; };
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field); for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -455,6 +474,19 @@ const ShopAnonymousEffects = {
_5: 'U7/w3.discountItems 删除键 ct3_1', _5: 'U7/w3.discountItems 删除键 ct3_1',
}; };
// screen 切片(WK / wY.ks5)的匿名动作:按 reducer 分支效果定名。
const ScreenAnonymousEffects = {
sC: 'foreground = false(`n.vs4(cd, cd, !1)`)',
oC: 'foreground = true(`n.vs4(cd, cd, !0)`)',
eY: 'screenStack = emptyList()(`n.vs4(cd, cd, cd, md())`)',
uY: 'isResetBackStackOnNextGoTo = true(第 6 个构造参数 !0)',
sY: 'isDelayedGoto = true(第 7 个构造参数 !0)',
oY: 'screenStack = stack.filter(predicate)(`for (…) e(o) && u.y(o)`,默认谓词 YK 恒 false)',
lY: 'enableBack = true(第 5 个构造参数 !0)',
cY: 'enableBack = false(第 5 个构造参数 !1)',
fY: 'screenStack = dropLast(1)(`f$(stack, 1)` 是 Kotlin dropLast)',
};
fs.writeFileSync( fs.writeFileSync(
path.join(P, 'names', 'root_wiring.json'), path.join(P, 'names', 'root_wiring.json'),
JSON.stringify( JSON.stringify(
@@ -477,6 +509,12 @@ fs.writeFileSync(
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名', DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect, Effect,
})), })),
...Object.entries(ScreenAnonymousEffects).map(([Local, Effect]) => ({
Local,
Name: AnonymousActionNames.get(Local),
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
Effect,
})),
], ],
Markers: [ Markers: [
{ {
@@ -489,6 +527,11 @@ fs.writeFileSync(
Kind: 'marker-action', Kind: 'marker-action',
Evidence: '`fd(x4, "", cd, Wm)`;根 reducer 的 `Xd(t, $4) || t instanceof x4` 同样走 U7+w3 快路径,本身无字段、无独立效果。', Evidence: '`fd(x4, "", cd, Wm)`;根 reducer 的 `Xd(t, $4) || t instanceof x4` 同样走 U7+w3 快路径,本身无字段、无独立效果。',
}, },
...[...NonActionInstanceof].map(([Local, Evidence]) => ({
Local,
Kind: 'screen-id-class',
Evidence,
})),
], ],
Rows, Rows,
}, },
@@ -422,7 +422,7 @@ object ActionCatalog {
ActionMeta("ReferralEventEnterPromoCodeStatus", ReferralEventEnterPromoCodeStatus::class, listOf(ActionFieldMeta("referralEventEnterCodeStatus", "Any?"))), ActionMeta("ReferralEventEnterPromoCodeStatus", ReferralEventEnterPromoCodeStatus::class, listOf(ActionFieldMeta("referralEventEnterCodeStatus", "Any?"))),
ActionMeta("TemporaryCashExpiredInfo", TemporaryCashExpiredInfo::class, listOf(ActionFieldMeta("temporaryCashData", "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("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("DisplayMainEntranceScreen", DisplayMainEntranceScreen::class, listOf(ActionFieldMeta("isDisplayed", "Boolean"))),
ActionMeta("PressedAnyButton", PressedAnyButton::class, listOf(ActionFieldMeta("isPressed", "Boolean"))), ActionMeta("PressedAnyButton", PressedAnyButton::class, listOf(ActionFieldMeta("isPressed", "Boolean"))),
ActionMeta("DisplayReauthorizationMessage", DisplayReauthorizationMessage::class, listOf(ActionFieldMeta("isDisplay", "Boolean"))), ActionMeta("DisplayReauthorizationMessage", DisplayReauthorizationMessage::class, listOf(ActionFieldMeta("isDisplay", "Boolean"))),
@@ -5,7 +5,7 @@ package App.State
import Core.Vector3 import Core.Vector3
// 动作/状态类共 816 个,字段 2150 个(已定类型 508 个) // 动作/状态类共 816 个,字段 2150 个(已定类型 509 个)
data class AnnouncementState( data class AnnouncementState(
val isEnabled: Any?, // 仅 copy 转发(无类型信息) val isEnabled: Any?, // 仅 copy 转发(无类型信息)
val modelLoaded: Any?, // 仅 copy 转发(无类型信息) val modelLoaded: Any?, // 仅 copy 转发(无类型信息)
@@ -2408,7 +2408,7 @@ data class Screen(
) )
data class GoTo( data class GoTo(
val screen: Any?, // 未收录类型: Any?(runtime) val screen: ScreenId, // override
val history: Boolean, // runtime 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)` 分支) // home → ReduceHome(s1 / 根 reducer 的 `(inline@home)` 分支)
// localBattleUserState → ReduceLocalBattleUser(lE / `(inline@localBattleUserState)` 分支) // localBattleUserState → ReduceLocalBattleUser(lE / `(inline@localBattleUserState)` 分支)
// shop → ReduceShop(j4 / `(inline@shop)` 分支) // shop → ReduceShop(j4 / `(inline@shop)` 分支)
// screen → ReduceScreen(WK / dY().ks5)
// //
// 其余 83 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态, // 其余 82 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。 // 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。 // 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State package App.State
@@ -19,6 +20,7 @@ import App.State.Reducers.ReduceHome
import App.State.Reducers.ReduceInvite import App.State.Reducers.ReduceInvite
import App.State.Reducers.ReduceLocalBattleUser import App.State.Reducers.ReduceLocalBattleUser
import App.State.Reducers.ReduceLoginByPassword import App.State.Reducers.ReduceLoginByPassword
import App.State.Reducers.ReduceScreen
import App.State.Reducers.ReduceShop import App.State.Reducers.ReduceShop
import App.State.Reducers.ReduceTwitch import App.State.Reducers.ReduceTwitch
@@ -41,6 +43,7 @@ object RootReducer {
Home = ReduceHome(State0.Home, Action), Home = ReduceHome(State0.Home, Action),
LocalBattleUserState = ReduceLocalBattleUser(State0.LocalBattleUserState, Action), LocalBattleUserState = ReduceLocalBattleUser(State0.LocalBattleUserState, Action),
Shop = ReduceShop(State0.Shop, 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.InviteStateSlice
import App.State.Reducers.LocalBattleUserStateSlice import App.State.Reducers.LocalBattleUserStateSlice
import App.State.Reducers.LoginByPasswordStateSlice import App.State.Reducers.LoginByPasswordStateSlice
import App.State.Reducers.ScreenStateSlice
import App.State.Reducers.ShopStateSlice import App.State.Reducers.ShopStateSlice
import App.State.Reducers.TwitchStateSlice import App.State.Reducers.TwitchStateSlice
@@ -18,8 +19,8 @@ import App.State.Reducers.TwitchStateSlice
data class RootState( data class RootState(
/** 原 user(t8 / M8().qtd)。未迁移:原样透传。 */ /** 原 user(t8 / M8().qtd)。未迁移:原样透传。 */
val User: Any? = null, val User: Any? = null,
/** 原 screen(WK / dY().ks5)。未迁移:原样透传。 */ /** 原 screen(WK / dY().ks5)。已迁移。 */
val Screen: Any? = null, val Screen: ScreenStateSlice = ScreenStateSlice(),
/** 原 modalScreen(nV / Fs.js1)。未迁移:原样透传。 */ /** 原 modalScreen(nV / Fs.js1)。未迁移:原样透传。 */
val ModalScreen: Any? = null, val ModalScreen: Any? = null,
/** 原 battleStatistics(jR / rr.fqr)。未迁移:原样透传。 */ /** 原 battleStatistics(jR / rr.fqr)。未迁移:原样透传。 */
@@ -15,7 +15,7 @@ data class SliceWiring(
object RootWiring { object RootWiring {
val All: List<SliceWiring> = listOf( 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("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("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("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")), 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 真正归约)的字段。 */ /** 已迁移(由 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 } 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 个真实派发名对齐 // ⑥ 动作目录:816 个类与 138 个真实派发名对齐
// ⑦ 回放:真实 toString 样例的类/字段/类型校验 // ⑦ 回放:真实 toString 样例的类/字段/类型校验
// ⑧ 全目录动作类都能按 KClass 路由 // ⑧ 全目录动作类都能按 KClass 路由
// ⑨~⑮ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState / // ⑨~⑰ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
// Shop(含 U7 类别表与 w3 物品表两个子 reducer);⑭ 根 reducer 的 89 槽位委派 // Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen;⑭ 根 reducer 的 89 槽位委派
// //
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1) // 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
package App package App
@@ -126,6 +126,24 @@ import App.State.SetReArmorTemporaryDisabled
import App.State.SetRememberMe import App.State.SetRememberMe
import App.State.SetStatus import App.State.SetStatus
import App.State.SetTwitchEnabled 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 AddFrags(val fragsCount: Int)
private data class SetScreen(val screen: String) private data class SetScreen(val screen: String)
@@ -563,14 +581,14 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)" LocalBattleW.Reducer == "(inline@localBattleUserState)"
) )
Check( Check(
"根:迁移表 = 六个已迁移字段,其余 83 个待迁", "根:迁移表 = 七个已迁移字段,其余 82 个待迁",
RootReducer.Migrated == RootReducer.Migrated ==
setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop") && setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen") &&
RootWiring.Unmigrated.size == 83 RootWiring.Unmigrated.size == 82
) )
// 一次 ReduceRoot 里五个切片各归约自己的动作,互不串片 // 一次 ReduceRoot 里六个切片各归约自己的动作,互不串片
val S0 = RootState(User = "user-1", Screen = "Preloader") val S0 = RootState(User = "user-1", Screen = ScreenStateSlice(Current = NamedScreen("Preloader")))
val S1 = ReduceRoot(S0, ApplyInvite("uid-1")) val S1 = ReduceRoot(S0, ApplyInvite("uid-1"))
Check( Check(
"根:ApplyInvite 只动 inviteState", "根:ApplyInvite 只动 inviteState",
@@ -619,10 +637,10 @@ object StateCheck {
// 未迁移字段原样透传;无关动作不改变任何字段 // 未迁移字段原样透传;无关动作不改变任何字段
Check( Check(
"根:未迁移字段原样透传", "根:未迁移字段原样透传",
S5.User == "user-1" && S5.Screen == "Preloader" S5.User == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
) )
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5) 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) }) val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN")) Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -924,6 +942,131 @@ object StateCheck {
// 69 个动作里只有 x4 是标记接口(没有独立效果),其余都已有名字 // 69 个动作里只有 x4 是标记接口(没有独立效果),其余都已有名字
val ShopUnknown = RootWiring.ByField("shop")!!.Actions.filter { it.startsWith("?") } val ShopUnknown = RootWiring.ByField("shop")!!.Actions.filter { it.startsWith("?") }
Check("shop 回放:唯一未具名的是标记 x4", ShopUnknown == listOf("?x4")) 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 } val Failed = Results.filter { !it.second }