the flight map goes stock, and vendors refuse in 1.12's own words

- the pet stable and the flight map are the reference's own files: `TaxiRouteFrame` maps to a plain Frame (factory `0x495ba0` allocates the base size, the ctor adds no field, no new virtual, no method table — and `GetObjectType` is not overridden, so its own kind would have been the divergence), and the swap found three silent behaviours — the close button did nothing (an unknown `inherits=` is a WARNING, so it loaded as a bare button), the "no paths" line drew empty, and the title is READ off `UnitName("npc")`, not received; the WMO skybox rides the interior crossfade — one weight (`[0xce9bdc]`, the same number the MFOG lerp rides), a 4 s alpha fade, so a skybox the flood publishes through a doorway draws at weight 0 instead of popping in whole
- a widget predicate answers the NUMBER 1 or nil, never a boolean: eleven of them (`IsShown`, `IsVisible`, `IsMovable`, `GetChecked`…) move to the 1.12 shape — the reference proves it in its own source (`tostring(this:GetChecked())` saved and read back `== "1"`; `tostring(true)` would never match and buff timers would never appear), 21 corpus sites compare directly (`IsVisible() == nil`, `GetChecked() == 1`), and one of our own tests had the guild rank editor sending `true` where a real client sends 1
- the vendor's refusals are the reference's table: `SMSG_BUY_FAILED`/`SMSG_SELL_ITEM` route through `0x5dcd60`/`0x5dd1f0`'s switches, six lines were wrong (code 3 printed "That vendor can't be found." — vmangos's identifier — where 1.12 prints "That merchant doesn't like you."), silence is now expressible, and keying them gives the vendor a voice (`ERR_NOT_ENOUGH_MONEY` carries error-speech line 0x28); the bank's three ids were right and its "purchase failed (n)" an invention; the logout refusal is one key, `ERR_LOGOUT_FAILED`, for every non-zero reason; the two mail codes we said had no string have one (`ERR_PLAYER_WRONG_FACTION`, `ERR_RESTRICTED_ACCOUNT`) and a sent mail says `ERR_MAIL_SENT` in yellow; a test runs the chain's own `GlobalStrings.lua` and requires non-empty text for all 193 `ERR_*` the workspace names
- a clicked quest timer no longer raises — stock `QuestTimerFrame.lua` calls `QuestLog_SetSelection`/`QuestLog_Update`, which existed nowhere in this tree; our quest log owns the reference's two names now, which also shrinks the delta when that window itself migrates

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-02 17:05:15 +07:00
co-authored by Claude Fable 5.1
parent b14dd42cf1
commit b8e0025bc9
38 changed files with 905 additions and 1564 deletions
@@ -603,8 +603,12 @@ fn get_chat_window_info_shown_matches_the_shipped_frames() {
for i in 1..=7 {
let agrees: bool = s
.eval(&format!(
// Both sides are normalised to a boolean before comparing: `IsShown` answers the
// NUMBER 1 or nil (1830), so comparing it straight against `shown ~= nil` compares
// a number with a boolean and is false for every window. This test is about the two
// AGREEING, not about either one's shape.
"local _, _, _, _, _, _, shown = GetChatWindowInfo({i})\n\
return (shown ~= nil) == ChatFrame{i}:IsShown()"
return (shown ~= nil) == (ChatFrame{i}:IsShown() ~= nil)"
))
.unwrap();
assert!(
@@ -743,7 +743,9 @@ fn the_rank_editor_loads_its_flags_and_arms_accept_on_an_edit() {
s.run("GuildControlPopupFrameCheckbox5:Click()").unwrap();
assert_eq!(
calls(&s),
"GuildControlSetRankFlag:5:true",
// The Lua hands `this:GetChecked()` straight through, and in 1.12 that is the NUMBER 1
// (1830) — so 1 is what the reference's own call carries too.
"GuildControlSetRankFlag:5:1",
"the checkbox's own ID is what reaches the engine"
);
assert!(enabled(&s, "GuildControlPopupAcceptButton"));
@@ -1432,20 +1432,6 @@ mod tests {
update; our `inventory_alerts` snapshot is recomputed on every inventory push \
instead, so the recompute exists and only the Lua verb that forces one does not.",
),
(
"QuestTimerFrame.xml",
"QuestLog_SetSelection",
"a FrameXML function, declared in stock `QuestLogFrame.lua:308` — which we do not \
source, because our own `QuestLogFrame.xml` is that window. Clicking a quest \
TIMER raises (`QuestTimerFrame.lua:43`). It arrives with the quest log's own \
migration and not before.",
),
(
"QuestTimerFrame.xml",
"QuestLog_Update",
"the twin of the line above, `QuestLogFrame.lua:107`, called on the next line \
(`QuestTimerFrame.lua:44`).",
),
];
let toc = &super::super::addons::Addon::builtin().toc.files;
+39 -6
View File
@@ -19,10 +19,30 @@ fn taxi_script() -> UiScript {
load_xml(&s, "UiPanels.xml");
load_xml(&s, "GameTooltip.xml"); // TaxiNodeOnButtonEnter's tooltip + SetTooltipMoney
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); // BenillaErrorsFrame_AddMessage — DrawOneHopLines' refusal
load_xml(&s, "TaxiFrame.xml");
// Three the reference's own TaxiFrame leans on that our transcription did not:
// · GlobalStrings — `ERR_TAXINOPATHS` is a GlobalString, and `AddMessage(nil)` draws an
// empty line rather than raising, so its absence is silent.
// · UIPanelTemplates (.lua then .xml) — `TaxiCloseButton` inherits `UIPanelCloseButton`,
// which lives there and NOT in our UiPanels.xml. Without it the close button loads as a
// bare Button with no handler and a click does nothing at all.
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua");
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml");
load_xml(&s, "Interface\\FrameXML\\TaxiFrame.xml");
s
}
/// Seat the flight master as the `"npc"` unit. Stock `TaxiFrame_OnEvent` fills its label from
/// `UnitName("npc")` (TaxiFrame.lua:27), where our deleted transcription read the `TAXIMAP_OPENED`
/// argument — so a fixture that only fires the event leaves the reference's label blank.
fn seat_flight_master(s: &mut UiScript, name: &str) {
let npc = benilla_ui::script::UnitState {
name: Some(name.into()),
..Default::default()
};
s.set_unit("npc", Some(npc));
}
/// A two-node snapshot: Stormwind (Current) and the verified Sentinel Hill hop (Reachable, 110
/// copper, one route segment) — [`crate::ui_taxi::build_nodes`]'s exact real-data shape.
fn menu() -> TaxiUiState {
@@ -59,6 +79,7 @@ fn shipped_taxi_frame_drives_end_to_end() {
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
s.set_taxi(Some(menu()));
seat_flight_master(&mut s, "Dungar Longdrink");
s.fire_event(
"TAXIMAP_OPENED",
vec![ScriptValue::Str("Dungar Longdrink".into())],
@@ -67,15 +88,22 @@ fn shipped_taxi_frame_drives_end_to_end() {
assert!(s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
assert_eq!(
s.eval::<String>("return TaxiNameText:GetText()").unwrap(),
// The reference names the flight-master label `TaxiMerchant` and fills it from
// `UnitName("npc")` (TaxiFrame.lua:27) — NOT from the event's argument, which is what our
// transcription's `TaxiNameText` read. So the fixture seats the npc unit above.
s.eval::<String>("return TaxiMerchant:GetText()").unwrap(),
"Dungar Longdrink"
);
// The two pushed nodes show; the pool's remainder (slot 3 onward) stays hidden.
// The two pushed nodes show, and there is no third button AT ALL — the reference builds its
// node buttons on demand, one `CreateFrame("Button", "TaxiButton"..i, TaxiRouteMap,
// "TaxiButtonTemplate")` per node (TaxiFrame.lua:39), where our transcription pre-made a fixed
// pool and hid the tail. Same thing on screen, a different mechanism behind it — so the
// expectation moves from "hidden" to "absent".
assert!(s.eval::<bool>("return TaxiButton1:IsVisible()").unwrap());
assert!(s.eval::<bool>("return TaxiButton2:IsVisible()").unwrap());
assert!(!s.eval::<bool>("return TaxiButton3:IsVisible()").unwrap());
assert!(!s.eval::<bool>("return TaxiButton50:IsVisible()").unwrap());
assert!(s.eval::<bool>("return TaxiButton3 == nil").unwrap());
assert!(s.eval::<bool>("return TaxiButton50 == nil").unwrap());
// A click on node 2 (Sentinel Hill) drains through TakeTaxiNode.
s.resolve();
@@ -113,6 +141,7 @@ fn no_single_hop_destination_posts_the_error_and_closes() {
routes: vec![],
}],
}));
seat_flight_master(&mut s, "Dungar Longdrink");
s.fire_event(
"TAXIMAP_OPENED",
vec![ScriptValue::Str("Dungar Longdrink".into())],
@@ -142,13 +171,17 @@ fn no_single_hop_destination_posts_the_error_and_closes() {
fn close_button_queues_the_intent_and_hides() {
let mut s = taxi_script();
s.set_taxi(Some(menu()));
seat_flight_master(&mut s, "Dungar Longdrink");
s.fire_event(
"TAXIMAP_OPENED",
vec![ScriptValue::Str("Dungar Longdrink".into())],
);
assert!(s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());
s.run("BenillaTaxiCloseButton_OnClick()").unwrap();
// The reference's close button is a plain `UIPanelCloseButton` (TaxiFrame.xml:133) — it has no
// handler of its own, so the click goes through the template's, which hides the parent panel.
// Ours carried a named `BenillaTaxiCloseButton_OnClick`; that went with the file.
s.run("TaxiCloseButton:Click()").unwrap();
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
assert!(s.take_taxi_close());
assert!(!s.eval::<bool>("return TaxiFrame:IsVisible()").unwrap());