From d3c1a501def09fce8b987daa215c988b6fa3dfd7 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 17 Aug 2026 17:06:24 +0700 Subject: [PATCH] buffed stats go green, and the bear stops stacking its roar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gear and buff bonuses finally render green in the character sheet, tooltips naming the unbuffed base — the stat-split fields are int on the wire and were decoded as float, which rounded every bonus to zero - the bear pet's aggro roar played 63 times in two minutes and stacked into a phasing growl; it now plays through the reference's per-unit voice channel, where a live bark drops the next - the chat bubble stops jittering: its anchor lagged the body by a frame, and it now snaps to the device pixel grid like the nameplate under it Co-Authored-By: Claude Fable 5 --- crates/benilla-app/src/capture/fixtures.rs | 11 +- crates/benilla-app/src/chat_bubble.rs | 157 ++++++++++++- crates/benilla-app/src/entities.rs | 26 ++- crates/benilla-app/src/sound/creature.rs | 139 ++++++++++- crates/benilla-app/src/sound/greeting.rs | 1 + crates/benilla-app/src/sound/kit.rs | 104 ++++++++- crates/benilla-app/src/sound/liquid_loop.rs | 1 + crates/benilla-app/src/sound/missile.rs | 1 + crates/benilla-app/src/sound/spell.rs | 1 + crates/benilla-app/src/sound/water.rs | 1 + crates/benilla-app/src/ui_char.rs | 12 +- crates/benilla-app/src/vplates.rs | 7 +- crates/benilla-app/tests/character_stats.rs | 215 ++++++++++++++++++ .../src/messages/update_object/fields/mod.rs | 33 ++- .../messages/update_object/fields/player.rs | 29 +-- .../messages/update_object/fields/tests.rs | 26 ++- crates/benilla-ui/src/script/char_stats.rs | 83 +++++-- 17 files changed, 762 insertions(+), 85 deletions(-) create mode 100644 crates/benilla-app/tests/character_stats.rs 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