fix(appearance): M91 r2 —— 真交叉断言 + unsafe 门禁跨行扫描 + α 上取整
P2-1「与 design 码值一致」的守门此前不存在 - 新增 crates/app-iced/src/platform/mod.rs::palette_source_codes_match_design_level_codes: 三个格子对照 PaletteSource::as_str() 与 unlucky_design::palette::Level::code(), 并覆盖反方向(from_key_value(level.code()) 必须还原同一档)与「码值在允许值表里」 - core 侧那条测试改名为 value_tables_are_self_consistent,注释改成事实: core 不依赖 design,只能钉自己的字面量;一致性由 app-iced 的那条对照断言守 - 实测:把 design 的 Level::Wallpaper 码值改成 "desktop" → 新断言红(design 自己 178 个测试仍全绿) P2-2 unsafe-scope 门禁:行级正则 → 跨行扫描 - 去注释 + 按方括号闭合把续行并成逻辑行,再逐个属性组判断 allow(...unsafe_code) - 抓得住原来两种绕过:多行属性 #![allow(\n unsafe_code\n)]、同行两属性 #[inline] #[allow(unsafe_code)] - 门禁注释补全已知边界(不再声称「依赖 fmt job 兜底」——已不依赖) O-1 α 量化方向:round → ceil - 0.08×255=20.4,round 得 20 → 实际下发 20/255=0.0784,低于 D36 名义下限 - ceil 得 21 → 0.0824;新增 applied_alpha_is_never_below_the_floor 断言 下发字节/255 ≥ 0.08 O-7 契约测试补三个新键 - appearance_keys_are_all_ai_writable_and_registered 的 audited 清单加 window_opacity / titlebar_mode / palette_source
This commit is contained in:
1 parent
7406785325
commit
fa2c6560fd
6 files changed
+108
-16
No files matched your search
@@ -111,4 +111,36 @@ mod tests {
|
||||
assert_eq!(effective_window_alpha(Backend::Cpu, 0.5), Some(0.5));
|
||||
assert_eq!(effective_window_alpha(Backend::Gpu, 0.5), Some(0.5));
|
||||
}
|
||||
|
||||
/// `appearance.palette_source` 的码值必须与 `design` 的 `Level::code()` 逐字一致。
|
||||
///
|
||||
/// 为什么这条断言住在这里:`unlucky-core` 按分层纪律不依赖 `unlucky-design`,
|
||||
/// 它的测试**看不到** design,所以 core 自己只能钉字面量。本 crate 同时依赖
|
||||
/// `unlucky-design`(`Level`)与 `unlucky-presenter`(重导出 `PaletteSource`),
|
||||
/// 是全仓唯一能真做这次对照的地方。
|
||||
///
|
||||
/// 抓得住:design 侧改任一档的稳定码(例如把 `wallpaper` 改成 `desktop`)、或有人改了
|
||||
/// core 的字符串表——两个方向都红(下面往返那条覆盖「只改一侧」)。
|
||||
/// 抓不住:档位的**语义**是否仍对齐(只保证字符串一致),以及 UI 是否真按这个码值取色。
|
||||
#[test]
|
||||
fn palette_source_codes_match_design_level_codes() {
|
||||
use unlucky_design::palette::Level;
|
||||
use unlucky_presenter::prefs::PaletteSource;
|
||||
|
||||
for (source, level) in [
|
||||
(PaletteSource::BackgroundImage, Level::BackgroundImage),
|
||||
(PaletteSource::Wallpaper, Level::Wallpaper),
|
||||
(PaletteSource::Preset, Level::Preset),
|
||||
] {
|
||||
assert_eq!(
|
||||
source.as_str(),
|
||||
level.code(),
|
||||
"core 的 {source:?} 码值与 design 的 {level:?} 不一致"
|
||||
);
|
||||
// 反方向:design 的码值喂回 core 必须还原成同一档(防「两个字符串互换了」)。
|
||||
assert_eq!(PaletteSource::from_key_value(level.code()), source);
|
||||
// 且必须真的在 core 的允许值表里(否则 schema 会拒绝写这个值)。
|
||||
assert!(unlucky_presenter::prefs::PALETTE_SOURCES.contains(&level.code()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,10 @@ pub enum AlphaRejection {
|
||||
///
|
||||
/// 输入先夹到 [`WINDOW_ALPHA_MIN`]`..=1.0`(NaN 取下限):调用方即使漏了
|
||||
/// [`super::super::effective_window_alpha`] 的过滤,也不会把窗口写成穿透。
|
||||
///
|
||||
/// **向上取整**(`ceil`)是刻意的:`bAlpha/255` 才是实际下发的 α,用 `round` 会让
|
||||
/// `0.08×255 = 20.4 → 20`,实际下发 `20/255 = 0.0784`,反而**低于** D36 的下限。
|
||||
/// 因此下发值可能比请求值略高(最多 1/255 ≈ 0.004),但绝不会低于下限。
|
||||
#[must_use]
|
||||
pub fn alpha_byte(alpha: f64) -> u8 {
|
||||
let clamped = if alpha.is_nan() {
|
||||
@@ -70,7 +74,7 @@ pub fn alpha_byte(alpha: f64) -> u8 {
|
||||
} else {
|
||||
alpha.clamp(WINDOW_ALPHA_MIN, 1.0)
|
||||
};
|
||||
(clamped * 255.0).round() as u8
|
||||
(clamped * 255.0).ceil() as u8
|
||||
}
|
||||
|
||||
/// 把整窗 α 下发到窗口。
|
||||
@@ -127,13 +131,13 @@ mod tests {
|
||||
use unlucky_presenter::prefs::WINDOW_ALPHA_MIN;
|
||||
|
||||
#[test]
|
||||
fn alpha_byte_quantizes_and_clamps() {
|
||||
fn alpha_byte_quantizes_upward_never_below_the_floor() {
|
||||
assert_eq!(alpha_byte(1.0), 255);
|
||||
assert_eq!(alpha_byte(0.5), 128, "0.5×255=127.5 → 四舍五入 128");
|
||||
assert_eq!(alpha_byte(0.5), 128, "0.5×255=127.5 → 向上取整 128");
|
||||
assert_eq!(
|
||||
alpha_byte(WINDOW_ALPHA_MIN),
|
||||
20,
|
||||
"D36 下限 0.08×255=20.4 → 20(字节值不许是 0)"
|
||||
21,
|
||||
"0.08×255=20.4 → 向上取整 21;用 round 会得到 20,实际下发 20/255=0.0784 低于 D36 下限"
|
||||
);
|
||||
// 漏过预处理的坏值也不会变成「穿透」:一律落到下限字节。
|
||||
for bad in [0.0, -3.0, f64::NAN] {
|
||||
@@ -149,6 +153,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applied_alpha_is_never_below_the_floor() {
|
||||
// 真正生效的 α 是 `bAlpha / 255`,不是传进来的 f64。这条把「改用 round」的行为钉死:
|
||||
// round 下 0.08 → 20 → 20/255 = 0.0784 < 0.08 → 这里红。
|
||||
for requested in [WINDOW_ALPHA_MIN, 0.081, 0.1, 0.33, 0.5, 0.77, 0.999, 1.0] {
|
||||
let byte = alpha_byte(requested);
|
||||
let applied = f64::from(byte) / 255.0;
|
||||
assert!(
|
||||
applied >= WINDOW_ALPHA_MIN - 1e-12,
|
||||
"requested={requested} → byte={byte} → 实际 α={applied},低于下限 {WINDOW_ALPHA_MIN}"
|
||||
);
|
||||
assert!(
|
||||
applied <= 1.0,
|
||||
"requested={requested} → 实际 α={applied} 超过 1.0"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_or_opaque_alpha_never_touches_the_window() {
|
||||
for none in [None, Some(0.0), Some(-0.5), Some(f64::NAN)] {
|
||||
|
||||
@@ -102,10 +102,13 @@ fn illegal_enum_falls_back_and_leaves_a_readable_diagnostic() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_tables_are_stable_and_match_design_level_codes() {
|
||||
fn value_tables_are_self_consistent() {
|
||||
assert_eq!(TITLEBAR_MODES, ["auto", "always", "never"], "D37 三态");
|
||||
// 与 `unlucky_design::palette::Level::code()` 逐字一致;core 不依赖 design(分层纪律),
|
||||
// 所以这条一致性只能靠字面量钉住——design 侧改码值就会在这里红。
|
||||
// 这里只钉 **core 自己的字面量**(改这三个字符串要过这一关)。
|
||||
// 与 `unlucky_design::palette::Level::code()` 的一致性 core 守不了——core 不依赖 design
|
||||
// (分层纪律),本 crate 的测试永远看不到 design 的代码。那条一致性由
|
||||
// `crates/app-iced/src/platform/mod.rs::palette_source_codes_match_design_level_codes`
|
||||
// 守(那个 crate 同时依赖 design 与 presenter,是唯一能对照的地方)。
|
||||
assert_eq!(
|
||||
PALETTE_SOURCES,
|
||||
["background-image", "wallpaper", "preset"],
|
||||
|
||||
@@ -96,6 +96,10 @@ fn appearance_keys_are_all_ai_writable_and_registered() {
|
||||
"appearance.panel_alpha",
|
||||
"appearance.glass",
|
||||
"appearance.accent_color",
|
||||
// M91(D36/D37/D38)的外观下层三键,同样 audited。
|
||||
"appearance.window_opacity",
|
||||
"appearance.titlebar_mode",
|
||||
"appearance.palette_source",
|
||||
];
|
||||
for key in audited_keys {
|
||||
let spec = schema.spec(key).unwrap_or_else(|| panic!("{key} 必须登记"));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
/// 背景图填充模式(09 §101)。直接复用 core 的枚举:取值与落盘词表只有一处真相。
|
||||
pub use unlucky_core::setting::appearance::BgFill;
|
||||
|
||||
pub use unlucky_core::setting::appearance::PALETTE_SOURCES;
|
||||
/// 外观下层的三张表/常量(D36·D37·D38):复用 core 的定义,一处真相。
|
||||
///
|
||||
/// [`WINDOW_ALPHA_MIN`] 还要给 `unlucky-app-iced` 的 win32 α 层用(它只依赖本 crate,不直接依赖
|
||||
|
||||
Reference in new issue
Block a user