AskaEth
|
8f5dac2ed6
|
fix(app-iced): 三处 Pixel 偏离按角色语义收敛(M248)
P2:Elevated 的容器改由高程令牌推导(Elevation::Level1.surface_role),解析值仍是
SurfaceContainerLow,图上逐像素不变;「比 Surface 高一档」从此只有 elevation.rs 一处定义,
组件里不再手抄角色名。
P3:Outlined 的 1dp 描边 OutlineVariant → Outline。design/color/mod.rs 的对比度纪律里
Outline 才是「非文字元素(目标 3:1)」,OutlineVariant 是纯装饰分隔线;图标按钮那一式本来
就是 Outline,文字按钮此前与它不一致(全站两套描边)。实测 Outline/Surface:Ink 5.08、
Silver 4.24(OutlineVariant 1.97 / 1.62)。
P4:进度轨道(线性 + 环形)SecondaryContainer → SurfaceContainerHighest——轨道是「还剩多少」
的背景尺,强调色只属于指示条。与 A1(LinearProgressIndicatorTokens.TrackColor)的差异在
progress.rs 模块文档与 geometry.rs 的 12.2 行登记,gallery 图注同步改成角色名。
判据:新增 buttons/tests/roles.rs 与进度轨道用例,断言的是「取到的角色」与 Ink / Silver /
Scheme::from_seed 三套方案下解析出的颜色,不钉任何 hex;三处各做一次变异自证(改回旧值即红)。
另修 feedback probe 的定位判据:轨道换成中性容器面后,「画布→轨道」的混色轴穿过中性文字,
按「计数」会把图注行数成轨道,改按「连续段」判(bands_by_run);顺带修掉 indeterminate_row
把标题行当主色带的既有问题(用改前的图 + 改前的探针复现过,与本次换色无关)。
|
2026-11-11 14:32:26 +08:00 |
|
AskaEth
|
81710ba38c
|
Merge branch 'feat/saves-write-operations-rename-delete-bac'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 14:32:12 +08:00 |
|
AskaEth
|
32384b952e
|
chore(presenter,app-iced): 复审 r2 三项(文档对齐 / 拆 dialog.rs / 收窄关闭路径)
1. 文档自相矛盾(复审 r2 第 1 项):`SavesState::apply` 的文档还写着「无论成败都清『在飞』与
『确认中』」——改完之后这是假的,而且与模块文档「安全判据第 3 条」互相打脸。现在逐支写清:
`Ok` 清 draft/pending、`confirm` 只在被确认的世界已不在快照里时清并登记 `closed_confirm`;
`Err` 只清 pending。生命周期表里的 `dismissed` 改成真字段名 `closed_confirm`(消费点是
`app/state/refresh.rs::settle_saves_confirm`)。
2. 拆 `app/update/dialog.rs`(复审裁决):397 行里 262 行是内联 `mod tests`,而它装的是
主按钮 / Esc / 次按钮三条不可逆分支。照仓内先例(`app/dialog.rs` + `app/dialog/tests.rs`)
把测试体搬到 `app/update/dialog/tests.rs`,**纯搬移**:`dialog.rs` 397 → 138,
`dialog/tests.rs` 262;从 `6c87e2a` 的 `dialog.rs` 里抽出 `mod tests` 去一层缩进后与
新文件**逐行比对 0 处差异**(262 = 262)。
3. 收窄关闭路径(复审 r2 加固):`SavesState::close_confirm(current)` 只在 `current` 等于登记的
那个框时才清;`clear_confirm()` 保持无条件(换实例/复位用)。`escape()` 与 `Secondary|Dismiss`
改成传当前框 id。新用例 `closing_another_dialog_leaves_our_target_alone`(别人关框不清我们的
目标;自己关框才清)。
|
2026-11-11 14:25:26 +08:00 |
|
AskaEth
|
6c87e2ad8a
|
fix(presenter,app-iced): 删除确认绑定到对话框身份 + 快照不再吃掉目标(M247 r2 复审)
复审 r1 P1-1(安全):`SavesState::confirm` 的「关闭即清」在 Esc 这条路径上没有落实,
而主按钮分支无条件先取目标 —— 「删存档 → Esc → 崩溃卡片 → 导出诊断包」会静默删掉那个世界。
两层修,各自有独立用例钉住:
1. **认框不认人**:`confirm` 改成 `Option<(DialogId, String)>`,`take_confirm(current)`
只在 id 相等时交付;`DialogMessage::Primary` 传 `dialogs.current().id`。即使别的路径漏清,
也核销不了别人那个框。用例 `a_leaked_target_cannot_be_redeemed_by_another_dialog`
(去掉 id 校验即红:实测「漏清的目标被别的框兑现了:世界被静默删除」)。
2. **关闭路径补全**:`update/dialog.rs::escape()` 补 `clear_confirm()`(次按钮/遮罩早就清了,
Esc 漏了)。用例 `escaping_the_delete_dialog_disarms_it_for_the_next_one` 复现整条事故链,
断言没有下发 `DeleteWorld`、框走自己的路关掉(去掉这行即红)。
P2-1:`changed()` 不再无条件清 `confirm`(一拍无关快照会让主按钮变成静默空操作)——
只在「被确认的世界已经从快照里消失」时清,并登记 `closed_confirm` 让 app 层**收起那个框**
(`refresh_view → settle_saves_confirm`),避免留一个点了没反应的按钮。`failed()` 同理不动 confirm。
用例:`an_unrelated_snapshot_keeps_the_confirmation_redeemable`、`a_vanished_world_closes_its_confirmation`、
`an_unrelated_snapshot_no_longer_drops_the_confirmation`、`the_confirmation_is_dropped_and_reported_when_the_world_vanishes`。
P2-2:`catalog/instances.rs` 那段「这一版只读 / InstancesSavesReadOnly」的陈旧说明改成现状
(写操作已接上,边界由 core 的 `write::boundary` 守)。
拆分(400 行):`presenter/src/state/saves.rs` 的交互态方法 → `state/saves/interaction.rs`、
判据 → `state/saves/tests.rs`;`app/update/dialog.rs` 的接线用例内联在该文件(5 条)。
|
2026-11-11 13:51:07 +08:00 |
|
AskaEth
|
8c67e4eca6
|
fix(presenter,app-iced): 「游戏在跑」挡在落 core 之前(M247 r2)
塔裁决:三个写操作在实例启动/运行时必须拒,判据不能只靠界面灰按钮 ——
Windows 上是 os error 32 兜底,**Linux 上删一个正在跑的世界会成功**,那是在
用户玩到一半时把存档删掉。
- host/dispatch.rs:`AppCommand::Saves` 与 `AppCommand::Mods` 同款,先查
`CoreHost::in_flight(&id)` 再把 `running` 传下去
- host/saves.rs::dispatch 增 `running`:为真时三个写操作**不 spawn、不碰盘**,
直接回一条 `SavesFailureKind::GameRunning` 的结构化失败;读路径与「打开文件夹」不受影响
- dto:新增 `GameRunning` 类别(不折进 WriteFailed —— 那不是"写失败",是"我们主动没写")
- i18n:「游戏正在运行(先退出再操作)」中英成对 + 金表同步
- 用例:在飞时三条写命令盘上指纹不变/世界还在/备份落点没建,且回 GameRunning;
反向用例钉住闸门没有把写操作一并关死;UI 侧断言三种动词的主句都不出现「写盘失败」且不画重试
- 拆分(400 行):host/saves.rs → host/saves/write.rs;两处测试各拆出 write 子模块
- SavesState 模块文档补「生命周期」表:draft / pending / confirm 谁写谁清、何时必须清,
并把 confirm「取走即清」(连点两下不删两个)写成安全不变量
- 出图:ULL_SAVES_FIXTURE=running 一张(失败那句话的样子)
|
2026-11-11 13:29:56 +08:00 |
|
AskaEth
|
c921b9c185
|
feat(core,presenter,app-iced): 存档三个写操作(重命名 / 删除 / 备份)+ 行操作区(M247 SAVESOPS)
core::game::saves 新增 write 模块,三个入口的第一句都是 boundary::guard:
- rename_world:saves/<old> → saves/<new>,撞名/同名/非法名字各自具名拒绝
- delete_world:递归删(不跟随符号链接、层级上限 64),先过边界校验
- backup_world:流式 zip(限额预检 → 临时文件 → 原子改名),
<世界名>-YYYYMMDD-HHMMSS.zip,撞名顺延,绝不覆盖上一份
guard 三道判据(每次写前重做,不信任调用方):名字必须是单段裸名字;
saves/ 必须是运行目录的真实直属子目录;世界目录必须是 saves/ 下的真目录
(解析后比父级,不比字符串前缀)。落点目录要求绝对路径(相对路径会落到进程 CWD,
实测过一次)。
presenter:SavesCommand 增 RenameWorld/DeleteWorld/BackupWorld,host 三个后台入口
写成功后重新列一遍目录并把「回执」挂在快照上(SavesOped:改名两端 / 被删的名字 /
备份落点+字节数+跳过的链接数)。SavesFailureView 增 file 与 op,写失败八类一一对应。
SavesState 增 draft / pending / confirm 三样交互态(草稿是行内输入缓冲,
pending 与 ModsState::pending 同一条先例,confirm 是删除确认框在等谁)。
app-iced:世界行右侧重命名/备份/删除;行内输入框改名(现成模态队列没有「输入变了」
这一条消息,用输入模态等于重写一套);删除走现成 modal 的 Confirm,主按钮落点接在
app/update/dialog.rs。回执行画在卡片里(备份落点看不见就是静默失败)。
i18n:新增 25 条中英成对 key + 金表同步;删掉 M235 的「这一版只读」那条(写操作接上
之后它是假的)。出图夹具 ULL_SAVES_FIXTURE=rename|confirm|backed-up 三种形态。
|
2026-11-11 13:29:56 +08:00 |
|
AskaEth
|
3e852331f4
|
Merge branch 'feat/m250-gpushotgate-gpu-screenshot-evidence'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 13:24:47 +08:00 |
|
AskaEth
|
ff1a1677f5
|
Merge branch 'feat/surface-unknown-launch-placeholders-inst'
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 11:40:08 +08:00 |
|
AskaEth
|
c97ec293c3
|
test(core): 「哪些键真的交给日志」补自动化证据(M244 r2 P2-1)
复审 P2-1:旧用例只断言 `collect_fresh`(纯函数)与 `unknown_keys()`(表级清单),
没人断言 `report` **真的**只把「新键」交给 sink;把 `for key in fresh` 改成
`for key in unknown`(每次渲染重复写日志)当时 6 条用例全绿,而「同键只报一次」正是判据 ③。
- `report_with(&self, unknown, sink: impl FnMut(&str))`:`report` 变成
`report_with(unknown, emit)`,`emit` 仍是 `cfg(test)` 空实现(不碰进程级单例,flake 不回潮);
- 新用例 `report_emits_only_fresh_keys`:假 sink 收集,断言一次渲染内重复只交一次、
跨渲染只有新键才交、顺序与 `collect_fresh` 一致;
- 顺手收掉自己列的边角:`unknown_log_line` 先把键名里的 `\r`/`\n` 换成空格
(`launcher.log` 是行导向的,恶意清单塞换行能把一个键变成两行假日志),
新用例 `log_lines_never_break_into_two_lines`。
变异自证(各自 exit 101,均已还原、文件 sha256 复原):
- `for key in &fresh` → `for key in unknown` ⇒ `report_emits_only_fresh_keys` 红,
`left: ["a", "a"] right: ["a"]`;
- 删掉 `key.replace(['\r','\n'], " ")` 那一行 ⇒ `log_lines_never_break_into_two_lines` 红,
`left: 2 right: 1`。
门禁:fmt / check -p unlucky-core --all-targets / test -p unlucky-core -p unlucky-cli
(1678 passed / 0 failed)/ clippy --workspace --all-targets -- -D warnings 全绿。
|
2026-11-11 11:32:27 +08:00 |
|
AskaEth
|
60607ae7b3
|
feat(core): 未登记占位符不再静默(M244 PLACEHOLDERWARN)
`PlaceholderMap::render` 一直把未登记的 `${key}` 原样写回 argv,运行期零校验零告警:
第三方 version JSON 里一个拼错的占位符会原样进 `cmd_built.argv` / `--dry-run`,
用户只看到一个 `${xxx}` 和零解释(M239 REDACTAUDIT §5.2 的机制风险)。
- `render_tracked` 返回 `RenderOutcome { text, unknown_keys }`,未登记键去重保序;
`unknown_keys()` 交表级全量清单,供后续接 `AssemblyWarning` / `--dry-run`。
- `render` 在此之上把每个**新**见到的键写一行 `launcher.log`(进程级去重,一个键一次启动
只报一次);未登记键仍原样保留,argv 逐字节不变(第三方清单兼容性优先)。
- 大小写/首尾空白变体按**不同的键**处理(替换本来就是精确匹配),日志行对近似已登记键的
附「疑似 ${known} 的笔误」提示。
- 测试构建里 `emit` 是空实现:`launcher_log` 是进程级单例,lib 用例并行跑时会把它自己的
`buffer_is_bounded_before_the_dir_is_known` 顶红(实测 205 行);行文本与「哪些键该记」
分别由 `unknown_log_line` / `collect_fresh` 的用例覆盖,落盘用一次性探针手工验过。
变异自证:把 `note_unknown` 改成空实现 ⇒ 5 条新用例红(exit 101),还原后绿。
门禁:fmt / check -p unlucky-core --all-targets / test -p unlucky-core -p unlucky-cli /
clippy --workspace --all-targets -- -D warnings 全绿。
|
2026-11-11 11:12:23 +08:00 |
|
AskaEth
|
0b40e1dfe2
|
test(core,presenter): 夹具口径跟随 M240 收窄(长类名不再被抹)
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
M240 让词形长路径不再被当成不透明串,而 M241 的两份夹具仍断言「必须被抹成
<redacted:opaque>」——两条各自的 pre-rebase 门禁都绿,合并 tip 上才互相矛盾,
维护主线短暂变红。
- core:raw_evidence_hits_... 改成断言「脱敏文本也保留类名、两边都能命中」,
并改名 ..._and_m240_keeps_it_visible_after_scrubbing;保留一条守卫:若将来
又把词形长路径抹掉,这条会红并提示改用 raw 窗口的用例说明其必要性。
- presenter:展示面与盘上的断言从「不许出现类名」翻成「应保留类名、且不许
出现凭据」——类名不是凭据,留着才看得懂崩溃原因。
|
2026-11-11 10:48:16 +08:00 |
|
AskaEth
|
8a65562595
|
Merge branch 'feat/mask-bearer-tokens-followed-by-trailing'
|
2026-11-11 10:48:07 +08:00 |
|
AskaEth
|
cc90c64b84
|
fix(core): 行尾句读不再让真 token 整串落盘(M243 BEARERDOT)
`Bearer <JWT>.`(行尾一个英文句点)整串原样落盘:句点自己属于 dotted 字符集,
扫描器把它算进 run 后 `split('.')` 多切出一个空段,「每段 ≥8」当场不成立。
M240 实测 old == new == 原文,影响 core 的全部落盘/展示面。
修法不是抬阈值:`shape::split_trailing_punct` 在判定前把末尾句读(`.` `,` `;` `)`)
切出去,`push_dotted` 再把句读原位写回——占位符只吃 token 本体,句子标点一个不丢。
只有 `.` 会进入字符集,其余三个本来就在外面,列全是为了「句读不参与判定」只有一处出处。
`has_dotted_token` 的闸门同步改成看同一份切分结果,否则闸门关着就静默不抹。
判据:`tests/bearerdot.rs` 三组——四种形状(`Bearer <JWT>.` / `… <JWT>. then` /
`<JWT>,` / `(<JWT>)`)必须抹且标点保留;句点结尾的普通文本(`Service.Unavailable.today.`)
必须逐字原样;两个入口在带句读的输入上必须一致。语料 68 文件 / 243 行仍 0 行被改,
JWT/sha256/base64 正例仍被抹。变异自证:把 trim 改回旧行为 → 主判据红(EXIT=101)。
外层两个既有红(diagnose::session / presenter m241 夹具)是 M240 收窄后的口径冲突,
stash 回 main HEAD 可复现,已各 file 一条 finding,不在本轮修。
|
2026-11-11 10:48:04 +08:00 |
|
AskaEth
|
ea108c5fc4
|
Merge branch 'feat/sync-cli-and-agent-diagnosis-with-stored'
|
2026-11-11 10:47:33 +08:00 |
|
AskaEth
|
1a06163407
|
feat(cli): M242 —— ull diag 与 AI diagnose_crash 改读会话里落盘的结论
两个入口原来都是「读回会话事件 → 在**已脱敏**文本上重跑规则」。脱敏一旦有损
(`≥64` 不透明串、带点票据串 → 占位符),重跑出来的不是「少一点细节」,而是
**换一条结论**:`fabric.loader_0_12`(critical)降级成 `integrity.no_class_def_found`
(warning,把「Fabric Loader 版本不配套」说成「库没下全」)。M241 起写会话时已经在
原始行上算过结论并落成 `diagnosis` 事件(且过 `scrub_json`),所以两个入口改成
**优先读落盘那份**,只有没有该事件的老会话才回落到重跑。规则 pattern 一个字符没动。
- `commands/sessions/diagnosis.rs`(新):结论来源选择(stored / rerun)、人类输出渲染;
`ull diag` 的 payload 增加 `conclusion_source`,人类输出多一行「结论来源」。
- `commands/sessions.rs`:`diag` 瘦成取结论 + 拼 payload(240 行级 → 拆文件守 ≤400 行)。
- `commands/agent/ports/diagnose.rs`:`diagnose_crash` 同改;`read_game_log` 与
`CrashDiagnosis.evidence` 仍是脱敏文本(隐私选择不变),只换结论来源。
- 用例(真 `SessionWriter` 写盘 → 真 CLI `crate::run` / 真工具注册 + handler):
`sessions/m242_tests.rs`、`agent/ports/diagnose/tests.rs`;含老会话回落用例。
变异自证:关掉落盘分支 ⇒ 两条用例红(`rerun`/`integrity.no_class_def_found`),已还原。
|
2026-11-11 10:34:18 +08:00 |
|
AskaEth
|
d5a6f5dc50
|
Merge branch 'feat/narrow-log-redaction-overreach-on-brands'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 10:15:51 +08:00 |
|
AskaEth
|
a07482f828
|
docs(core): M240 r2 复审 —— 两处注释口径(= 边界代价写清、DNS 形归属改正)
纯注释,判据/阈值/测试断言/扫描器零改动。
① `is_dotted_token_char` 的 doc 原写「JWT 自己的 `=` 只可能是末尾填充,拆出去不损伤
覆盖面」——作为无条件说法不成立。改成有范围的措辞,并把这条例外并入模块文档
「旧行为抹、新行为留」第 1 条(不新开章节):`-DaccessToken=abcdefgh.ijklmnop.qrstuvwx`
(整段 40 字符、三段各 ≥8,旧规则抹)现在 `=` 切段后值只剩 26 < 40 → 原样留下。
旧/新两侧都实测过(`git show 74be510` + rustc 探针 / 当前代码探针)。
② `cdn_and_mirror_urls_survive` 里 `resources.download.minecraft.example-cdn` 的注释
原写「全靠必须含大写这条必要条件挡住」——不成立:它是 DNS 形状,由 `is_url_authority`
→ `is_dns_host` 豁免(删掉大写判据该测试仍绿)。改成正确归属,与
`lowercase_dotted_names_are_spared` / `the_uppercase_requirement_is_load_bearing` 的
doc 口径对齐(DNS 形靠主机名豁免、非 DNS 形靠必须含大写)。
|
2026-11-11 10:15:49 +08:00 |
|
AskaEth
|
66929786c0
|
docs(core): M240 复审 P2-1/P2-2/P2-3 —— 口径收窄、大写判据钉住、找回丢掉的负例
P2-1:「抹除能力没有下降」是绝对承诺,已改成准确口径——收窄的就是三类形状
(`=` 边界、填充不计段长、全小写点分串),每条都在 shape 模块文档里写明,并用
`known_narrowings_old_masked_new_keeps` 把「旧行为抹、新行为留」钉住。三条的旧行为
是用 M240 之前的真实代码(`git show 74be510:…` + rustc 探针)实测的,不是推断。
P2-2:「必须含大写」原先没有任何测试钉住(复审实测:删掉它 13 条仍全绿)。
查明原因:DNS 形状的全小写点分串早就被"首段是主机名"那条豁免救下,大写判据
只对**非 DNS 形状**的全小写点分串承担区分作用(标签带 `_`、标签超 63 字符、
`/` 前只有一段主机名)。选择保留(复审的 (b)),并补
`the_uppercase_requirement_is_load_bearing`(`sophisticated_backpacks.upgrades.reference`
等两条)——删掉判据 → 该测试与 `lowercase_dotted_names_are_spared` 红、其余 15 条绿。
同时改正事实错误:不是"第三段必为大写",而是头的 base64url **第三个字符**恒为
`ey` + `I/J/K/L`(`7B 22 xx` 的第三组 6 位 = `b1` 低 4 位 ++ `b2` 高 2 位 → 索引 8..11)。
P2-3:找回重构时丢掉的老负例 `dotted_token_rule_does_not_eat_normal_text`
(4 条用例 + 直接断言 `!has_dotted_token(...)`,含 `eyJhbGciOiJIUzI1NiJ9.short.abc`);
语料测试的 doc 注明它**只守一个方向**,不得当"正例仍被抹"的背书。
判据与阈值一个字没动(40 / 3×8 / 64),扫描器代码零改动,纯文档 + 测试。
|
2026-11-11 10:15:49 +08:00 |
|
AskaEth
|
43f77d8865
|
fix(core): 收窄日志脱敏的三类误杀(品牌串 / CDN 主机名 / 长路径)
M239 实测两条形状规则会误杀三类正常文本,被抹掉的正是用户要复制的面
(`--dry-run` 的 argv、`cmd_built.argv`、诊断规则引擎的输入)。修法不是抬阈值
(40 / 3×8 / 64 一个字没动),而是给每条规则加**必要条件**:
- `-D<key>=<品牌>` 被当三段式 token:`=` 是赋值边界、退出 dotted 字符集
(JWT 的 `=` 只可能是末尾填充),键里的点号不再把值拖进段数统计。
- CDN/镜像 URL 变成 `https:<redacted:token>`:加"必须含大写"与"首段不是 DNS
主机名"两条必要条件(JWT 头是 JSON,base64url(`{"`) = `eyJ` 必含大写;
base64url 不含 `/`)。扫描器先切主机名,路径那半继续判——JWT 放进 URL
路径里照样抹。
- ≥64 无点号长串吞掉整条路径:路径形状(被 `/` 切开且每段是词形名字)整条
保留,但**段本身**≥64 的照抹(`/…/objects/<64 位 hex>` 只抹那一段)。
真凭据的抹除能力不变:登记过明文值的替换路径没碰;正例(本仓 JWT / MSA JWT /
`-DaccessToken=` 粘着 / URL 路径里的 JWT / 标准 base64 三段式 / sha256 hex /
base64 ≥64 / `A`×64)全部仍被抹。回归语料(M239 的 68 文件 / 243 行)修前被改
1 行 → 修后 0 行。判据与扫描器拆成 redact.rs + redact/{shape,tests}.rs。
|
2026-11-11 10:15:48 +08:00 |
|
AskaEth
|
775bb75b92
|
Merge branch 'feat/feed-rule-engine-raw-text-while-keeping'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 09:41:54 +08:00 |
|
AskaEth
|
edd6402fd5
|
fix(core): 规则结论改在脱敏前算,会话路上 fabric.loader_0_12 能命中(M241)
会话 JSONL 是写前脱敏的,而脱敏的「≥64 无点号不透明串」是有损的:
fabric.loader_0_12 唯一的证据行(Sponge mixin 类路径 66 字符)被抹成
<redacted:opaque>,规则在会话路上永不命中。
修法不动脱敏策略、也不往盘上多写一份原文:
1. SessionWriter 在内存里留一个 2000 行的原始尾窗(不落盘);
2. 收尾时把这些**原始**事实交给新的 diagnose::session::conclude 跑规则,
产出契约形状的 diagnosis payload —— 它落盘前仍过 Redactor::scrub_json;
3. 日志页优先读这条落盘结论,老会话才回落到「在脱敏文本上重跑规则」。
为什么不是「原文与脱敏文本并存」:会话 JSONL 里存原文会踩
core/tests/launch/pipeline_cases.rs 早已钉死的「事件流不得含凭据明文」。
顺带把 EventKind 纯搬移到 launch/session/event_kind.rs 守 400 行上限。
|
2026-11-11 09:19:42 +08:00 |
|
AskaEth
|
74be510114
|
Merge branch 'feat/fix-narrow-window-chips-squeeze-and-menu'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 08:41:04 +08:00 |
|
AskaEth
|
df0944894f
|
fix(app-iced): 面板预算按内容推导 + 版本菜单不画 L2 阴影(M238)
两条都在「视口不终止内容」这一族里,修法各一半:
① 三栏档面板预算从死的 `PANEL_BUDGET = 300` 改成 `panel_budget(menu_items)`
(内边距 24 + 首屏 76 + 间距 8 + 钉死区 88 + 菜单高 + 列表↔面板 21)。
旧常数不含列表↔面板那 21、也不随版本菜单行数变,于是 760/900 档面板实得
286/294px,滚动视口被挤到 54/62px——六枚 loader chip 只露出 10px(736 与 1180
同样严重,纵向预算与窗宽无关)。修后 760 档视口 98px(chips 31px 整枚在屏)、
900 档 106px;列表按整行让出:760 档 4→3 整行、900 档 7→6 整行;只有一条
加载器版本时菜单只占 64px,列表仍是 4 整行。
② 版本菜单改用 `Menu::flat()`:不是「把阴影裁掉」,而是**不在滚动容器里画阴影**。
`iced_tiny_skia` 0.14 画 quad 阴影走 `draw_pixmap(.., mask: None)`
(engine.rs:136-143),绕过裁切掩码;`iced_wgpu` 那边是整层 scissor
(quad.rs:96-101)⇒ 同一棵树两个后端画出来不一样。修前菜单底下 54px 一条被压暗
(浅色 249→231,−7.2%,含 8px 缝与实例名框内上半部),修后逐像素等于画布色。
菜单的 L2 令牌不动,真实浮层照旧带阴影。
判据(两条,抓得住的/抓不住的都写进文档注释):
- 结构:`rows/tail.rs` 断言三栏档 760/900 的滚动视口 ≥ 首屏 76px(对生产常量做算术);
- 像素:`options_shot/compact.rs` 断言 chip 描边上下两行都在、且实例名框上方那条缝
与框内都是纯画布色。
变异自证:预算退回 300 ⇒ 结构断言红("滚动视口只有 54px < 首屏 76px");阴影无视
`flat()` ⇒ 像素断言红("y=660 x=304 是 [231,231,232] 而不是画布色")。
没验:滚动中/底部三态拍不出来(harness 无 `scroll_to` 派发通道,已另开 finding)。
|
2026-11-11 08:28:06 +08:00 |
|
AskaEth
|
dbc5274965
|
Merge branch 'feat/wire-disabled-control-base-into-product'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 07:56:14 +08:00 |
|
AskaEth
|
ba441351dd
|
fix(app-iced): 复审 r2 三条收口(M237)
- P1-1 判据会让 CI 一亮红:`java_shot.rs` 原来写「没喂图时 `assert!(CI 未设置)`」,
而 `.github/workflows/ci.yml` 的 test 作业在 ubuntu + windows 上都跑
`cargo test --workspace --all-targets`,出图步骤只给 `options_shot` 备了 `ULL_DL_*_SHOT`
(本判据的图不在出图清单里)⇒ `CI=true` 实测 exit 101,一合即红。改成:没喂图**一律响亮跳过**
(`not_fed()` + CI 时多一句「本仓 CI 不出这张图,跳过是预期」),模块文档写清「要让它守门需要
跨 scope 加出图步骤 + 注入 `ULL_M237_JAVA_SHOT`,以及那时该怎么改回硬失败」。
自证:`CI=true` 不喂图 → exit 0(改前 101);无 CI 不喂图 → exit 0;喂图(浅/深)→ 真跑,exit 0。
- P2-1 同类错漏一处:实例列表卡画的是 `MaterialLayer::Card`(`instances.rs:263`),
但 `list_row` 的 `ListItem` 没传底 ⇒ 行 hover/按下按 `Base::CANVAS` 合成。
改成 `item.base(frame::BASE)`(同一层,不另立常量),并加进接线守卫。
像素证据(`--force-state=hover`,1180×760 列表卡矩形 `(112,160)-(448,735)` 内 before/after 差):
浅色 57923 px `(232,232,233)→(226,226,227)`,深色 57923 px `(32,38,31)→(35,40,35)`
—— 即 hover 8% 从画布底换到卡底。
- 守卫自身升硬:`base_wiring.rs` 的计数原来按整段文本数,**把接线注释掉仍算数**(r2 实测绿)。
改成先剔掉整行 `//` 注释再数;变异自证:注释掉 `item.base(frame::BASE)` ⇒ 红
(`instances.rs: .base(..) 出现 0 次,期望 1 次`,退出码 101),还原后绿。
- 门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-237):fmt 0 / check --all-targets 0 /
clippy --workspace --all-targets -D warnings 0 / test -p unlucky-app-iced --lib 907 通过 0 失败 /
test --workspace 3344 通过 0 失败。
|
2026-11-11 07:56:11 +08:00 |
|
AskaEth
|
3dcd5fd117
|
feat(app-iced): 页面层把卡片底接进禁用/交互控件(M237)
M234 把 `interaction::Base` 与组件侧的 `.base(..)` 接好了,但页面层仍是默认的画布底:
卡片(`Role::SurfaceContainerLow`)里的禁用按钮 / chip / 开关把 10% / 12% / 38% 叠在画布色上。
M223 记的「卡片里禁用控件 CR 1.05、形丢失」因此只在 gallery 里修好,产品页没修。
- 导出容器底:`pages::frame::BASE = Base::of(MaterialLayer::Card.surface_role())`
—— 取的是 `card()` **真正画出来的那一层**(不是「反正是 surface」);`logs::panel::BASE`
是同一个常量的别名(两张卡画的是同一个材质层)。
- 接线(改前一律 `Base::CANVAS`,改后 `frame::BASE`):设置·Java(扫描 / 取消 / 设为默认 chip /
三个输入框 / 添加 / 保存 / 预置项 chip / 下载徽标);设置·通用(语言三档、主题三档、日志行
两个手写按钮 → 状态层叠 `BASE.role()`);实例详情(MODS 开关、SAVES 的重试与打开文件夹、
启动参数区的两个输入框 + 后端 chip + 保存 / 恢复);日志页(诊断卡页脚 `Copy summary`)。
详情头的启动 / 停止 / 动作行**不在卡里**,保持画布底并加注释。
- 判据:`pages/frame/tests.rs` 逐值断言「`BASE` == 卡片画出来的颜色」+ 源码结构盯 `card()`
不许改画别的材质层;`pages/settings/tests/java_shot.rs` 是**页面级像素判据**(读 1180×760
真截图、逐位匹配:卡区里「叠卡底」3136 px vs「叠画布」21 px,深色 3128 / 1);
`pages/tests/base_wiring.rs` 数接线点,守像素够不到的页面(实例详情 MODS / SAVES)。
- 变异自证:撤掉「立即扫描」的 `.base(BASE)` ⇒ 像素判据红(73 px vs 3083 px,退出码 101);
撤掉诊断卡页脚的 `.base(..)` ⇒ 接线判据红(0 vs 1,退出码 101)。均已还原。
- 门禁:fmt / check -p unlucky-app-iced --all-targets / test --workspace(3344 通过,0 失败)/
clippy --workspace --all-targets -D warnings / 文件 ≤400(最大 389),全部本地过。
- 已知未尽:面板 α<1 的半透明卡(`Base` 只装一个 `Role`)、GPU 档、实例详情与日志页在
1180×760 出图里没有可量测的禁用控件(夹具缺 running,已报 finding);app_bar / adopt pick /
设置页分组列仍未导出 `BASE`;设置·下载 / 更新 / 助手、账户页、内容库、下载页仍未接线。
|
2026-11-11 07:56:11 +08:00 |
|
AskaEth
|
cf8a951e29
|
Merge branch 'feat/fix-accounts-aba-false-red-and-probe-wal'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 06:53:54 +08:00 |
|
AskaEth
|
4952ee2706
|
fix(accounts,probe): 测试缝换稳定身份(ABA)+ 超时墙钟对齐真实预算(M236)
两条 M230 台账判定「真开着」的测试债:
1. accounts 测试缝的 key 是 Arc::as_ptr(&job.cancelled)。job 一 drop,控制块
立刻可能分给下一个 LoginJob,于是新 job 读到旧 job 的记录(ABA),并行跑的
「设备码入口不许置位 browser_first」因此偶发假红。改成 LoginJob 自带的
稳定身份 JobId(cfg(test) 字段,进程内单调递增、永不复用);ENTRIES 从
Vec<(usize,bool)> 改成带 Weak 的 Entry:record 时 retain 收走已 drop 的 job,
同一 job 覆写成「最后一次」(消掉 mode_for 取第一条的歧义)。
反向证据:身份退回堆地址 ⇒ the_test_seam_does_not_reuse_a_heap_address_as_identity
红(mode_for(&younger) 读到 Some(true))。
2. probe 的 silent_server_times_out 断 `elapsed < 3s`,跟真实预算(400ms)无关。
改成 预算 + SLACK(400ms + 1s):抓「预算没生效」(那要等假服务器睡满 5s),
放开毫秒级精度;并新增两条判据——detail 里的时限就是本次预算(证明走的是
预算分支)、假服务器真的收到 1 次请求(不是没连上就早退)。
反向证据:摘掉 tokio::time::timeout ⇒ 5.08s / unreachable 红;把计时值硬编码
3s ⇒ 3.006s > 1.4s 红。
|
2026-11-11 06:53:52 +08:00 |
|
AskaEth
|
30d4163816
|
refactor(accounts): 登录两条流拆到 accounts/login.rs(纯搬移,M236 前置)
accounts.rs 正好 400 行零余量,本轮要给测试缝换稳定身份,改前必须先拆。
搬移口径:start_login / start_browser_login / msa_auth / fail / finish /
store_session 的每一行(含签名与 doc)与搬移前逐行相同(130 行非空行
diff 为空,只有空行分隔差异);公开路径由 `pub use login::{...}` 保住
(host::accounts::start_login / start_browser_login 不变)。
列表/增删/LoginJob/MsaEntry/测试缝留在父文件:那是测试要用的私有件,
搬走它们就得放宽可见性,纯搬移口径会随之破掉。
|
2026-11-11 06:53:51 +08:00 |
|
AskaEth
|
32951df3d6
|
Merge branch 'feat/fix-disabled-control-compositing-base-at'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 06:34:31 +08:00 |
|
AskaEth
|
9bb3c33b8c
|
fix(app-iced): 复审 r1 四条 P2 收口(M234)
- P2-1 判据恒绿:`gallery/probe/bases.rs` 的「反向检查」把字段档计数(`:172` 遮蔽后的
`hits`)拿去比按钮档 stray ⇒ 恒绿。改成 `button_hits` / `field_hits` 两个计数,各比各的
stray,失败信息的数值与单位对齐量测对象。变异自证:卡列里留一颗忘了 `.base(..)` 的按钮
⇒ 该断言单独红(2137 px vs 3178 px,退出码 101)。
- P2-2 FAB 的底错了:禁用态背景仍是满档 `PrimaryContainer`(`Disabled.layer() == None`),
38% 内容却合在画布上。改成 `Base::of(PrimaryContainer)` + `on_container = false`
(那一层 10% 在 FAB 身上不存在),两处调用收进一个 `content_color()`;新增值判据
`disabled_fab_ink_composites_over_its_own_container`(修前 #8CA0AA / 修后 #548FBC 量级)。
- P2-3 漏改:`selection::paint::state_layer()`(hover/focus/pressed)与 `list_item` 的
状态层叠底仍写死 `Role::Surface`,改成跟 `base`;同类两处(`assemble::transparent_state_color`
的 Outlined/Text hover、drawer 项药丸)一并跟上。
- P2-4 文档失真:`feedback/{action,mod}.rs` 删掉「本地 α 合成 / 规则只认两档」的旧描述
与失效链接;`list_item` 的旧签名;三处 `probe/base.rs` → `probe/bases.rs`;
新增 `surfaces::elevated::tests::the_card_base_constant_is_the_role_the_card_paints`
真断言(原来只在文档里声称那条测试守 `BASE`),并把这句描述改准。
- nit:`disabledbase.rs` 字符串里塌成一行的续行空格、probe 汇总行把列左缘横坐标说成
「像素计数」的措辞。
门禁(CARGO_TARGET_DIR=C:/Temp/ull-target/wt-234):fmt / check --all-targets /
test -p unlucky-app-iced(896 + 6 通过,0 失败)/ clippy --workspace --all-targets -D warnings /
文件 ≤400 全绿;probe 浅色与深色都过。
|
2026-11-11 06:34:24 +08:00 |
|
AskaEth
|
35dbefb08d
|
feat(app-iced): 禁用态合成底改成容器给的 Base(M234)
`interaction::disabled` 的四个入口一直收一个「底」,但 8 个调用点全传死
`Role::Surface`:卡片(SurfaceContainer)/ 面板(SurfaceContainerLow)里的禁用控件
把 10%(按钮族)/ 12%(面状控件族)叠在画布色上 —— M223「CR 1.05、形丢失」的根因。
- 新增 `interaction::Base`(画布 / 容器底的一等参数,`From<Role>` 兼容历史调用点);
四个入口改成 `(ui, base, …)`,内部不再出现任何写死的 `Role::Surface`。
- 容器导出自己的 `BASE`:`surfaces::elevated::BASE`(卡)、`inputs::menu::PANEL_BASE`
(菜单面板,项的 38% 与状态层都改叠它)、`navigation::drawer::BASE`。
- 控件收 `.base(..)`:Button / icon_on / Field / SearchBar / Slider / Checkbox / Radio /
Switch / Chip / ListItem / Tabs;Fab 与 icon() 是画布默认值。
- `feedback/action.rs` 的本地 α 合成删掉,改走 `disabled_content_over_role` 并给真实容器底
(Snackbar `InverseSurface` / Dialog `SurfaceContainerHigh`)。
- 判据:`interaction::disabled::tests`(卡片/面板/画布三档逐值)、
`buttons::tests::base`(Button::base → StyleSpec → style_closure 接线)、
`gallery` 新增 `disabledbase` 段 + `gallery::probe::bases` 精确像素断言
(±10 容差区分不了 1/255 的差,所以自己数逐位相等的像素)。
- 变异自证:assemble 的调用点改回画布底后,上面两条都红(见 artifacts/m234/README.md)。
已知未尽:`crates/app-iced/src/pages/**` 不在 scope 内,页面侧「把控件放进卡片/面板」的
调用点尚未接 `.base(..)`(行为与本 mission 之前一致);`pages/…/java/tests/marker.rs`
有一行被迫更新(入口多了一个实参,语义不变 = 画布底)。
|
2026-11-11 06:34:23 +08:00 |
|
AskaEth
|
a9d1cb4c44
|
Merge branch 'feat/instance-saves-read-only-list-mvp-storem'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 06:00:29 +08:00 |
|
AskaEth
|
f217d83680
|
feat(saves): 实例存档 / 截图的只读列表与打开文件夹(M235 STOREMVP)
用户诉求是 PCL 差距四项里的「存档截图 / 导入」,M230 审计确认三层全缺。塔裁决 U3
把这一版钉成「只读列表 + 打开文件夹」,不做缩略图墙(缩略图要引图片解码依赖,而
AGENTS §2 只允许 palette-extract 引 image 类依赖;截图/导入产物形态也没定型)。
core
- `game::saves`(新):只读列 `<run_dir>/saves`(世界目录)与 `<run_dir>/screenshots`
(图片文件)。只调 symlink_metadata / read_dir / metadata,不建目录、不写文件、不读
文件内容(`icon.png` 只判存在)。目录不存在 ⇒ 空列表不是错误;存在但不可用(符号
链接 / 不是目录 / 读不了)⇒ 具名错误。条目级异常逐条登记原因,不静默丢。
- `platform::opener`(新):`open_dir` 是**目录**打开器的唯一收敛点(explorer / open /
xdg-open),内部复用 `child::quiet`,cfg 分支只此一处。塔明确要求按 AGENTS §6 放
platform 而不是业务模块,并要一条可执行判据防退化
(`the_launcher_literals_live_here_only` 扫 `crates/core/src`:除本文件外不许出现
`"explorer"` / `"xdg-open"` 字面量)。
presenter
- `dto::saves`:SavesCommand(List / OpenFolder)、SavesListView、SavesFailureView
(结构 + OS 原文,不拼中文)、`SavesListView` 的计数助手。
- `host::saves`:走 `job::spawn`,UI 只收快照;运行目录仍是
`isolation::derive_game_dir` 的同一个答案(与启动 / mod 列表同源,**不写盘**)。
「打开文件夹」打开哪个目录由 host 定:`saves/` → `screenshots/` → 运行目录。
失败分三类(InstanceUnavailable / DirUnusable / OpenFailed),可重试。
- `state::saves`:快照整体替换、迟到结果丢弃、失败保留绑定(重试才有意义)。
- 接线:CoreEvent::Saves / AppCommand::Saves / HostTask::Saves。
app-iced
- `pages::instances::saves`:详情页 SAVES 卡片(MODS 之后、启动参数之前;**不套第二层
滚动条**,整列那一条是唯一入口)。世界行带 UTC 时间与「有图标」,截图行带大小与时间;
第一行固定画「这一版只读」(U3 边界让用户看得见)。读盘失败画一条红字 + 「重试」;
打开文件夹失败**不画**重试(那个动作的重试就是再点一次按钮)。
- 接线:Message::{ReloadSaves, OpenSavesFolder}、`sync_saves`(与 `sync_mods` 同一判据)、
Model::saves、`saves_shot` 出图夹具(ULL_SAVES_FIXTURE=1)。
i18n
- `InstancesSaves*` 20 条中英成对,catalog 与金表在**同一条锚点**后追加、逐字节一致。
证据
- core 只读判据:`game::saves::tests` 在真盘建世界/截图,调用前后各拍一次
(相对路径, 是否目录, 字节数, mtime)指纹并逐条比对;另有用例钉「目录缺失不建目录」。
- presenter:`host::saves::tests` 真盘端到端(custom 档位派生运行目录 → 快照)。
- GUI:`--software-renderer` 1180×760 浅/深各一张 + 1180×1280 两张「整块卡片可见」
(1180×760 下 SAVES 卡片位于折叠线附近,只能看到卡头;第二对用 ULL_WINDOW_SIZE
把窗口拉高,口径与 M98 的矩阵一致),落 `DevDocs/artifacts/M235/`。
没做的(U3 边界内,见 handoff):缩略图墙、导入、删除/重命名、`level.dat` 的
LevelName 解析(世界名暂时就是目录名)。
|
2026-11-11 06:00:27 +08:00 |
|
AskaEth
|
3789cb7e61
|
Merge branch 'feat/fix-launch-dry-run-disk-side-effect-dryr'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 05:27:43 +08:00 |
|
AskaEth
|
c917bfa18f
|
fix(core,cli): M231 复审三条 P2 —— 恢复误删用例 / 收窄 --dry-run 文案 / 判定链去重
P2-1:`natives_cases.rs` 里 `missing_native_jar_becomes_missing_component` 是上一轮
批量截断时**误伤**掉的(我把「tree_snapshot 之后的所有内容」整段砍掉,连带这条与
dry-run 无关的用例),不是有意删。现已从 main 原样贴回,`MissingNativeJar` /
natives 组件缺失这条用户可见失败路径恢复覆盖。
P2-2:help 的「也不写盘」是过度承诺 —— `commands/launch.rs` 的 `launcher_log::set_dir`
/`record_start`/`record` 都在 `if args.dry_run` 之前,dry-run 仍会 append
`<数据根>/logs/launcher.log`(连父目录 create_dir_all)。文案收窄成「不碰版本树
(不解压 natives、不建 natives 目录);启动器自身的运行日志照记」。core 的 `DryRun`
文档同步加了「本层承诺 / 调用方日志另算」的边界。
P2-3:`extract` 与 `preview` 的「无 jar / 戳记命中 / 重解」判定链合成 `inspect()` 一份,
返回 `DirStatus`(NoJars / Ready(files) / Stale)+ 源 jar 指纹;两个入口只在这里分叉。
另:`tree_snapshot_notices_a_transient_directory` 在全量并行跑时偶发红(本轮门禁抓到 1 次,
单跑 3 次全绿)——「建了立刻删」对父目录 mtime 的影响会被 NTFS 合并。换成确定性自检
`tree_snapshot_sees_new_nodes_and_mtime_changes`(新节点 + 同内容重写的 mtime 变化),
并把这条能力边界写进 `tree_snapshot` 的「抓不住」。
|
2026-11-11 05:18:56 +08:00 |
|
AskaEth
|
1fa30c4885
|
fix(core): launch --dry-run 不再落盘(natives 只预览,零建目录/零写文件)
`ull launch --dry-run` 在「环境准备」段走的是真解压:即使只打印命令行,也会在
`versions/<id>/natives/` 下建目录、解 dll、写戳记,把别人的版本树改脏(父目录
mtime 也变)。失败路径(缺 jar 报 missing_components)照样留下这些副作用。
- natives 新增 `preview()`:与 `extract()` 共用条目筛选与戳记口径,但零落盘
(无 create_dir_all / clear_natives / write_stamp),只清点「将写下几个原生库」;
jar 打不开照样报错,预览不藏坏包。
- `NativesReport` 增 `planned` / `dir_exists`:预览报告自报家门,人读行与 JSON
说清「未落盘」「目录尚不存在」。
- `Launcher::dry_run` 改走 `preview_environment`,`spawn` 仍走 `prepare_environment`。
- 报告类型拆到 `natives/report.rs`(mod.rs 曾破 400 行红线)。
- 回归测试(natives_dry_run_cases.rs):有 jar / 无 jar / 空 jar / 已解压过四条
零落盘判据,用「节点类型 + 纳秒 mtime」快照逐字比较版本树,并带一条判据自检
(瞬时建删目录必须被快照看见,防假绿)。
- help:`--dry-run` 的承诺补成「不写盘、不解压 natives、不建目录」。
证据(外部版本树 versions/1.16.5 + 真 natives jar):
修复前 dry-run 后 versions/1.16.5 mtime 1794342830.6861426 → 1794342832.4544542,
新增 natives/ + lwjgl.dll + OpenAL.dll + .ull-natives.json;修复后同一场景快照逐字
不变(mtime 1794343730.6618167),JSON natives.planned=true / dir_exists=false。
|
2026-11-11 05:18:54 +08:00 |
|
AskaEth
|
774a547aa0
|
Merge branch 'feat/clean-stale-comments-and-cli-loader-text'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 04:52:10 +08:00 |
|
AskaEth
|
38d1f3c20f
|
docs: DOCFIX-2 收口三处旧口径注释(M232)
- cli/cli/model.rs:147 `InstallArgs::loader` 文档补 `liteloader`(第六个可装取值),
并点明 `optifine` 是「认得出但装不了」→ `loader_unsupported`;
- commands/instances/tests.rs:103 删掉「只在认得出但装不了的那两家会红」的旧口径,
改成真实理由(第三方前缀形状、没有 `<mc>-<kind>-<ver>` 段);:231 同句副本改成
「optifine 装不了 / liteloader 自 M222 起能装」;
- addon/loader/liteloader.rs:44 `DEFAULT_REPO` 文档由「BMCLAPI 有 bmclapi-liteloader
改写规则」改为「直连官方」:该规则自 M225 起 mirrors.json 里是 enabled=false
(镜像侧实测 404),并按 M225 finding 的方向补上「库走 CENTRAL_REPO」。
纯文档,零行为变化。
|
2026-11-11 04:38:53 +08:00 |
|
AskaEth
|
654e12c100
|
Merge branch 'feat/m229-ink-cat-skin-rollout-app-iced-inksk'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-11 01:49:19 +08:00 |
|
AskaEth
|
36bac74822
|
docs(app-iced): probe 模块文档的复现命令补 SECTION(M229 复审 r3 P2-1)
r5 报告说「模块文档复现命令已补 `ULL_GALLERY_SECTION`」,但 diff 只碰了 `shot_path()` 与
测试体——模块文档 9-15 行一个字没改,照抄那条命令仍 exit 101(panic 报只支持 buttons/divider)。
这轮补齐:
- 模块文档复现命令改成两组(`ULL_GALLERY_SECTION=buttons` / `=divider`),产物路径用
`${CARGO_TARGET_DIR:-target}`(本仓一树一 target,旧命令的 `./target/debug` 在 worktree 里根本
没有 exe),并写明 `all` 不支持及其理由;
- `shot_path()` 的文档改真:它只读 `ULL_GALLERY_SHOT`,「这张图属于哪一段」由测试体开头那条
assert 管(assert 早已搬走,且现在也认 divider)——原文还写着「此处不满足就失败」;
- 收掉 assert 文案里的九连空格接缝;
- 矩阵带过滤的注释从「`all` 默认会画全段」改成「buttons 段没有后续段,过滤是防御性的」。
自证:按文档两组命令实跑,四条命令全部 exit 0;`grep -n ULL_GALLERY_SECTION probe.rs` 命中
8/15/17/21/23(模块文档)与 144/150/357(实现)。
|
2026-11-11 01:49:16 +08:00 |
|
AskaEth
|
f5a2610e4d
|
docs(app-iced): 值字级注释改真 + probe 只认 buttons/divider + 截图主题钉死回归测试(M229 复审 r2)
1) P1-2 的注释值与量测对齐(四处):`elevated.rs` 的 `PAD`/`Texts::title`/`texts()` 与
`pages/instances/detail.rs` 还写着「值 14/400」,而实现是 12/400;`PAD` 文档更把 12px 的
118px 量测挂在「14px 下」。全部改成真值,并把 14px=138px 与 12px=118px 两个量测并列写清。
2) probe 门禁只解决一半:`SECTION=buttons` 绿、`SECTION=all` 仍红(sizes 段的实心块撞「按钮
40 高」判据),断言文案却写「all 或 buttons」。现在:
- 只支持 `buttons`(按钮矩阵,实心块判据**只量矩阵区**,后续段不再参与)与 `divider`
(分隔线两条判据,独立出口);
- `all` / 其它 / 未给 → 明确失败并给理由(整页比 1180×760 高,分隔线段与按钮矩阵不可能同屏;
整页图的 chip/卡片会被当按钮量);
- 模块文档的复现命令补上 `ULL_GALLERY_SECTION`(以前照着跑会 panic);
- `gallery/mod.rs` 的「五段」→ 七段;`gallery/buttons.rs` 的「8 行 + icon·standard」→ 9 行 + 四式。
3) 截图通道的主题档钉死此前没有回归测试(复审 r2 P2-a)。新增
`the_screenshot_channel_pins_the_theme_mode_instead_of_reading_the_file`:两份不同的机器配置 +
同一组截图参数 ⇒ 同一个档位且等于参数;反证非截图通道下两份配置确实分出两档。
变异自证:把分支改回 `bridge.load().1` → 该测试红(left: Dark, right: Light)。
门禁:fmt --check 0 / check -p unlucky-app-iced --all-targets 0 / test -p unlucky-app-iced 0
(887 passed + 6 integration)/ clippy -p unlucky-app-iced --all-targets -D warnings 0 / 400 行扫描干净。
probe 亲跑:buttons 浅+深两张 sheet OK,divider 一张 OK,`all` 按预期被拒。
|
2026-11-11 01:49:16 +08:00 |
|
AskaEth
|
8958b7f67d
|
fix(app-iced): IconButton Outlined 真的画环 + ELEVATED CARD 值串单行(M229 复审 P1)
P1-1:`icon()` 把 `StyleSpec.stroke` 写死 `0.0`,`IconVariant::Outlined` 的描边角色只到
`roles()` 为止、没画出来 —— Outlined 与 Plain 逐像素相同。改成「有描边角色就是 1px」,
并把「角色 → 填充/内容/描边」抽成 `icon_style_spec()`(`icon()` 直接吃它),单测因此能断言
**接线**而不只是断言 `icon_stroke()` 这个纯函数(第一版正是这样漏掉调用点的)。
回归门两条:纯函数/接线(`buttons/tests/icon.rs`,变异回 0.0 时红)+ 渲染层
(`gallery/probe/rows.rs`:图标按钮左缘 4px 带里 Outlined 有环墨、Plain 必须 0 px;
变异后重出图再跑会红)。环在软渲染里是 Outline 叠画布的抗锯齿混色(实测 110/124/110,
本体 123/138/123),所以判据是「这一带有墨/没墨」而不是颜色匹配。
P1-2:ELEVATED CARD 内距 24 + 值 16px 让「2026-10-29 19:22 UTC」折行,同排卡底不齐;
旧测试还断言 20/14(不变量 118+2×padding ≤ 160 变成 166 > 160,假绿)。
按塔裁决「收内距/字号、不破卡片宽度契约」:`PAD` 24 → 20(= card_padding)、值字级 → BodySmall 12。
量测:时戳 14px 墨水宽 138px(160 外框内宽 120 放不下)、12px 为 118px(放得下,余 2px)。
测试改成四条绑一起(字级 12 / 内距 20 / 160−2×PAD ≥ 118 / 2 列实际宽度也 ≥ 118),
并反证「14px 确实放不下」防登记被当噪音删掉;变异回 14px 时红。
P2(顺带):
- 截图通道的主题档由 `--screenshot-theme` 钉死(此前读机器 launcher.json,设置页主题行会漂)
- `gallery::probe` 只认 gallery sheet(必须给 `ULL_GALLERY_SECTION`,否则整页图的 chip 会被当按钮量)、
行表 8 → 9 行(四式图标按钮)、分隔线判据只在渲染了 divider 段时执行、逐行阈值 40 → 30 并加
「满档色像素必须少于 38% 像素」的反向判据、probe 拆出 `probe/rows.rs` 守 400 行
- FAB extended 的重复分支合成一处,右内距提取 `EXTENDED_PAD_END`;iced 0.14 无 `Length::Min`,
「最小宽随标签增长」退化为固定宽已登记
- 五处注释谎言改真(theme/tests、general/theme、elevated、launch_args、app/tests/theme)
`buttons/tests.rs` 加用例后 402 行 ⇒ 图标按钮那组拆到 `buttons/tests/icon.rs`。
|
2026-11-11 01:49:15 +08:00 |
|
AskaEth
|
6ed26626db
|
docs(app-iced): 登记禁用滑块激活轨 38% 为 Pixel 级偏离 #9(M229 复审 r3)
复审核对出 artifacts README 的归因错误:`page-settings-g1-dark.png` 里禁用滑块轨是
`on-surface@38% over surface`(轨带 6030 px),不是面状控件族的 12%;上一版引用的 7 px
12% 小簇在 Image path 字段标签附近,与滑轨无关。
按塔裁决**不改实现**:规格卡只定义了面状控件族(chip 描边 / switch 轨 / 字段底 / 滑轨未激活段)的
12%,而禁用**激活轨**沿用 M3 `SliderTokens.DisabledActiveTrack` 的 38% —— 两者是两个族。
登记写在本文件参考值表下方(`DevDocs/` 被 gitignore,README 里的同一段无法入库,故以代码为准)。
|
2026-11-11 01:49:15 +08:00 |
|
AskaEth
|
4ce24d6fbd
|
fix(app-iced): 禁用态与遮罩改从品牌板合成(M229 复审 F1)+ 选择类图注回填(F2)
F1(核心):三处 `Scheme::baseline(ui.flavor())` 自己重算了一份方案,绕开 `Ui` 里注入的品牌板,
于是 Ink 板上的禁用控件与对话框遮罩仍按 tone 派生的天蓝中性合成(复审量到滑块轨道 #58646C、
填充框 #222E36、遮罩 #060E13,全是蓝系)。修法:`Ui::scheme()` 公开已注入的那份,
`interaction/disabled.rs`(两族合成 + 内容色)、`feedback/dialog.rs`(scrim)、
`feedback/action.rs`(禁用动作文字)改为从它取。
修完 Ink 下实测:禁用容器(12%)#292F28、按钮容器(10%)#252B24、遮罩 #0A0E0A(绿系中性,
R = B);Silver 档遮罩 #A9A9AA。旧三个蓝系值在 7 张深色图里出现次数均为 0。
回归门:`disabled_colors_follow_the_brand_board_not_the_tone_baseline`(按板合成 + 与 baseline
不等 + 深色档 G ≥ B)、`scrim_is_scrimalpha_precomposited_onto_the_canvas` 增加「不许退回 baseline」。
F2:`selection/{matrix,mod,checkbox,radio,geometry}.rs` 里「20×20 / rx2 / 外圈 20」回填成实际渲染的
「18×18 / rx4 / 外圈 18」(勾形比例不变 ⇒ 10.8dp),并按塔裁决把搜索药丸容器从
`SurfaceContainerHigh` 抬到 **`SurfaceContainerHighest`**(贴规格卡,与 filled 字段同底);
`search.rs` / `probe/search.rs` / gallery `inputs` 图注同步。展开的搜索面板与菜单是浮层,
仍留 High 档(已在文档里写明不是漏改)。
`interaction/disabled.rs` 加测试后 405 行 ⇒ 单测纯搬移到 `interaction/disabled/tests.rs`。
|
2026-11-11 01:49:14 +08:00 |
|
AskaEth
|
f9877a89d8
|
fix(app-iced): 下载页页头「内容库」选中态反转(M229 复审 F3)
`pages/content.rs::section_switcher` 把 `on_content` 写死成 `true`,而下载页也调它,
于是版本管理页的页头把「内容库」画成选中、「版本管理」画成未选中。
改成由调用方给位置:下载页传 false、内容库页自己传 true;并加一条源码层面的调用点断言
(iced 的 Element 不可内省,判据只有一行,写错必红)。
不是 M229 引入的缺陷(M109 就有),按复审要求单独提交,不与收官换肤混在一起。
|
2026-11-11 01:49:14 +08:00 |
|
AskaEth
|
db541c820e
|
feat(app-iced): Ink Cat / Silver Cat 换肤 —— 双色板默认 + 规格卡组件几何(M229)
产品出厂外观从 tone 派生的 `Scheme::baseline` 切到品牌色板:深色 = Ink(淡绿)、
浅色 = Silver(钢蓝),D38 取色链(背景图 / 壁纸)仍然优先,取色失败回落到色板。
主题三档接进设置页「通用 → 主题」:Ink / Silver / 跟随系统(落盘仍是
`appearance.theme_mode` 的 dark / light / auto,不新增配置键)。
组件几何按规格卡对齐(M228 的 `inkcat` 登记项逐条落地):
- 卡片圆角 12 → 16;实例元信息卡改用新的 ELEVATED CARD(无描边、SurfaceContainer
底、内距 24、上标注 11/600)
- Snackbar 48/4 → 52/16(双行 72)
- Checkbox 20/rx2 → 18/rx4;Radio 20 → 18 Ø(内点仍 10)
- 按钮加 XS~XL 五档(产品仍只用 S=40),内距改走 ControlSize::pad_x
- 新增 FAB 四态(40/56/80/160×56)与图标按钮第四式 outlined
- 语言与主题两行 chips 统一成规格卡 chip(32 高 / rx8)
`theme.rs` 原本正好 400 行,先把单测纯搬移到 `theme/tests.rs` 再加色板字段;
`app/tests.rs`、`settings/general.rs` 同样按纯搬移拆分守 400 行硬线。
验证:cargo fmt --all --check、cargo check -p unlucky-app-iced --all-targets、
cargo test --workspace(除 clippy 前的一轮 flake,见报告)、cargo clippy --workspace
--all-targets -- -D warnings、全仓 400 行扫描。视觉矩阵 42 张在
DevDocs/artifacts/m229/(gitignored,不进提交)。
|
2026-11-11 01:49:13 +08:00 |
|
AskaEth
|
a06b7ff4ec
|
Merge branch 'feat/m227-narrow-window-layout-budget-narrowf'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-10 23:07:23 +08:00 |
|
AskaEth
|
4f5a76f238
|
docs(download): r2 复审返工——改名后的旧注释三处、视口高度口径如实分档、fade 单测改成对生产值断言(M227)
P2-1(三处改名后旧注释 + 一处口径,纯文档/注释,无像素变化,未重出图):
- `install.rs` 段标题「两个 Shared 分支」→「两支「面板先保预算」的分支(Shared / Narrow)」,
两条子项分别标注 `(Shared)` / `(Narrow)`(改名后只剩一个 Shared,另一支已是 Narrow)。
- `options/view.rs` 的 `Shared` 条目删掉「(chips 档还可能有错误行)」——chips 档被 arm 1 接走
给 `Narrow`,`Shared` 走不到错误行;`Narrow` 条目本来就写对。
- `tests/options_shot/chips.rs` 断言③ 的注释:实测这条断言在窗口 518–610 里命中的是
y=550、宽 94px 的**实心「安装」药丸**,版本菜单容器上沿在 636–639(窗口外)——删掉
「菜单容器的满宽底边」这个错误替代项,并把实测值写进注释。
- 滚动区高度口径:八审原文恢复为「1180×760 ≈96–104px」(署名真实性),另起一句写 M227 在
tip 上的复量:1180×760 = 101px、1180×900 = 109px、窄档 = 120px,并注明 r2 量的 116px
与 109px 只差「钉死区从哪一行算起」。`DevDocs/artifacts/m227/README.md` 同句一并改。
P2-2(fade 单测空转,r2 实测变异 STEPS=6 仍绿):
- 步数与 α 曲线提成 `pub(super) const VIEWPORT_FADE_STEPS` + `pub(super) fn step_alpha(i)`,
`viewport_fade` 用它们;单测改成对**生产值**断言四条:步数 = 高度像素数(1px/条)、
α 严格递增且除末条外 <1、相邻增量非递减(曲线要凸)、末条 = 1.0。
变异验证(自己跑,红过再还原):
- `VIEWPORT_FADE_STEPS` 12 → 6:`panicked … 步数必须等于高度像素数:12px 要 1px 一条,
现在 6 条 ⇒ 2px/条`,MUTATION_EXIT=101;还原 12 后 GREEN_EXIT=0。
- `t.powf(1.6)` → `t.powf(1.0)`:`panicked … 第 2 条比前一条浓得更少…曲线要凸(t^1.6)`,
MUTATION2_EXIT=101;还原后绿。
门禁(真实退出码,CARGO_TARGET_DIR=C:/Temp/ull-target/wt-227,-j 4):
fmt 0 / check -p unlucky-app-iced --all-targets 0 / test --workspace 0(67 个 ok,
app-iced lib 875 passed,四条像素守门 + fade 单测全绿)/ clippy --workspace --all-targets
-D warnings 0 / .rs 扫描 >400 行 0 个、<2 字节 0 个。
|
2026-11-10 23:07:20 +08:00 |
|
AskaEth
|
122accdd02
|
fix(download): 窄档滚动视口底部加 12px 淡出遮罩,去掉被硬裁的「空壳蓝条」(M227 返工)
D30 视觉审 hold 的必改项:滚动视口在 y=660 硬裁,把版本菜单的容器上沿切成 639–659 = 21px、
**零字形墨迹**的一条色块(深色下 (22,33,41) vs 画布 (9,21,28),CR 1.99),读起来像渲染错
而不是「下面还有内容可滚」。
改法(reviewer 推荐方案①,保留发现性):
- 新增 `options/fade.rs::viewport_fade` + `VIEWPORT_FADE_PX = 12`:12 条 1px、α 单调上升的
**画布色**实色块拼成的斜坡(iced 0.14 没有渐变原语;1px 步进的差别在 1/255 量级);
- `options::section` 的窄档分支把滚动区包一层 `stack![滚动区, 底部对齐的遮罩]`,遮罩贴着
视口内沿(≤660),压不到钉死区的实例名(≥668);
- 颜色取 `ui.layer(MaterialLayer::Canvas)`:浅色淡出到纸面、**深色淡出到墨底**(不是白雾),
与它下方那块背景同色,所以两档都不新增颜色;
- 三栏档不铺这条遮罩:那里滚动区内容恒 76px < 视口 ≈118px,不会出现半截内容,铺了只是
「画布盖画布」,反而会动 M222 已验的 1180 像素;
- `options/view.rs` 已 422 行、撞 400 行硬线,遮罩拆成 `options/fade.rs`(含一条几何自检单测)。
实测(720×760):
- 浅色 x=600 竖列 648 (211,229,245) → 659 (246,250,255) 单调升到画布色;深色 648 (55,72,85)
→ 659 (9,21,28),无白雾;中文与浅色逐值同串;
- 可见带 21px → 墨迹口径 18px + 底 12px 连续渐变(硬裁边消失,右侧滚动条仍在);
- 逐像素零回退:`77d675b`(遮罩前)vs 本轮 720 浅/深/中 + 700 守门,差异 bbox 只有
(112,648)–(696,660) 这 12 行,其余像素一个都没动;1180×760 浅/深/中 与 1180×900 对
M222 基线仍 **IDENTICAL**。
门禁:fmt ✓ / check ✓ / test --workspace(喂四张守门图)exit 0(app-iced lib 860 passed、
四条像素守门 + 400 行门全绿)/ clippy --workspace --all-targets -D warnings exit 0 /
.rs 全仓扫描 >400 行 0 个、<2 字节 0 个。
已知残留(本轮不动,按债务台账口径登记):menu 的 L2 阴影不随滚动视口裁切(压在实例名输入框上
约 6.9%,三栏档 fabric 同样存在)——根因在 `components::inputs::menu`,不在 M227 scope,
待专门 mission;finding `.tower/comms/findings/20261110-worker-m227-bug-l2.md`。
|
2026-11-10 23:07:20 +08:00 |
|
AskaEth
|
a70d5f8010
|
fix(download): 窄档面板加专属预算,首屏露出 head+六枚 chip+说明、实例名回到 56px(M227)
720×760(chips 档)原先走 `TailShare::Shared`:面板与列表各吃 Fill 的一半,面板钉死区
(说明 + 菜单 + 实例名 ≈180px)把它吃穿 ⇒ 滚动区视口 0,head(版本 id + 安装按钮)、
六枚加载器 chip、说明一个像素都不在屏,实例名被挤成 35px 且降部被自身边框裁掉
(M222 D30 五审 `artifacts/m222/visual-verdict-r5-hold.md`)。
改法(把 M222 在 1180 档验证过的「面板先保预算、列表吃整行」落到窄档):
- `TailShare` 拆出第三支 `Narrow`(chips 档 + 面板占高度),`of()` 的三条 arm 与
`Shared` 的机制注释按 r8 finding 重写(旧文档只写 M125 那一条条件);
- 新增 `CHIPS_STACK` 121 / `CHIPS_LIST_CHROME` 119 / `CHIPS_PANEL_BUDGET` 192 与
`narrow_rows()`:720/760 下面板先拿预算,列表 = **132px = 3 整行**(M125 像素守门的下限);
- `options::section` 三分支:窄档钉死区**只留实例名**,head / 六枚 chip / 说明 / 版本菜单
进滚动区(滚动区 120px ≥ 首屏 104,菜单露容器上沿 + 滚动条可见);
- `TailShare::list_height()` 把「列表吃多少」收到一处,两个装配点共用(三栏档行为不变)。
取舍:窄档列表 4 整行 → 3 整行;菜单从钉死改为可滚。理由(459px 里「钉住菜单」+「3 整行
列表」互斥)写在 `rows/tail.rs::CHIPS_PANEL_BUDGET`。
实测(tiny-skia 出图 + 自写 measure.py,DevDocs/artifacts/m227/):
720×760 分隔线 559→514、滚动区 0→120、head 539–578、chips 583–614(32px)、说明 623–634
(8.98:1 浅 / 10.83:1 深)、实例名 668–723 = 56px(inset 12/9/17);
700×760(options-fabric)实例名从「完全不在屏」回到 56px;四条像素守门全绿;
1180×760 浅/深/中 与 1180×900 **逐像素零变化**。
|
2026-11-10 23:07:19 +08:00 |
|
AskaEth
|
6c060a09e2
|
Merge branch 'feat/m228-ink-cat-dual-theme-design-tokens-in'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-10 22:34:08 +08:00 |
|
AskaEth
|
15309394ac
|
docs(design): M228 复审 r2 两处文档口径(Ink 矩阵上限、测试指针)
P2-1 统一全矩阵口径:palette/visibility.rs 实测表 Ink 行第二列「1.54 ~ 12.13」是旧的「对
surface-container-highest 单一层」量法,改成 4×5 全矩阵(4 个状态容器 × 5 个容器层)的上限
**16.47**(primary-container 压 surface-container-lowest),下限 1.54(secondary-container 压
highest)不变——Silver 行本来就是全矩阵(1.02 ~ 1.80),现在两行同口径。表头补「4×5 全矩阵」
说明,避免下次又被按单层读。
P2-2 修正失效指针:visibility.rs 模块文档的「实测」小节把测试指向
`tests/ink_cat_boards.rs`,但容器层可视性那几条在 r1 返工时已搬到
`tests/ink_cat_visibility.rs` —— 改指后者。boards/mod.rs:42 的「26 组文字/图标对
(tests/ink_cat_boards.rs)」仍正确,未动。
纯文档两行,无数值/代码变更。fmt --all --check=0;test -p unlucky-design --all-targets=0
(267 passed = 186 lib + 81 集成;ink_cat_boards 7 / ink_cat_visibility 4 / ink_cat_geometry 3)。
|
2026-11-10 22:34:05 +08:00 |
|
AskaEth
|
b8003177d7
|
fix(design): M228 复审 r1 三条(字段底角色对齐、四处小疵、产品默认口径)
P2-1 字段底角色不对称(塔裁决方案 a):Silver 的 #dcddde 从 surface-container-high 改判到
surface-container-highest,与 Ink 的 #262b25 同位——component_map 的 TextField 口径就是最高
容器层,单一角色接法才不会一边掉层。Silver 的 high 改取推导值 #e4e4e5(tone 90),阶梯
94.12 → 90.61 → 88.07 保持单调、步长 ≥1.74 L*。实测:Error on 新 highest = 4.686(原按单一
角色接到 high 时的 4.379 失守已消除)、OnSurfaceVariant = 6.383、OnSurface = 12.576。
测试同步:Silver 三条字段用法 pin 从 high 改 highest(与 Ink 对称),并新增
`field_backdrop_is_the_same_role_on_both_boards` 把这个不对称钉住(顺带断言 high 比 highest
更亮、highest 就是规格卡的 #dcddde)。
P2-2 四处小疵:
① inkcat/size.rs 扩展 FAB 起始内距 16 → **28**(规格卡画的数):框 x=292 → 图标盒 320(`+`
font-size 24 居中于 x=332),28 才是图上的距离;16 是 M3 官方口径,已在 doc 里登记为偏离,
并说明「要回 M3 的 16 得同时把图标中心挪回 316,那是要出截图的视觉改动」。测试补一条自洽
断言:292 + 28 + 24/2 = 332。
② inkcat/metrics.rs 的 SWITCH_THUMB_INSET 注释重写(原句断成碎片且读起来像算错):写明
轨道 y=1060..1092 / 拇指 y=1064..1088 ⇒ 上下各 4、右端 116−112=4,以及行程
52−24−2×4 = 20 的推导。
③ Silver「状态容器 / 容器层」数字统一成实测口径:boards/mod.rs 与 palette/visibility.rs 都写
「最低 1.02」(Ink 1.54),并点名最差的一组是 tertiary-container 压 surface-container-high
= 1.016;visibility 表的 Silver 阶梯步长改写 −1.7 ~ −4.1。
④ inkcat/type_scale.rs 正文清单只列 11 个**文字**字号(去掉 FAB 字形 20/26/34),并注明那三个
归 FabSize::icon_px、SpecText 没有 20 这一档。
P2-3 产品默认口径统一:palettes.rs 的「两个入口」表与 Palettes::baseline 文档、color/mod.rs
的「出厂默认色」小节、Scheme::baseline 文档统一写明「M228 起出厂外观入口是
DesignTokens::brand,本入口是 D38 取色链中性锚点」,与 lib.rs 同口径;顺带把 BASELINE_SEED /
DEFAULT_SEED / DEFAULT_NEUTRAL_CHROMA 的「产品默认」措辞改成取色链口径。
另:tests/ink_cat_boards.rs 已 400 行贴线,把 4 条容器层可视性测试拆到新文件
tests/ink_cat_visibility.rs(纯搬移 + 补 import);palettes.rs 因新增口径说明到 403 行,按
「合并相邻段落、不删信息」压回 399 行;size.rs 现在 323 行。
五步(design 口径):fmt --all --check=0;check -p unlucky-design --all-targets=0(零 warning);
test -p unlucky-design --all-targets=0(267 passed);clippy -p unlucky-design --all-targets
-- -D warnings=0;400 行扫描只剩 README.md(豁免)、无 <2 字节 .rs。
|
2026-11-10 22:34:05 +08:00 |
|
AskaEth
|
0169d4c8fb
|
feat(design): 加 Ink/Silver 双主题色板 + 规格卡几何与字号令牌(M228)
规格卡 DevDocs/artifacts/components-ink.svg 的令牌化,只动 crates/design,零依赖不变。
- color::boards:BrandBoard(Ink 深 / Silver 浅)→ 49 角色 × 2 套固化色板。
spec 直写值进 ANCHORS(测试逐条比 hex,规格卡改了代码不改会红);规格卡没给的
角色按「同色相 + M3 tone 关系」推导后固化,规则写在 boards/ink.rs 的行注释里。
Scheme::from_board / DesignTokens::brand 是两个新入口;Scheme::baseline 一字未改
(它是 D38 取色链的中性锚点,改它等于同时改 app-iced 观感 + 取色语义)。
- palette::visibility:容器层可视性裁决,即债务台账「SecondaryContainer 对 Surface
仅 1.06」的令牌层结论。状态容器(*-container)对 surface ≥1.20:1 是唯一数字门禁;
容器层之间不设亮度门禁,改为「选中态 = 容器底 + 文字对比 + 形状/图标」三件套的构成
要求(浅色板实测 1.05~1.24,任何有意义的门禁都过不了,详情见模块文档实测表)。
- inkcat:规格卡的尺寸与形状数据。ControlSize(按钮 XS~XL = 32/40/56/96/136,圆角
一律半高)、FabSize(40/56/80/160×56,圆角 12/16/20/16)、控件方框(卡片 16、
对话框 28、snackbar 52@16、列表行 60 药丸、chip 32@8、字段 4/28 + 内距 16/32、
复选 18@4、单选 18、开关 52×32 + 拇指 24、进度 4/6)、SpecText(规格卡 11 个字号
→ 命中 / 落档 / 低于 11px 下限三类结论)。
component_map 的表值一处未动:app-iced 的 pages/frame/tests.rs:42 与 shell/tests.rs:30
按 Medium(12) pin 住卡片,改表等于连带改观感且破坏既有断言;差异在 inkcat 模块文档
逐条登记,换肤时由 M229 一次切换 + 截图验收。
- 测试:186 lib(+13 条新单测)+ tests/ink_cat_boards.rs(10 条:完整性/锚点/双主题
切换/26 对 WCAG AA/25 组规格卡用法/状态容器 1.2 门禁/阶梯单调)+ tests/ink_cat_geometry.rs
(3 条:按钮 FAB 几何、控件圆角、字号校核)。
|
2026-11-10 22:34:05 +08:00 |
|
AskaEth
|
7fe38fb240
|
Merge branch 'feat/m224-local-mods-enable-disable-modtoggle'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-10 22:07:09 +08:00 |
|
AskaEth
|
71144dfca8
|
Merge branch 'feat/m223-gui-adopt-import-wiring-guiadopt'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-10 20:18:14 +08:00 |
|
AskaEth
|
44c00b4873
|
fix(app-iced): 测试助手改用 matches!,消掉 deny 级 clippy(复审 r2 P1-1)
`dispatched_adopt` 内层 `match (&**command, want) { … _ => false }` 被判
`match expression looks like matches! macro`(deny 级 ⟹ CI 的 `--workspace --all-targets
-D warnings` 必红;558fbf2 原树 `cargo clippy -p unlucky-app-iced --all-targets` 实测 exit 101)。
改成 `let AppCommand::Adopt(..) = command else { return false }` + 一条 `matches!`,
判据与原来逐字等价(四种命令种类 × 对应 slug)。同一处的注释补一句「为什么不能再写 match」,
免得下一个人改回去。
教训记账:r2 自验只跑了 fmt/check/test,漏了 clippy(T23「五步少一步等于那步不存在」);
本轮五步齐跑,退出码见完成报告。
|
2026-11-10 19:37:45 +08:00 |
|
AskaEth
|
fa3fab9086
|
fix(app-iced): 复审 r1 三条(在飞守卫补 Back/Target、预检按版本过期、shell 文档如实)
P2-1(行为):`Busy::Importing` 的早退只盖了 Close。「返回」会派 Scan、「换目标档」会派
零写盘 Preview,两条都把 `adopt_epoch` +1,于是在飞的 Confirm 交卷时 Done 被代次丢掉——
实例写进去了,界面什么都没说(正是 Close 守卫要防的形状)。两处补同款早退,且守卫写在
改本地状态**之前**:挡下来的换档不该把界面改成 B 档而盘上写的是 A 档。
P2-2(显示陈旧):`Form::fresh_plan` 原来只按目标档过期。先选 1.20.1 + 版本目录档,再改选
1.19.4,目标档没变,旧预检的 notes(说的是 `versions/1.20.1`)会继续当新版本的后果原文画。
现在连 `plan.version_id == selected` 一起判;没选中版本时也返回 None。同处那句
「版本一致性由 Self::candidate 那一路保证(选中即重扫)」与代码不符(Select 只改本地字段、
不发命令),改成如实说明过期判定只能在这里做。
P2-3(文档):`app/adopt/shell.rs` 的「纯搬移:函数体一行未改」不成立——同一提交里卡壳几何、
页脚去重、预览摘要都改过。改成逐条列出改了什么与为什么。
用例(两条都是先写红再修,红时 panic 信息就是错在哪):
- back_and_target_are_ignored_while_the_registration_is_in_flight(记账后端断言「不许派
Scan / 不许派 Preview」);红:`登记在飞时 Back 不许派 Scan(会把 Done 的代次顶掉)`;
- fresh_plan_expires_when_the_selected_version_changes;红:`换了版本之后旧预检必须过期
(否则画的是 1.20.1 的后果原文)`。
顺带补图:`ULL_ADOPT_STATE` 加 `pick-selected` 一档(选中行底色 + 预检启用态),
出图 `DevDocs/artifacts/m223/adopt-pick-selected-light.png`(r1 复审说这格只有代码没截图)。
|
2026-11-10 19:37:45 +08:00 |
|
AskaEth
|
6ffbd51f60
|
feat(app-iced): 向导的出图种子 + 卡壳几何修正(M223 视觉自检)
D30 的截图通道在本机可用(`--software-renderer` 走 tiny-skia,1180×760 落盘),所以不再把视觉
证据留空:新增 `ULL_ADOPT_STATE`(与 `ULL_JAVA_STATE` / `ULL_AI_STATE` 同一族,**只在
`--screenshot` 下生效**),可把向导摆到七种状态之一(pick / empty / preview / shared / failed /
done + 深色),于是出图不靠「赌异步时序」。
顺带修掉三处只有出图才看得见的问题:
1. **卡壳把页脚裁掉了**:内容高于 `max_height` 时 iced 直接裁,第一版 PICK 段的「实例名」与
按钮整排不可见。现在列表段(Pick/Failed)定高 + 中间区可滚、页脚固定;短内容段
(Preview/Done)按内容收高,不留一大片空白;
2. **两枚一模一样的「关闭」**:Failed/Done 的主按钮就是关闭,文字关闭不该再出现;
3. Preview 段少了实例名一行,且标题下的摘要重复了「共用目录 · 共用游戏目录」。
夹具只造候选/问题/运行目录这些**版面数据**,后果原文仍由 core 的 `target_notes()` 现算,
图证明版面与文案、不证明 core 判据(见 `DevDocs/artifacts/m223/README.md` 的真/假表)。
为守 400 行(`components::gate` 实测 412 行红),卡壳/标题/页脚纯搬移到 `app/adopt/shell.rs`。
|
2026-11-10 19:37:44 +08:00 |
|
AskaEth
|
a36e17a5d3
|
fix(app-iced): 点选共享档立刻显示后果原文,登记在飞时不吃关闭(M223 自检)
两处自己复审抓到的问题:
1. 契约 §5.2 要求 Shared/VersionDir 被**点选**就展示 target_notes() 原文,而上一版要等
用户再点一次「预检」才出现。现在点选这两档会顺手发一次**零写盘**的预检,结果落在
Pick 段当提示读(不把用户甩到确认页:确认仍由显式的「预检」按钮触发,Form::checked)。
改档之后旧 plan 因目标档不一致而不再显示(Form::fresh_plan),避免画上一档的后果原文。
2. 「关闭」在登记在飞时被忽略:core 那条线程已经开跑,关面板会让 Done 事件被代次丢掉,
用户看到的就是「实例写进去了,界面什么都没说」。
用例:a_foreign_target_shows_its_notes_without_jumping_to_confirm /
choosing_a_foreign_target_previews_but_waits_for_the_user /
closing_is_ignored_while_the_registration_is_in_flight。
|
2026-11-10 19:37:43 +08:00 |
|
AskaEth
|
9ca5ee8ae7
|
docs(core): adopt 的两条文档小疵(r4 复审留的,M223 顺手修)
- plan.rs 第 11-12 行:一个悬空半句(「…必须落在」后面断掉)补完,并把 fspaths 的引用接上;
- run_dir.rs 第 16 行:删掉「配置写面看 <.ULL>、并额外排除游戏根」这条**已移交**
fspaths::ensure_surfaces_clear_of_game_tree 的职责描述,明说本模块只负责数据写面这一条。
纯文档,零行为变化。
|
2026-11-10 19:37:43 +08:00 |
|
AskaEth
|
41caae3571
|
feat(app-iced): 实例页「导入已有版本」向导(M223)
页头「导入」按钮(以前是占位动作,点了只打日志)现在打开四段向导:
| 段 | 画什么 |
|---|---|
| Pick | 候选版本(可导入的才可点)+ 目标三档 chip + 实例名(缺省 InstanceDir) |
| Preview | 生效运行目录(回程给的)+ 隔离档 + target_notes() 原文逐条 |
| Failed | 稳定码 + problems 逐条(不折叠成一句「导入失败」) |
| Done | 实例名 + 运行目录 + 提示原文 |
- 向导是叠在页面之上的一层(遮罩 + 居中卡),不搅乱 list-detail 的版式判据;
- 表单态在 App(app/adopt.rs 的 Form),事件真相在 presenter 的 AdoptState;
- 在等回程时主按钮禁用(连点「确认」不会写两条实例);
- i18n 中英目录 + 金表补 27 条向导文案(实例域末尾追加)。
|
2026-11-10 19:37:42 +08:00 |
|
AskaEth
|
5a59cb33b2
|
feat(presenter): adopt 的指令/事件/任务接线 + install 那句假话变真话(M223)
- AppCommand::Adopt(Box<AdoptCommand>)、CoreEvent::Adopt(Box<AdoptEvent>)、
HostTask::Adopt { event, epoch }:三条穷尽枚举各加一条变体(代次独立于安装与 pack);
- dispatch.rs 一条分支、handle.rs 一条分支、state.rs 的 adopt 字段与一条 apply 分支;
- host/install.rs 的「还没有接入安装管线…请先在实例页导入已有版本」改成指向真入口:
那里以前是一句假话(实例页根本没有导入动作),现在向导落地了,它才成立。
|
2026-11-10 19:37:41 +08:00 |
|
AskaEth
|
26e4fbb91d
|
feat(presenter): 导入已有版本的 DTO 与三条后台线程(M223)
core 的能力(M221)接进 presenter 的 IPC 面:
- dto/adopt.rs:AdoptCommand / AdoptEvent / 六种视图 / AdoptState + AdoptStage。
全部 Serialize+Deserialize,**不 import 任何 core 类型**(契约 §5.4);
- host/adopt.rs:spawn_scan(adopt::scan)/ spawn_adopt(plan 或 adopt),结果经
HostTask::Adopt 回流;core 的候选/计划/结果/失败逐字段搬进 DTO,problems[] 一条不少;
- CoreHost 加 adopt_epoch(每条命令 +1、关面板也 +1),连点与迟到的回程不会点亮面板;
- 登记成功后补一次 refresh_instances(新实例这一拍就出现在列表里)。
运行目录只从 plan()/adopt() 的返回值取(缺省档可能带 -2 后缀,只有 core 知道用了哪个);
target_notes() 的原文随 AdoptPlanView::notes 上屏,界面不另写一份后果话术。
|
2026-11-10 19:37:23 +08:00 |
|
AskaEth
|
9ab909d01c
|
refactor(presenter): 两处纯搬移腾出 400 行余量(events 用例 / AI 栏事件投递)
events.rs 399 行、state.rs 400 行(AGENTS §3.1 与 §3.9 的零余量贴线文件)。M223 要往
AppCommand/CoreEvent 各加一条「导入已有版本」的变体、往 AppState 加一份事件态,都得先腾地方。
纯搬移、一行未改:
- events.rs 尾部 #[cfg(test)] mod tests(80 行)→ dto/events/tests.rs(399 → 321);
- state.rs 的 apply_agent + agent_rail_visible(28 行)→ state/agent.rs(400 → 376)。
方法路径不变(impl 写在子模块、pub use 无需新增),断言一条未动,搬移 diff 可逐行对。
|
2026-11-10 19:37:23 +08:00 |
|
AskaEth
|
13a317d35c
|
Merge branch 'feat/m226-fix-instances-module-doc-stale-clai'
|
2026-11-10 19:36:47 +08:00 |
|
AskaEth
|
938ce9866b
|
docs(cli): instances 模块文档的约定二限定到 optifine,注明 liteloader 自 M222 可装
同一句假话(optifine / liteloader 都装不了、--loader 只有五个取值)在 cli/help.rs 的那份
已由 55e2f23 修好,这里是第二份副本。现在把限定收窄到 optifine(要跑外部 Patcher,报
loader_unsupported,只能继承进来),并写明 liteloader 自 M222 起可以 `install --loader
liteloader`,取值表指向 `ull help install`(原来指向 crate::cli::model——那份字段文档自己
也是旧口径)。认得出仍是七个,装得了是六个。
纯模块文档改动,不动任何断言。
|
2026-11-10 19:36:38 +08:00 |
|
AskaEth
|
77f73a307f
|
Merge branch 'feat/m222-liteloader-install-route-and-suppor'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-10 19:36:09 +08:00 |
|
AskaEth
|
a16aadd467
|
docs(app-iced): 按 TailShare::of 的真实分支清扫下载页面板注释(r7 三条 + 全量扫)
(i) 上轮新引入的错误声明 rescope:错误行「恒为 None / 滚动区只有 head+chips」只在**三栏档 +
带版本列表**这一支成立(has_loader_versions() 要求 problem.is_none())。**chips 档**(可用宽
< 600,TailShare::of 对 Options/Install 无条件 Shared)里 problem 可以是 Some(静态不兼容 /
列表取不到 / 空表三种),错误行会进滚动区且可能被滚走;show_versions() 为假时钉死区只剩
实例名。install.rs 与 options/view.rs 都按这两支分别写清,并注明该行为域由 M227 重设计。
(ii) install.rs 的「显式给对半分档标志」「对半分:面板吃满自己那一半」改成预算优先口径
(与同文件十行上方「不是早期的对半分」不再矛盾)。
(iii) rows.rs 三处:三栏档「照旧钉死」限定为「**不带版本列表**的选项区」;`content` 的 share
说明改成引用 TailShare::of 的真实两支,不再复述「三栏钉死 / chips 各吃一半」;
「只有对半分那一档才按整行取高」改成「只有 Shared(面板先保预算)那一档」。
全量扫附带修正:tail.rs 的 ROW_PITCH / PANEL_BUDGET 账(760 例改成 176/437、900 改成 569;
预算拆成滚动区 76 + 钉死区 153);options/model.rs 的 has_loader_versions 文档改成预算口径。
零代码零测试变更。
|
2026-11-10 19:34:15 +08:00 |
|
AskaEth
|
409c742aa8
|
docs(app-iced): 扫掉 r6 的三处文档残留(B 结构合入前最后一改)
(a) install.rs 的 panel/panel_with 文档按当前结构重写:`shared` 不再是「对半分」(现为
面板先保预算 + 列表按整行,见 rows/tail.rs::whole_rows);滚动区实际只有 head + 六枚 chips
(菜单 9358668 进钉死区、说明 a6f7974 进钉死区),钉死区 = 说明或标签 + 菜单 + 实例名;
并写明错误行在 Shared 档恒为 None(静态不兼容时不画版本菜单、走不滚动那条路)。
(b) options/view.rs 里「标签与说明都搬到钉死区」自相矛盾:改成「**说明**搬到钉死区;
『安装选项』标签在 Shared 档**删掉**(八审裁决),Pinned 档保留」。
(c) 同文件 section() 文档里「滚动区溢出时被滚走的是错误行」描述的是不可达情形:改成
「滚动区只有 head + chips,用户可见内容不会被滚走(错误行在 Shared 档恒为 None)」。
零代码零测试变更。
|
2026-11-10 19:34:15 +08:00 |
|
AskaEth
|
33d05689e8
|
fix(app-iced): 说明只画一份(删滚动区重复)+ 口径三处改真
八审唯一阻断:中性说明画了两份(滚动区 546–557 与钉死区 576–587 像素级相同、重影)。
删掉滚动区那份,钉死区保留;01/02/05 与 900 的说明墨迹带现在只有一条(576–587 / 716–727)。
口径三处:
1. `section` 文档的滚动区内容改成实际(head + 六枚 chips + 错误行);
2. 旧的「≈150px / 无需滚动」改成八审实测(可见 96–104px、内容 76px、head 墨迹 462–501、
chips 506–537 四角闭合);
3. 钉死区注释「标签与说明都搬到钉死区」改成事实:Shared 档的「安装选项」标签是**删掉**的
(设计决定,八审裁决),只有说明是搬下去的;Pinned 档仍保留标签。
|
2026-11-10 19:34:15 +08:00 |
|
AskaEth
|
e2e3c39f61
|
docs(app-iced): 扫掉 r5 的五处文档漂移(返工链留下的旧描述)
1. install.rs 模块文档:删掉「钉页脚本轮没做」(6f87c8b 已做:钉死区现在是菜单 + 实例名),
三层滚动的第 3 层改引 `version_menu_height`(VERSION_ROWS 已改),并写明「选项区自己滚」
这层是 M222 之后才有的、`Options` 态不再套外层 scrollable(只有 `Install` 态用)。
2. rows.rs:五个 loader chip → 六枚;「实例名那一格滚一下就到」→ 已钉死;分法描述改成
「面板先保预算 + 列表按整行」(PANEL_BUDGET / whole_rows)。
3. rows/tail.rs 的 TailShare 表:两半各 Fill → 面板先吃预算;补上「带加载器版本列表」这一例外行;
判据注释改成「像素由 window_height 算」(view_sized 已接)。
4. rows/tail.rs 的 M222 分支注释:删「仍欠一项 S1b」(107d619 已修、26ec3f4 已换预算公式),
写清现在 1180×760 = 176px/4 行、900 = 308px/7 行。
5. options/view.rs 控件树示意:改成当前真实顺序(滚动区 = head + chips;钉死区 = 说明/标签 +
菜单 + 实例名),并补上钉死档另走一条路的说明。
|
2026-11-10 19:34:15 +08:00 |
|
AskaEth
|
7ea3dbf8f0
|
fix(app-iced): 方案 B——说明与「安装选项」标签撤出滚动区,滚动区只装控件
六审算术:滚动视口 103px < 内容 130px,说明 0 像素 + chip 底边框被裁 2 行。方案 C(标签并排)
不可行——六枚 chip 已占 404px、正文列 424px,并排必横向溢出裁掉第六枚,等于用一个已完成项
换另一个。按塔授权走 B:
- **滚动区**(仅 Shared 档):head + 六枚 chips,间距 X2→X1;
- **钉死区**:说明行(有说明时占「加载器版本」标签那一行,否则显示标签)+ 版本菜单 + 实例名。
钉死区高度不变(两者都是一行),列表仍 4 行;
- **钉死档保持旧结构**:把标签撤出会让原版面板矮 26px、列表涨到 302px(6.86 行、切出半行),
实测把 `04` 从 531 挪到 563 —— 第一版就是这个坑,已按此回退。
实测:01/02/05 分隔线 437、列表 **176px = 4.00 整行**、说明墨迹带 y 576–587(真像素);
04 **531 未变**;03/06 268 未变;900 档 569 / 308px = 7.00 整行。
|
2026-11-10 19:34:14 +08:00 |
|
AskaEth
|
cb2cffb3ca
|
fix(app-iced,i18n): 分高改「面板先保预算」+ 说明压成单行(D30 五审一次性重分配)
五审:菜单文字/720 列表/44-97 标定/04 都过,但滚动区被压到 82px(宽)/24px(窄)——宽档切
「安装选项」字身、六枚 chip 0 像素。塔裁决停止逐轮调顺序,一次性重分配:
1. **分高从「对半分」改成「面板先保预算」**:`PANEL_BUDGET = 300`(滚动区 128 + 钉死区 136),
`whole_rows = floor((column − LIST_CHROME − PANEL_BUDGET)/44) × 44`。
1180×760:580−97−300 = 183 ⇒ **176px = 4 整行**(原 5 行),面板 307px ⇒ 滚动区 ≈150px;
1180×900:323 ⇒ **308px = 7 整行**(未变)。720 档走 `None`(Fill),未动。
2. **说明压成单行**(`DownloadOptionsLiteLoaderVanillaOnly`):两行 28px 是滚动区最大的可压项。
en `Vanilla parent only — Forge goes through the CLI` / zh `只叠原版;叠 Forge 请走命令行`;
完整命令行说法留在 `ull help install`。单测断言随之改成「含该句 + 只有一行」并写明原因。
文档按六审要求改口:滚走的是说明/错误行(非控件);说明变单行后 760 档滚动区已能装下
head+标签+chips+单行说明,**无需滚动**。
|
2026-11-10 19:34:14 +08:00 |
|
AskaEth
|
6eb7132ab0
|
fix(app-iced): 加载器版本菜单搬进钉死区(裁切位置由钉死区高度决定,不是首屏分配)
四审点破根因:面板的裁切 y 由**钉死区高度**决定、与窗宽无关(1180 与 720 都切在同一处),
所以上一轮「把说明挪到菜单之后」只是换了被切的对象——菜单容器 642–706 只露 18px、条目文字
一个都没画,说明整块不可见。
按 tower 裁决改成结构方案:
- **滚动区**:head(版本 id + 安装按钮)+ 加载器 chips + 中性说明 + 错误行;
- **钉死区**:加载器版本菜单(标签 + 菜单,按条目数占高)+ 实例名输入框。
菜单不再依赖首屏剩余高度分配,可达性由结构保证;溢出时被滚走的是说明/错误行(非控件)。
实测(未变的部分也一并核对):01/02/05 分隔线 **481**、列表 **220px = 5 整行**;04 对照 **531 未变**;
03/06 窄窗 **268 未变**(本轮结构改动没有挤动列表);900 高加测 569 ⇒ **308px = 7 整行**。
文档按四审改正:`section` 的文档不再写「首屏能装下菜单/切点不再切字」,改成「裁切位置由钉死区
高度决定」;面板结构写清 head 在滚动区、菜单与实例名在钉死区。
|
2026-11-10 19:34:14 +08:00 |
|
AskaEth
|
0ca28d24ff
|
fix(app-iced): 版本行节距/列表 chrome 标定重算(44/97)+ 中性说明挪到版本菜单之后
三审给了独立量测,两处都按它的数字收敛:
1. `ROW_PITCH` 278/6=46.33 → **44.0**(278 里含列表与面板之间的 8px `Space::X2`);
`LIST_CHROME` 93 → **97**(列顶 156 → 列表顶 253)。1180×760 分隔线 492 → **481**、
列表视口 **220px = 5 整行**;1180×900 加测 = 308px = 7 整行,三审报的 900 高残影不复发。
2. S7 钉页脚引入的新问题:面板上半块的滚动切点压在字形上(宽窗切「加载器版本」标签上沿、
窄窗把 chips 切成 4px 条)。把中性说明从菜单**之前**移到**之后**——它两行高,排前面正是把
唯一的加载器专属控件顶出首屏的元凶。
顺带纠正一处文档:面板结构不是「头部 + 可滚上半 + 钉页脚」,head(版本 id + 安装按钮)是上半块
的第一个子项、**安装按钮也在滚动区**,只有实例名是钉死的。
|
2026-11-10 19:34:14 +08:00 |
|
AskaEth
|
954281d71a
|
fix(app-iced): 版本列表在三栏对半分档按整行取高(S1b/S9)
列表视口高度不再是「Fill 剩下的」:正文列高度由仓里已有的 `frame::body_height` 算出,
经 `view_sized` → `body` → `rails::three_column` → `rows::content` 传下来,Shared 档据此
`floor(((h − LIST_CHROME)/2) / ROW_PITCH) * ROW_PITCH` 取整行。
实测(1180×760):列表 245px → **239px**(分隔线 498 → 492,腾出的 6px 归面板),
`04` 对照**未变**(531 / 278px),720 窄窗**未变**(本来就是 4.0 整行、底缘 0 墨迹)。
两处修正写在提交里:
1. 第一版给所有三栏场合取整,把 `04` 也缩到 239px ⇒ 收窄为只在 `TailShare::Shared` 取整;
2. `rows.rs` 涨到 419 行撞 400 行门禁 ⇒ `TailShare` 与三个标定量纯搬移到 `rows/tail.rs`,
`rows.rs` 回到 355 行(门禁 `components::gate` 与全仓扫描都绿)。
`view` 保留 4 参数(`#[cfg(test)]`,只有单测用),产品路径走 `view_sized`。
|
2026-11-10 19:34:13 +08:00 |
|
AskaEth
|
f29ca609ec
|
fix(app-iced,i18n): 实例名钉成面板页脚(S7)+ 中文占位符还原(S8)+ 注释实测值(S6)
D30 r2 视觉二审四条:
1. **S7(新回归)**:S1 的对半分让面板变矮之后实例名被挤出 1180×760 首屏。按 `install.rs`
早已登记的方案改成**可滚上半块 + 钉死页脚**:`install::panel_with(.., shared)` →
`options::section(.., scroll)`;`rows::content` 对 `PanelState::Options` 不再整块套
scrollable(那层既把实例名卷进滚动区,也在窄窗产生第二条并列滚动条)。页脚不参与滚动,
可达性由结构保证,S2 随之关闭。
2. **S8**:`catalog/download.rs` 与金表同一行的中文命令占位符 `<Forge 版本 id>` 照抄不可执行,
还原成 `<forge id>`(两处一起改,金表顺序未动)。
3. **S6**:`rows.rs` 里「两半各 ≈215px / 列表 4.6 行」的估算换成实测(列表 245px = 5.29 行、
面板 237px、分隔线 y=498),并写明 S1b 仍欠一项与其正确落点。
**S1b/S9(列表底缘整行)本轮没修成**:试过 `responsive` 拿「分给列表的高度」再 floor,
实测无效(`Fill` 子项上拿到非确定高度,量测显示列表区最后墨迹行仍在分隔线前一行),已回退,
不留无效代码。正确落点是把列表可用高度从 `pages/download.rs` / `rails.rs`(都在 scope 外)
传进 `rows::content`。
测试:`-p unlucky-app-iced` 862 passed、`-p unlucky-i18n` 36 passed(金表改动后仍绿)。
|
2026-11-10 19:34:13 +08:00 |
|
AskaEth
|
cd7813f8eb
|
fix(app-iced): 选项区变高时与版本列表对半分(S1)+ 中英出图注入 + 注释同步
D30 r1 视觉驳回的三条里,代码能修的两条:
1. **S1 版面**:带加载器版本列表的选项区在三栏档把版本列表从 6.0 行压到 2.4 行(1180×760
实测 278px→110px,底缘切在第三行图标上)。`rows::TailShare::of` 现在让这类面板在**任何档位**
都与列表对半分(`scrollable` 分支早已存在),另把加载器版本菜单改成**按条目数**占高
(1 条 64px / 2 条 112px,原先只有 1 条也固定撑两行)。实测:列表回到 **245px = 5.3 行**。
2. **注释同步(S6)**:`options/view.rs` 模块头原先写「加载器分成两行」+ 两行示意,实际是
六枚一行(末枚右缘约 404px / 正文列 424);那条「本轮无看图通道,未出截图」也换成实测值。
顺带(使能中文出图):`pages/mod.rs::demo_model` 读 `ULL_DL_LANG=zh` 把 `model.lang` 与下载页
目录一起切中文。之前 `LC_ALL=zh_CN.UTF-8` 对出图**完全无效**——页面文案来自 `model.lang`,
而 `Model::new` 写死 `En`、`demo_model` 不碰它(`i18n::detect_env` 不在这条链上)。
测试:`options/s1_tests.rs` 新增两条(菜单只按整行占高;带版本列表的选项区在每个档位都 Shared,
原版/不兼容仍 Pinned),`-p unlucky-app-iced` 862 passed。
未完成:S2 窄窗「滚到底」图——iced 0.14 没有自由函数 `scroll_to`,程序化滚动要手写
`widget::Operation`(约 40 行),本轮不拿半成品充数;面板 `scrollable` 的稳定 Id 已就位。
|
2026-11-10 19:34:13 +08:00 |
|
AskaEth
|
841c17152a
|
test(app-iced): 下载页加 options-liteloader 出图取样点(D30 视觉取证)
M222 的视觉门禁一直缺图:本机没有看图通道,但**出图通道可用**(`--software-renderer` 走
tiny-skia CPU 后端,无桌面也能落盘)。下载页早有专属夹具 `ULL_DL_DEMO`(`demo.rs`,产品路径
不读),M105 就用它出过安装选项区三态,所以这里加第四个取样点,而不是再造一个平行 env:
ULL_DL_DEMO=options-liteloader pages_demo --screen=download --width=1180 --height=760 \
--screenshot=out.png --screenshot-theme=light --software-renderer
取样点是「选中 1.12.2 + LiteLoader」(官方版本表只到 1.12.2),走**产品那条真转换**
`Download::from_snapshot`:chip 行、中性说明「只叠原版」、确认键状态都是产品行为。
版本列表**只在本次状态里**补一条 1.12.2——不动 `demo_versions()`,因为那是别处基线图的行集
(`tests/view.rs` 钉着 8 行)。
四张图与真假表落在 `DevDocs/artifacts/m222/`(git 忽略区,不入库):浅色 / 深色 / 720 窄窗 /
原版对照,README 里写明哪张证明什么、哪些是夹具、哪些是产品真转换。图的内容判定未做——
出图者无图像能力,交给视觉 reviewer。
|
2026-11-10 19:34:13 +08:00 |
|
AskaEth
|
7d43f1d686
|
fix(cli): help instances 里把「install 不接受 liteloader」改成只限 optifine
M222 让 `install --loader liteloader` 可用后,`help instances` 那句「optifine / liteloader 只能由
别的启动器装好后继承进来」的后半句就成了假话,而且与同文件 `help install` 的记载
(`--loader` 取值含 liteloader、optifine 报 loader_unsupported)自相矛盾。
现在这条限定只落在 optifine,并注明 liteloader 自 M222 起可装。`kind` 仍是「认得出」的口径——
这句话的主体(kind=vanilla 只表示 id 里没痕迹、权威结论要读 libraries[])没动。
无测试断言过旧措辞:`help_vocabulary_tests::help_lists_every_stable_id` 只按 `LoaderKind::ALL`
遍历「id 出现在文案里」,新文案两个 id 都在,因此不需要同步改期望。
|
2026-11-10 19:34:12 +08:00 |
|
AskaEth
|
04a123deab
|
fix(app-iced,presenter): 修 clippy -D warnings 抓到的三处(文档注释位置、manual_range_contains、单元素 for)
1. view.rs:我删掉手写 `LOADERS` 常量后,那段文档注释落到了 `VERSION_ROWS` 头上并留了空行
(clippy::empty_line_after_doc_comments)。说明本身是给「chip 顺序为什么委派」用的,
改成普通 `//` 注释,不冒充某个条目的文档。
2. model.rs:LiteLoader 的版本区间判定改成 `((1, 5, 2)..=(1, 12, 2)).contains(&version)`
(clippy::manual_range_contains),语义一字未变。
3. install_tests.rs:只剩 OptiFine 一家后,单元素 `for` 改成直调(clippy::single_element_loop)。
|
2026-11-10 19:34:12 +08:00 |
|
AskaEth
|
3114fa2d4f
|
fix(core): 首轮编译/测试抓到的四个问题(BTreeMap、测试模块布局、display_loader_version、未用 import)
1. liteloader.rs 拆文件后漏了 `use std::collections::BTreeMap`(`loader_library` 里用)。
2. `tests/liteloader.rs` 是 **crate root**,`mod guards;` 会被找去 `tests/guards.rs`;
改成 `tests/liteloader/main.rs`(与 `tests/game/main.rs` 同形),target 名仍是 liteloader。
3. `display_loader_version` 去通配时丢了 NeoForge@1.20.1(forge 布局)去前缀的语义:
既有用例 `neoforge_1201_uses_forge_layout` 当场抓住(1.20.1-47.1.106 → 47.1.106)。
现在按「加载器 + 是否 forge 布局」显式决定,四家原有行为逐字不变。
4. `dto/instances.rs` 的 `pub use super::LaunchArgsView` 在非 test 构建里没人用(-D warnings 会红),
改由 `dto/instances/tests.rs` 直接 `use crate::dto::LaunchArgsView`。
顺带修两条**我自己写错的测试期望**(不是产品 bug):
- 取消用例里那条 `resolve` 断言用错了父版本表(父版本在内存表里,当然解析成功);
- 新式父链用例误以为 `minecraftArguments` 会是 None,实际会沿链继承祖父层那份
—— 断言改成「继承来的 legacy 串里不许出现 LiteLoaderTweaker」。
|
2026-11-10 19:34:12 +08:00 |
|
AskaEth
|
51ea32284d
|
docs(core): inherit::merge 里写清 minecraftArguments(子覆盖父)与 arguments.game(追加)的区别
M222 的准入条件就是这条;不给结论留档,下一个人还会以为「叠一层」随便往哪个字段写都行。
|
2026-11-10 19:34:12 +08:00 |
|
AskaEth
|
c311032080
|
chore(cli): report_loader 测试里省一行,给 400 行硬线留出余量
|
2026-11-10 19:34:11 +08:00 |
|
AskaEth
|
0eaefebf3b
|
fix(core): 同一 loader 版本 id 撞两条父链时具名报错,不覆盖盘上清单(旧实例会静默换父链)
|
2026-11-10 19:34:11 +08:00 |
|
AskaEth
|
9c6b5eaa9b
|
chore(core): 去掉 liteloader 集成测试与 versions.rs 里未使用的 import(-D warnings 会红)
|
2026-11-10 19:34:10 +08:00 |
|
AskaEth
|
c8bf536c37
|
feat(app-iced): 下载页在选 LiteLoader 时给中性说明(GUI 只叠原版、Forge 组合走 CLI)
新增 Key::DownloadOptionsLiteLoaderVanillaOnly(中英各一句,不含花括号占位符);
金表按 Key::ALL 的真实位置插入(DownloadOptionsNeedsAtLeast 之后、DownloadCancelled 之前),
只加新键不重排旧键。说明走 OptionsView 的新字段 note,不塞 problem(那是错误色 + 禁用确认)。
|
2026-11-10 19:34:10 +08:00 |
|
AskaEth
|
7ed954f7fa
|
fix(core): LiteLoader 快照仓固定为 repo.mumfrey.com/snapshots(1.10.2 的 repo.url 是 /repo/ 形态,照它会 404)
|
2026-11-10 19:34:10 +08:00 |
|
AskaEth
|
944b1610c8
|
feat(core): LiteLoader 安装路线(官方版本表 + 从零合成 launchwrapper 清单)
按 M218 SURVEY-p1 §4.1 的清单接上 LiteLoader 的安装路线,不动 OptiFine。
- core: 新增 addon::loader::liteloader(官方 versions.json 解析 + 合成清单)与
addon::loader::parent(父版本链读盘定位原版版本);LoaderKind 加 LiteLoader;
LoaderRelease 加 parent_version_id、LoaderPlanInput::vanilla_installed → parent_installed;
LoaderInstallPlan::build 改返回 Result(父版本缺失具名报错);LoaderSource::resolve_on
支持指定父版本(仅 LiteLoader,其它组合具名 unsupported)。
- 继承边界(本 mission 第一步核实):arguments.game 父在前子在后追加,minecraftArguments
子覆盖父,启动顺序 legacy(24) → arguments.game(25)。所以 tweakClass 只写 arguments.game,
绝不写 minecraftArguments(会顶掉 Forge 1.12.2 的 FMLTweaker)。
- maven/fabric 的 `_ =>` 通配改成穷尽分支:非 maven 族不再静默回落 Forge 的仓库;
maven::{metadata_url,installer_url} 改返回 Option<String>。
- presenter: supported() 收 LiteLoader、wizard_choices 六选、dto::instances 按关注点拆成
instances/{loader,runtime,create,tests}.rs(原文件 395 行,动手前先拆)。
- cli: --loader 区分「认不出」(loader_unknown) 与「认得但装不了」(loader_unsupported);
<version> 位写加载器版本 id 时按父版本处理(只有 liteloader 接受);help 只改 install 段。
- app-iced: 下载页加载器 chip 加 LiteLoader(1.5.2~1.12.2,两头按官方没有构建拒),
chip 清单改为委派 Loader::ALL。
- 测试: 真实响应的裁剪夹具(dl.liteloader.com 与 repo.mumfrey.com 的 maven-metadata)
驱动「解析→计划→落盘→继承→启动参数顺序」全链;Forge 父链有回归(FMLTweaker 必须还在
且排在 LiteLoader 之前);父版本缺失/组合不支持/取消/坏表各自具名错误。
|
2026-11-10 19:34:09 +08:00 |
|
AskaEth
|
307d29239a
|
Merge branch 'feat/m225-fix-liteloader-mirror-404-and-repo'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-10 14:32:13 +08:00 |
|
AskaEth
|
146eb3d8db
|
fix(download): LiteLoader 不再套 bmclapi 改写,Central 库接上 bmclapi/maven
bmclapi-liteloader 改成 enabled=false(规则保留作形状留档):2026-11-10 实探
`apply()` 的真实产物 https://bmclapi2.bangbang93.com/maven/versions.json 404 / 9 B、
/maven/com/mumfrey/liteloader/1.7.10/liteloader-1.7.10_04.jar 404 / 9 B、
/maven/com/mumfrey/liteloader/{maven-metadata.xml,1.12.2-SNAPSHOT/maven-metadata.xml}
全 404 / 9 B;官方 dl.liteloader.com 的 versions.json 200 / 49129 B、artifact
200 / 533903 B。引擎遇 404 只会白丢一次请求再前进(NotFound 不重试),官方不可达时
更是直接失败,所以镜像优先用户不该再为每个 LiteLoader 文件试一次镜像。
新增 bmclapi-maven-central(repo1.maven.org/maven2 → bmclapi/maven):bmclapi 的
/maven 不只是 libraries.minecraft.net 的镜像,Central 的路径逐字节一致(asm-all 5.2
247787 B、gson 2.10.1 283367 B、log4j-core 2.17.1 1790452 B,sha1 逐一相同),不存在
的坐标两侧都是 404(镜像回 9 B 纯文本,没有「200 + HTML」假阳性)。
快照仓 repo.mumfrey.com 不加规则:bmclapi /maven、maven.aliyun.com/repository/central、
repo.huaweicloud.com/repository/maven 对 com/mumfrey/liteloader/1.12.2-SNAPSHOT/
maven-metadata.xml 全是 404,只有官方 200 / 358 B,所以只登记探测结果、不铺未验证的规则。
测试:新增 liteloader_urls_stay_official_only(镜像优先模式也只出官方一条,把 enabled
翻回 true 的变异实测会红);library_urls_expand_to_the_maven_subpath_on_bmclapi 补
Central 用例;形态判据的 EXPECTED 表加 bmclapi-maven-central。不探外网。
version 保持 3 不升 4:download/jdk_sources.rs 与 tests/game/java_vendor_chain.rs 两处
pin 了 version == 3,后者不在本 mission 的 scope(第一轮 workspace test 就是被这条 pin
抓红的),升版留给专门一轮;规则改动与探测记录写进 mirrors.json 的 notes。
|
2026-11-10 13:56:58 +08:00 |
|
AskaEth
|
54995a4af8
|
Merge branch 'feat/m221-adoptcore-existing-versions-read-on'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-10 12:57:03 +08:00 |
|
AskaEth
|
347a3cc90d
|
fix(game): 复审 r2 三项——Win32 尾点/尾空格别名 + 写面与游戏树重叠 + 更高祖先断链
r1 五项里 R4/R5 与 slug/大小写等直接案例已由 r2 复审独立通过;这三条是它补证出的**剩余边界**,
只做限定修复,不推翻重写:
r2.1(未存在认领路径的 Win32 尾点/尾空格别名):比较键新增 `fspaths::comparison_real()`——
已存在前缀用 `canonicalize`,**尚不存在的尾段按平台语义归一**:Windows 普通路径折叠尾 `.`/空格
(`Foo.` 与 `Foo ` 都是 `Foo`,Win32 建目录时就这么做),**verbatim 路径(`\?\…`)不折叠**
(那是另一个物理目录,合法)。原先 `dir_key` 只统一分隔符与大小写,于是一个尚未创建的
`custom` 认领 `Foo.` 与缺省派生 `Foo` 键不相等却被判定「不撞」,两个实例共用一个运行目录。
r2.2(`data`/`.ULL` 整体映射到受保护游戏树时仍静默外写):新增
`fspaths::ensure_surfaces_clear_of_game_tree()`——默认写盘面(`.ULL`、`config/`、
`config/instances/`、`profiles.json`、`data/`,以及缺省档的运行目录)**解析后不得落进游戏根**;
`plan`(dry-run 也报)与 `adopt`(写盘前)各核一次,重叠即 `adopt_write_surface_in_game_tree`
具名拒绝并提示把数据目录挪到游戏树之外。整体迁移到游戏树**之外**照旧允许(判据是落点不是链接)。
配置写面判据同时收窄职责:`ensure_config_write_surface` 只管「在 `.ULL` 内」,
不再带「`.ULL` 在游戏根里」的豁免特例——重叠由上面那条统一负责。
r2.3(游戏根更高祖先断链仍当空列表):`scan` 的 `NotFound` 分支改为沿祖先找**最近的已存在入口**,
它能 `lstat` 到但 `canonicalize` 不出来(断链的符号链接/目录联接、目标被搬走)⇒
`adopt_versions_unreadable`;一路到盘根都不存在才是「真正的新数据根」,空结果仍允许。
原先只查 `versions` 与游戏根两层,迁移联接在更高层断开时会误报空库。
新错误码:`adopt_write_surface_in_game_tree`(CLI hint 与 help 拒绝清单同步)。
验证(持久日志在 `target/m221-r2/`,git 忽略、不入库;上一轮 `target/m221-r1/` 证据未覆盖):
- `cargo fmt --all --check` 0;`cargo check -p unlucky-core -p unlucky-cli --all-targets` 0;
- `cargo test -p unlucky-core --test adopt` **43 passed / 0 failed**(另跑一次
`ULL_ADOPT_TEST_SYMLINK_REQUIRE=1` 同样 43 passed);
- `cargo test -p unlucky-cli --test adopt_test` **13 passed / 0 failed**;
- `cargo clippy -p unlucky-core -p unlucky-cli --all-targets -- -D warnings` 0;
- 额外:`cargo test -p unlucky-core --lib` **778 passed**、`cargo test -p unlucky-cli` 全部目标通过;
- 三次**实现-only 变异**(不折叠 Win32 尾点尾空格 / 不核游戏树重叠 / NotFound 直接当空)
全部以**断言红**收场(1 / 4 / 2 条失败,非编译错误、非环境错误),每次还原后与基线逐文件一致,
收尾再跑 core+cli 双绿。
界限:只覆盖 core+cli 的定向目标(不等于 workspace 全量);没有启动真实 Minecraft;GUI 未接;
判据是检查那一刻的快照(TOCTOU 看不到);Win32 8.3 短名与非 junction 重解析点未覆盖。
|
2026-11-09 23:56:02 +08:00 |
|
AskaEth
|
8110cde3f5
|
fix(game): 复审 r1 五项——运行目录不许被两实例共用 + 配置写面越界 + versions 断链 + 链接守卫反转 + 快照漏目录
复审 p2-5items 的五项,全部按「先能红的用例,再改实现」:
R1(默认目录可被不同实例隐式共用,产品缺陷):新增 `adopt/run_dir.rs`——登记前读**每个已有实例**
的 `gameDir`(现役设置 + M27 前档案的遗留字段),用「真实路径 + 拼回不存在的尾段 + 大小写归一」
(Windows/macOS)比较;撞上就顺延 `<base>-2/-3…`(上限 32,撞满 `adopt_no_free_run_dir`)并在
`notes` 点名撞的是哪个实例;**非空且无人认领**仍拒绝(那是用户数据,不偷偷换目录)。这样
`Better MC` 与 `Better-MC`(slug 同名)、Windows 上 `Foo` 与 `foo`(同目录)都不会共用一个运行目录。
大小写归一在 macOS 上是**保守**选择:大小写敏感卷上会多一次顺延,换来的是「绝不共用」在任何卷上成立。
R2(配置写面 junction 写穿到外部游戏根,产品缺陷):`run_dir::ensure_config_write_surface()`
在 `plan`(dry-run 也报)与 `adopt`(写盘前)各核一次——`config/`、`config/instances/`、
`profiles.json` 的真实落点必须在 `<.ULL>` 内,且**不得经链接落进游戏根**(错误按真实归属分类:
「逃出 .ULL」vs「落进游戏根」)。`.ULL` 本身在游戏根内是布局事实,附加判据在该布局下零判别力,
主判据仍然拦下一切跑出 `.ULL` 的落点——这不是给链接开后门。新码 `adopt_config_surface_unsafe`。
R3(dangling versions 被当不存在,产品缺陷):`scan` 的 `read_dir` `NotFound` 再三分——
`lstat(versions)` 看见入口(断链符号链接/目录联接/目标被移走)或游戏根解析失败(祖先断链)⇒
`adopt_versions_unreadable`;两者都不是才算「真没有」。`inspect` 也补了同族判据(条目存在但
不是可读目录 ⇒ `directory_unreadable`,不再混成「没装」)。
R4(强制链接测试逻辑反转,假绿):`fixture::link_gate(created, required) -> Run|Skip|Fail` 是纯
真值表,`link_guard_with()` 显式传参(`link_guard()` 只读环境变量);新增
`link_guard_executes_all_three_branches` 用 `catch_unwind` **执行** Fail/Skip 两个分支。
四处调用点统一走 guard;建链接/联接的夹具收进 `fixture.rs`。
R5(快照遗漏目录节点,假绿):新增 `tests/adopt/snapshot.rs`——全节点快照(根/目录/文件/链接
各记类型 + 适用 mtime;目录记 mtime、链接只记目标不跟随、Windows 用重解析点属性认 junction、
读盘失败直接 panic)。**CLI 测试用 `#[path]` 复用同一份**,判据只一处;新增 `fixture_cases`
五条工具自测,`plan`/dry-run 的零写盘判据改用 `snapshot_opt`(连空目录都不许出现)。
验证(本轮持久日志,`target/m221-r1/`,git 忽略、不入库):
- `cargo fmt --all --check` 0;`cargo check -p unlucky-core -p unlucky-cli --all-targets` 0;
- `cargo test -p unlucky-core --test adopt` **37 passed / 0 failed**(另跑一次
`ULL_ADOPT_TEST_SYMLINK_REQUIRE=1` 同样 37 passed);
- `cargo test -p unlucky-cli --test adopt_test` **11 passed / 0 failed**;
- `cargo clippy -p unlucky-core -p unlucky-cli --all-targets -- -D warnings` 0;
- 四次**实现-only 变异**(缺省不查认领 / 配置写面不核 / versions 的 NotFound 当空 / 快照不记目录)
全部以**断言红**收场(不是编译错误、不是环境错误),每次还原后与基线清单逐文件一致,收尾再跑绿。
界限:只覆盖 core+cli 的定向目标(不等于 workspace 全量);没有启动真实 Minecraft;GUI 未接;
判据是检查那一刻的快照,TOCTOU 与非 junction 重解析点仍列边界。
|
2026-11-09 23:13:57 +08:00 |
|
AskaEth
|
9f5c001387
|
feat(game): 从其他启动器只读登记已有版本(ull instances scan|adopt)+ 登记可一并定隔离档(M221)
盘上已经铺好的版本(多半是 PCL/HMCL 装的)今天在 ULL 里看不见,用户只能重装一遍。这个
commit 把它们登记成实例,**只读外部、只写自己的配置**:不搬文件、不补清单、不改任何外部
文件的 mtime(versions/ libraries/ assets/ 一个字节都不写)。
core(`game::adopt`,新模块):
- `scan` / `inspect`:只读扫描 + 逐项检查,坏 JSON(带行列号)/ 缺 id / id 与目录名不一致 /
缺父 / 成环 / 读不出来 / 越界 / 同名冲突一律**结构化**报错(稳定 `code`),不吞成空列表;
`versions/` 不存在才是空结果,读不了就是错误;
- 越界判据用 canonicalize 后的前缀比较,同时覆盖**符号链接、Windows 目录联接(junction)与
祖先链接**(junction 不需要管理员权限,是最常见的「把别人版本目录挂进来」);越界条目在
边界检查就停手,一个字节都不去读;
- `plan`(零写盘,喂 dry-run 与后续 GUI 预检)/ `adopt`(plan + 只写自己的配置):
**缺省运行目录落 ULL 数据区 `<data>/instances/<实例目录名>`(custom 档),绝不沿用全局
`isolated`**——那条缺省的运行目录正是 `versions/<versionId>/`,会在他人的版本目录里长出
mods/saves/config;`shared` / `isolated` 必须显式选,且 `target_notes()` 给出后果话术;
- 缺省运行目录已存在且非空 ⇒ 拒绝(不覆盖、不猜「合并进已有实例」);数据区里已有的祖先
若解析到数据根之外(符号链接/联接)⇒ 拒绝,不沿它写盘;
- 登记与隔离档一次写清:`install/instance` 拆出 `register` 子模块,新增
`register_with(InstanceSettings)`;写盘顺序改成「先实例设置、后 profiles」(中断只留孤立
设置文件,不会留下按全局档启动的实例),profiles 写失败会撤掉刚写的设置。`register`
签名与行为不变(`register` = `register_with(inherit)`;同版本同名的幂等复用照旧)。
CLI:
- `ull instances scan`(只读列出候选与原因)、`ull instances adopt <version-id> [--name N]
[--isolation shared|isolated|custom] [--game-dir DIR] [--dry-run]`;`--game-dir` 单独给即
custom,相对路径在解析期就报用法错误;
- 人读/`--json`/help 三条出口齐备(`error.detail.problems` 逐条可分流),已有命令未改。
测试(真磁盘夹具,`cargo test -p unlucky-core --test adopt` 22 passed /
`cargo test -p unlucky-cli --test adopt_test` 10 passed):
- 一份「别的启动器铺好的」`.minecraft`(versions/libraries/assets/mods/saves/config +
launcher_profiles.json),扫描与登记前后做「路径集合 + 字节数 + 内容哈希 + mtime」全等断言;
- 正常 / 缺清单 / 坏 JSON / 缺父 / 成环 / id 不匹配 / 非法 id / 符号链接越界 / 根内符号链接
放行 / **junction 越界** / 数据区 junction 写盘边界 / 缺省非 isolated / shared 与 isolated
的话术 / 重名 / 重复 / 非空目录 / 空目录复用 / dry-run 零写盘;
- CLI 侧验 `launch --dry-run` 的 `--gameDir` 指向选定运行目录(argv 已脱敏,比尾段;
`data.game_dir` 全等);
- 变异核对(只改实现、跑完即回滚):缺省档改 `VersionDir` ⇒ 红;边界判据摘掉 ⇒ 符号链接与
junction 两条红;写盘边界摘掉 ⇒ 数据区 junction 那条红;缺父当链终点 ⇒ 两条红。
界限:没有启动真实 Minecraft;GUI(presenter/app-iced)接线另派;D20 单游戏根模型未改
(不扫第二个 `.minecraft`)。
|
2026-11-09 22:21:37 +08:00 |
|
AskaEth
|
0577ee87a3
|
Merge branch 'feat/m220-javapinnit-m214-java-pin-and-nit-sw'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 21:31:23 +08:00 |
|
AskaEth
|
75aa5ee82c
|
fix(cli): 启动用的 Java 钉死 D4 校验过的那个 + M214 复审 nit1/nit2/nit3
- override_path 五来源统一钉住 D4 选中的运行时:以前只有 managed 钉路径,
JAVA_HOME/PATH/system 三种会被 snapshot::prepare 的 selection() 按环境再挑一遍,
D4 判过的 Java 根本不是真启动的那个。现在 `plan_java` 只做「选中 → 钉路径」,
人读行写出「版本(来源 · 具体可执行文件)」,--json 的 java 块并排给出
binary(校验过的)/ launch_binary(装配落地的)/ source,两者不一致时当场告警。
实例设置/全局设置里的 javaPath 也并入「谁指定了 Java」,不会被自动挑的结果顶掉。
- nit3:D4 的 Java 不符拦截挪到 identity::resolve 之前(微软轨解析身份会联网刷新
令牌、还会回写凭据),拦下的启动不再先触发一次刷新;不符提示补上「挑中的是哪个
java」,文案只加不减。
- nit1:error.rs / launcher.rs 的人读串里残留的 ** 标记去掉,并补两条回归断言。
- nit2:java_launch.rs 里「只提示不阻断」等旧注释对齐 D4 现状。
- launch.rs(399 行零余量):D20 的隔离档位派生与切换检测整体搬到新文件
launch_isolation.rs,纯挪函数体(逐行 diff 只多 6 处 pub(super)),
用 #[path] 挂载以免改 commands/mod.rs。
用例:钉路径纯函数单测 3 条 + 端到端 2 条(JAVA_HOME 置空证明非 managed 来源也钉住;
数据根放两个账户证明 D4 拦在身份解析之前)+ 人读串无 ** 的 2 条回归。
两条变异(①退回只钉 Managed ②退回身份解析之后)各自让新用例变红,已实测。
|
2026-11-09 21:30:55 +08:00 |
|
AskaEth
|
1f6e15127a
|
Merge branch 'feat/m217-guardrange-child-spawn-guard-across'
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Dev Guards / 架构与合规守门(同 main 口径) (push) Canceled after 0s
Dev Guards / fmt (rustfmt --check) (push) Canceled after 0s
Dev Guards / clippy (ubuntu) (push) Canceled after 0s
|
2026-11-09 21:12:24 +08:00 |
|