a cinematic holds your body, and all seven chat windows exist

- a cinematic holds the body while the camera flies: the streaming focus follows the eye — a Tauren's intro opens 1741 yards from the body — so the body's own ground unloaded under it and it fell (z=59.4 to −62 in four seconds, the server yanking it back); it now takes the same hold a teleport takes, released when the focus comes home to a resident tile, and the loading screen stops covering the fly-by, because "the player's ground is missing" is not a load while the eye is deliberately elsewhere; the letterbox measurement prints as an ordinary info line, and scripts/cine.sh plays any one cinematic and prints what the client did to the world while the camera was away
- a cinematic cuts the zone music: the reference asserts exactly what /console EnableMusic 0 asserts, through the same setter — a cut, not a fade, the music pump dead for the duration, resume at +3.000 s — and benilla asserts the runtime flag, never the CVar, or quitting during a 102-second intro would save your music off for good; ambience is deliberately untouched (it has the analogous flag and the cinematic never writes it); the narration has no category at all — its flags route it around every volume slider but not around the Enable Sound Effects gate, so unchecking that silences it — and the listener follows the cinematic camera for the duration
- addons can walk a UI they did not build: GetChildren/GetNumChildren/GetRegions/GetNumRegions land — the basis of the whole reskin genre, and the wall four of the twenty most-installed 1.12 addons died on: pfUI drew nothing before and now paints 495 quads, ShaguDPS 71 → 566, pfQuest 3 → 498 — with the reference's own quirks: the title region is NOT in the list (its creation path never reaches the region linker), hidden regions are returned and counted, oldest first; the Model widget gets its Lua surface (SetModel, SetSequence and siblings, read out of the shipped image, where SetRotation and SetFacing are two names for one slot); and the Minimap carries the nine engine-created Model children no XML declares, built before the frame descent, the ninth being the player arrow Questie indexes as ({Minimap:GetChildren()})[9]
- all seven chat windows come from one template, tabs and scroll buttons included — whether a window is shown has nothing to do with whether its globals resolve, and pfUI reads every piece of that furniture for all seven, unguarded; isDocked only on windows 1 and 2 (the chat-cache's own DOCKED byte), SELECTED_CHAT_FRAME seated beside DEFAULT_CHAT_FRAME (the window you last picked vs the one FrameXML prints to), and the four per-window fields ChatFrame_OnLoad seats; two more one-name walls fell — a Button's label answers to its second spelling NormalText, which KLHThreatMeter's column headers die without, and ABANDON_QUEST_WITH_ITEMS is declared because a dialog table is read at load whatever the client later shows, leaving Questie with zero errors of ours — and the addon harness gains --probe/--eval, one addon's VM answering a question in ~5 s instead of a 90 s corpus run

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-31 19:50:08 +07:00
co-authored by Claude Fable 5
parent 4ad8718b2a
commit f3ecb5c968
30 changed files with 2360 additions and 261 deletions
@@ -198,6 +198,101 @@ pub(super) fn install(lua: &Lua, m: &Table) -> mlua::Result<()> {
}
})?,
)?;
// ── The four structure queries: GetChildren / GetNumChildren / GetRegions / GetNumRegions ──
//
// Registered bindings in 1.12 (`GetNumRegions 0x773e60`, `GetRegions 0x773f60`,
// `GetNumChildren 0x774080`, `GetChildren 0x774180` — wow-re `ui/ledger.tsv`, classified
// ORCHESTRATION: each marshals and delegates, with no fidelity math of its own).
//
// **Why these four are worth more than their size.** They are how an addon walks a frame it did
// not build, which is the whole basis of the "reskin the default UI" genre — and that genre is
// the top of the 1.12 popularity list. `pfUI.api.StripTextures` is the canonical shape:
//
// for _, v in ipairs({ frame:GetRegions() }) do
// if v.SetTexture then ... v:SetTexture(nil) end
// end
//
// With `GetRegions` absent that is `attempt to call method 'GetRegions' (a nil value)`, raised
// out of a library file every one of pfUI, pfQuest, pfQuest-turtle and ShaguDPS embeds — four
// separate top-20 addons dying on one missing verb, plus Questie on `GetChildren`.
//
// **Two lists, one walk each, in the arena's own order.** `Frame::children` is insertion order,
// which our own arena documents as the client's `+0x300` child-list order; `Frame::regions` is
// creation order. Neither is re-sorted here — a structure query reports the structure, and the
// draw order is `crate::order`'s separate concern.
//
// **A detached region is not in the list.** `Region:SetParent(nil)` sets `Region::detached`
// rather than removing the entry, because the owner's `Vec` is what `WidgetArena::destroy`
// frees from — but the client's re-link virtual `0x77fd10` with a null parent genuinely
// **unlinks from the old parent's draw layer and region list** (wow-re
// `widget-api-batch-benilla.md` Q7, VERIFIED), so a detached region must be invisible to both
// region verbs. Our flag is a representation choice; the list these report is the client's.
//
// **The count and the list come from ONE function** ([`regions_of`] / [`children_of`]), never
// two walks with the same filter written twice. `GetNumRegions` disagreeing with
// `#{GetRegions()}` is a bug an addon would hit as an off-by-one deep inside a loop it did not
// write, and the only way to make it impossible is to have one of them BE the other.
//
// **Settled by a wow-re §5 quartet** (`ui/scratch/widget-list-bindings.md`), which confirmed two
// of these choices and corrected a third:
//
// · **The order is link order, oldest first, and no reversal.** `CSimpleFrame` embeds a punned
// `TSList` header whose ctor sets `tail = &header`, `head = (&header)|1`, and BOTH linkers
// append at the TAIL (`0x76a750` for regions, `0x76aa20` for children — one caller each).
// Our insertion-order `Vec` is the same order. (wow-re's own `draw-order-law.md` said
// "tail→head"; that was a direction mislabel and was corrected in the same round.)
// · **Hidden children and regions are returned and counted** — a VERIFIED negative: the
// counting loops never load the visibility word at all.
// · **The TITLE REGION IS NOT RETURNED**, and that one corrected us. Both creation paths
// (`0x773910`, `0x769b79`) dispatch a vtable slot that is a bare `[this+0x9c] = parent` and
// never reach the linker `0x77fd10`, so a title region is not in the list this walks —
// corroborated by `Hide`/`Show` carrying an explicit extra `[frame+0xa8]` case *because* the
// list walk misses it. Ours is in `Frame::regions` (so the arena can free it), so
// [`regions_of`] filters it out.
// · **A Button's label (`+0x338`) and its four state textures ARE in the list** — they link
// through `0x77fd10` like any region, which is what ours do too.
// · **The return shape**: N separate values, never a table; a frame with none returns ZERO
// values (no `lua_newtable` in either body), and `GetNum*` returns one number — `0` when
// empty, never nil.
//
// The one thing still out of reach is not these bindings': Questie reads
// `({Minimap:GetChildren()})[9]` for the player arrow, and the §5 found why that index works —
// `CMinimap`'s ctor `0x4edbc0` builds NINE engine-owned `Model` children before the XML
// `<Frames>` descent, the ninth (`[Minimap+0x338]`) being the arrow `SetPlayerFacing 0x4eb8e0`
// writes. We create none of them, so index 9 is nil here until the minimap grows its engine
// children.
m.set(
"GetChildren",
lua.create_function(|lua, this: Table| {
let ids = children_of(lua, &this)?;
let mut out = Vec::with_capacity(ids.len());
for id in ids {
out.push(Value::Table(frame_wrapper(lua, id)?));
}
Ok(mlua::MultiValue::from_vec(out))
})?,
)?;
m.set(
"GetNumChildren",
lua.create_function(|lua, this: Table| Ok(children_of(lua, &this)?.len()))?,
)?;
m.set(
"GetRegions",
lua.create_function(|lua, this: Table| {
let ids = regions_of(lua, &this)?;
let mut out = Vec::with_capacity(ids.len());
for id in ids {
out.push(Value::Table(crate::script::region::region_wrapper(
lua, id,
)?));
}
Ok(mlua::MultiValue::from_vec(out))
})?,
)?;
m.set(
"GetNumRegions",
lua.create_function(|lua, this: Table| Ok(regions_of(lua, &this)?.len()))?,
)?;
// SetParent — the runtime reparent, per the byte-verified law (wow-re
// `ui/scratch/setparent-runtime-strata-level.md`; decision 1323). The binding half
// (`0x7a1550`): the parent argument is a frame table, a NAME string (`0x76c760`), or an
@@ -763,3 +858,52 @@ fn set_shown(lua: &Lua, this: &Table, shown: bool) -> mlua::Result<()> {
event::fire_visibility_changes(lua, changed);
Ok(())
}
/// This frame's child frames as stable ids, in the arena's insertion order — the one walk
/// `GetChildren` and `GetNumChildren` share (see their comment on why they must share it).
fn children_of(lua: &Lua, this: &Table) -> mlua::Result<Vec<u32>> {
let h = frame_handle_of(lua, this)?;
let mut model = lua.app_data_mut::<Model>().expect("model");
let Some(children) = model.arena.frame(h).map(|f| f.children.clone()) else {
return Ok(Vec::new());
};
Ok(children.into_iter().map(|c| model.frame_id(c)).collect())
}
/// This frame's live regions as stable ids, in creation order — **the title region and detached
/// ones excluded** — the one walk `GetRegions` and `GetNumRegions` share.
///
/// Both exclusions are the client's list, not tidiness, and both are VERIFIED (wow-re
/// `ui/scratch/widget-list-bindings.md`, §5 quartet):
///
/// - **Detached.** `SetParent(nil)` reaches `0x77fd55` → the removal virtual `0x76a7f0`, which
/// unlinks the node and frees it, and then skips every re-link. We keep the entry only so the
/// arena can still free the slot; reporting it would hand an addon an object the reference never
/// would, and `StripTextures`-shaped code would "strip" a region already off the screen.
/// - **The title region.** Its two creation paths (`0x773910`, `0x769b79`) dispatch a vtable slot
/// that is a bare `[this+0x9c] = parent` and never reach the linker at all, so it was never in
/// this list — corroborated by `Hide`/`Show` needing an explicit extra `[frame+0xa8]` case
/// *because* the walk misses it. Ours is in [`crate::widget::Frame::regions`] so that
/// `WidgetArena::destroy` still frees it, which makes that a representation detail this walk
/// must not leak.
///
/// A Button's label and its four state textures are deliberately NOT excluded: they link through
/// `0x77fd10` like any other region in the reference, and ours go through `create_region` like any
/// other region here.
fn regions_of(lua: &Lua, this: &Table) -> mlua::Result<Vec<u32>> {
let h = frame_handle_of(lua, this)?;
let mut model = lua.app_data_mut::<Model>().expect("model");
let Some((regions, title)) = model
.arena
.frame(h)
.map(|f| (f.regions.clone(), f.title_region))
else {
return Ok(Vec::new());
};
let live: Vec<_> = regions
.into_iter()
.filter(|r| Some(*r) != title)
.filter(|r| model.arena.region(*r).is_some_and(|reg| !reg.detached))
.collect();
Ok(live.into_iter().map(|r| model.region_id(r)).collect())
}