the map opens full screen again after an addon windowed it

- the world map opens full screen again on a character that once ran Cartographer's windowed map: a saved frame position or size outlives the addon only while the frame is movable or resizable (2193)
- the layout cache gates both the write and the apply on `userPlaced AND (movable|resizable)`, the reference's four sites, where benilla had only the setter's (2193)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-15 16:51:58 +07:00
co-authored by Claude Fable 5.1
parent 3e92b4f015
commit 1116efbb68
5 changed files with 296 additions and 19 deletions
@@ -987,6 +987,9 @@ fn a_repeating_error_is_one_row_with_a_count_not_a_flood() {
/// userPlaced bit (`SetUserPlaced` refuses a frame that is neither movable nor resizable).
/// Parentless, so its anchor is the screen root — the file's `-` target — which is what lets this
/// need no FrameXML and no install.
/// **Both flags, deliberately**: the layout cache's apply is gated per arm — position behind
/// `movable`, size behind `resizable` (decision 2193) — so a probe standing in for a window the
/// player both moved and resized has to carry both, or half its geometry is correctly left behind.
fn place_a_window(world: &mut World) {
world
.get_non_send_resource_mut::<benilla_ui::script::UiScript>()
@@ -995,7 +998,7 @@ fn place_a_window(world: &mut World) {
"local f = CreateFrame(\"Frame\", \"B353Probe\") \
f:SetWidth(413) f:SetHeight(147) \
f:SetPoint(\"BOTTOMLEFT\", 61, 29) \
f:SetMovable(true) f:SetUserPlaced(true)",
f:SetMovable(true) f:SetResizable(true) f:SetUserPlaced(true)",
)
.expect("place the probe window");
}
@@ -1099,6 +1102,10 @@ fn each_character_gets_its_own_layout_cache() {
/// A window as FrameXML would author it — the shape the restore has to overwrite. Same name as
/// [`place_a_window`]'s, different geometry, and **not** user-placed: this is the fresh tree a
/// relog meets.
///
/// It carries the same `movable`/`resizable` pair, because those are the window's **authored**
/// state — XML attributes on a real resizable window, rebuilt with it — and the layout cache's
/// apply reads them off the live frame to decide which arm runs (decision 2193).
fn author_a_window(world: &mut World) {
world
.get_non_send_resource_mut::<benilla_ui::script::UiScript>()
@@ -1107,7 +1114,7 @@ fn author_a_window(world: &mut World) {
"local f = CreateFrame(\"Frame\", \"B353Probe\") \
f:SetWidth(100) f:SetHeight(100) \
f:SetPoint(\"BOTTOMLEFT\", 0, 0) \
f:SetMovable(true)",
f:SetMovable(true) f:SetResizable(true)",
)
.expect("author the probe window");
}
@@ -814,3 +814,104 @@ fn the_real_feralas_catalog_names_dire_maul_under_the_cursor() {
"the stock OnUpdate puts the hovered sub-area's name in the label"
);
}
/// **A layout row an addon stamped does not outlive the addon** — director report: the world map
/// stopped opening full screen, "leaving the left and bottom open", on a character whose addons
/// were all disabled.
///
/// `Cartographer/Modules/LookNFeel.lua` windows the map on enable — `SetMovable(true)`,
/// `SetResizable(true)`, `SetWidth(1024)`, `SetHeight(768)`, `StartMoving(); StopMovingOrSizing()`,
/// then `SetPoint("CENTER", UIParent, "CENTER", db.x, db.y)` — and the drag entry stamps the
/// userPlaced bit on the way through (`0x7652b0` @`0x7652e5`, unconditionally). benilla's layout
/// cache was gated on that bit **alone**, so it persisted the row and re-seated it at every login
/// on every character, replacing the stock `setAllPoints="true"` forever; the blackout, which
/// `WorldMapFrame_OnLoad` sizes to the screen and anchors BOTTOMLEFT of the map, slid up-and-right
/// with it and left the world showing at the left and the bottom.
///
/// The reference gates both ends on `movable|resizable` as well (`0x490e97 test ah,0x3` at the
/// writer; `0x490600 test ah,0x1` / `0x490689 test ah,0x2` at the two arms of the apply), and
/// stock `WorldMapFrame` carries neither flag — so the stale row is inert the first session the
/// addon does not load, and falls out of the file at that logout. Decision 2193.
#[test]
fn a_stale_layout_row_cannot_seat_a_stock_frame() {
let _data = benilla_formats::wow_data_or_skip!();
let mut s = UiScript::new().unwrap();
s.set_screen_size(1600.0, 900.0);
s.set_unit(
"player",
Some(benilla_ui::script::UnitState {
exists: true,
name: Some("Probefour".into()),
level: 60,
..Default::default()
}),
);
let failures = super::load_default_ui(&s);
assert!(failures.is_empty(), "manifest load errors: {failures:#?}");
s.resolve();
// The row verbatim as an affected `benilla-config/layout/<realm>-<character>.txt` carries it:
// Cartographer's own saved `LookNFeel` profile offsets, and the size it forces.
let row = || benilla_ui::script::FrameLayout {
name: "WorldMapFrame".into(),
width: 1024.0,
height: 768.0,
points: vec![benilla_ui::script::LayoutPoint {
point: "CENTER".into(),
relative_to: Some("UIParent".into()),
relative_point: "CENTER".into(),
x: -0.6369222,
y: 53.861614,
}],
};
s.restore_user_placed_layouts([row()]);
s.resolve();
s.run("ShowUIPanel(WorldMapFrame)").unwrap();
s.resolve();
let (w, h, sw, sh) = s
.eval::<(f64, f64, f64, f64)>(
"return WorldMapFrame:GetWidth(), WorldMapFrame:GetHeight(), \
GetScreenWidth(), GetScreenHeight()",
)
.unwrap();
assert_eq!(
(w, h),
(sw, sh),
"the stale row seated nothing — the map is still the screen, as its setAllPoints authored it"
);
assert!(
!s.eval::<bool>("return WorldMapFrame:IsUserPlaced()")
.unwrap(),
"and the apply's userPlaced stamp lives inside the position arm it never took"
);
assert!(
s.user_placed_layouts().is_empty(),
"so the row falls out of the file at this logout instead of being written again"
);
// The positive control: the gate is the frame's own flags, not a special case for this frame.
// With the addon's `SetMovable`/`SetResizable` in place the same row seats exactly as before.
s.run("WorldMapFrame:SetMovable(true) WorldMapFrame:SetResizable(true)")
.unwrap();
s.restore_user_placed_layouts([row()]);
s.resolve();
let (w, h) = s
.eval::<(f64, f64)>("return WorldMapFrame:GetWidth(), WorldMapFrame:GetHeight()")
.unwrap();
assert_eq!(
(w, h),
(1024.0, 768.0),
"a movable+resizable frame takes it"
);
assert!(
s.eval::<bool>("return WorldMapFrame:IsUserPlaced()")
.unwrap(),
"and the position arm stamps the bit it was written under"
);
let saved = s.user_placed_layouts();
assert_eq!(saved.len(), 1, "which is what puts the row back: {saved:?}");
assert_eq!(saved[0], row(), "unchanged through the round trip");
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}