feat(state): 收口 launchParams 切片为强类型透传(11/89)

This commit is contained in:
TermiNexus committed 2026-10-23 15:29:05 +08:00
1 parent 1194c395b4
commit 435cd3191e
8 files changed
+87 -26

No files matched your search

+8 -5
View File
@@ -143,7 +143,7 @@
`names/root_wiring.json` + `TankiOffline/.../App/State/RootState.kt`、`RootWiring.kt`。
- 根状态 `z6`(`TOState`)的 **89 个槽位**全部入表;字段顺序取 `toString`,槽位取构造函数 `<名>_1`。
- 根 reducer `k6` 的逐切片委派记全:reducer 引用、委派表达式、顶层 `instanceof` 动作名。
- 已迁移切片 **10/89**:
- 已迁移切片 **11/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)
@@ -181,13 +181,16 @@
`TankPartData` / `TankPreviewSettings` / `HullEngineType` / `TurretMechanism` 见 `GaragePreviewModels.kt`;
12 个匿名动作按效果定名(`BG`→`CaptureScreenshot` … `tH`→`ClearDroneData`),
3 个单例臂定名 `ClearGaragePreviewParams` / `ShowGaragePreview` / `HideGaragePreview`,不在 `Actions` 表里。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 只归约上述十片,其余 **79 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 10/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **275 项全绿**,含 89 槽位完整性、十片一次归约互不串片、未迁移字段透传,
- `launchParams` → `(pass-through)`:原根 reducer 对 `hpx_1` 没有调用,缺省是 `ag()` 空 Map;
`RootState.LaunchParams` 收口为 `LaunchParamsStateSlice = Map<String, String>`,不新增 reducer/动作。
- `App/State/RootReducer.kt`:`ReduceRoot(State0, Action)` 归约上述 10 个有 reducer 的切片,
`launchParams` 与其余 **78 个字段原样透传**;
迁移进度用 `RootReducer.Coverage`(`已迁移 11/89`)与 `RootWiring.Unmigrated` 暴露。
- 自检:`TankiOffline/tools/RunStateTest.ps1` **280 项全绿**,含 89 槽位完整性、10 个归约切片一次归约互不串片、未迁移字段透传,
以及用 `action_trace_merged.json` 真实派发的两条商店动作(`SetCoinPaymentProviderUrl` / `ToggleShopActivity`)回放。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
`launchParams` 是纯透传(`(pass-through)`,无 reducer)。
`launchParams` 是纯透传(`(pass-through)`,无 reducer),已把字段收口为 `Map<String, String>`。
- 无 `toString` 的默认动作按证据定名(`root_wiring.json` 的 `AnonymousActions`):
`o1` → `ShowShopDiscountNotification`(`$_$.gl` 单例,由 module_18334 的 `Qx.qge` 派发;
效果是把 `HomeScreen.shopDiscountNotification` 置 true,对应主菜单商店项的折扣提示);
+4 -3
View File
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-10-23T06:35:15.369Z",
"generatedAt": "2026-10-23T07:24:48.002Z",
"module": "module_10032.js",
"RootState": {
"Class": "z6",
@@ -16,7 +16,8 @@
"screen",
"lootBoxes",
"garage",
"garagePreview"
"garagePreview",
"launchParams"
],
"AnonymousActions": [
{
@@ -1781,7 +1782,7 @@
"Delegate": "n.hpx_1",
"Actions": [],
"Default": "ag()",
"Migrated": false
"Migrated": true
},
{
"Field": "partner",
+13 -3
View File
@@ -489,6 +489,13 @@ const Migrated = {
lootBoxes: { Type: 'LootBoxesStateSlice', Reduce: 'ReduceLootBoxes' },
garage: { Type: 'GarageStateSlice', Reduce: 'ReduceGarage' },
garagePreview: { Type: 'GaragePreviewStateSlice', Reduce: 'ReduceGaragePreview' },
// 原根 reducer 对这一片没有调用,只有默认值 `ag()`(空 Map);这里仅做类型收口。
launchParams: {
Type: 'LaunchParamsStateSlice',
Default: 'emptyMap()',
Import: false,
PassThrough: true,
},
};
for (const R of Rows) R.Migrated = Object.prototype.hasOwnProperty.call(Migrated, R.Field);
@@ -683,6 +690,7 @@ Lines.push('// 已迁移的切片用强类型;其余保持 Any?(原样透传
Lines.push('package App.State');
Lines.push('');
for (const F of Object.keys(Migrated).sort()) {
if (Migrated[F].Import === false) continue;
Lines.push('import App.State.Reducers.' + Migrated[F].Type);
}
Lines.push('');
@@ -695,8 +703,10 @@ for (const R of Rows) {
const Prop = KotlinName(R.Field);
const M = Migrated[R.Field];
if (M) {
Lines.push(` /** 原 ${R.Field}(${R.SliceClass || '?'} / ${R.Reducer})。已迁移。 */`);
Lines.push(` val ${Prop}: ${M.Type} = ${M.Type}(),`);
const Kind = M.PassThrough ? '已迁移(透传,强类型)' : '已迁移';
const Init = M.Default || M.Type + '()';
Lines.push(` /** 原 ${R.Field}(${R.SliceClass || '?'} / ${R.Reducer})。${Kind}。 */`);
Lines.push(` val ${Prop}: ${M.Type} = ${Init},`);
} else {
Lines.push(` /** 原 ${R.Field}(${R.SliceClass || '?'} / ${R.Reducer})。未迁移:原样透传。 */`);
Lines.push(` val ${Prop}: Any? = null,`);
@@ -737,7 +747,7 @@ for (const R of Rows) {
}
WLines.push(' )');
WLines.push('');
WLines.push(' /** 已迁移(由 RootReducer 真正归约)的字段。 */');
WLines.push(' /** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */');
WLines.push(' val Migrated: Set<String> = setOf(' + Object.keys(Migrated).map((F) => KStr(F)).join(', ') + ')');
WLines.push('');
WLines.push(' /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */');
@@ -0,0 +1,9 @@
// LaunchParams.kt —— launchParams 切片(原根状态 z6 的 hpx_1)
//
// 原构造缺省值是 `ag()`,即空 Map;根 reducer k6 没有为这一片调用任何 reducer,
// 只是原样 copy 到下一状态。这里仅做类型收口:键和值都来自启动/URL 查询串,
// 原包用 `z2(key)`(Map.get)和 `x2(key)`(containsKey)读取。
package App.State
/** 启动参数表:`Map<String, String>`,缺省空表。 */
typealias LaunchParamsStateSlice = Map<String, String>
@@ -1,7 +1,7 @@
// RootReducer.kt —— 根 reducer 骨架(对应 module_10032 的根状态 z6 / TOState 与根 reducer k6)
//
// 原包 k6 对 89 个切片逐个调用各自的 reducer,再 `return n.cti(...)`(copy 出新状态)。
// 这里采用同一形状,但只真正归约已迁移的切片:
// 这里采用同一形状。已迁移的 11 个切片里,10 个真正调用 reducer,launchParams 只做类型收口:
//
// inviteState → ReduceInvite(aD / pD.er5)
// loginByPassword → ReduceLoginByPassword(gD / MD.cr6)
@@ -13,8 +13,9 @@
// lootBoxes → ReduceLootBoxes(aH / EH.wrv)
// garage → ReduceGarage(UB / hF,根委派 `i = hF(t, n)`)
// garagePreview → ReduceGaragePreview(IG / fs.eru,根委派 `fs.eru(t, n.epv_1)`)
// launchParams → 无 reducer(原根 reducer 原样透传;默认 `ag()` = emptyMap())
//
// 其余 79 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 其余 78 个字段按原样透传(defaultValue 取原构造)。每个切片 reducer 对无关动作都返回原状态,
// 所以一次 copy 里串起全部已迁移 reducer,与按动作路由再归约的结果一致。
// 迁移进度与每个字段的证据见生成物 RootWiring.kt(不要手改)。
package App.State
@@ -33,7 +34,7 @@ import App.State.Reducers.ReduceTwitch
/** 根 reducer:`k6`。 */
object RootReducer {
/** 已迁移(真正归约)的字段名,来自生成物 RootWiring.Migrated。 */
/** 已迁移(强类型;launchParams 为透传)的字段名,来自生成物 RootWiring.Migrated。 */
val Migrated: Set<String> get() = RootWiring.Migrated
/** 形如「已迁移 3/89」,给日志与自检用。 */
@@ -142,8 +142,8 @@ data class RootState(
val MainQuest: Any? = null,
/** 原 language(? / m6().jtg)。未迁移:原样透传。 */
val Language: Any? = null,
/** 原 launchParams(? / (pass-through))。未迁移:原样透传。 */
val LaunchParams: Any? = null,
/** 原 launchParams(? / (pass-through))。已迁移(透传,强类型)。 */
val LaunchParams: LaunchParamsStateSlice = emptyMap(),
/** 原 partner(UJ / (inline@partner))。未迁移:原样透传。 */
val Partner: Any? = null,
/** 原 entrance(hD / Bv.pth)。未迁移:原样透传。 */
@@ -105,8 +105,8 @@ object RootWiring {
SliceWiring("supportUrl", "ipy_1", "I4", "Yl.ct7", "Yl.ct7(t, n.ipy_1)", listOf("?R4", "SetUrl")),
)
/** 已迁移(由 RootReducer 真正归约)的字段。 */
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview")
/** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */
val Migrated: Set<String> = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "launchParams")
/** 未迁移字段:归约时原样透传,作为后续迁移清单。 */
val Unmigrated: List<SliceWiring> get() = All.filter { it.Field !in Migrated }
@@ -9,9 +9,9 @@
// ⑥ 动作目录:816 个类与 138 个真实派发名对齐
// ⑦ 回放:真实 toString 样例的类/字段/类型校验
// ⑧ 全目录动作类都能按 KClass 路由
// ⑨~⑳ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
// ⑨~㉑ 切片 reducer:InviteState / LoginByPasswordState / TwitchState / HomeScreen / LocalBattleUserState /
// Shop(含 U7 类别表与 w3 物品表两个子 reducer)/ Screen / LootBoxes / Garage / GaragePreview;
// ⑭ 根 reducer 的 89 槽位委派
// ㉑ launchParams 是纯透传切片(无 reducer/动作),只做 Map 类型收口;⑭ 根 reducer 的 89 槽位委派
//
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
package App
@@ -653,7 +653,7 @@ object StateCheck {
Check("切片 LocalBattleUser:接 Store 派发后状态已归约", Store.State.IsReArmorTemporaryDisabled)
}
// ⑭ 根 reducer:89 个槽位的委派表 + 十个已迁移切片一次归约
// ⑭ 根 reducer:89 个槽位的委派表 + 11 个已迁移切片(10 归约 + launchParams 透传)
run {
Check("根:根状态 89 个切片槽位", RootWiring.All.size == 89)
Check(
@@ -683,7 +683,7 @@ object StateCheck {
RootWiring.ByField("home")!!.Actions.none { it.startsWith("?") }
)
Check(
"根:只有 1 个纯透传未具名",
"根:唯一纯透传切片是 launchParams(已强类型收口)",
RootWiring.All.count { it.Reducer == "(pass-through)" } == 1 &&
RootWiring.ByField("launchParams")!!.Actions.isEmpty()
)
@@ -717,14 +717,14 @@ object StateCheck {
LocalBattleW.Reducer == "(inline@localBattleUserState)"
)
Check(
"根:迁移表 = 十个已迁移字段,其余 79 个待迁",
"根:迁移表 = 11 个已迁移字段,其余 78 个待迁",
RootReducer.Migrated ==
setOf(
"inviteState", "loginByPassword", "twitchState", "home",
"localBattleUserState", "shop", "screen", "lootBoxes", "garage",
"garagePreview",
"garagePreview", "launchParams",
) &&
RootWiring.Unmigrated.size == 79
RootWiring.Unmigrated.size == 78
)
// 一次 ReduceRoot 里八个切片各归约自己的动作,互不串片
@@ -780,7 +780,7 @@ object StateCheck {
S5.User == "user-1" && S5.Screen.Current == NamedScreen("Preloader")
)
Check("根:无关动作返回等值状态", ReduceRoot(S5, AddFrags(1)) == S5)
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 10/89")
Check("根:Coverage 文本", RootReducer.Coverage == "已迁移 11/89")
val Store = ReduxStore(RootState(), { S, A -> ReduceRoot(S, A) })
Store.Dispatch(SetStatus("LOGGING_IN"))
@@ -2292,6 +2292,43 @@ object StateCheck {
)
}
// ㉑ 切片类型收口:launchParams(原 hpx_1;根 reducer 纯透传,无动作)
run {
val S0 = RootState()
Check(
"切片 LaunchParams:默认是空 Map",
S0.LaunchParams.isEmpty()
)
val Params = mapOf(
"token" to "token-1",
"singleUseHash" to "hash-1",
"changeUidHash" to "uid-hash-1",
"failed-redirect-url" to "https://example.test/failed",
)
val S1 = S0.copy(LaunchParams = Params)
Check(
"根:launchParams 在无关动作后原样保留",
ReduceRoot(S1, AddFrags(1)).LaunchParams == Params
)
Check(
"根:其他切片动作不修改 launchParams",
ReduceRoot(S1, ApplyInvite("uid-1")).LaunchParams == Params
)
val W = RootWiring.ByField("launchParams")!!
Check(
"根:launchParams 是无 SliceClass / 无动作的纯透传",
W.SliceClass == "?" && W.Reducer == "(pass-through)" &&
W.Delegate == "n.hpx_1" && W.Actions.isEmpty()
)
Check(
"根:launchParams 计入迁移表且不再出现在待迁清单",
"launchParams" in RootReducer.Migrated &&
RootWiring.Unmigrated.none { it.Field == "launchParams" }
)
}
val Failed = Results.filter { !it.second }
println("TankiOffline 状态层自检:${Results.size} 项")
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)