diff --git a/wiki/interface/bevy-visual-floor.md b/wiki/interface/bevy-visual-floor.md --- a/wiki/interface/bevy-visual-floor.md +++ b/wiki/interface/bevy-visual-floor.md @@ -193,7 +193,11 @@ The default Bevy font is acceptable for now, but text hierarchy must come from size, weight, spacing, slab sections, and alignment. Labels are quieter than -values; values are quieter than alerts. +values; values are quieter than alerts. Bevy's anti-aliased glyph atlases +inherit the image plugin's default sampler, so the frontend keeps that default +linear at every window and Retina backing scale. A future pixel-art image must +request nearest-neighbor sampling on that asset; it may not make the global +image default nearest and degrade every text atlas. ## Acceptance criteria @@ -203,6 +207,10 @@ known map cluster is framed prominently, unknown space has deliberate sensor-dark treatment, earned devices read as amber nodes/topology, and the command band and on-demand drawers are designed surfaces rather than a text dump on black. +1a. Bevy UI and world-label text remains clean and continuous at the default + and minimum window sizes and under a high-density backing scale. The + frontend default sampler is linear for anti-aliased font atlases; any + nearest-neighbor raster asset opts in locally. 2. The cursor/process marker is non-humanoid and amber-coded; no purple/teal humanoid process sprite appears in the B1 default frame. 3. Unknown, blueprint/remembered, and seen/telemetry states are visually diff --git a/wiki/log/2026-08-02-bevy-text-sampling.md b/wiki/log/2026-08-02-bevy-text-sampling.md new file mode 100644 --- /dev/null +++ b/wiki/log/2026-08-02-bevy-text-sampling.md @@ -0,0 +1,29 @@ +# 2026-08-02 — Keep Bevy text linearly sampled + +``` +Type: log +``` + +Cameron reported that text in the graphical frontend looked fuzzy rather than +crisp. The font and Retina scale path were already correct: Bevy rasterizes +`TextFont` glyphs at the window backing scale. The frontend instead replaced +Bevy's linear image default with global nearest-neighbor sampling. Smoothed +font atlases inherit that default, so their grayscale edge coverage was being +sampled as though it were pixel art. + +The frontend now keeps Bevy's linear image sampler. Misaligned currently loads +no raster world texture that needs a global nearest override; a future pixel +asset must opt into nearest sampling locally. A regression pins linear min, +mag, and mipmap filtering on the composition root's image plugin. + +The deterministic `command-band` harness captured the same 2560x1440 Retina +frame before and after the change. The former nearest-sampled capture +(`b82e5ba0...`) shows stair-stepped edges and uneven stems; the linear capture +(`712a1bf1...`) keeps continuous glyph strokes. A native high-resolution app +run then checked the origin picker, blind opening, teaching card, and mature +command band without changing their layout. + +Defense: `wiki/art/visual-identity.md` requires clean, sharp, precise UI and +`wiki/interface/bevy-visual-floor.md` makes typography part of the Bevy visual +floor. Restoring the sampler expected by Bevy's anti-aliased font atlas repairs +that contract without changing layout, wording, simulation, or save state. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -16,6 +16,11 @@ - Intent: Cameron adopted the first recommendation from the GUI game-feel playtest: right-clicking a local action surface should pause time, and the principle should persist through the menus. The existing action explanation could also disappear when focus moved with the pointer, making... - Log: [wiki/log/2026-08-02-paused-context-command.md](2026-08-02-paused-context-command.md) +## 2026-08-02 - Keep Bevy text linearly sampled + +- Intent: (see session log) +- Log: [wiki/log/2026-08-02-bevy-text-sampling.md](2026-08-02-bevy-text-sampling.md) + ## 2026-08-01 - Keep one autonomous outcome visible across task boundaries - Intent: (see session log) diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -135,6 +135,17 @@ const DEFAULT_WINDOW_HEIGHT: f32 = 720.0; const MIN_WINDOW_WIDTH: f32 = 960.0; const MIN_WINDOW_HEIGHT: f32 = 540.0; + +/// Keep anti-aliased glyph atlases linearly sampled at every window scale. +/// +/// Bevy's smoothed font atlases inherit the [`ImagePlugin`] default sampler. +/// A global nearest-neighbour default therefore makes the vector font's +/// grayscale edge pixels crawl and break up, especially on Retina displays. +/// Misaligned's world is procedural; any future pixel-art image should opt +/// into nearest sampling on that asset instead of degrading every text atlas. +fn frontend_image_plugin() -> ImagePlugin { + ImagePlugin::default_linear() +} const SIDEBAR_SCROLL_LINE: f32 = 18.0; const SIDEBAR_SCROLL_PAGE: f32 = 180.0; const COMPUTE_CHANNELS: usize = 3; @@ -1695,7 +1706,7 @@ }), ..default() }) - .set(ImagePlugin::default_nearest()), + .set(frontend_image_plugin()), ) .add_plugins(MaterialEffectsPlugin) .insert_resource(ClearColor(Color::srgb(0.01, 0.01, 0.02))) @@ -7440,5 +7451,22 @@ Duration::from_millis(400), "the Bevy timer must receive the same selected cadence" ); + } +} + +#[cfg(test)] +mod text_rendering_tests { + use super::frontend_image_plugin; + use bevy::image::ImageFilterMode; + + /// Anti-aliased Bevy font atlases use the image plugin's default sampler. + /// Nearest filtering turns their grayscale edge coverage into visibly + /// broken glyphs when the window or Retina backing scale is not 1:1. + #[test] + fn anti_aliased_font_atlases_keep_linear_sampling() { + let sampler = frontend_image_plugin().default_sampler; + assert_eq!(sampler.mag_filter, ImageFilterMode::Linear); + assert_eq!(sampler.min_filter, ImageFilterMode::Linear); + assert_eq!(sampler.mipmap_filter, ImageFilterMode::Linear); } }