the minimap and world map go stock, and the chat loses its white plate
- the minimap cluster and the battleground list are the reference's own `Minimap.xml` and `BattlefieldFrame.xml`, ours deleted, and the sixteen engine names the static probe found unbound are built, not stubbed: the battleground list and queue family (the as-group join refused client-side with no packet when the map's `MaxPlayers` is below the group), the bodyless `CMSG_BATTLEFIELD_STATUS` on world enter — benilla never sent it, so a queue joined before a reconnect was invisible — the 5.5556 yd `BATTLEFIELDS_CLOSED` leash, and the meeting-stone pair over the `0x295` five-way table with both asymmetries; the ping's lifetime is the stock Lua's now (held 5 s, faded 0.5 s), `GetPingPosition` answers two numbers always — our nil was a Lua error on every frame of the stock `Minimap_OnUpdate` — and the map-change drop the reference never had is gone (1974)
- the world map is the reference's own `WorldMapFrame.xml`, our transcription deleted; built for it: the arrow-frame family (an anonymous `Model` child at its 33.6 px footprint, drawn app-side as the minimap's own arrow sprite through one shared constant), the battleground position family over `MSG_BATTLEGROUND_PLAYER_POSITIONS` both ways (the reference's 5000 ms request throttle), and `GetPlayerMapPosition` resolving `raid1..raid40` where it knew only the party (B320); `SetupFullscreenScale` — the engine binding `0x48c270` the map's and the cinematic frame's OnShow call, which our map had stood in for with Lua — is bound to the carved law `min(0.75 · aspect, 1)` off the configured aspect, written through the frame's own `SetScale` (1.0 at 4:3 and wider, 0.9375 at 5:4); a hovered POI writes the map's own area label, not the tooltip; named gap: `WorldMapPing` draws nothing yet (1980, 1981)
- the tutorial frame is the reference's own `TutorialFrame.xml`, over a tutorial system benilla never had: two bit banks filled from `SMSG_TUTORIAL_FLAGS` — the fire-once bank `TriggerTutorial` reads and sets (never a send) and the acknowledged bank `FlagTutorial` writes and sends; there is no tutorial CVar in the binary (210 `CVar::Register` sites censused), so "disable tutorials" is every bit acknowledged; the sites this app can produce are wired (the world-enter Welcome cascade, the level-up arm, item pushes, the loot sparkle, the meeting-stone and battleground arms), the rest named; the loader learned `<TitleRegion>` — the stock frame's drag handle, a region that emits no quad; and the `TabardModel` widget kind arrives ahead of its window, five ints bounded by the `.rdata` table `{170, 17, 6, 17, 51}` and the constant 100 000 tabard cost (1976, 1977)
- the chat window drew as an opaque white plate: the chat-cache loader, gated on `InWorld` alone, restored into the boot VM 130 ms before the deferred entry load filled it, and its per-VM memo then blocked the restore that would have reached the real frames — the plate the stock Lua tints black stayed the XML's white; between the entry edge and the load there is now no VM in the world (the edge parks the boot VM, the load takes it back), so no in-world feed pushes into a VM with no interface; the perf pill reads `59 fps 7.0 ms 17.3 cpu` — the MAIN thread's milliseconds in full text and the process-wide sum dim, because the sum of every thread's CPU time had been read as a frame time (a raid read 17 on it at a solid 60 with the main thread at 7); and `WOW_PROBE_UI_ONE_TEX=1` is a pricing lever, never a look, for the draw-count ceiling a UI atlas would reach before one is built (1978, 1954)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
//! The tutorial system's four Lua verbs (decision 1976; wow-re
|
||||
//! `system/ui/scratch/tutorial-flags.md` §6): the registrar table at `.data 0x846b04` —
|
||||
//! `TutorialsEnabled`, `FlagTutorial`, `ClearTutorials`, `ResetTutorials`.
|
||||
//!
|
||||
//! The banks are the app's (they are wire state, and the fire-once trigger law lives beside the
|
||||
//! sites that trigger): the app pushes the **acknowledged** bank's bytes here when they change,
|
||||
//! which is all `TutorialsEnabled` reads, and drains the three writes.
|
||||
//!
|
||||
//! | verb | shape |
|
||||
//! |---|---|
|
||||
//! | `TutorialsEnabled()` | one value on both legs: the number `1` if ANY byte of the acknowledged bank is not `0xFF`, else `nil` — over the whole bank, not the 50 real ids; `nil` with no bank |
|
||||
//! | `FlagTutorial(n)` | `n` must pass `lua_isnumber` else `Usage: FlagTutorial("tutorial")` (the quoted usage is the reference's own, and misleading — a number is required); truncated, `n − 1` must lie in `0..50` else a SILENT no-op; zero values |
|
||||
//! | `ClearTutorials()` | zero values; every bit set in both banks and `CMSG_TUTORIAL_CLEAR` |
|
||||
//! | `ResetTutorials()` | zero values; every bit cleared in both banks and `CMSG_TUTORIAL_RESET` |
|
||||
|
||||
use mlua::{Lua, Value};
|
||||
|
||||
use super::binding_abi::{number_arg, predicate};
|
||||
use super::Model;
|
||||
|
||||
/// `FlagTutorial`'s clamp: `0 ≤ n − 1 < 0x32` — the fifty ids `GlobalStrings.lua` names.
|
||||
const TUTORIAL_IDS: i32 = 0x32;
|
||||
|
||||
impl super::UiScript {
|
||||
/// The acknowledged bank's bytes (`None` before `SMSG_TUTORIAL_FLAGS` lands) — what
|
||||
/// `TutorialsEnabled()` scans.
|
||||
pub fn set_tutorial_bank(&mut self, bank: Option<Vec<u8>>) {
|
||||
self.model_mut().tutorial_bank = bank;
|
||||
}
|
||||
|
||||
/// `FlagTutorial(n)` calls since the last drain: the **0-based** ids (`n − 1`), in range.
|
||||
pub fn take_tutorial_flag_requests(&mut self) -> Vec<u32> {
|
||||
std::mem::take(&mut self.model_mut().tutorial_flag_requests)
|
||||
}
|
||||
|
||||
/// `ClearTutorials()` calls since the last drain.
|
||||
pub fn take_tutorial_clears(&mut self) -> u32 {
|
||||
std::mem::take(&mut self.model_mut().tutorial_clears)
|
||||
}
|
||||
|
||||
/// `ResetTutorials()` calls since the last drain.
|
||||
pub fn take_tutorial_resets(&mut self) -> u32 {
|
||||
std::mem::take(&mut self.model_mut().tutorial_resets)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
let g = lua.globals();
|
||||
|
||||
// `TutorialsEnabled()` (`0x4b5960`): scans `[0xb711ec] * 4` bytes of bank B for one ≠ 0xFF;
|
||||
// `1.0` or nil, one value; an unallocated bank skips the loop and answers nil.
|
||||
g.set(
|
||||
"TutorialsEnabled",
|
||||
lua.create_function(|lua, ()| {
|
||||
let model = lua.app_data_ref::<Model>().expect("model app_data");
|
||||
let enabled = model
|
||||
.tutorial_bank
|
||||
.as_ref()
|
||||
.is_some_and(|b| b.iter().any(|&byte| byte != 0xFF));
|
||||
Ok(predicate(enabled))
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// `FlagTutorial(n)` (`0x4b59b0`): `lua_isnumber` else the Usage raise; `__ftol`; `dec`; `js`
|
||||
// and `cmp 0x32 / jge` bail silently; else the acknowledge-and-send setter with the 0-based id.
|
||||
g.set(
|
||||
"FlagTutorial",
|
||||
lua.create_function(|lua, n: Value| {
|
||||
let n = number_arg(lua, n, "Usage: FlagTutorial(\"tutorial\")")?;
|
||||
let id = n.wrapping_sub(1);
|
||||
if !(0..TUTORIAL_IDS).contains(&id) {
|
||||
return Ok(());
|
||||
}
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
model.tutorial_flag_requests.push(id as u32);
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
g.set(
|
||||
"ClearTutorials",
|
||||
lua.create_function(|lua, ()| {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
model.tutorial_clears += 1;
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
g.set(
|
||||
"ResetTutorials",
|
||||
lua.create_function(|lua, ()| {
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
model.tutorial_resets += 1;
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::script::UiScript;
|
||||
|
||||
/// `1` or nil, one value, over the WHOLE bank — and nil with none.
|
||||
#[test]
|
||||
fn tutorials_enabled_scans_the_whole_acknowledged_bank() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return select('#', TutorialsEnabled())")
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert!(
|
||||
s.eval::<bool>("return TutorialsEnabled() == nil").unwrap(),
|
||||
"no bank"
|
||||
);
|
||||
s.set_tutorial_bank(Some(vec![0xFF; 32]));
|
||||
assert!(
|
||||
s.eval::<bool>("return TutorialsEnabled() == nil").unwrap(),
|
||||
"every bit acknowledged: disabled"
|
||||
);
|
||||
let mut bank = vec![0xFF; 32];
|
||||
bank[31] = 0x7F; // a bit past the fifty real ids
|
||||
s.set_tutorial_bank(Some(bank));
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return TutorialsEnabled()").unwrap(),
|
||||
1,
|
||||
"the scan covers the whole bank, not the 50 ids"
|
||||
);
|
||||
s.set_tutorial_bank(Some(vec![0; 32]));
|
||||
assert_eq!(s.eval::<i64>("return TutorialsEnabled()").unwrap(), 1);
|
||||
}
|
||||
|
||||
/// `FlagTutorial`: the number gate's raise, `n − 1`, the silent out-of-range legs, truncation.
|
||||
#[test]
|
||||
fn flag_tutorial_takes_a_one_based_number_and_bails_silently_off_the_fifty() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.run("FlagTutorial(1) FlagTutorial(50) FlagTutorial('42') FlagTutorial(7.9) FlagTutorial(0) FlagTutorial(51) FlagTutorial(-3)")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.take_tutorial_flag_requests(),
|
||||
vec![0, 49, 41, 6],
|
||||
"0-based, truncated toward zero, 0/51/−3 dropped"
|
||||
);
|
||||
assert!(s.take_tutorial_flag_requests().is_empty(), "drained");
|
||||
for call in ["FlagTutorial()", "FlagTutorial(nil)", "FlagTutorial({})"] {
|
||||
let err = s.run(call).unwrap_err().to_string();
|
||||
assert!(
|
||||
err.contains("Usage: FlagTutorial(\"tutorial\")"),
|
||||
"{call}: {err}"
|
||||
);
|
||||
}
|
||||
s.run("ClearTutorials() ClearTutorials() ResetTutorials()")
|
||||
.unwrap();
|
||||
assert_eq!(s.take_tutorial_clears(), 2);
|
||||
assert_eq!(s.take_tutorial_resets(), 1);
|
||||
assert_eq!(s.take_tutorial_clears(), 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user