AskaEth
|
31cdfd5bbd
|
fix(renderer): M97 后端判据 —— 静默降级改具名错误,取证通道拒绝降级出图
M97 的入口是一份 macOS M1 真机报告:探测认出了 Apple M1(adapters=1 /
hardware=true),日志却写着 `renderer = auto (wgpu -> tiny-skia)`,看起来
像在跑 CPU。读源码发现那行是**常量**:`probe.rs` 按 `ICED_BACKEND` 未设就把
`"auto (wgpu -> tiny-skia)"` 打出来,与实际后端无关。真正的失败被 iced 吞掉:
`iced_renderer-0.14.0/src/fallback.rs` 的 `Compositor::with_backend` 把 wgpu 的
错误装进 `errors` 向量,tiny-skia 一成功就整体丢弃。
## 判据改为单项,不再交给 iced 回退
`ICED_BACKEND` 现在**永远是单项**(`wgpu` / `tiny-skia`):能跑起来就必定是它;
起不来就必然是 `iced::Error::GraphicsCreationFailed` 里的具名错误。判据是纯函数
`renderer::decide(request, probe, gpu_compiled)`,落在 `renderer/decision.rs`,
表驱动测试逐行钉死 1~6 行 + 「除强制 CPU / 未编 wgpu 外必须留兜底」的守门。
判据表(条件 / 选哪个 / 日志该打什么 / 谁能验证)在 `renderer/mod.rs` 顶部。
## 安全网改成换进程
第一版是「打印错误 → 在本进程再 run() 一次」,Windows 实测直接 panic:
`Create event loop: RecreationAttempt`(winit 一个进程只能建一次事件循环)。
改成 `respawn`:子进程只换 `ICED_BACKEND` + `ULL_GPU_FALLBACK`,父进程透传它的
退出码。子进程看到 `ULL_GPU_FALLBACK` 必须跳过探测,否则会无限递归重开。
## 取证通道不许自己降级
带 `--screenshot`(含 `--screen=gallery`)时 wgpu 起不来就**不出图**、退出码 1,
因为换了后端出的图会撒谎;交互启动才允许降级。每张图的日志现在打**实际**后端
(`[shot] … renderer=wgpu`),以前打的是请求值 `auto`——同一个病(在需要结果的
地方打请求)在仓库里出现了两次,这是第二次。
## 可观测性
- `log` 桥(`renderer/log_bridge.rs`)把 `iced_wgpu` 的
`Selected: AdapterInfo{…}` 接到 stderr:这是本仓库第一次能自证「真在用 GPU」。
按 target 分级(`iced_wgpu*` Info、`wgpu*` Warn),否则 Vulkan loader 会刷 90 行噪声。
- `renderer/preflight.rs`(默认关,`ULL_PREFLIGHT=1`):开窗前拿 wgpu 的具名错误。
实测 Windows RX 6600 XT 上 took=717ms,所以不进默认路径。
- `app/backend.rs` 的档位判据跟着改成读单项;**真降级到 CPU 的那一次面板 α 现在会
正确强制 1.0**(以前静默降级后仍按 GPU 档判)。
Windows 实测:auto → wgpu + `Selected: AdapterInfo{…backend: Vulkan}` 出图;
`--renderer=cpu` 逐字不变;`WGPU_BACKEND=metal` 造失败 → 具名错误 → 交互路径降级
不崩(首帧 531ms)/ 取证路径拒绝出图 exit 1。macOS Metal 路径需真机复核,
命令与期望日志行写在 `renderer/mod.rs` 的「真机验收」一节。
|
2026-11-03 00:26:55 +08:00 |
|
AskaEth
|
22e4c423fa
|
feat(app-iced): M93 r2 —— 背景图真图直出 + 面板 α 收敛到 Ui::layer
复审前补齐两处(tower 扩 scope:Cargo.toml / pages/frame.* / theme.rs / presenter::prefs)。
1) 背景图**真图直出**
- `iced` 开 `image-without-codecs`(刻意不用 `image`:后者会打开 `image/default` 全套编解码器);
编解码能力由本 crate 的 `image` features 决定,补了 `jpeg` / `bmp`(用户挑的背景图/壁纸多半是 jpg)。
- `pages/appearance/picture.rs`:`Cover`/`Contain` 走渲染器 `ContentFit`,`Tile` 按 `background::tiles`
算行列(上限 64 块)真的铺 N 个图块;暗化 = 一层「版面底色 @ α=dim」叠在图上。
- 36dp 马赛克那条路**已删除**(不再是主路径,也不留死代码)。
- 踩坑记录:`Handle::from_bytes` 的纹理分配赶不上截图第 3 帧(实测画面全空),改用
`Handle::from_path`(渲染器自己解码),已在 `picture::image_handle` 的文档里写明。
2) 面板 α **收敛到唯一来源**
- `Ui::layer()` 直接乘上面板 α(新增 `Ui::panel_alpha` / `with_panel_alpha`,默认 1.0、
`scale_alpha(1.0)` 逐字节不变),`app::rebuild_ui` 注入生效值。
- 于是外壳(根容器/标题栏/导航轨)、`frame::card`(实例/下载/日志三页卡片)、
`frame::header_bar`(新增底色)、外观分组卡片、空态图标块**全部同源**,
调用点不再有任何 `panel_color(..)` 自乘(该 helper 已删)。
- 有背景图时根容器不再盖一层实色画布(否则 panel α=1 时背景图整块被挡)。
3) `presenter::prefs` 补重导出 `PANEL_ALPHA_MIN` / `BG_DIM_MAX`,app-iced 里两份 0.4 / 0.95
字面量改为转发常量。
验收:11+3 张图(`C:\Temp\ull-shots\wt-93\r2-*.png`)——三档填充肉眼可分、`.jpg` 能加载、
GPU 档 panel55 时卡片/分组卡/页头带都透出背景、CPU 档仍强制 1.0 且背景在面板空隙可见;
另附实例/下载/日志三页 panel55 各一张,证明三页都吃到同一个 α。
|
2026-11-02 18:07:32 +08:00 |
|
AskaEth
|
e77a5df6e8
|
feat(i18n): 建国际化地基——语言探测 / 中英消息目录 / 中文字体策略(D28)
M92 只建地基、不接页面(页面接线是后续 mission,避免「先硬编码再全量重写」)。
新建的 crates/i18n 是**零依赖** crate:`cargo tree -p unlucky-i18n --edges normal`
只打印它自己,所以 iced 版、将来的别的壳、presenter 都能依赖它而不锁死渲染框架。
- lang:`Lang::{En, ZhCn}` + 可注入的 `LocaleSource`(`EnvLocale` 按 POSIX 优先级读
LC_ALL > LC_MESSAGES > LANG)。未知语种一律英文——把 ja/fr 猜成中文会连带触发中文字体
下载,比英文界面更糟。配置值(`ui.language` 的 zh_cn/en_us)走严格解析,locale 走宽松
解析,两条路刻意分开。**已知缺口**:Windows 上这三个变量通常都不存在,
`GetUserDefaultLocaleName` 属于 platform 的 FFI(后续 mission)。
- catalog:`Key` 枚举(拼错的 key = 编译错误,附 compile_fail 文档测试守着)
+ `catalog!` 宏「一行 = 一个 key 的全部语言」⇒ 内置两套目录缺译在结构上不可能发生。
缺译诊断(`Catalog::diagnose`)留给注入目录与回归守门:`get` 严格返回 None、
`text` 返回 ⟪missing⟫、`render` 返回 ⟪missing:KeyName⟫——**不回落**到另一种语言。
带变量的句子走 `{name}` 占位符,缺参时占位符原样留在文案里(可见,不静默变空)。
- font:`decide(lang, installed)` 是纯函数。英文环境恒 `NotNeeded`(D28 的零字体/零网络);
中文环境按候选族名单命中系统字体,扫不到就发 `NeedsDownload(Noto Sans SC)` 信号,
**本 crate 不联网**;`Fetcher` 是注入式接口,网络实现留给后续 mission。
URL/哈希显式留白(`Source::PendingPin`)——不编造 CDN 路径,猜错的地址会被后面所有人当事实用。
- bridge:文案边界规则全文(R1 不许拼句 / R2 core 中文串不进 UI / R3 未知串不许画原文)
+ `classify()` 示例契约:过渡表把已知漏到界面的中文串映射到 key,品牌专名显式放行,
其余一律 `Untranslated`(UI 只许显示兜底 key)。
- app-iced/fonts:新增 `fonts/cjk.rs` 适配层,把系统字面表喂给 `decide`;读不到字体库时给
`Unprobeable` 而不是「需下载」(否则会误触发一次 CDN 下载)。结论并入同一行启动日志:
`[font] … | lang=zh-CN locale=zh_CN.UTF-8 cjk=system:"Microsoft YaHei"`。
测试:26 条(i18n 22 + app-iced fonts/cjk 4),每条断言都写了「抓得住什么 / 抓不住什么」。
|
2026-11-02 16:13:22 +08:00 |
|
AskaEth
|
1aac62f6f0
|
feat(palette-extract): 图片解码适配层(D40 第 2 层,唯一引入 image 的地方)
- 新 crate unlucky-palette-extract:路径/字节 → RGBA → design 的种子
- image 只开 png + jpeg(关 default-features,不拖 rayon/avif/webp)
- 解码前按 Limits 拒超大尺寸(解压炸弹),解码后最长边缩到 256
- input(&Result<Rgba8, _>) 把失败翻成 ImageInput::Undecodable(回落链靠它区分「没设」与「坏了」)
- 11 个单测覆盖纯色/JPEG/抄错扩展名/垃圾字节/截断/缺文件/1px/灰图/大图/超限/零拷贝
|
2026-11-02 11:04:52 +08:00 |
|
AskaEth
|
f159529936
|
feat(presenter): 新建 unlucky-presenter,把 host/状态/DTO/设置桥从 app 迁出(M85 第 1 块)
D26(iced 重写)的关键一次性动作:UI 框架无关的宿主层独立成 crate,
Slint 版(crates/app)与 iced 版(crates/app-iced)共用,Slint 退役后不再重写一遍。
- 新建 crates/presenter(package `unlucky-presenter` / lib `unlucky_presenter`),
workspace 登记 + crate 文档头写清「这层为什么存在」、对外表面、UI 侧允许/禁止、
iced 接线骨架、以及哪些东西留在了 crates/app 及为什么。
- `git mv`(保留文件历史)迁入:host/(25 文件)、dto/(11 文件)、state.rs(+tests)、
prefs.rs、settings_bridge.rs、toast.rs、router.rs、dialogs.rs、wizard.rs、
mock.rs、mock_data.rs、backend.rs。模块路径与公开 API 名称不变(`host::install` 等),
内部实现零改动。
- 新增 `theme`:把原本混在 ui_bind/theme_runtime 里的**纯判据**(CONFIG_POLL_TICKS /
SYSTEM_THEME_POLL_TICKS / TILE_REGENERATE_STEP / BackgroundKey / tile_viewport /
system_theme_value / 两个 poll 判据)抽出来,附 5 个单测;app 侧改成调用它们。
- crates/app 改为消费者:删掉迁走的文件,`crate::host|state|dto|prefs|settings_bridge|
toast|router|dialogs|wizard|mock|mock_data|backend` 全部改写为 `unlucky_presenter::*`
(64 处),backend.rs 的 config_path/build_backend 提为 pub。
- crates/app-iced 只登记 unlucky-presenter 依赖,本 mission 不接线。
- prefs.rs 的 2 处 slint 文档引用改写;`grep -rn slint crates/presenter` 现在无输出。
|
2026-11-02 09:29:31 +08:00 |
|
AskaEth
|
1c7285cea9
|
feat(cli): GUI 二进制内建 CLI 子命令分发,ull 纳入发布资产(M82)
一个二进制两种用法:两个 GUI 的 main.rs 在进 UI 之前分发子命令,命中就把控制权交给
unlucky_cli::run —— 不建窗口、不探后端、不扫字体、不 dlopen 图形库。
分发判据只有一条,子命令表不重复:crates/cli 的解析器是唯一真相源。
- 首个 token 是裸词 → 子命令位置,交 CLI(未知子命令由 CLI 报错 + 提示 --help,退出 1,
不静默开窗)
- 首个 token 是 flag → 只有 CLI 解析出真命令才算 CLI 调用;--data-dir X 解析成 Welcome、
--screenshot / --renderer / --diff 等 GUI 参数解析失败,一律仍走 GUI(CI 脚本零改动)
顺带:unlucky-launcher --version 以前会直接开窗,现在走 CLI 分发报版本。
packaging:ull 作为独立裸资产随每个槽位发布(make_artifacts.py + release.yml 的 assets
数组与 SHA256SUMS glob),矩阵完整性检查改成 GUI/CLI 各查一次。
app-iced 的 x11 / wayland / linux-theme-detection 收进 target 专属的 cfg(target_os) 段。
门禁实跑:cargo fmt --all --check 通过;cargo clippy --workspace --all-targets -D warnings
通过(exit 0);cargo test --workspace 通过(51 个测试二进制 / 1369 个用例,含本次新增的
5 + 5 条分发判据用例)。
|
2026-11-02 08:08:09 +08:00 |
|
AskaEth
|
df4ff373b7
|
feat(app-iced): M68 —— iced 0.14 骨架(后端探测/字体基座/自绘标题栏/导航轨/深浅主题/截图通道)
新增 crates/app-iced(package `unlucky-app-iced`,bin `unlucky-launcher-iced`),与 Slint 版并存,
为 D26「UI 从头写」打地基。本 mission 只做骨架:能开窗、颜色/字体对、层级在,不做业务页面。
- 后端(D27):启动探测 wgpu 适配器(只看 device_type,避开 WARP 误判)→ 写 ICED_BACKEND;
auto/gpu/cpu 三条路径都保留 tiny-skia 兜底,绝不硬失败;`--renderer=cpu` 跳过探测(省 ~0.85s)
- 字体(D28 前半):令牌三平台字体栈 → 用 iced 全局 FontSystem 查装机情况 → 族名与 400/500/700
字重落点(缺档按 Nearest/Synthetic 记录)打一行启动日志
- 令牌适配层 theme::Ui:唯一把 unlucky-design 的值翻成 iced Color/Font/Border/Radius/Theme 的地方;
49 个 M3 角色留在自己的 Scheme 里,iced 的 6 色 Palette 只给 devtools
- 骨架:1180×760 无边框窗口、48 高自绘标题栏(drag + 东/南 drag_resize + 最小化/最大化/关闭)、
88 宽导航轨(56×32 SecondaryContainer 药丸,由令牌推导)、页头 56 + 一张 OutlinedCard
- 主题:系统主题探测 + --theme,切主题只重建 Ui(Theme::name 变化触发重算),不动 UI 状态
- 截图通道:`--screenshot <png>` + `--screenshot-theme`(真开窗第 3 帧出图),以及
`--diff <a> <b>` 自带逐像素对比(CI 不依赖 Python/ImageMagick)
- 图标:令牌 SVG path → iced svg 控件(颜色走 alpha 蒙版,深浅共用一份位图缓存)
- 门禁:tests/no_hardcoded_tokens.rs 扫源码挡色值/尺寸字面量 + 挡 slint 依赖(自检也算过)
实测(Windows Server 2025 / 96 DPI / RX 6600 XT):浅/深 × GPU/CPU 四张真开窗截图;GPU vs CPU
差异 0.72% 像素、全部落在字形/图标抗锯齿与 1px 圆角描边、最佳对齐 (0,0)、平坦区颜色逐字节相同;
release 体积双后端 6.44 MiB / 仅 CPU 3.75 MiB;首帧 GPU 1.18~1.31s、CPU 33~37ms。
未做/未验证:macOS 与 Wayland 全链路、西/北边缩放热区、letter spacing(iced 0.14 无此属性)、
中文 CDN 字体、玻璃;翻牌步骤写在 DevDocs/SubAgentsTalk/app-iced-骨架与翻牌.md(不入库)。
|
2026-11-01 17:11:24 +08:00 |
|
AskaEth
|
65ca0ed29a
|
feat(design): 加 unlucky-design 令牌 crate(M3 色彩/排版/形状/动效/图标)
D26 把视觉基线锁到 Material 3 Expressive,本 crate 是「UI 整体性」的地基:
令牌唯一真源,后续 iced 页面只消费不新增。零第三方依赖、零 UI 框架依赖
(cargo tree -p unlucky-design --edges normal 只有它自己一行)。
- 色彩:49 个 M3 语义角色 × light/dark,种子色派生 6 条 tone 调色板;
tone 轴严格用 CIELAB L*(偏差 ≤1),色相/彩度用 OKLCh 近似 CAM16 并在
模块文档里写明口径与已知偏差(不宣称是标准 HCT 动态取色)
- 对比度:26 组文字对 × 6 组种子 × 2 模式全部 ≥4.5:1;3 条例外显式登记
(Outline 非文字 3:1、OutlineVariant 装饰、SurfaceVariant 是容器色)
- 排版:M3 type scale 15 档 + 三平台字体族回退栈(只写族名,不内嵌受限字体)
- 形状/高程/动效:Expressive 形状刻度 + shape morph 数据、elevation 级别与
surface container 映射、时长/缓动/弹簧(纯函数可采样)
- 图标:24 个从 Slint 版迁移的 SVG path + 4 个新增,IconKind 冻结只可末尾追加;
写明 Slint 的 stroke-width 是绝对像素、SVG 是 viewBox 用户单位的语义差异,
换算封装成 Icon::stroke_width
- 组件档位表:20 个组件的形状/高程/字体/动效一次取全
TODO(M63):elevation shadow 的 blur/offset/alpha 与四组弹簧参数是占位值,
接口先定、数值待玻璃视觉规格表(tower 已通告边界)。
|
2026-11-01 15:34:04 +08:00 |
|
AskaEth
|
ddc0577f83
|
feat(app): GUI 全量接线,五页改吃 core 真实数据(M43)
app 从「mock 骨架」切到「真实 core host」:新增 src/host 装配层(env/job/
game/launch/identity/versions/install/meter/java/accounts/logs/settings/engine/
dispatch),是 app 里除 settings_bridge 外唯一 use unlucky_core 的地方,出口只有
crate::dto 的 DTO/事件(三通道纪律,AGENTS §6)。
页面接线:
- 实例页:profiles+版本 JSON 继承链+会话索引 → 磁贴(真实版本/加载器徽标/最后启动/
隔离档位/载荷缺失);启动按钮走 LaunchRequest 装配 → Launcher::spawn → JSONL
尾随 → 运行/失败回写;M30 ModeChangeWarning 进横幅通知条。
- 新建实例向导:版本 → 加载器 → 名字 → 确认 → 真实安装进度;Fabric/Quilt 可装,
Forge/NeoForge 因需要 installer processors(仅 CLI 有)显式拒装并给出 CLI 命令。
- 下载页:远端 manifest(12h 缓存)+ 内嵌 unlisted → 分类(正式/预览/远古四子类);
安装进度(文件数/字节/速度/ETA,速度由相邻采样差分)+ 阶段边界可取消;
Java 分组:四家发行版 + 回退链可视化(计划链/真实 ChainLog)+ 本地运行时。
- 账户页:三轨账户列表(只读公开字段)+ 离线新增 + 删除/切换 + 微软设备码登录
(用户码/链接/轮询状态;取消后丢弃结果)。凭据只在 host/identity 与 host/accounts
出现,DTO 无 token 字段(单测钉住)。
- 设置页:新增 download/java/data/ai/mcp 五组,行由 core::setting schema 键生成,
写回走 SettingStore::set(与 ull config set / AI set_config 同源 launcher.json)。
- 日志页:会话索引 → 列表;事件 JSONL → 日志行(等级高亮)+ D8 规则引擎诊断卡片;
启动中实时尾随(600ms/拍,只读新增字节);崩溃日志导入分析(hs_err/crash/latest)。
架构与守线:
- 新页面状态走 ui/page_state.slint 的 PageState global(main.slint 再导出),
Rust 侧 page_sync.rs 灌入、callbacks_pages.rs 接线;避免属性逐层转发顶破 400 行。
- DTO 全部 Serialize+Deserialize(IPC 契约),新增 accounts/java/settings/install/
notices 五个子模块;事件扩展 LogAppended/LaunchFinished/AccountsChanged/
DeviceLoginChanged/JavaChanged/InstallProgress/SettingsChanged/NoticesChanged。
- 截图通道扩展:--data-dir(钉数据根且 settings 同源)/--mock/--screenshot-page/
--screenshot-wizard/--screenshot-session/--screenshot-install/--screenshot-launch
(真实中间态,取图后自动 stop 防孤儿进程)。
已知边界:GUI 账户「当前启动账户」仅内存态(core schema 无 auth.* 键,app 不自建键);
Forge/NeoForge GUI 安装待 processors 执行器上移。
|
2026-10-31 21:55:58 +08:00 |
|
AskaEth
|
bc41dfcf7d
|
Unlucky Launcher v0.0.1-alpha
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
|
2026-10-31 21:54:10 +08:00 |
|