From 43e508aa21ef5f332f75c0f477840ba2362ce966 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Wed, 14 Oct 2026 04:45:41 +0200 Subject: [PATCH] AlphaMode::Blend, not Mask -- the cutout-first ordering was moot Sets the terrain material's alpha mode, which is the first draw-path change the GS work has actually produced. The material used `..default()`, which is AlphaMode::Opaque: every texel drawn fully solid regardless of what its alpha said. Given 0.98% of texels on the disc are fully clear, that means roughly 227,000 texels that should have shown background are drawn as opaque geometry. The choice is Blend, not Mask, and the reason is the histogram from 25d73b4: - Mask(f32) is a *quantising* cutout. Below the threshold -> transparent, at or above it -> fully opaque. Feeding it the 1.08% partial texels would flatten every one of them to solid, discarding exactly the soft edges they exist to produce. 126 distinct values become two. - Blend handles all three populations at once. A clear texel blends to invisible, a partial one stays smooth, an opaque one stays opaque. Which retires the conclusion I gave two commits ago. I said cutout-first, on the reasoning that the clear texels need discarding and a partial texel drawn opaque is a failure nobody notices. But Blend does not need a cutout to hide those texels -- a zero-alpha fragment is invisible by definition. The cutout was solving a problem Blend solves for free, so the ordering was moot before it was tested. Three commits on the alpha path and the second conclusion has now also fallen; the surviving facts are the histogram and the rule that 0x80 is opaque. One real fidelity gap, recorded rather than papered over: the GS can alpha-test and blend the same primitive at once. bevy's AlphaMode is a single enum where Mask and Blend are mutually exclusive, so it cannot express that. Where the original combines them, this port has to pick one, and the choice will not be free. That belongs in the Phase 5 frame-for-frame gate rather than being treated as settled here. Clippy clean, builds. No test added: this is a material parameter with no headless assertion available, and asserting a bevy enum value would be a test of the bevy API rather than of anything in this project. --- crates/gc-engine/src/main.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/gc-engine/src/main.rs b/crates/gc-engine/src/main.rs index 77aa541..c45d05f 100644 --- a/crates/gc-engine/src/main.rs +++ b/crates/gc-engine/src/main.rs @@ -20,6 +20,7 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use bevy::asset::RenderAssetUsages; use bevy::image::{ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor}; +use bevy::material::AlphaMode; use bevy::prelude::*; use bevy::render::mesh::{Indices, PrimitiveTopology}; use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat}; @@ -308,6 +309,12 @@ fn spawn_level( // Double-sided: the strips as stored do not carry a consistent // winding, and the game relies on GS defaults rather than culling. cull_mode: None, + // Blend, not Mask. The disc's alpha is 97.94% opaque, 0.98% clear and + // 1.08% partial across 126 values, and Blend handles all three: a + // clear texel blends to invisible, a partial one stays smooth. Mask + // would flatten those 1.08% to fully opaque, discarding exactly the + // soft edges they exist to produce. + alpha_mode: AlphaMode::Blend, ..default() }); commands.spawn((