Files
Unlucky-Launcher/crates/design/tests/ink_cat_visibility.rs
T
AskaEth a5d1dec495
CI / fmt (rustfmt --check) (push) Waiting to run
CI / clippy (ubuntu-latest) (push) Waiting to run
CI / clippy (windows-latest) (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
CI / build release (ubuntu-latest) (push) Waiting to run
CI / build release (windows-latest) (push) Waiting to run
CI / runtime smoke (ubuntu x64, xvfb) (push) Waiting to run
CI / runtime smoke (macos arm64, probe) (push) Skipped
CI / update-e2e (windows x64) (push) Waiting to run
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Waiting to run
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Waiting to run
Guards / core 零 UI 守门 (push) Waiting to run
Guards / presenter 零 UI 守门 (push) Waiting to run
Guards / design 零第三方依赖守门 (push) Waiting to run
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Waiting to run
Guards / 文件行数 ≤400 守门 (push) Waiting to run
Guards / 依赖许可与来源(cargo-deny) (push) Waiting to run
Release guard / 发布标记语义守门(push tag → 稳定版) (push) Waiting to run
Signing status(播报,不是门禁) / 判据自测(三行/退出码/变异重放/接线) (push) Waiting to run
Signing status(播报,不是门禁) / 签名就绪度(播报) (push) Waiting to run
Release / 选槽位(默认四个:Windows 两档 + Linux 两档) (push) Waiting to run
release: v0.2.4 里程碑快照(孤儿提交,无父;树 = dev 68362e5)
本批内容见 CHANGELOG.md 的 0.2.4 段。按 D41 用孤儿快照推 main,保留「公开史只有里程碑」的模型。
2026-11-11 21:41:38 +08:00

134 lines
5.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Ink Cat 容器层可视性门禁(M228 债务裁决的可测一半):状态容器 1.20 门禁、阶梯单调、
//! 以及「容器层不能当状态指示」的实测钉子。
//!
//! 与 `ink_cat_boards.rs` / `ink_cat_geometry.rs` 分开是单文件 ≤400 行的硬约束。
use unlucky_design::color::{BrandBoard, Color, Role, SchemeFlavor};
use unlucky_design::palette::visibility::{
check_surface_hierarchy, LAYER_ROLES, STATE_CONTAINERS, STATE_CONTAINER_MIN,
};
use unlucky_design::palette::wcag::AA_TEXT;
#[test]
fn state_containers_clear_the_visibility_floor() {
// 债务台账「SecondaryContainer 对 Surface 仅 1.06」的令牌层裁决:状态容器 ≥1.20:1。
for board in BrandBoard::ALL {
let report = check_surface_hierarchy(&board.scheme());
assert!(
report.state_failures().is_empty(),
"{} 的状态容器未达 {STATE_CONTAINER_MIN}:1:{:?}",
board.name(),
report.state_failures()
);
let (role, ratio) = report.worst_state();
assert!(
ratio >= STATE_CONTAINER_MIN,
"{} 最弱的状态容器 {} 只有 {ratio:.3}",
board.name(),
role.name()
);
// 旧 baseline 的现场值 1.057 必须被显著甩开,否则「修了但没修好」。
assert!(
ratio > 1.20,
"{} 只是刚好卡线({ratio:.3}),等于没修",
board.name()
);
}
// 两套板覆盖 4 个状态容器角色,一个不落。
assert_eq!(STATE_CONTAINERS.len(), 4);
}
#[test]
fn surface_container_ladder_is_monotone_with_visible_steps() {
for board in BrandBoard::ALL {
let report = check_surface_hierarchy(&board.scheme());
assert!(
report.layer_violations().is_empty(),
"{} 的容器阶梯有断层或方向错:{:?}",
board.name(),
report.layer_violations()
);
assert!(report.is_clean(), "{} 的容器层体检没过", board.name());
// 方向:深色板越靠上越亮、浅色板越靠上越暗。
let steps = report.layer_steps();
for step in steps {
let expect_positive = board.flavor() == SchemeFlavor::Dark;
assert_eq!(
step.delta_lstar > 0.0,
expect_positive,
"{} 的 {} 阶梯方向错",
board.name(),
step.role.name()
);
}
assert_eq!(LAYER_ROLES.len(), 5);
}
}
#[test]
fn container_layers_are_not_state_indicators() {
// 裁决第 3 条的可测化一半:把「状态容器压在容器层上」的最差值钉住,提醒后来者
// 别把它当唯一指示(浅色板必然贴到 1.02,深色板 1.54)。
let worst_over_layers = |board: BrandBoard| {
let scheme = board.scheme();
STATE_CONTAINERS
.iter()
.flat_map(|role| {
LAYER_ROLES
.iter()
.map(move |layer| (*role, *layer, scheme.contrast(*role, *layer)))
})
.map(|(_, _, ratio)| ratio)
.fold(f32::INFINITY, f32::min)
};
let silver = worst_over_layers(BrandBoard::Silver);
assert!(
(1.0..1.1).contains(&silver),
"Silver 的状态容器对容器层最低 = {silver:.3},与「浅色 pale-on-pale 必然贴近」的登记(1.02)不符"
);
// Ink 相反:深色板的状态容器与容器层拉得开,所以深色可以靠底色差做弱指示。
let ink = worst_over_layers(BrandBoard::Ink);
assert!(
ink >= 1.5,
"Ink 最弱的状态容器对容器层 = {ink:.3},应明显高于浅色板"
);
// 两条一起说明「为什么这不是调色问题而是构成问题」:同一套算法下深色能做的弱指示,
// 浅色做不到——所以门禁不能按容器层设,只能按 surface 设。
assert!(silver < ink);
}
#[test]
fn field_backdrop_is_the_same_role_on_both_boards() {
// P2-1 的回归钉子:字段底在两套板都是 `surface-container-highest`。
// 若把 Silver 的字段底挪回 `surface-container-high`(#E4E4E5),错误标签会从 4.69 掉到 4.38(AA 失守)。
for board in BrandBoard::ALL {
let scheme = board.scheme();
for (fg, usage) in [
(Role::OnSurfaceVariant, "字段标签"),
(Role::OnSurface, "字段内容"),
(Role::Error, "错误字段标签"),
] {
let ratio = scheme.contrast(fg, Role::SurfaceContainerHighest);
assert!(
ratio >= AA_TEXT,
"{} 的{usage}({} on surface-container-highest)= {ratio:.3}",
board.name(),
fg.name()
);
}
}
// 浅色板的 high 必须比 highest 更亮、且是另一个色(阶梯方向 + 不许两档同值)。
let silver = BrandBoard::Silver.scheme();
let high = silver.get(Role::SurfaceContainerHigh);
let highest = silver.get(Role::SurfaceContainerHighest);
assert_ne!(high, highest);
assert!(
high.lstar() > highest.lstar(),
"Silver 的 surface-container-high 应比 highest 更亮(高 {} vs 最高 {})",
high.lstar(),
highest.lstar()
);
// 规格卡给的字段底就是 highest 那个值。
assert_eq!(highest, Color::from_hex("#DCDDDE").expect("规格卡字段底"));
}