feat(state): 迁移屏幕导航切片(13 个动作 + ScreenId)
This commit is contained in:
1 parent
b7afe07894
commit
974a9f49ad
12 files changed
+446
-36
No files matched your search
@@ -143,7 +143,7 @@
|
||||
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
|
||||
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
|
||||
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
|
||||
- 已迁移切片 **6/89**:
|
||||
- 已迁移切片 **7/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)
|
||||
@@ -153,9 +153,14 @@
|
||||
嵌套 `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` 按效果定名
|
||||
(`ShopLoaded`/`ResetShop`/…/`AddCategoriesWithBonus`/…),原名保留在 `ShopReducer.kt` 注释里。
|
||||
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述六片,其余 **83 个字段原样透传**;
|
||||
迁移进度用 `RootReducer.Coverage`(`已迁移 6/89`)与 `RootWiring.Unmigrated` 暴露。
|
||||
- 自检:`TankiOffline/tools/RunStateTest.ps1` **120 项全绿**,含 89 槽位完整性、六片一次归约互不串片、未迁移字段透传,
|
||||
- `screen` → `WK` / `dY().ks5`(`ScreenReducer.kt`):10 字段,13 个 `instanceof` 臂全量翻译。
|
||||
`GoTo` 的新栈规则 = `isResetBackStackOnNextGoTo ? [] : history ? stack + current : stack`,
|
||||
`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`)回放。
|
||||
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
|
||||
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-10-22T18:11:10.717Z",
|
||||
"generatedAt": "2026-10-22T19:22:01.390Z",
|
||||
"module": "module_10032.js",
|
||||
"RootState": {
|
||||
"Class": "z6",
|
||||
@@ -12,7 +12,8 @@
|
||||
"twitchState",
|
||||
"home",
|
||||
"localBattleUserState",
|
||||
"shop"
|
||||
"shop",
|
||||
"screen"
|
||||
],
|
||||
"AnonymousActions": [
|
||||
{
|
||||
@@ -123,6 +124,60 @@
|
||||
"Name": "RemoveDiscountItemForItem",
|
||||
"DispatchedAt": "未在 action_trace_merged.json 中出现;按 reducer 分支效果定名",
|
||||
"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": [
|
||||
@@ -135,6 +190,16 @@
|
||||
"Local": "x4",
|
||||
"Kind": "marker-action",
|
||||
"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": [
|
||||
@@ -183,24 +248,22 @@
|
||||
"Reducer": "dY().ks5",
|
||||
"Delegate": "dY().ks5(t, n.zpu_1)",
|
||||
"Actions": [
|
||||
"?LX",
|
||||
"?RX",
|
||||
"?cY",
|
||||
"?eY",
|
||||
"?fY",
|
||||
"?lY",
|
||||
"?oC",
|
||||
"?oY",
|
||||
"?sC",
|
||||
"?sY",
|
||||
"?uY",
|
||||
"ClearScreenStack",
|
||||
"DelayNextGoTo",
|
||||
"DisableBack",
|
||||
"DisableForeground",
|
||||
"DisplayMainEntranceScreen",
|
||||
"DisplayReauthorizationMessage",
|
||||
"EnableBack",
|
||||
"EnableForeground",
|
||||
"FilterScreenStack",
|
||||
"GoTo",
|
||||
"PressedAnyButton"
|
||||
"PopScreen",
|
||||
"PressedAnyButton",
|
||||
"ResetScreenStackOnNextGoTo"
|
||||
],
|
||||
"Default": "new WK(PX())",
|
||||
"Migrated": false
|
||||
"Migrated": true
|
||||
},
|
||||
{
|
||||
"Field": "modalScreen",
|
||||
|
||||
@@ -563,6 +563,14 @@ function InferDeep(Expr, Mod, Off, Depth) {
|
||||
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 = [];
|
||||
// 动作目录:类名 + 字段(名, 类型),用于生成 ActionCatalog.kt(运行时按名字查类)
|
||||
const Catalog = [];
|
||||
@@ -639,6 +647,8 @@ for (const R of Inventory.Rows) {
|
||||
if (C.Ok) Type = C.Type;
|
||||
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 (Stats && !Type) {
|
||||
for (const S of Sites) {
|
||||
|
||||
@@ -58,6 +58,23 @@ const AnonymousActionNames = new Map([
|
||||
['s5', 'InitPaymentModeAsShopItem'],
|
||||
['o5', 'SetupDiscountItemForItem'],
|
||||
['_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。 */
|
||||
@@ -296,6 +313,7 @@ function BodyOfRef(Ref) {
|
||||
function NamesInMasked(Masked) {
|
||||
const Names = new Set();
|
||||
for (const M of Masked.matchAll(/instanceof ([A-Za-z0-9_$]+)/g)) {
|
||||
if (NonActionInstanceof.has(M[1])) continue;
|
||||
const N = ActionNameOf(M[1]);
|
||||
Names.add(N || '?' + M[1]);
|
||||
}
|
||||
@@ -414,6 +432,7 @@ const Migrated = {
|
||||
home: { Type: 'HomeStateSlice', Reduce: 'ReduceHome' },
|
||||
localBattleUserState: { Type: 'LocalBattleUserStateSlice', Reduce: 'ReduceLocalBattleUser' },
|
||||
shop: { Type: 'ShopStateSlice', Reduce: 'ReduceShop' },
|
||||
screen: { Type: 'ScreenStateSlice', Reduce: 'ReduceScreen' },
|
||||
};
|
||||
|
||||
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',
|
||||
};
|
||||
|
||||
// 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(
|
||||
path.join(P, 'names', 'root_wiring.json'),
|
||||
JSON.stringify(
|
||||
@@ -477,6 +509,12 @@ fs.writeFileSync(
|
||||
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
|
||||
Effect,
|
||||
})),
|
||||
...Object.entries(ScreenAnonymousEffects).map(([Local, Effect]) => ({
|
||||
Local,
|
||||
Name: AnonymousActionNames.get(Local),
|
||||
DispatchedAt: '未在 action_trace_merged.json 中出现;按 reducer 分支效果定名',
|
||||
Effect,
|
||||
})),
|
||||
],
|
||||
Markers: [
|
||||
{
|
||||
@@ -489,6 +527,11 @@ fs.writeFileSync(
|
||||
Kind: 'marker-action',
|
||||
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,
|
||||
},
|
||||
|
||||
@@ -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 }
|
||||
|
||||
Reference in new issue
Block a user