the auction house opens, the Honor tab lands, and a day of fixes

- the auction house end to end: right-clicking an auctioneer opens Browse/Bids/Auctions on the server's reply — search, the category tree from your own DBCs, the reference's 50-a-page pager, multi-key sorting (directions corrected at the bytes), bid/buyout, the sell slot with deposit and duration, cancel with its fee popup; prices sit on the reference's own money-frame kit in lined-up columns, the twenty auction messages split refusals-red / outcomes-to-chat, and a won or sold auction reads as a proper mail receipt — the window's Lua re-worded in our own terms after the overlap ratchet caught the first cut leaning on the reference's text
- the PvP honor system whole: the Honor tab and the inspect page over a real wire model, with current rank and highest-ever rank correctly two different fields
- a day of reported fixes: WASD survives a /logout re-entry, addon settings save on a window close and Cmd+Q (exactly once — a mac close could fire the tail twice), clams open again, random-enchant items name themselves on every surface, the loot window's rows hold their seats with legible counts, idle players stop sinking through building floors, the shooter re-pulls between Auto Shots, portraits loosen ~19% to the client's own aspect-1.0 matrix, and big creatures thump the camera off the CameraShakes table
- character select clicks like 1.12 — captions whiten on hover, buttons fire on release, Humans face straight ahead off the stage bone's real yaw, the facing is per-character, and hunter/warlock pets stand beside you at their family-ramp size with their own emitters (the imp's flames), the Voidwalker's sequence-hidden shackles culled
- the smaller player-facing set: nameplate on/off rows in Options with the CanAttack category gate (emissaries stop plating), the bonus bar slides up with igBonusBarOpen, the camera's framing height glides on a shapeshift, the open letter evicts the centre seat, the far-band fog hull stops printing a pale horizon stripe at low view distance, the guide arrow is gold with per-source rim art, error text fades through its shadow too, and /convertraid converts the party
- README: the auction house and PvP honor move out of "not built yet"

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-22 19:04:27 +07:00
co-authored by Claude Fable 5
parent 6aa78bc468
commit cf581aa2ac
170 changed files with 20573 additions and 1578 deletions
+26 -11
View File
@@ -1006,10 +1006,36 @@ fn convert_entry(
//
// `assets` missing ENTIRELY is a data-less run (the headless UI tests), not a bad
// path — those keep the old behaviour rather than blanking every textured quad.
//
// `<TexCoords>`/`SetTexCoord` slices the sampled sub-rect. The 4-edge form
// `[left,right,top,bottom]` maps to raw UV corners (`left→u0, right→u1, top→v0,
// bottom→v1`) — carried through [`UvRect`] rather than a normalized `Rect` so a
// mirrored slice (`left>right`, e.g. PlayerFrameTexture) keeps its flip to the
// vertex buffer. The 8-arg affine form is already per-corner in the `push_quad`
// winding (the route-line quads). Absent = the full texture. Resolved BEFORE the
// handle because the wrap mode follows it:
let uv = match tex_coords {
Some(TexCoords::Rect(edges)) => UvRect::from_tex_coords(edges),
Some(TexCoords::Corners(corners)) => UvRect::from_corners(corners),
None => UvRect::FULL,
};
// A slice that runs PAST the texture is the reference's tiling idiom, not a crop:
// `SetTexCoord(0, n, 0, 1)` on an n-slots-wide strip repeats the art n times — the
// stance shelf's middle carries one slot per extra form exactly that way
// (`StanceBar.xml`, `ShapeshiftBar_Update`). Clamp-sampled it smears the last column
// across the extra width instead. Clamp/repeat bake into the `Image`, so this picks
// the tiled GPU image + cache entry, as the `Backdrop` arm's `tile` does below.
let tiled = uv
.corners
.iter()
.flatten()
.any(|c| !(-0.001..=1.001).contains(c));
let handle = match (path.as_deref(), assets.as_mut()) {
(Some(p), Some(a)) => {
let resolved = if circular {
a.portrait_texture(p, images)
} else if tiled {
a.sprite_texture_tiled(p, images)
} else {
a.sprite_texture(p, images)
};
@@ -1023,17 +1049,6 @@ fn convert_entry(
// A pathless Texture region is a solid color; a textured one tints by it.
let mut color = color.unwrap_or([1.0, 1.0, 1.0, 1.0]);
color[3] *= eq.alpha;
// `<TexCoords>`/`SetTexCoord` slices the sampled sub-rect. The 4-edge form
// `[left,right,top,bottom]` maps to raw UV corners (`left→u0, right→u1, top→v0,
// bottom→v1`) — carried through [`UvRect`] rather than a normalized `Rect` so a
// mirrored slice (`left>right`, e.g. PlayerFrameTexture) keeps its flip to the
// vertex buffer. The 8-arg affine form is already per-corner in the `push_quad`
// winding (the route-line quads). Absent = the full texture.
let uv = match tex_coords {
Some(TexCoords::Rect(edges)) => UvRect::from_tex_coords(edges),
Some(TexCoords::Corners(corners)) => UvRect::from_corners(corners),
None => UvRect::FULL,
};
out.push(UiQuad {
rect,
z_key: eq.z,
@@ -239,14 +239,14 @@ pub(super) fn emit(
shadow_spec,
);
for q in &mut sq {
// Flatten rgb to the shadow color (markup tints ride the fill only), but multiply
// the layout's own alpha — it carries the shadow's base alpha AND the write-on
// gradient's per-glyph ramp (a revealing char's shadow fades with its fill).
// Flatten rgb to the shadow color (markup tints ride the fill only); the alpha is
// every scalar the fill rides plus the shadow's own — see [`shadow_alpha`], which
// names the set and the symptom a missing member wears.
q.color = [
sh.color[0],
sh.color[1],
sh.color[2],
q.color[3] * host.alpha,
shadow_alpha(q.color[3], base_color[3], host.alpha),
];
// Every glyph quad inherits the owning Text quad's ScrollFrame clip — the
// FontString's own extract-time clip, not a per-glyph concept.
@@ -394,9 +394,50 @@ fn shadow_offset_px(scaled: f32) -> f32 {
}
}
/// A shadow glyph's alpha: **every scalar the fill rides, plus its own.**
///
/// Named because the set is the whole law and a missing member is invisible until something fades
/// through the one that was dropped. There are three:
///
/// - `layout` — what the shadow pass laid out with: the font object's `<Shadow>` colour alpha,
/// times the write-on gradient's per-glyph ramp.
/// - `fill` — the FILL colour's alpha (`base_color[3]`): `SetTextColor(r,g,b,a)`, and every
/// MessageFrame line, whose fade is a colour ramp rather than a frame-alpha one.
/// - `host` — the region's effective frame alpha (`SetAlpha` and every parent's).
///
/// `fill` was the one missing. A frame that fades through its colour (UIErrorsFrame,
/// RaidWarningFrame, chat) holds `host` at 1.0 throughout, so the shadow stayed a fully opaque
/// black copy of a vanishing string: the text read as turning BLACK across the ramp and then
/// popping out when the line retired, instead of fading.
fn shadow_alpha(layout: f32, fill: f32, host: f32) -> f32 {
layout * fill * host
}
#[cfg(test)]
mod tests {
use super::{band_clip_slack, shadow_offset_px};
use super::{band_clip_slack, shadow_alpha, shadow_offset_px};
/// A drop shadow fades with the string it belongs to — through EITHER fade lane.
///
/// The regression this pins: `UIErrorsFrame`'s "Requires Mining" and every other MessageFrame
/// line ramps its COLOUR alpha and holds frame alpha at 1.0, so a shadow that only rode
/// `host` stayed opaque black under a vanishing red fill — the text turned black across the
/// ramp and then vanished in one frame (director, 2026-08-22). Anything that fades through
/// `SetTextColor`'s alpha wore the same defect.
#[test]
fn a_drop_shadow_fades_through_the_colour_lane_as_well_as_the_frame_lane() {
// Nothing fading: an opaque shadow, as before.
assert_eq!(shadow_alpha(1.0, 1.0, 1.0), 1.0);
// The colour lane alone (a message-frame line mid-ramp): the shadow follows it. This is
// the assertion that fails without the fix — it used to answer 1.0.
assert_eq!(shadow_alpha(1.0, 0.25, 1.0), 0.25);
// The frame lane alone (UIFrameFadeOut, the zone splash): unchanged behaviour.
assert_eq!(shadow_alpha(1.0, 1.0, 0.25), 0.25);
// Both lanes compose, and the font object's own semi-transparent shadow rides on top.
assert_eq!(shadow_alpha(0.8, 0.5, 0.5), 0.2);
// Fully faded fill leaves NO shadow behind — the black that used to outlive the text.
assert_eq!(shadow_alpha(1.0, 0.0, 1.0), 0.0);
}
/// The newest chat line's band bottom IS the frame's bottom edge, so every px the renderer
/// adds below the band is a px the scissor would otherwise eat — the descender tails and