feat(state): 迁移 home 与 localBattleUserState 两个内联切片

This commit is contained in:
TermiNexus committed 2026-10-23 01:24:21 +08:00
1 parent fba31fb63c
commit cd680708bd
9 files changed
+196 -33

No files matched your search

+6 -4
View File
@@ -143,13 +143,15 @@
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
- 已迁移切片 **3/89**:
- 已迁移切片 **5/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)
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述三片,其余 **86 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 3/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **61 项全绿**,含 89 槽位完整性、三片一次归约互不串片、未迁移字段透传。
- `home` → `s1` / 根 reducer `(inline@home)` 分支(`HomeReducer.kt`;`ShopDiscountNotification`)
- `localBattleUserState` → `lE` / `(inline@localBattleUserState)` 分支(`LocalBattleUserReducer.kt`)
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述五片,其余 **84 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 5/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **78 项全绿**,含 89 槽位完整性、五片一次归约互不串片、未迁移字段透传。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
`launchParams` 是纯透传(`(pass-through)`,无 reducer)。
+6 -4
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-22T17:18:31.765Z",
"generatedAt": "2026-10-22T17:21:56.934Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -9,7 +9,9 @@
"Migrated": [
"inviteState",
"loginByPassword",
"twitchState"
"twitchState",
"home",
"localBattleUserState"
],
"AnonymousActions": [
{
@@ -1122,7 +1124,7 @@
"ShowShopDiscountNotification"
],
"Default": "new s1()",
"Migrated": false
"Migrated": true
},
{
"Field": "localBattleUserState",
@@ -1135,7 +1137,7 @@
"SetReArmorTemporaryDisabled"
],
"Default": "new lE()",
"Migrated": false
"Migrated": true
},
{
"Field": "soundSettings",
+5 -3
View File
@@ -393,6 +393,8 @@ const Migrated = {
inviteState: { Type: 'InviteStateSlice', Reduce: 'ReduceInvite' },
loginByPassword: { Type: 'LoginByPasswordStateSlice', Reduce: 'ReduceLoginByPassword' },
twitchState: { Type: 'TwitchStateSlice', Reduce: 'ReduceTwitch' },
home: { Type: 'HomeStateSlice', Reduce: 'ReduceHome' },
localBattleUserState: { Type: 'LocalBattleUserStateSlice', Reduce: 'ReduceLocalBattleUser' },
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -446,9 +448,9 @@ Lines.push('// 与根 reducer ' + RootReducerFn.Name + '(逐切片归约
Lines.push('// 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。');
Lines.push('package App.State');
Lines.push('');
Lines.push('import App.State.Reducers.InviteStateSlice');
Lines.push('import App.State.Reducers.LoginByPasswordStateSlice');
Lines.push('import App.State.Reducers.TwitchStateSlice');
for (const F of Object.keys(Migrated).sort()) {
Lines.push('import App.State.Reducers.' + Migrated[F].Type);
}
Lines.push('');
Lines.push('/**');
Lines.push(' * 原 `' + RootClass + '` / `TOState`:' + Rows.length + ' 个切片字段,顺序与 toString 一致。');
@@ -0,0 +1,30 @@
// HomeReducer.kt —— 首页切片(对应 module_10032 的 s1 状态 + 根 reducer 的 home 内联分支)
//
// 证据链:
// 状态类 s1:`function s1(t) { t = t !== cd && t; this.esm_1 = t; }`
// toString:`HomeScreen(shopDiscountNotification=… )`,默认 false
// 归约:根 reducer `k6` 的 home 分支(原内联 IIFE,委派表记为 `(inline@home)`)
// `(function (t, n) { return t instanceof o1 ? n.f7j(!0) : n; })(t, n.tpw_1)`
// `ld(s1).f7j = function (t) { return new s1(t); }` 是 copy,`!0` 即新值 true
// 动作 o1:无字段、无 toString(10032 以 `$_$.gl` 导出单例 `Eh`),按 reducer 效果定名
// `ShowShopDiscountNotification`;命名证据见 `3DTank/names/root_wiring.json` 的 AnonymousActions
// (派发点 module_18334 的 `nt(Qx).qge`,即主菜单商店项上的折扣提示)。
package App.State.Reducers
/**
* 原 `o1`:无字段、无 toString 的默认动作;效果是 `HomeScreen.copy(shopDiscountNotification = true)`。
*/
class ShowShopDiscountNotification
/**
* 原 `s1` / `HomeScreen`:`HomeScreen(shopDiscountNotification)`,默认 false。
*/
data class HomeStateSlice(
val ShopDiscountNotification: Boolean = false,
)
/** 根 reducer 的 home 分支(原内联 IIFE,`(inline@home)`)。 */
fun ReduceHome(State0: HomeStateSlice, A: Any): HomeStateSlice = when (A) {
is ShowShopDiscountNotification -> State0.copy(ShopDiscountNotification = true)
else -> State0
}
@@ -0,0 +1,38 @@
// LocalBattleUserReducer.kt —— 本地战斗用户切片(对应 module_10032 的 lE 状态 + 根 reducer 的 upw_1 内联分支)
//
// 证据链:
// 状态类 lE:`function lE(t, n) { t = t === cd ? null : t; n = n !== cd && n;
// this.uq8_1 = t; this.vq8_1 = n; }`
// toString:`LocalBattleUserState(weaponResistanceProperty=…, isReArmorTemporaryDisabled=…)`
// 默认值:`null / false`
// 归约:根 reducer `k6` 的 localBattleUserState 分支(原内联 IIFE,委派表记为 `(inline@localBattleUserState)`)
// `(function (t, n) {
// return t instanceof fE ? n.xq8(t.yq8_1)
// : t instanceof vE ? n.xq8(cd, t.zq8_1)
// : n;
// })(t, n.upw_1)`
// `ld(lE).xq8` 是 copy(缺省实参取自身字段)
// 动作(都在 action_inventory_10032 里):
// fE → SetLocalWeaponResistanceProperty(itemProperty: String) 10032 导出 `$_$.ru`
// vE → SetReArmorTemporaryDisabled(disabled: Boolean) 10032 导出 `$_$.su`
// 派发点例:module_70130 的 `this.z1vd_1.uar(new _a(!t.equals(oa())))`
package App.State.Reducers
import App.State.SetLocalWeaponResistanceProperty
import App.State.SetReArmorTemporaryDisabled
/**
* 原 `lE`:`LocalBattleUserState(weaponResistanceProperty, isReArmorTemporaryDisabled)`。
* 默认值取自原构造函数:`null / false`。
*/
data class LocalBattleUserStateSlice(
val WeaponResistanceProperty: String? = null,
val IsReArmorTemporaryDisabled: Boolean = false,
)
/** 根 reducer 的 localBattleUserState 分支(原内联 IIFE,`(inline@localBattleUserState)`)。 */
fun ReduceLocalBattleUser(State0: LocalBattleUserStateSlice, A: Any): LocalBattleUserStateSlice = when (A) {
is SetLocalWeaponResistanceProperty -> State0.copy(WeaponResistanceProperty = A.itemProperty)
is SetReArmorTemporaryDisabled -> State0.copy(IsReArmorTemporaryDisabled = A.disabled)
else -> State0
}
@@ -6,13 +6,17 @@
// inviteState → ReduceInvite(aD / pD.er5)
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
// twitchState → ReduceTwitch(g9 / y9.gta)
// home → ReduceHome(s1 / 根 reducer 的 `(inline@home)` 分支)
// localBattleUserState → ReduceLocalBattleUser(lE / `(inline@localBattleUserState)` 分支)
//
// 其余 86 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 84 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
import App.State.Reducers.ReduceHome
import App.State.Reducers.ReduceInvite
import App.State.Reducers.ReduceLocalBattleUser
import App.State.Reducers.ReduceLoginByPassword
import App.State.Reducers.ReduceTwitch
@@ -32,6 +36,8 @@ object RootReducer {
InviteState = ReduceInvite(State0.InviteState, Action),
LoginByPassword = ReduceLoginByPassword(State0.LoginByPassword, Action),
TwitchState = ReduceTwitch(State0.TwitchState, Action),
Home = ReduceHome(State0.Home, Action),
LocalBattleUserState = ReduceLocalBattleUser(State0.LocalBattleUserState, Action),
)
}
@@ -4,7 +4,9 @@
// 已迁移的切片用强类型;其余保持 Any?(原样透传),迁移一个改一个再重新生成。
package App.State
import App.State.Reducers.HomeStateSlice
import App.State.Reducers.InviteStateSlice
import App.State.Reducers.LocalBattleUserStateSlice
import App.State.Reducers.LoginByPasswordStateSlice
import App.State.Reducers.TwitchStateSlice
@@ -107,10 +109,10 @@ data class RootState(
val BattleChat: Any? = null,
/** 原 droppableGold(Vet / Wp.kth)。未迁移:原样透传。 */
val DroppableGold: Any? = null,
/** 原 home(s1 / (inline@home))。未迁移:原样透传。 */
val Home: Any? = null,
/** 原 localBattleUserState(lE / (inline@localBattleUserState))。未迁移:原样透传。 */
val LocalBattleUserState: Any? = null,
/** 原 home(s1 / (inline@home))。已迁移。 */
val Home: HomeStateSlice = HomeStateSlice(),
/** 原 localBattleUserState(lE / (inline@localBattleUserState))。已迁移。 */
val LocalBattleUserState: LocalBattleUserStateSlice = LocalBattleUserStateSlice(),
/** 原 soundSettings(Det / Wet().jth)。未迁移:原样透传。 */
val SoundSettings: Any? = null,
/** 原 gameSettings(c2 / g2().zss)。未迁移:原样透传。 */
@@ -106,7 +106,7 @@ object RootWiring {
)
/** 已迁移(由 RootReducer 真正归约)的字段。 */
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState")
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
@@ -9,7 +9,7 @@
// ⑥ 动作目录:816 个类与 138 个真实派发名对齐
// ⑦ 回放:真实 toString 样例的类/字段/类型校验
// ⑧ 全目录动作类都能按 KClass 路由
// ⑨~⑫ 切片 reducer:InviteState / LoginByPasswordState / TwitchState;⑫ 根 reducer 的 89 槽位委派
// ⑨~⑭ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
package App
@@ -22,15 +22,20 @@ import App.State.ActionCatalog
import App.State.ActionSamples
import App.State.ApplyInvite
import App.State.Reducers.ByPasswordStatus
import App.State.Reducers.HomeStateSlice
import App.State.Reducers.InviteStateSlice
import App.State.Reducers.InviteStatus
import App.State.Reducers.LocalBattleUserStateSlice
import App.State.Reducers.LoginByPasswordStateSlice
import App.State.Reducers.LoginOnly
import App.State.Reducers.ReduceHome
import App.State.Reducers.ReduceInvite
import App.State.Reducers.ReduceLocalBattleUser
import App.State.Reducers.ReduceLoginByPassword
import App.State.Reducers.ReduceTwitch
import App.State.Reducers.ResetLoginByPassword
import App.State.Reducers.ResetLoginByPasswordStatus
import App.State.Reducers.ShowShopDiscountNotification
import App.State.Reducers.TwitchStateSlice
import App.State.ReduceRoot
import App.State.RootReducer
@@ -42,8 +47,10 @@ import App.State.SetAccount
import App.State.SetDaysForNextLinking
import App.State.SetInviteNeed
import App.State.SetInviteStatus
import App.State.SetLocalWeaponResistanceProperty
import App.State.SetLogin
import App.State.SetPassword
import App.State.SetReArmorTemporaryDisabled
import App.State.SetRememberMe
import App.State.SetStatus
import App.State.SetTwitchEnabled
@@ -377,7 +384,50 @@ object StateCheck {
Check("切片 Twitch:接 Store 派发后状态已归约", Store.State.DaysForNextLinking == 2)
}
// ⑫ 根 reducer:89 个槽位的委派表 + 三个已迁移切片一次归约
// ⑫ 切片 reducer:HomeScreen(原 module_10032 的 s1 + 根 reducer `(inline@home)` 分支)
run {
val S0 = HomeStateSlice()
Check("切片 Home:默认值与 HomeScreen 一致", !S0.ShopDiscountNotification)
// 动作 o1(无 toString,定名 ShowShopDiscountNotification)→ copy(shopDiscountNotification = true)
val S1 = ReduceHome(S0, ShowShopDiscountNotification())
Check("切片 Home:ShowShopDiscountNotification 把折扣提示置 true", S1.ShopDiscountNotification)
Check("切片 Home:无关动作不改状态", ReduceHome(S1, AddFrags(1)) == S1)
val Store = ReduxStore(S0, { S, A -> ReduceHome(S, A) })
Store.Dispatch(ShowShopDiscountNotification())
Check("切片 Home:接 Store 派发后状态已归约", Store.State.ShopDiscountNotification)
}
// ⑬ 切片 reducer:LocalBattleUserState(原 module_10032 的 lE + 根 reducer `(inline@localBattleUserState)`)
run {
val S0 = LocalBattleUserStateSlice()
Check(
"切片 LocalBattleUser:默认值与原构造一致",
S0.WeaponResistanceProperty == null && !S0.IsReArmorTemporaryDisabled
)
val S1 = ReduceLocalBattleUser(S0, SetLocalWeaponResistanceProperty("FREEZE_RESISTANCE"))
Check(
"切片 LocalBattleUser:SetLocalWeaponResistanceProperty 只写武器抗性属性",
S1.WeaponResistanceProperty == "FREEZE_RESISTANCE" && !S1.IsReArmorTemporaryDisabled
)
val S2 = ReduceLocalBattleUser(S1, SetReArmorTemporaryDisabled(true))
Check(
"切片 LocalBattleUser:SetReArmorTemporaryDisabled 只改开关",
S2.IsReArmorTemporaryDisabled && S2.WeaponResistanceProperty == "FREEZE_RESISTANCE"
)
Check("切片 LocalBattleUser:SetReArmorTemporaryDisabled(false) 关掉", !ReduceLocalBattleUser(S2, SetReArmorTemporaryDisabled(false)).IsReArmorTemporaryDisabled)
Check("切片 LocalBattleUser:无关动作不改状态", ReduceLocalBattleUser(S2, AddFrags(1)) == S2)
val Store = ReduxStore(S0, { S, A -> ReduceLocalBattleUser(S, A) })
Store.Dispatch(SetReArmorTemporaryDisabled(true))
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 五个已迁移切片一次归约
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -428,20 +478,34 @@ object StateCheck {
"根:twitchState 委派到 g9 / uf.gta",
TwitchW != null && TwitchW.SliceClass == "g9" && TwitchW.Reducer == "uf.gta"
)
// 后两片是内联 IIFE,委派名是 `(inline@<field>)`
val HomeW = RootWiring.ByField("home")
val LocalBattleW = RootWiring.ByField("localBattleUserState")
Check(
"根:迁移表 = 三个已迁移字段,其余 86 个待迁",
RootReducer.Migrated == setOf("inviteState", "loginByPassword", "twitchState") &&
RootWiring.Unmigrated.size == 86
"根:home 委派到 s1 / (inline@home)",
HomeW != null && HomeW.SliceClass == "s1" && HomeW.Reducer == "(inline@home)"
)
Check(
"根:localBattleUserState 委派到 lE / (inline@localBattleUserState)",
LocalBattleW != null && LocalBattleW.SliceClass == "lE" &&
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 五个已迁移字段,其余 84 个待迁",
RootReducer.Migrated ==
setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState") &&
RootWiring.Unmigrated.size == 84
)
// 一次 ReduceRoot 里三个切片各归约自己的动作,互不串片
// 一次 ReduceRoot 里五个切片各归约自己的动作,互不串片
val S0 = RootState(User = "user-1", Screen = "Preloader")
val S1 = ReduceRoot(S0, ApplyInvite("uid-1"))
Check(
"根:ApplyInvite 只动 inviteState",
S1.InviteState.Invite == "uid-1" &&
S1.LoginByPassword == S0.LoginByPassword &&
S1.TwitchState == S0.TwitchState
S1.TwitchState == S0.TwitchState &&
S1.Home == S0.Home
)
val S2 = ReduceRoot(S1, SetLogin("pilot"))
@@ -459,27 +523,44 @@ object StateCheck {
S3.InviteState == S2.InviteState &&
S3.LoginByPassword == S2.LoginByPassword
)
val S4 = ReduceRoot(S3, ShowShopDiscountNotification())
Check(
"根:三次归约后三个切片各自保留结果",
S3.InviteState.Invite == "uid-1" &&
S3.LoginByPassword.Login == "pilot" &&
S3.TwitchState.TwitchAccount == "tank_tv"
"根:ShowShopDiscountNotification 只动 home",
S4.Home.ShopDiscountNotification && S4.TwitchState == S3.TwitchState
)
val S5 = ReduceRoot(S4, SetReArmorTemporaryDisabled(true))
Check(
"根:SetReArmorTemporaryDisabled 只动 localBattleUserState",
S5.LocalBattleUserState.IsReArmorTemporaryDisabled && S5.Home == S4.Home
)
Check(
"根:五次归约后五个切片各自保留结果",
S5.InviteState.Invite == "uid-1" &&
S5.LoginByPassword.Login == "pilot" &&
S5.TwitchState.TwitchAccount == "tank_tv" &&
S5.Home.ShopDiscountNotification &&
S5.LocalBattleUserState.IsReArmorTemporaryDisabled
)
// 未迁移字段原样透传;无关动作不改变任何字段
Check(
"根:未迁移字段原样透传",
S3.User == "user-1" && S3.Screen == "Preloader"
S5.User == "user-1" && S5.Screen == "Preloader"
)
Check("根:无关动作返回等值状态", ReduceRoot(S3, AddFrags(1)) == S3)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 3/89")
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 5/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
Store.Dispatch(SetTwitchEnabled(true))
Store.Dispatch(ShowShopDiscountNotification())
Check(
"根:接 Store 派发后两片同时归约",
Store.State.LoginByPassword.Status == ByPasswordStatus.LOGGING_IN && Store.State.TwitchState.IsTwitchEnabled
"根:接 Store 派发后三片同时归约",
Store.State.LoginByPassword.Status == ByPasswordStatus.LOGGING_IN &&
Store.State.TwitchState.IsTwitchEnabled &&
Store.State.Home.ShopDiscountNotification
)
}