fix(launch): M157 r3 周快照门限按继承根版本 + 保存失败保留草稿(复审 P2-1/P2-2)

P2-1 周快照 + 加载器的 --graphicsBackend 静默丢失:presenter host::launch::build_request 与 cli snapshot::prepare 给 SnapshotInput.game_version 传 None,装配按带加载器后缀的 version_id(26w14a-fabric-0.16.5)判门限,GameVersion::is_weekly_snapshot 的 6 字符形态认不出来。两个调用点改为传继承链的 MC 根版本(25 行注释说明「门槛与拼装必须同一口径」)。回归:core launch::overrides 的周快照实际装配用例(正/负两档)、presenter host::launch::tests、cli snapshot::tests 各一条根版本断言。

P2-2 保存失败丢草稿:app/update/instances.rs 的 Save 分支派发前就把 launch_form.dirty 清掉,失败事件后 pump 用旧快照重建视图并覆盖表单。改为派发不清脏;LaunchForm::sync 只在「回程快照已等于表单」(= 落盘确认)时清脏并重播种,否则保留草稿。回归:failed_save_keeps_the_draft / confirmed_save_clears_dirty_and_reseeds / save_dispatch_keeps_the_dirty_flag。

顺带:launch::overrides 测试拆到 overrides/tests.rs(守 400 行)。
This commit is contained in:
AskaEth committed 2026-11-07 10:36:37 +08:00
1 parent c76a1437e6
commit 211e07b470
9 files changed
+640 -311

No files matched your search

+8 -1
View File
@@ -241,7 +241,14 @@ fn backend_gate(ctx: &Ctx, version_id: &str) -> (bool, String) {
}
/// 继承链根(原版层)的 id——能力位与版本门限都以它为准(loader 层的 id 带加载器后缀)。
fn root_version_id(layout: &unlucky_core::game::GameLayout, manifest: &VersionJson) -> String {
///
/// `pub(crate)`:`snapshot::prepare` 的装配(`SnapshotInput.game_version`)也要这一份口径,
/// 门槛判定(本模块的 `backend_gate`)与拼装必须用**同一个**根版本,否则会出现
/// 「UI 说可用、命令行里没有这个参数」的静默偏差。
pub(crate) fn root_version_id(
layout: &unlucky_core::game::GameLayout,
manifest: &VersionJson,
) -> String {
let mut current = manifest.id.clone().unwrap_or_default();
let mut parent = manifest.inherits_from.clone().filter(|id| !id.is_empty());
for _ in 0..16 {
+95 -1
View File
@@ -131,11 +131,14 @@ pub fn prepare(ctx: &Ctx, input: &PlanInput) -> Result<Plan, CliError> {
.clone()
.or_else(|| resolved.asset_index.as_ref().map(|index| index.id.clone()));
// 同 presenter:装配门限按**继承链的 MC 根版本**判(loader 实例的 version_id 带加载器后缀,
// 周快照的 6 字符形态判据在那儿失效,`--graphicsBackend` 会被静默跳过)。
let root_version = super::instance_launch::root_version_id(&game, &manifest);
let snapshot = SnapshotInput {
version_id: &version_id,
version_name: Some(&input.instance_name),
version_type: None,
game_version: None,
game_version: Some(root_version.as_str()),
main_class: resolved.main_class.as_deref(),
jvm_arguments: &expanded.jvm,
game_arguments: &expanded.game,
@@ -235,3 +238,94 @@ fn describe_warning(warning: &AdapterWarning) -> String {
}
}
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use super::*;
/// 一次性临时数据根(`--data-dir` 的等价物:custom 模式 → `<root>/.ULL` + `<root>/.minecraft`)。
struct TempDir(PathBuf);
impl TempDir {
fn new(tag: &str) -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"ull-cli-snapshot-{tag}-{}-{seq}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&path);
std::fs::create_dir_all(&path).expect("建临时目录");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn ctx_at(dir: &TempDir) -> Ctx {
let globals = crate::cli::Globals {
data_dir: Some(dir.path().to_path_buf()),
..crate::cli::Globals::default()
};
Ctx::resolve(&globals).expect("解析数据根")
}
fn write_version(ctx: &Ctx, id: &str, json: &str) {
let path = ctx.game().version_json(id);
std::fs::create_dir_all(path.parent().expect("版本目录")).expect("版本目录");
std::fs::write(&path, json).expect("写版本 JSON");
}
/// **周快照 + 加载器实例**(M157 r3 复审 P2-1):`prepare` 交给装配的游戏版本是
/// 继承链根的 `26w14a`,不是 `26w14a-fabric-0.16.5`。
///
/// 抓得住:`SnapshotInput.game_version` 回退成 `None`(装配用带后缀的 id 判门限,
/// `--graphicsBackend` 会被静默跳过,而 CLI 自己的门槛显示「可下发」)。
/// 抓不住:完整 argv(`crates/core/src/launch/overrides/tests.rs` 的周快照用例)。
#[test]
fn weekly_loader_instance_is_assembled_against_the_mc_root_version() {
let dir = TempDir::new("weekly");
let ctx = ctx_at(&dir);
write_version(
&ctx,
"26w14a",
r#"{"id":"26w14a","mainClass":"net.minecraft.client.main.Main",
"libraries":[{"name":"org.lwjgl:lwjgl-vulkan:3.4.3"}]}"#,
);
write_version(
&ctx,
"26w14a-fabric-0.16.5",
r#"{"id":"26w14a-fabric-0.16.5","inheritsFrom":"26w14a",
"mainClass":"net.fabricmc.loader.impl.launch.knot.KnotClient"}"#,
);
let plan = prepare(
&ctx,
&PlanInput {
version_id: "26w14a-fabric-0.16.5".to_string(),
instance_name: "周快照 Fabric".to_string(),
..PlanInput::default()
},
)
.expect("装配应成功");
assert_eq!(
plan.request.instance.version.raw(),
"26w14a",
"装配的游戏版本必须是继承链的 MC 根版本(CLI 门槛与拼装必须同一口径)"
);
assert!(plan.request.instance.version.is_weekly_snapshot());
assert!(plan.request.manifest.has_vulkan_backend);
}
}