diff --git a/crates/benilla-app/src/capture/fixtures.rs b/crates/benilla-app/src/capture/fixtures.rs index 83c5bc88..12cbc287 100644 --- a/crates/benilla-app/src/capture/fixtures.rs +++ b/crates/benilla-app/src/capture/fixtures.rs @@ -808,10 +808,13 @@ pub(super) fn seed_ui_fixture( (722, 24 | 60 << 16), (724, 45), (725, 55 | 60 << 16), - // Stat buffs (floats): +10 stamina, −5 spirit; +10 fire resistance. - (1179, 10.0f32.to_bits()), // POSSTAT2 - (1186, (-5.0f32).to_bits()), // NEGSTAT4 - (1189, 10.0f32.to_bits()), // RESISTANCEBUFFMODSPOSITIVE[2] (fire) + // Stat buffs (INT on the wire — decision 1397): +10 stamina, −5 spirit; +10 fire + // resistance. The −5 is the two's-complement word an x86-hosted server sends; an + // arm64 host saturates that same debuff to a flat 0, so the RED leg of the sheet is + // exercisable here and not on this deploy. + (1179, 10), // POSSTAT2 + (1186, (-5i32) as u32), // NEGSTAT4 + (1189, 10), // RESISTANCEBUFFMODSPOSITIVE[2] (fire) // Equipment guids (INV_SLOT_HEAD base 486 + 2·slot): chest 4, main hand 15, // ranged 17; the first backpack slot (PACK_SLOT_1 base 532) holds the arrows. (494, G_CHEST as u32), diff --git a/crates/benilla-app/src/chat_bubble.rs b/crates/benilla-app/src/chat_bubble.rs index 7887f6b5..9be467fc 100644 --- a/crates/benilla-app/src/chat_bubble.rs +++ b/crates/benilla-app/src/chat_bubble.rs @@ -65,7 +65,7 @@ use crate::net::{Embodied, Guid, NetEntity, SelfGuid}; use crate::ui_chat::{default_color, ChatEventKind}; use crate::ui_pass::{UiQuad, UiQuadAppend, UiQuads, UvRect}; use crate::ui_text::{layout_text_quads, measure_text, FontSpec, Justify, UiFontAtlas}; -use crate::vplates::{gx_px, plate_basis, text_px, VPlateSet, VPlates}; +use crate::vplates::{device_snap, gx_px, plate_basis, text_px, VPlateSet, VPlates}; use benilla_assets::{AssetSet, WorldAssets}; use benilla_world::view::WorldCamera; @@ -204,6 +204,22 @@ fn sanitize(text: &str) -> String { out } +/// The frame's bottom-left origin for a projected seat: BOTTOM-CENTER on the seat, then both +/// coordinates snapped onto the **device** pixel grid ([`device_snap`], the plate's law). +/// +/// Split out so the snap law is nameable and pinned. It shipped as a plain logical `round()`, +/// which is 1 device pixel only at scale 1: on the 2× display we play on it stepped the bubble +/// two physical pixels per axis over a continuously-sliding world, and at a fractional scale +/// (1.25/1.5) it never landed on a texel boundary at all — so it paid the stepping without even +/// buying the crispness it exists for. Same bug, same fix as the plate (0188's snap → the plate's +/// device grid); the bubble was the site that never got it (1398). +fn seat_origin(seat: Vec2, w: f32, scale: f32) -> Vec2 { + Vec2::new( + device_snap(seat.x - w * 0.5, scale), + device_snap(seat.y, scale), + ) +} + /// The border-unit in logical px for this viewport: 16/1024 of the screen width, carried /// through the damped size basis (so it shrinks in step with plate/text sizes past the knee). fn border_px(viewport: Vec2, basis: f32) -> f32 { @@ -314,6 +330,8 @@ fn drive_bubbles( Query<(), With>, ), time: Res