feat(state): 收口 launchParams 切片为强类型透传(11/89)
This commit is contained in:
1 parent
1194c395b4
commit
435cd3191e
8 files changed
+87
-26
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` 动作名。
|
||||
- 已迁移切片 **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,对应主菜单商店项的折扣提示);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user