//! The draw-order primitive — the strata/layer vocabulary and the packed total-order key //! (`ZKey`), plus the visible-tree [`traversal`] that realizes the client's painter order //! (decision 0068). //! //! ## Ground truth (wow-5875-re, binary-verified) //! //! The 1.12.1 client draws in a **flat painter's order**, *not* a hierarchical walk: every visible //! frame — children included — is an independent entry in a per-`(strata, level)` bucket //! (`propagation.md`, `propagation-anchors.md`). A child frame with a *lower* strata/level than its //! parent therefore draws *before* the parent — the order is global, keyed on the tuple below, not //! on the tree shape. //! //! **Inside one `(strata, level)` bucket, the DRAW LAYER outranks the frame.** The five render //! batches belong to the level node (`levelNode+0x1c`), not to any frame, and the emitter //! `0x765920` loops `layer = 0..4` outside and frames inside. So a bucket emits every frame's //! BACKGROUND, then every frame's BORDER, and so on — regions are *not* grouped behind their owning //! frame. Within one layer the same is true of kind: the batch holds a quad sub-array and a text //! sub-array, and `0x76fb00` drains all quads before any text, so **all** textures of a //! `(strata, level, layer)` precede **all** its font strings. (Decision 0884, wow-re //! `ui/scratch/draw-order-law.md`. This supersedes the earlier recorded key, which had no layer //! term at all and read the last term backwards.) //! //! ## The total order (most- to least-significant) //! //! `(stratum, frame level, draw layer, texture u8 { self as u8 } } impl Default for Strata { /// `MEDIUM` — the client's default `frameStrata` (ctor writes `3`, `frame-model.md`). fn default() -> Strata { Strata::Medium } } // ───────────────────────────────────────────────────────────────────────────────────────────── // Draw layers — the within-frame region order // ───────────────────────────────────────────────────────────────────────────────────────────── /// The five draw layers a region sits in, in draw order (`frame-model.md`, the frame's five /// region lists ~`0x1c0`): BACKGROUND, BORDER, ARTWORK, OVERLAY, HIGHLIGHT. #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DrawLayer { /// Drawn first (behind the frame's other regions). Background = 0, Border = 1, /// The default layer for a texture/fontstring with no explicit ``. Artwork = 2, Overlay = 3, /// Drawn last (mouse-over highlights). Highlight = 4, } impl DrawLayer { /// The layer index (0..=4). #[inline] pub const fn index(self) -> u8 { self as u8 } } impl Default for DrawLayer { /// `ARTWORK` — the layer a texture/fontstring with no explicit `` lands in. fn default() -> DrawLayer { DrawLayer::Artwork } } // ───────────────────────────────────────────────────────────────────────────────────────────── // ZKey — the packed total-order key // ───────────────────────────────────────────────────────────────────────────────────────────── // // Bit layout (u64), most-significant field = highest draw priority. The whole render list is a // single ascending sort on this key. // // bits 60..=63 (4) stratum Strata::index() 0..=9 (< 16) // bits 44..=59 (16) frame level u16 (< 65536) // bits 41..=43 (3) draw layer DrawLayer::index() 0..=4 (< 8) // bit 40 (1) fontstring 0 = texture, 1 = fontstring — BUCKET-WIDE, above the frame // bits 21..=39 (19) frame link-stamp monotonic (re)link seq (< 524288) // bit 20 (1) is-region 0 = the frame itself, 1 = one of its regions // bits 12..=19 (8) sub-level i8 biased by +128 (0..=255) — INERT on 1.12 // bits 0..=11 (12) declaration seq region index within its owner frame (< 4096) // // ## Why the draw layer outranks the frame (decision 0884 — §5-VERIFIED) // // **The five render batches belong to the LEVEL NODE, not to the frame** (`levelNode+0x1c`, five // 0x30-byte batches), and the emitter `0x765920` loops `layer = 0..4` on the OUTSIDE and frames on // the inside. So the draw layer is a **bucket-wide** key: every frame sharing a `(strata, level)` // emits its BACKGROUND regions, then every frame its BORDER, then ARTWORK, then OVERLAY, then // HIGHLIGHT. It is not a within-frame ordering — which is what benilla modeled until 0884, with // the frame term ranked above the layer so a frame's regions stayed glued behind it. // // That inversion is what put the mirror timer's blue fill over its own border art and caption // (director report, 2026-08-02). The reference builds those bars as a Frame whose OVERLAY layer // carries the border + caption, with a child StatusBar whose `` takes the default // **ARTWORK** layer and whose only OnLoad is `SetFrameLevel(GetFrameLevel() - 1)`. That line does // **not** put the child below the parent: a child is born at `parent.level + 1` (`SetParent // 0x76ab10` @`0x76ab65`), so the `-1` lands it at *exactly* the parent's level. Its whole job is to // **create the tie**, so the layer key can decide — ARTWORK(2) before OVERLAY(3), fill under // chrome. Ranked below the frame, there was no tie to resolve and the child always trailed. // // **`texture < fontstring` is bucket-wide too, and outranks the frame.** A layer's batch holds two // independent sub-arrays — the quad array `[batch+0x10]` (every `CSimpleTexture` appends via // `0x7706e0` → `0x772fd0`) and the text sub-batch `[batch+0x18]` (`0x772e50` → `0x773080`) — and // `0x76fb00` drains the WHOLE quad array before it touches the text one. So for two frames A, B at // one `(strata, level, layer)`, each with a texture and a font string, the client emits // `A.tex, B.tex, A.text, B.text` — never `A.tex, A.text, B.tex, B.text`. // // **The frame link-stamp below that is exact for font strings and a deterministic stand-in for // textures.** The client re-sorts a layer's quad array by *texture handle* (`0x7731a0`'s qsort, // comparator `0x7731c0`) and never sorts the text sub-batch. The handle is an internal allocation // our texture manager assigns differently, so reproducing that sort is neither achievable nor // desirable; we reproduce the client's PRE-sort array, which agrees wherever a layer's quads share // a texture or don't overlap (the common case). **No content may depend on the relative order of // two overlapping textures within one layer** — that is not a fidelity invariant. // // wow-re's own previously-recorded key — `(strata, level, insertion order)` — is superseded by the // same §5: it carried no layer term at all. // // A frame's own entry zeroes the layer and everything below `is-region`, so it precedes its own // BACKGROUND regions. That slot is benilla's backdrop/scissor hook, not the client's — in the // binary a frame has no drawable of its own — so it sits *below* the layer key, never above it. // // **`sub-level` is inert on 1.12**: §5 found no sub-level in 5875 at all (`0x76a860` takes only // `(region, layer)`, head-inserts with no comparator, and the per-layer header is a plain 0xc-byte // triple with nowhere to keep a sort key). The field stays as the modeled Era delta; no 1.12 // content sets it, so it is always the `0` bias. const STRATUM_SHIFT: u32 = 60; const LEVEL_SHIFT: u32 = 44; const LAYER_SHIFT: u32 = 41; const FONTSTRING_SHIFT: u32 = 40; const INSERTION_SHIFT: u32 = 21; const IS_REGION_SHIFT: u32 = 20; const SUBLEVEL_SHIFT: u32 = 12; const DECL_SHIFT: u32 = 0; pub(crate) const INSERTION_BITS: u32 = 19; const DECL_BITS: u32 = 12; /// The packed draw-order key: a single `u64` whose natural `Ord` *is* the client's total draw /// order. Build one with [`ZKey::frame`] or [`ZKey::region`]; sort ascending to get painter order. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ZKey(u64); impl ZKey { /// The key for a **frame's own** entry — every region-level field is zero, so this sorts before /// all of the frame's regions. pub fn frame(strata: Strata, level: u16, insertion: u32) -> ZKey { debug_assert!( insertion < (1 << INSERTION_BITS), "frame insertion {insertion} exceeds {INSERTION_BITS} bits" ); let bits = (u64::from(strata.index()) << STRATUM_SHIFT) | (u64::from(level) << LEVEL_SHIFT) | (u64::from(insertion) << INSERTION_SHIFT); ZKey(bits) } /// The key for one **region** of a frame. `strata`/`level`/`insertion` are the *owner frame's* /// (so the region stays grouped behind its frame); `is_fontstring` places textures before /// fontstrings at equal (layer, sub-level). #[allow(clippy::too_many_arguments)] pub fn region( strata: Strata, level: u16, insertion: u32, layer: DrawLayer, sub_level: i8, is_fontstring: bool, decl: u16, ) -> ZKey { debug_assert!( insertion < (1 << INSERTION_BITS), "frame insertion {insertion} exceeds {INSERTION_BITS} bits" ); debug_assert!( u32::from(decl) < (1 << DECL_BITS), "region decl seq {decl} exceeds {DECL_BITS} bits" ); // sub_level (i8, -128..=127) biased into 0..=255 so higher sub-level sorts later. let sub_biased = (i16::from(sub_level) + 128) as u64; let bits = (u64::from(strata.index()) << STRATUM_SHIFT) | (u64::from(level) << LEVEL_SHIFT) | (u64::from(insertion) << INSERTION_SHIFT) | (1u64 << IS_REGION_SHIFT) | (u64::from(layer.index()) << LAYER_SHIFT) | (sub_biased << SUBLEVEL_SHIFT) | (u64::from(is_fontstring) << FONTSTRING_SHIFT) | (u64::from(decl) << DECL_SHIFT); ZKey(bits) } /// The key a frame's own **content** draws at: its bare [`ZKey::frame`] slot promoted into its /// region band at `layer` — behind that layer's textures, ahead of its declared font strings. /// /// Content is what a widget kind draws *itself*, with no region to hang it on — today only a /// ScrollingMessageFrame's ring lines, which the client materializes as the frame's own font /// strings (its `` child is their font instance, and a font string with no /// `` is `ARTWORK`). At the bare frame slot they sorted before **all** of the frame's /// regions, so the chat window's own `$parentBackground` — a `BACKGROUND` texture, faded in /// on hover — painted over the messages and dimmed them (director, 2026-07-26; the white /// text measured 192, exactly `255 × (1 − 0.25)`, the box's own alpha). The frame slot stays /// what it was — the backdrop/scissor setup that must precede everything the frame owns. #[inline] #[must_use] pub const fn content(self, layer: DrawLayer) -> ZKey { ZKey( self.0 | (1u64 << IS_REGION_SHIFT) | ((layer.index() as u64) << LAYER_SHIFT) // Sub-level 0 in the same +128 bias `region` uses, and the font-string bit: content // is text, so it takes the same textures-draw-first slot a font string would. | (128u64 << SUBLEVEL_SHIFT) | (1u64 << FONTSTRING_SHIFT), ) } /// The raw packed value (the sort key). #[inline] pub const fn raw(self) -> u64 { self.0 } /// Unpack this key back into its fields. #[inline] pub const fn parts(self) -> ZParts { unpack(self.0) } } /// The fields of a packed [`ZKey`], in significance order — the readable face of an opaque `u64`. /// /// A draw-order key is a single number by design (one sort produces the whole render list), which /// makes every question *about* an order — "why is this over that?", "which pairs would a re-rank /// invert?" — unanswerable at a glance. [`unpack`] is that answer, and it is why /// [`crate::script::ExtractedQuad::z`] can stay a bare `u64`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ZParts { /// The frame strata bucket id (0..=9, [`Strata::index`]). pub strata: u8, /// The frame level within the strata bucket. pub level: u16, /// The draw layer (0..=4, [`DrawLayer::index`]) — bucket-wide, above the frame (decision 0884). pub layer: u8, /// `true` for a font string: all textures of a `(strata, level, layer)` precede all its text. pub is_fontstring: bool, /// The owning frame's link-stamp — its position in the client's intrusive bucket list. pub insertion: u32, /// `false` for a frame's own slot, `true` for one of its regions. pub is_region: bool, /// The Era `textureSubLevel` knob; inert for 1.12 content (always 0 there). pub sub_level: i8, /// Declaration order within the owning frame's layer. pub decl: u16, } /// Unpack a raw [`ZKey`] (e.g. [`crate::script::ExtractedQuad::z`]) into its fields. #[inline] pub const fn unpack(raw: u64) -> ZParts { ZParts { strata: ((raw >> STRATUM_SHIFT) & 0xf) as u8, level: ((raw >> LEVEL_SHIFT) & 0xffff) as u16, layer: ((raw >> LAYER_SHIFT) & 0x7) as u8, is_fontstring: (raw >> FONTSTRING_SHIFT) & 1 == 1, insertion: ((raw >> INSERTION_SHIFT) & ((1 << INSERTION_BITS) - 1)) as u32, is_region: (raw >> IS_REGION_SHIFT) & 1 == 1, sub_level: (((raw >> SUBLEVEL_SHIFT) & 0xff) as i16 - 128) as i8, decl: ((raw >> DECL_SHIFT) & ((1 << DECL_BITS) - 1)) as u16, } } // ───────────────────────────────────────────────────────────────────────────────────────────── // Traversal — the visible render list in the client's painter order // ───────────────────────────────────────────────────────────────────────────────────────────── /// What a [`ZKey`] entry points at: a frame's own draw slot, or one of its regions. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum ZTarget { /// The frame itself (backdrop/scissor slot) — sorts before its regions. Frame(FrameHandle), /// A texture or fontstring belonging to a frame. Region(RegionHandle), } /// Produce the render list in the client's exact `render_traverse_order 0x765650` order. /// /// Only **effective-visible** frames contribute: because `effective_visible` is maintained across /// the whole subtree by the arena's propagation (`set_shown`/`set_parent`), filtering on the flag is /// equivalent to the client's "recurse to child frames, a hidden mid-tree frame blocks its subtree" /// (`propagation.md`) — a child of a hidden frame already carries `effective_visible == false` and is /// skipped here. Each visible frame emits its own [`ZTarget::Frame`] entry followed by one /// [`ZTarget::Region`] per owned region; the returned vec is sorted ascending by [`ZKey`], which *is* /// the total draw order (strata → frame level → **draw layer** → texture