GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
refactor: Frost::Unfrosted, a `plate { frost … }` block, and plate.pane.color
Three renamings that were each a trap:
Frost::Opaque is Frost::Unfrosted. The variant means the plate never
samples its backdrop; a translucent tint stays translucent with a sharp
view through it, which "opaque" kept reading as "solid".
The default plate material's frost is one block, `style.surface.plate
{ frost radius=5.5 compression=0 refraction=0 }` — the shape a named
material's `frost` child already had. A bare `frost` is frosted at the
defaults, `frost (bool)false` is not. The four scattered keys it replaces
(`blur` as the switch, `radius`, `backdrop_compression`, `refraction`)
are still read as aliases, and the block governs when both are present.
cce-relief writes whichever spelling the config already uses, and spells
a named material's knob `compression` (both are read).
The pane tint has a clear spelling, `style.surface.plate.pane.color`,
whose alpha is the whole tint strength; the legacy `param.color` is
still multiplied by the top-level `plate_opacity` line as it always was
(color::pane_color_is_whole says which was used).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CLAUDE.md | 15 +++++++
src/bin/cce-relief.rs | 22 +++++++---
src/color.rs | 53 ++++++++++++++++++++++++
src/scene/material.rs | 109 ++++++++++++++++++++++++++++++++++++++------------
src/scene/paint.rs | 2 +-
5 files changed, 168 insertions(+), 33 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index ac8ffef..e8bfa32 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -400,6 +400,21 @@ What this buys, and where the code is heading:
**6-digit** hex sets alpha to **1.0**, so dropping the last byte off a
translucent plate colour makes it fully opaque rather than leaving it
alone.
+- **Frost is one block, and "unfrosted" is not "solid"** (2026-09-28). The
+ default plate material's recipe is spelled `style.surface.plate { frost
+ radius=5.5 compression=0 refraction=0 }` — the same `frost` child a named
+ material has — with a bare `frost` frosted at the defaults and `frost
+ (bool)false` off. The four keys it replaces (`blur` as the switch,
+ `radius`, `backdrop_compression`, `refraction`) are still read as aliases,
+ and the block governs when both are present; cce-relief writes whichever
+ spelling the config already uses. The enum variant is `Frost::Unfrosted`
+ (was `Opaque`): it means the plate never samples its backdrop, and a
+ translucent tint stays translucent with a SHARP view through it — which
+ is what the old name kept reading as "covers everything". The pane tint
+ has a clear spelling too, `style.surface.plate.pane.color`, whose alpha
+ is the whole tint strength; the legacy `style.surface.param.color` is
+ still multiplied by the top-level `plate_opacity` line, as it always was
+ (`color::pane_color_is_whole`).
- **A frosted plate's legibility is `backdrop_compression`, not opacity.**
Blur destroys a backdrop's spatial DETAIL and preserves its mean LUMINANCE,
and text contrast is a mean-luminance property — so `resolve_blur`'s closing
diff --git a/src/bin/cce-relief.rs b/src/bin/cce-relief.rs
index 9e92714..61b4085 100644
--- a/src/bin/cce-relief.rs
+++ b/src/bin/cce-relief.rs
@@ -1138,17 +1138,27 @@ impl BevelPopup {
& w(&format!("{m}.finish.shininess"), &shine)
& w(&format!("{m}.finish.curvature"), &curv)
& (!frosted
- || (w(&format!("{m}.frost.backdrop_compression"), &comp)
+ || (w(&format!("{m}.frost.compression"), &comp)
& w(&format!("{m}.frost.refraction"), &refr)
& w(&format!("{m}.frost.radius"), &radius)))
}
None => {
+ // Into the `plate { frost … }` block when the config spells
+ // it that way (the block governs, so a write to the old
+ // scattered keys would be read and then overridden), else
+ // the scattered keys the config already uses.
+ let block = cce_ui::config::cached_config().pointer("/style/surface/plate/frost").is_some();
+ let (kc, kr, kd) = if block {
+ ("style.surface.plate.frost.compression", "style.surface.plate.frost.refraction", "style.surface.plate.frost.radius")
+ } else {
+ ("style.surface.plate.backdrop_compression", "style.surface.plate.refraction", "style.surface.plate.radius")
+ };
w("style.surface.relief.spec", &spec)
& w("style.surface.relief.shininess", &shine)
& w("style.surface.relief.curvature", &curv)
- & w("style.surface.plate.backdrop_compression", &comp)
- & w("style.surface.plate.refraction", &refr)
- & w("style.surface.plate.radius", &radius)
+ & w(kc, &comp)
+ & w(kr, &refr)
+ & w(kd, &radius)
}
}
}
@@ -1499,9 +1509,9 @@ impl Application for BevelPopup {
};
let (pcomp, prefr, pradius) = match pane.frost {
cce_ui::scene::Frost::Frosted { compression, refraction, radius } => (compression, refraction, radius),
- cce_ui::scene::Frost::Opaque => match cce_ui::scene::Frost::from_style() {
+ cce_ui::scene::Frost::Unfrosted => match cce_ui::scene::Frost::from_style() {
cce_ui::scene::Frost::Frosted { compression, refraction, radius } => (compression, refraction, radius),
- cce_ui::scene::Frost::Opaque => (0.0, 0.0, cce_ui::scene::Frost::DEFAULT_RADIUS),
+ cce_ui::scene::Frost::Unfrosted => (0.0, 0.0, cce_ui::scene::Frost::DEFAULT_RADIUS),
},
};
let frost_seed = |k: &str, plate_key: &str, process: f32| -> f32 {
diff --git a/src/color.rs b/src/color.rs
index fc253e4..1ae7f6c 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -664,6 +664,13 @@ fn parse_and_set_colors(content: &str) {
}
if let Some(c) = get_color("/style/surface/param/color") {
if let Ok(mut lock) = PARAM_BG_COLOR.write() { *lock = c; }
+ if let Ok(mut lock) = PANE_COLOR_WHOLE.write() { *lock = false; }
+ }
+ // The clear spelling of the same tint: the PANE plate's colour, whole —
+ // its alpha is the tint strength and nothing multiplies it.
+ if let Some(c) = get_color("/style/surface/plate/pane/color") {
+ if let Ok(mut lock) = PARAM_BG_COLOR.write() { *lock = c; }
+ if let Ok(mut lock) = PANE_COLOR_WHOLE.write() { *lock = true; }
}
if let Some(c) = val
.pointer("/style/surface/plate/backdrop_compression")
@@ -682,6 +689,35 @@ fn parse_and_set_colors(content: &str) {
if let Some(r) = val.pointer("/style/surface/plate/radius").and_then(|v| v.as_f64()) {
if let Ok(mut lock) = PLATE_FROST_RADIUS.write() { *lock = (r as f32).max(0.0); }
}
+ // The one-block spelling (2026-09-28), the same shape a named
+ // material's `frost` child has: `plate { frost radius=5.5
+ // compression=0 refraction=0 }` is frosted with those knobs, a bare
+ // `frost` is frosted at the defaults, `frost (bool)false` is not
+ // frosted. Read AFTER the four scattered keys (`blur`, `radius`,
+ // `backdrop_compression`, `refraction`), which stay as aliases, so a
+ // config that says both is governed by the block.
+ match val.pointer("/style/surface/plate/frost") {
+ Some(serde_json::Value::Object(fo)) => {
+ let num = |k: &str| fo.get(k).and_then(|v| v.as_f64()).map(|v| v as f32);
+ if let Ok(mut lock) = PLATE_BLUR.write() { *lock = true; }
+ if let Some(c) = num("compression").or_else(|| num("backdrop_compression")) {
+ if let Ok(mut lock) = PLATE_BACKDROP_COMPRESSION.write() { *lock = c.clamp(0.0, 1.0); }
+ }
+ if let Some(r) = num("refraction") {
+ if let Ok(mut lock) = PLATE_REFRACTION.write() { *lock = r.clamp(0.0, 1.0); }
+ }
+ if let Some(r) = num("radius") {
+ if let Ok(mut lock) = PLATE_FROST_RADIUS.write() { *lock = r.max(0.0); }
+ }
+ }
+ Some(serde_json::Value::Null) => {
+ if let Ok(mut lock) = PLATE_BLUR.write() { *lock = true; }
+ }
+ Some(serde_json::Value::Bool(on)) => {
+ if let Ok(mut lock) = PLATE_BLUR.write() { *lock = *on; }
+ }
+ _ => {}
+ }
// The DE finish beyond its strength (`relief.depth` / `light`, which
// lives in the style registry): the three terms that were literals.
@@ -965,12 +1001,25 @@ pub const PARAM_BG: [f32; 4] = [0.10, 0.10, 0.14, 0.25];
/// runtime (the designer's Style section "Plate Color"). Alpha doubles as the
/// frost strength under plate blur.
static PARAM_BG_COLOR: RwLock<[f32; 4]> = RwLock::new(PARAM_BG);
+/// Whether the pane tint came in as `style.surface.plate.pane.color` — the
+/// spelling whose alpha IS the tint strength — rather than the legacy
+/// `style.surface.param.color`, which the top-level `plate_opacity` line
+/// still multiplies (`Material::pane_legacy`). Two spellings, one tint.
+static PANE_COLOR_WHOLE: RwLock<bool> = RwLock::new(false);
pub fn param_bg_color() -> [f32; 4] {
load_colors_once();
style_read(&PARAM_BG_COLOR)
}
+/// Whether the pane tint was spelled `style.surface.plate.pane.color`, in
+/// which case its alpha is the whole tint strength and `plate_opacity` does
+/// not multiply it. See `PANE_COLOR_WHOLE`.
+pub fn pane_color_is_whole() -> bool {
+ load_colors_once();
+ style_read(&PANE_COLOR_WHOLE)
+}
+
pub fn set_param_bg_color(color: [f32; 4]) {
style_write(&PARAM_BG_COLOR, color);
}
@@ -1845,6 +1894,10 @@ pub fn set_plate_refraction(r: f32) {
style_write(&PLATE_REFRACTION, r.clamp(0.0, 1.0));
}
+/// Whether the default plate material is frosted at all — the switch on
+/// `Frost::from_style`. Spelled `style.surface.plate.frost` (a block, the
+/// knobs inside it; see the loader) or, as an alias, the older
+/// `style.surface.plate.blur` bool.
static PLATE_BLUR: RwLock<bool> = RwLock::new(false);
/// The finish's three fixed terms — specular strength, shininess exponent,
diff --git a/src/scene/material.rs b/src/scene/material.rs
index 6fe26fb..56ed111 100644
--- a/src/scene/material.rs
+++ b/src/scene/material.rs
@@ -63,8 +63,12 @@ impl Finish {
/// question.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Frost {
- /// The tint alone, composited at its alpha. Not a sample of the backdrop.
- Opaque,
+ /// No frost: the tint alone, composited at its ALPHA — a translucent
+ /// tint is still translucent, and what shows through is sharp. This
+ /// says nothing about coverage; it says the plate never samples its
+ /// backdrop. (Named `Opaque` until 2026-09-28, which read as "solid"
+ /// and was not.)
+ Unfrosted,
/// Frosted glass: the backdrop blurred, luminance-compressed toward the
/// tint's key, tinted at the tint's alpha; the rim refracts.
Frosted {
@@ -104,11 +108,11 @@ impl Frost {
/// The recipe as the plate branch reads it: `[p_host.z, p_host.w]` —
/// compression and refraction packed in `z`, the blur radius in `w` as
/// the kernel sigma in PHYSICAL px (the shader samples the backdrop in
- /// physical px). `Opaque` packs to zeros: nothing reads them, and a
+ /// physical px). `Unfrosted` packs to zeros: nothing reads them, and a
/// plate that was never frosted pushes the bytes it always did.
pub fn pack(&self, scale: f32) -> [f32; 2] {
match *self {
- Frost::Opaque => [0.0, 0.0],
+ Frost::Unfrosted => [0.0, 0.0],
Frost::Frosted { compression, refraction, radius } => {
let q = |v: f32| (v.clamp(0.0, 1.0) * Self::PACK_MAX).round();
[q(compression) * Self::PACK_BASE + q(refraction), radius.max(0.0) * scale]
@@ -138,10 +142,10 @@ impl Frost {
}
}
- /// [`Frost::from_style`] when `on`, else [`Frost::Opaque`] — the shape of
+ /// [`Frost::from_style`] when `on`, else [`Frost::Unfrosted`] — the shape of
/// every `blur: bool` the toolkit carries today.
pub fn from_flag(on: bool) -> Self {
- if on { Self::from_style() } else { Frost::Opaque }
+ if on { Self::from_style() } else { Frost::Unfrosted }
}
pub fn is_frosted(&self) -> bool {
@@ -205,7 +209,7 @@ impl MaterialDef {
refraction: f.refraction.unwrap_or(0.0).clamp(0.0, 1.0),
radius: f.radius.unwrap_or(Frost::DEFAULT_RADIUS).max(0.0),
},
- None => Frost::Opaque,
+ None => Frost::Unfrosted,
};
let mut finish = base.finish;
if let Some(l) = self.light {
@@ -249,7 +253,7 @@ pub struct Material {
impl Material {
/// An opaque material of `tint` under the DE's finish.
pub fn opaque(tint: [f32; 4]) -> Self {
- Self { tint, frost: Frost::Opaque, finish: Finish::from_style() }
+ Self { tint, frost: Frost::Unfrosted, finish: Finish::from_style() }
}
pub fn with_tint(mut self, tint: [f32; 4]) -> Self {
@@ -272,7 +276,7 @@ impl Material {
/// The root rung: `plate { root material="…" }` when bound, else the
/// window's background, `style.surface.plate.root.color`
/// (`color::page_low_color`, whose alpha IS `root_plate_opacity`).
- /// `Frost::Opaque` on the client side by construction: a root plate's
+ /// `Frost::Unfrosted` on the client side by construction: a root plate's
/// frost is the COMPOSITOR's (`plate.root.blur`, which the client never
/// reads), and [`Material::fill`] under [`PlateRole::Root`] would ignore
/// a `Frosted` here anyway.
@@ -315,7 +319,12 @@ impl Material {
fn pane_legacy() -> Self {
let mut tint = crate::color::param_bg_color();
- tint[3] *= crate::layout::plate_opacity();
+ // `style.surface.plate.pane.color` is the whole tint, alpha
+ // included; the legacy `param.color` is still multiplied by the
+ // top-level `plate_opacity` line, as it always was.
+ if !crate::color::pane_color_is_whole() {
+ tint[3] *= crate::layout::plate_opacity();
+ }
let frost = if crate::color::plate_blur() {
Frost::Frosted {
compression: crate::color::plate_backdrop_compression(),
@@ -323,7 +332,7 @@ impl Material {
radius: crate::color::plate_frost_radius(),
}
} else {
- Frost::Opaque
+ Frost::Unfrosted
};
Self { tint, frost, finish: Finish::from_style() }
}
@@ -356,7 +365,7 @@ impl Material {
/// The legacy bridge: a fill as the renderer consumed it before this
/// type existed. A negative alpha is the frost sentinel — `Frosted` at
- /// the DE recipe, tint alpha `|a|`; otherwise `Opaque` with the colour
+ /// the DE recipe, tint alpha `|a|`; otherwise `Unfrosted` with the colour
/// as is. `from_fill(c).fill(Nested) == c` for every `c` a caller could
/// hand the old API. For a call site that holds an encoded colour; a
/// site that knows what it means says `Material::opaque` / `with_frost`.
@@ -380,7 +389,7 @@ impl Material {
/// the tessellator encodes every prim as nested.
pub fn for_role(&self, role: PlateRole) -> Self {
match role {
- PlateRole::Root => Material { frost: Frost::Opaque, ..*self },
+ PlateRole::Root => Material { frost: Frost::Unfrosted, ..*self },
PlateRole::Nested => *self,
}
}
@@ -458,7 +467,7 @@ impl Material {
/// plate's frost is the compositor's blur-behind, never the in-app pass.
/// - nested + [`Frost::Frosted`] → the in-app frost pass's negative-alpha
/// sentinel, `-|alpha|`.
- /// - nested + [`Frost::Opaque`] → the tint as is.
+ /// - nested + [`Frost::Unfrosted`] → the tint as is.
///
/// Static so a caller holding a tint and a flag (today's `PlateSpec`)
/// encodes through the same rule without resolving a finish it does not
@@ -488,12 +497,12 @@ mod tests {
fn fill_encodes_by_role() {
let tint = [0.1, 0.2, 0.3, 0.8];
assert_eq!(Material::fill_tint(tint, frosted(), PlateRole::Root)[3], 0.8, "root frost is the compositor's");
- assert_eq!(Material::fill_tint(tint, Frost::Opaque, PlateRole::Root)[3], 0.8);
+ assert_eq!(Material::fill_tint(tint, Frost::Unfrosted, PlateRole::Root)[3], 0.8);
assert_eq!(Material::fill_tint(tint, frosted(), PlateRole::Nested)[3], -0.8, "nested frost = sentinel");
- assert_eq!(Material::fill_tint(tint, Frost::Opaque, PlateRole::Nested), tint, "no frost, no encoding");
+ assert_eq!(Material::fill_tint(tint, Frost::Unfrosted, PlateRole::Nested), tint, "no frost, no encoding");
// A caller that hands a negative alpha in is normalised, not doubled.
assert_eq!(Material::fill_tint([0.0, 0.0, 0.0, -0.5], frosted(), PlateRole::Nested)[3], -0.5);
- assert_eq!(Material::fill_tint([0.0, 0.0, 0.0, -0.5], Frost::Opaque, PlateRole::Root)[3], 0.5);
+ assert_eq!(Material::fill_tint([0.0, 0.0, 0.0, -0.5], Frost::Unfrosted, PlateRole::Root)[3], 0.5);
let m = Material::opaque(tint).with_frost(frosted());
assert_eq!(m.fill(PlateRole::Nested), Material::fill_tint(tint, frosted(), PlateRole::Nested));
}
@@ -542,7 +551,7 @@ mod tests {
crate::color::set_plate_backdrop_compression(0.6);
crate::color::set_plate_refraction(0.3);
assert_eq!(Frost::from_style(), frosted());
- assert_eq!(Frost::from_flag(false), Frost::Opaque);
+ assert_eq!(Frost::from_flag(false), Frost::Unfrosted);
assert!(Frost::from_flag(true).is_frosted());
crate::color::set_plate_backdrop_compression(0.0);
crate::color::set_plate_refraction(0.0);
@@ -600,7 +609,7 @@ mod tests {
// 0.6 / 0.3 (the designer's recipe) survive to better than a 1/255 step.
let (c, r) = Frost::unpack(Frost::Frosted { compression: 0.6, refraction: 0.3, radius: 0.0 }.pack(1.0)[0]);
assert!((c - 0.6).abs() < 1.0 / 510.0 && (r - 0.3).abs() < 1.0 / 510.0);
- assert_eq!(Frost::Opaque.pack(2.0), [0.0, 0.0]);
+ assert_eq!(Frost::Unfrosted.pack(2.0), [0.0, 0.0]);
assert_eq!(Frost::Frosted { compression: 0.0, refraction: 0.0, radius: 0.0 }.pack(2.0), [0.0, 0.0]);
let wgsl = include_str!("../vk/shader2d.wgsl");
@@ -684,9 +693,57 @@ mod tests {
assert_eq!(crate::color::material_binding(PlateRung::Pane), None);
}
- /// A binding wins over the legacy keys, a node without `frost` is opaque
- /// whatever `plate blur` says, unset fields fall back to the rung, and a
- /// binding to an undefined name degrades to the legacy material.
+ /// The default material's frost as ONE block — `plate { frost radius=…
+ /// compression=… refraction=… }`, the shape a named material's `frost`
+ /// child already had — with the four scattered keys (`blur`, `radius`,
+ /// `backdrop_compression`, `refraction`) still read as aliases: a bare
+ /// `frost` is frosted at the defaults, `frost (bool)false` is not, and
+ /// when both spellings are present the block governs.
+ #[test]
+ fn the_frost_block_is_one_spelling_of_the_default_recipe() {
+ let _lock = crate::color::test_color_state_lock();
+ crate::layout::lazy_init_style_registry();
+ let _ = crate::color::plate_blur();
+ let load = |plate: &str| {
+ crate::color::reload_colors(&format!("style {{\n surface {{\n plate {{\n {plate}\n }}\n }}\n}}\n"));
+ (crate::color::plate_blur(), Frost::from_style())
+ };
+ let frosted = |c: f32, r: f32, rad: f32| Frost::Frosted { compression: c, refraction: r, radius: rad };
+ assert_eq!(load("frost radius=(f64)3.0 compression=(f64)0.4 refraction=(f64)0.1"), (true, frosted(0.4, 0.1, 3.0)));
+ assert_eq!(load("frost backdrop_compression=(f64)0.2"), (true, frosted(0.2, 0.1, 3.0)), "the old knob name is accepted inside the block; unset knobs keep their last value");
+ assert_eq!(load("frost").0, true, "a bare `frost` is frosted");
+ assert_eq!(load("frost (bool)false").0, false);
+ assert_eq!(load("blur (bool)true\n radius (f64)2.0"), (true, frosted(0.2, 0.1, 2.0)), "the scattered spelling still works");
+ assert_eq!(load("blur (bool)false\n frost compression=(f64)0.7"), (true, frosted(0.7, 0.1, 2.0)), "the block governs");
+ crate::color::reload_colors("");
+ }
+
+ /// `style.surface.plate.pane.color` is the pane tint WHOLE — its alpha is
+ /// the tint strength — where the legacy `param.color` is still multiplied
+ /// by the top-level `plate_opacity` line.
+ #[test]
+ fn the_pane_colour_spelling_is_the_whole_tint() {
+ let _lock = crate::color::test_color_state_lock();
+ crate::layout::lazy_init_style_registry();
+ let _ = crate::color::plate_blur();
+ let was = crate::layout::plate_opacity();
+ crate::layout::set_plate_opacity(0.5);
+ crate::color::reload_colors("style {\n surface {\n param color=(rgba)\"#10101880\"\n }\n}\n");
+ assert!(!crate::color::pane_color_is_whole());
+ let a = Material::pane().tint[3];
+ assert!((a - 0.25).abs() < 1e-2, "legacy: alpha 0.5 x plate_opacity 0.5, got {a}");
+ crate::color::reload_colors("style {\n surface {\n plate {\n pane color=(rgba)\"#10101880\"\n }\n }\n}\n");
+ assert!(crate::color::pane_color_is_whole());
+ let a = Material::pane().tint[3];
+ assert!((a - 0.5).abs() < 1e-2, "spelled whole: alpha 0.5 as written, got {a}");
+ crate::layout::set_plate_opacity(was);
+ crate::color::reload_colors("");
+ }
+
+ /// A binding wins over the legacy keys, a node without `frost` is
+ /// unfrosted whatever `plate blur` says, unset fields fall back to the
+ /// rung, and a binding to an undefined name degrades to the legacy
+ /// material.
#[test]
fn binding_semantics() {
let _lock = crate::color::test_color_state_lock();
@@ -711,7 +768,7 @@ mod tests {
}
"##);
let pane = Material::pane();
- assert_eq!(pane.frost, Frost::Opaque, "no frost child = opaque, blur flag or not");
+ assert_eq!(pane.frost, Frost::Unfrosted, "no frost child = unfrosted, blur flag or not");
assert_eq!(pane.tint, Material::legacy(PlateRung::Pane).tint, "no color = the rung's tint");
assert_eq!(pane.finish.spec, 0.25);
assert_eq!(pane.finish.shininess, 12.0);
@@ -759,9 +816,9 @@ mod tests {
fn control_face_is_opaque_or_none() {
let face = Material::control_face([0.2, 0.3, 0.4, 0.5]).expect("a fill is a face");
assert_eq!(face.tint, [0.2, 0.3, 0.4, 1.0]);
- assert_eq!(face.frost, Frost::Opaque);
+ assert_eq!(face.frost, Frost::Unfrosted);
assert!(Material::control_face([0.2, 0.3, 0.4, 0.0]).is_none());
- assert_eq!(Material::control().frost, Frost::Opaque);
- assert_eq!(Material::root().frost, Frost::Opaque);
+ assert_eq!(Material::control().frost, Frost::Unfrosted);
+ assert_eq!(Material::root().frost, Frost::Unfrosted);
}
}
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index 3c23c61..e1cf75f 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -1840,7 +1840,7 @@ mod tests {
assert!(!spec.is_root());
assert_eq!(spec.role(), PlateRole::Nested);
assert!(spec.fill()[3] < 0.0, "nested frost = negative-alpha sentinel");
- spec.material.frost = Frost::Opaque;
+ spec.material.frost = Frost::Unfrosted;
assert_eq!(spec.fill()[3], 0.8, "no frost, no encoding");
// The detach role flip (RFC 7c): a frosted nested pane becomes a