diff --git a/crates/benilla-app/src/ui_world_map.rs b/crates/benilla-app/src/ui_world_map.rs index 61c6fcbf..8220f139 100644 --- a/crates/benilla-app/src/ui_world_map.rs +++ b/crates/benilla-app/src/ui_world_map.rs @@ -64,6 +64,50 @@ struct DirectAreaEntry { view: WorldMapZoneView, } +/// `WOW_MAP_TRACE=1`, read once: the map-art trace below is off unless a player is being asked +/// to reproduce something, since it runs a Lua chunk per map change. +fn map_trace_on() -> bool { + static ON: std::sync::OnceLock = std::sync::OnceLock::new(); + *ON.get_or_init(|| std::env::var_os("WOW_MAP_TRACE").is_some()) +} + +/// The chunk [`map_trace_on`] runs. Pure reads; `GetTexture` answers the path the stock repaint +/// set, and a texture created with no size of its own answers the file's own extent through the +/// host's size probe, which is why a scratch region is used to measure the file rather than the +/// tile (the tile declares 256x256 in `WorldMapFrame.xml` and would answer that whatever the file +/// holds). +const MAP_ART_TRACE: &str = r#" +local d = WorldMapDetailFrame +if not d then return "no WorldMapDetailFrame" end +local function num(v) return v and string.format("%.0f", v) or "?" end +local out = { string.format("detail %sx%s @(%s,%s) scale %.2f shown=%s", + num(d:GetWidth()), num(d:GetHeight()), num(d:GetLeft()), num(d:GetTop()), + d:GetEffectiveScale() or 1, tostring(d:IsShown())) } +-- One scratch texture, re-pointed per tile: with no size of its own it answers the file's extent. +if not BenillaMapTraceProbe then + BenillaMapTraceProbe = UIParent:CreateTexture(nil, "BACKGROUND") + BenillaMapTraceProbe:Hide() +end +local probe = BenillaMapTraceProbe +for i = 1, 12 do + local t = _G["WorldMapDetailTile" .. i] + if t then + local path = t:GetTexture() + local fw, fh = "?", "?" + if path then + probe:SetWidth(0) probe:SetHeight(0) + probe:SetTexture(path) + fw, fh = num(probe:GetWidth()), num(probe:GetHeight()) + end + out[#out + 1] = string.format("%d %sx%s @(%s,%s) file %sx%s %s", i, + num(t:GetWidth()), num(t:GetHeight()), num(t:GetLeft()), num(t:GetTop()), + fw, fh, tostring(path)) + end +end +out[#out + 1] = "overlays " .. tostring(GetNumMapOverlays and GetNumMapOverlays() or "?") +return table.concat(out, " | ") +"#; + fn zone_rect(a: &WorldMapArea) -> ZoneRect { ZoneRect { left: a.loc_left, @@ -647,6 +691,9 @@ struct FeedMemos { /// line prints on change rather than every frame — the selection is in the key because a /// selection that stops following the player is the thing the line exists to show. zone_logged: Option<(u32, u32, u32, u32, u32, u32, u32)>, + /// The map selection the `WOW_MAP_TRACE` art line last reported, so it prints once per map + /// rather than once per frame. + art_logged: Option<(u32, u32, Option)>, /// The resolved pair last seen, so the watchdog in [`feed_world_map`] can tell the player /// moving to a new zone from the map being browsed away from the player's. last_resolved: Option<(u32, u32, Option)>, @@ -981,6 +1028,25 @@ fn feed_world_map( } } + // `WOW_MAP_TRACE=1`: what the map's base art actually is, once per map change. A player's + // report about how the map *looks* cannot be answered from this side — the offline harness + // seats no renderer and the capture harness seats no UI — and every number that decides the + // picture is readable from the VM, so this prints them rather than guessing from a + // screenshot: the detail frame's own rect and effective scale, then per tile its rect, the + // texture path it holds and the decoded size of that file. A tile whose rect is not 256x256 + // at a 256 pitch, or whose file is not the size the layout assumes, names the fault outright. + if map_trace_on() { + let key = script.world_map_selection(); + let memo = memo.get(&script); + if memo.art_logged != Some(key) { + memo.art_logged = Some(key); + match script.eval::(MAP_ART_TRACE) { + Ok(line) => info!("world map art: {line}"), + Err(e) => warn!("world map art: trace failed: {e}"), + } + } + } + // First world enter: the zone updater `0x494780` (`crate::area`), finding the cached zone id // 0, selects the player's zone itself through `0x4a6650` and the setter `0x4a67a0` before any // Lua asks. Once per VM, each login being a first enter.