From 435cd3191e605feb8bd18bf77b61b654f7370982 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 23 Oct 2026 15:29:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(state):=20=E6=94=B6=E5=8F=A3=20launchParam?= =?UTF-8?q?s=20=E5=88=87=E7=89=87=E4=B8=BA=E5=BC=BA=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E9=80=8F=E4=BC=A0=EF=BC=8811/89=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DTank/docs/KtRebuild_Plan.md | 13 +++-- 3DTank/names/root_wiring.json | 7 +-- 3DTank/tools/gen_root_wiring.js | 16 ++++-- .../jsMain/kotlin/App/State/LaunchParams.kt | 9 ++++ .../jsMain/kotlin/App/State/RootReducer.kt | 7 +-- .../src/jsMain/kotlin/App/State/RootState.kt | 4 +- .../src/jsMain/kotlin/App/State/RootWiring.kt | 4 +- .../app/src/jsMain/kotlin/App/StateCheck.kt | 53 ++++++++++++++++--- 8 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/State/LaunchParams.kt diff --git a/3DTank/docs/KtRebuild_Plan.md b/3DTank/docs/KtRebuild_Plan.md index b44f855..e641c71 100644 --- a/3DTank/docs/KtRebuild_Plan.md +++ b/3DTank/docs/KtRebuild_Plan.md @@ -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`,不新增 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@)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); - `launchParams` 是纯透传(`(pass-through)`,无 reducer)。 + `launchParams` 是纯透传(`(pass-through)`,无 reducer),已把字段收口为 `Map`。 - 无 `toString` 的默认动作按证据定名(`root_wiring.json` 的 `AnonymousActions`): `o1` → `ShowShopDiscountNotification`(`$_$.gl` 单例,由 module_18334 的 `Qx.qge` 派发; 效果是把 `HomeScreen.shopDiscountNotification` 置 true,对应主菜单商店项的折扣提示); diff --git a/3DTank/names/root_wiring.json b/3DTank/names/root_wiring.json index d3bccca..82b06d4 100644 --- a/3DTank/names/root_wiring.json +++ b/3DTank/names/root_wiring.json @@ -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", diff --git a/3DTank/tools/gen_root_wiring.js b/3DTank/tools/gen_root_wiring.js index 2f222ba..dded6b8 100644 --- a/3DTank/tools/gen_root_wiring.js +++ b/3DTank/tools/gen_root_wiring.js @@ -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 = setOf(' + Object.keys(Migrated).map((F) => KStr(F)).join(', ') + ')'); WLines.push(''); WLines.push(' /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */'); diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/LaunchParams.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/LaunchParams.kt new file mode 100644 index 0000000..e61df19 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/LaunchParams.kt @@ -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`,缺省空表。 */ +typealias LaunchParamsStateSlice = Map diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt index 0deaa61..cf3477e 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootReducer.kt @@ -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 get() = RootWiring.Migrated /** 形如「已迁移 3/89」,给日志与自检用。 */ diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt index d42b3dc..5311c49 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootState.kt @@ -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)。未迁移:原样透传。 */ diff --git a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt index dd140cd..f4efe3f 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/State/RootWiring.kt @@ -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 = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview") + /** 已迁移(强类型;launchParams 为透传,不调用 reducer)的字段。 */ + val Migrated: Set = setOf("inviteState", "loginByPassword", "twitchState", "home", "localBattleUserState", "shop", "screen", "lootBoxes", "garage", "garagePreview", "launchParams") /** 未迁移字段:归约时原样透传,作为后续迁移清单。 */ val Unmigrated: List get() = All.filter { it.Field !in Migrated } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt index 3b0198d..bb47e12 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/StateCheck.kt @@ -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)