docs(m126 r3): 收窄「唯一一份」的说法,并把 ③ 的承诺改成真话

纯文档,零逻辑改动(`git diff` 里每一行都是 `//!` / `///`)。

P2-1「唯一一份」不成立:`cli/src/cli/help.rs` 里 `ull help instances` 的词表行是
**会打印给用户看的第二份逐字副本**。四处说法收窄为「源头表在 core + 另一处逐字副本在
help.rs,由 `help_lists_every_stable_id` 按 `ALL` 遍历守着」:`game/loader.rs` 的模块
文档(原「生产代码里这七个串只有一处」)与 `as_str` doc(原「生产代码里唯一一份 id
字面量」)、`cli/commands/instances.rs` 的模块文档(原「全仓唯一一份 id 字面量」)与
`loader_json` doc(原「两样都只该有一份」)、`presenter/dto/instances.rs` 的 as_str doc
与契约测试 doc、`addon/loader/mod.rs` 的两处「唯一真相源」。

P2-2「③ 拦得住静默变原版」对显示路径不成立:`presenter/src/host/game.rs::dto_kind` 里
糊一支 `=> Vanilla`、同时把契约与 help 补齐,三个 crate 全绿而磁贴静默显示「原版」。
现在写的是「③ 拦的是**对外词表**(加种类、改词表都红),但**拦不住显示路径**,那处目前
没有守卫(已知缺口,另行记账)」。

顺带只做排版级的行数回收:loader.rs 400 → 397(≤400,仍留余量)。
门禁:cargo fmt --all --check → FMT_OK(本轮按要求只跑 fmt)。
This commit is contained in:
AskaEth committed 2026-11-05 18:09:00 +08:00
1 parent 14ac0dc248
commit f6b7759aa8
4 files changed
+35 -30

No files matched your search

+8 -6
View File
@@ -8,10 +8,12 @@
//!
//! 每一行实例带一个 `loader` 对象:`{"kind": <稳定 id>, "version": <版本段>}`。它是
//! **`version_id` 的解析结果**,解析本身不在这里——[`unlucky_core::game::InstanceLoader::parse`]
//! 是全仓唯一的 id → 加载器入口(M122 收敛)。种类 → 稳定 id 的词表同样不在本 crate:
//! [`unlucky_core::game::LoaderKind::as_str`] 是全仓唯一一份 id 字面量(M126 收敛),这里直接
//! 调它,**连翻译层都没有**——词表与 id 匹配一样,两处各算一遍就会漂(M120 的「刚装的
//! Fabric 显示成原版」)。
//! 是全仓唯一的 id → 加载器入口(M122 收敛)。种类 → 稳定 id 的词表也不在本 crate 的代码里:
//! [`unlucky_core::game::LoaderKind::as_str`] 是生产代码里 id 的**源头表**(M126 收敛),这里
//! 直接调它,**连翻译层都没有**——词表与 id 匹配一样,两处各算一遍就会漂(M120 的「刚装的
//! Fabric 显示成原版」)。生产代码里唯一的逐字副本是 `cli::help` 里 `ull help instances` 的
//! 词表行(会打印给用户),由本模块的 `help_vocabulary_tests::help_lists_every_stable_id`
//! 按 `ALL` 遍历守着。
//!
//! ## 约定一:`loader` 恒存在,原版是 `{"kind":"vanilla","version":""}`
//!
@@ -131,8 +133,8 @@ fn option_path(path: Option<&PathBuf>) -> Option<String> {
/// 版本 id → `loader` 对象(`{"kind": …, "version": …}`)。
///
/// 解析走 [`InstanceLoader::parse`](全仓唯一入口);种类 → 稳定 id 走
/// [`unlucky_core::game::LoaderKind::as_str`](词表的唯一真相源,M126)。这里既**不写 id
/// 匹配**,也**不写词表**:两样都只该有一份,且都在 core。原版同样给出对象
/// [`unlucky_core::game::LoaderKind::as_str`](词表的源头,M126)。这里既**不写 id
/// 匹配**,也**不写词表**:词表的源头只在 core(副本见模块文档)。原版同样给出对象
/// (`kind = vanilla`、`version = ""`),不缺键、不为 `null`——见模块文档的「约定一」。
fn loader_json(version_id: &str) -> Value {
let parsed = InstanceLoader::parse(version_id);
+3 -3
View File
@@ -64,7 +64,7 @@ pub enum LoaderKind {
impl LoaderKind {
/// 解析命令行 / 整合包清单里的加载器名(大小写不敏感)。
///
/// 七个 id 的匹配**委派**给 [`crate::game::LoaderKind::parse`](词表唯一真相源,M126),
/// 七个 id 的匹配**委派**给 [`crate::game::LoaderKind::parse`](词表的源头,M126),
/// 本方法只多收一个**输入别名** `neo-forge`:那是 Modrinth 的拼法(mrpack
/// `dependencies` 的键、CF 包里也有这种写法),**只进不出**——[`Self::as_str`] 绝不产出
/// 它。识别得出但本启动器装不了的几家(原版 / OptiFine / LiteLoader)返回 `None`。
@@ -94,8 +94,8 @@ impl LoaderKind {
/// 稳定字符串 id(进版本 id、JSON 输出与日志)。
///
/// 词表**不在本模块**:[`crate::game::LoaderKind::as_str`] 是「种类 → 对外稳定 id」的唯一
/// 真相源(M126)。这里只做**结构映射**(一个 id 字面量都没有)——安装侧拼出来的
/// 词表**不在本模块**:[`crate::game::LoaderKind::as_str`] 是「种类 → 对外稳定 id」的源头
/// (M126)。这里只做**结构映射**(一个 id 字面量都没有)——安装侧拼出来的
/// `<mc>-<kind>-<loader>` 必须能被 [`crate::game::InstanceLoader::parse`] 认回来,委派让
/// 「两边各写一份、改一边忘了另一边」这件事物理上做不到。
#[must_use]
+20 -18
View File
@@ -1,5 +1,5 @@
//! 版本 id → 加载器:**全仓唯一**的 id 解析入口(M122),外加「种类 → 对外稳定 id」的
//! **全仓唯一**命名词表(M126,见 [`LoaderKind::as_str`])。
//! 版本 id → 加载器:**全仓唯一**的 id 解析入口(M122),外加「种类 → 对外稳定 id」的命名词表
//! **源头**(M126,见 [`LoaderKind::as_str`];副本只有一处,见下)。
//!
//! # 为什么要这一个模块
//!
@@ -16,24 +16,25 @@
//! # 命名词表只有一张表(M126)
//!
//! 契约(0.2.0 已发布形状,**不许改名**):`vanilla` / `fabric` / `quilt` / `forge` /
//! `neoforge` / `optifine` / `liteloader`。生产代码里这七个串只有**一处**:下面那次
//! `neoforge` / `optifine` / `liteloader`。**生产代码里这份词表有两处,都要改**:① 下面那次
//! `loader_kind_vocab!` 展开——枚举、`ALL`、`as_str`、`has_marker` 全从同一张表长出来,所以
//! 「加了一支却漏进清单」物理上做不到(理由见宏的注释)。安装侧与 presenter 的 `as_str` /
//! `parse` 都是**委派**,CLI 直接调;唯一不在词表里的字面量是安装侧收的输入别名 `neo-forge`
//! (Modrinth 拼法,只进不出)。
//! 「加了一支却漏进清单」物理上做不到(理由见宏的注释);② 会打印给用户看的
//! `crates/cli/src/cli/help.rs` 里 `ull help instances` 的词表行——**逐字副本**、没有委派,
//! 由 CLI 的 `help_lists_every_stable_id`(按 `ALL` 遍历)守着。安装侧与 presenter 的 `as_str` /
//! `parse` 都是**委派**,CLI 的 `loader.kind` 直接调;唯一不进词表的字面量是安装侧收的输入别名
//! `neo-forge`(Modrinth 拼法,只进不出)。
//!
//! 加一家加载器要动:① `loader_kind_vocab!` 表里加一行(id + 认不认标记)→ 枚举 / `ALL` /
//! `as_str` / `has_marker` / 解析顺序一起长出来,漏不了;② presenter 的 `to_core`·`from_core`
//! **编不过**(逼你决定 DTO 怎么显示它),能装的话安装侧 `as_str`·`from_core` 也各差一支;
//! ③ 契约测试**会红**:presenter 的 `loader_kind_ids_are_the_released_contract` 把 core 的
//! `ALL` 与独立手写的七个 id 做**集合比较**,CLI 的 `help_lists_every_stable_id` 按 `ALL`
//! 遍历 help 词表,`json_rows_*` 钉真二进制输出面——三处都逼你确认「对外词表真的要多一个
//! id」,因为 0.2.0 已经发出去过。
//! ③ 契约测试**会红**:presenter 的 `loader_kind_ids_are_the_released_contract` 拿 core 的 `ALL`
//! 与独立手写的七个 id 做**集合比较**,CLI 的 `help_lists_every_stable_id` 按 `ALL` 遍历 help
//! 词表,`json_rows_*` 钉真二进制输出面(0.2.0 已发出去过,所以这步是确认而不是可选)。
//!
//! ⚠️ 分工要看准:**编译器只拦「漏 match」**。新种类在三处消费方用最省事的 `=> Vanilla` 糊
//! 过去是**编得过**的(那时它会被静默当成原版);真正拦住这件事的是 ③ 里那条集合断言,不是
//! 编译器。**改名**(`neoforge` → `neo_forge`)同理:往返测试是绿的(词表与解析同源、改名自
//! 洽),只有 ③ 会红——那里**故意**再抄一遍七个串。
//! ⚠️ 分工要看准:**编译器只拦「漏 match」**,新种类在消费方用 `=> Vanilla` 糊过去编得过。
//! ③ 那条集合断言拦的是**对外词表**,但**拦不住显示路径**:`presenter/src/host/game.rs::dto_kind`
//! 里糊一支 `=> Vanilla` 并把契约与 help 补齐后,三个 crate 全绿而磁贴静默显示「原版」——那处
//! 目前**没有守卫**(已知缺口,另行记账)。**改名**同理:往返测试绿,只有 ③ 会红(故意再抄)。
//!
//! # 解析规则(按整段前缀匹配,大小写不敏感,**最靠左的标记胜**)
//!
@@ -42,9 +43,8 @@
//! 段(`fabric-loader-…`)或同段里的 `_loader` 尾巴算标记的一部分,不是版本段;
//! 2. 该段里标记之后剩下的部分,加上它右边的所有段,按 `-` 拼回版本段
//! (`_` 之类的原样保留,所以 `1.13.2-OptiFine_HD_U_E6` 的版本是 `HD_U_E6`);
//! 3. 例外:版本段有 **≥2 段** 且**最后一段**长得像 MC 版本(`1.20.1` 这种纯数字点号)时
//! 砍掉最后一段——那是老 HMCL 的 `fabric-loader-<loader>-<mc>` 写法。一段都不剩的
//! `0.16.5` 不会被砍;`0.20.0-beta.9` 的末段不是数字,也不会被砍;
//! 3. 例外:版本段 **≥2 段**且末段像 MC 版本(`1.20.1` 这种纯数字点号)时砍掉末段——老 HMCL 的
//! `fabric-loader-<loader>-<mc>` 写法;`0.16.5`(只剩一段)与 `0.20.0-beta.9` 不受影响;
//! 4. 一个标记都没找到 ⇒ 原版(版本段空串)。只把加载器写进版本 JSON、id 里没痕迹的实例
//! (整包导入时被重命名成 `我的整合包` 那种)会**诚实**返回「原版」——那不等于没装加载器。
@@ -80,7 +80,9 @@ macro_rules! loader_kind_vocab {
/// 对外稳定 id(`--json` 的 `loader.kind`、GUI 词表、`--loader` 的取值)。
///
/// **生产代码里唯一的一份 id 字面量**(0.2.0 已发布契约);穷尽 match:加一支就编不过。
/// 这是**生产代码里 id 的源头表**(0.2.0 已发布契约):别处要么委派到这里,要么是逐字
/// 副本(`ull help instances` 的词表行,由 CLI 的 `help_lists_every_stable_id` 守着)。
/// 穷尽 match:加一支就编不过。
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
+4 -3
View File
@@ -76,8 +76,8 @@ impl LoaderKind {
/// 稳定 id(`vanilla` / `fabric` / …;GUI 与 `--json` 同词表)。
///
/// **委派**给 [`CoreLoaderKind::as_str`](M126):整张词表在 core 只有一份字面量
/// (0.2.0 已发布契约),这里只剩一次结构映射。
/// **委派**给 [`CoreLoaderKind::as_str`](M126):词表的源头表在 core(0.2.0 已发布契约;
/// 另一处逐字副本是 `ull help instances` 的词表行),这里只剩一次结构映射。
#[must_use]
pub fn as_str(self) -> &'static str {
self.to_core().as_str()
@@ -282,7 +282,8 @@ mod tests {
}
/// 对外契约:七个 id 在测试里**独立抄一遍**(0.2.0 已发布形状),并与 core 的 `ALL` 做
/// **集合比较**——这是「加了一支枚举却没人更新对外词表」的唯一守卫。
/// **集合比较**——这是对外词表的守卫(与 CLI 的 help 守卫互补;显示路径
/// `presenter::host::game::dto_kind` 目前没有守卫,见 `game::loader` 的模块文档)。
///
/// 为什么不是编译器管:编译期只拦得到「漏 match」,而新种类在三处消费方用最省事的
/// `=> Vanilla` 糊过去**编得过**,那时新加载器会静默显示成原版(`game::loader` 模块文档