Files
benilla-octo/crates/benilla-app/src/addon_harness/probe.rs
T
Sam SungandClaude Opus 5.5 f98c1a5396 Comments in benilla-app, the glue screens, harness, settings and the rest: one line of fact each, no history, no record numbers (#365)
Part of #41: the last of five `benilla-app` pieces. This one is everything the others did not cover: the addon harness, the settings registry, the glue screens (login, realm list, character select and create), key bindings, the minimap, nameplates, the loading screen, transports, the performance instruments, the smaller modules, the integration tests, and the UI asset files (`OptionsFrame.xml`, `benilla.toc` and the rest). Their comments are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 27,351 comment lines become 11,010.

Fixes #33. Fixes #103.

- **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal.
- **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them:
  - The weapon trail's emissive is 0 (`0x70ca30`): the trail is a type-5 render record, so its ambient-only light term is exact.
  - The camera-style dropdown writes "1", "2", "0" (`UIOptionsFrame.lua:525,536,547`); several register-site addresses in the settings table are corrected.
  - The graphics settings are held until `RestartGx`, as in 1.12; `Fonts.xml`'s early load no longer carries a false reason.
  - vmangos sends a player-name invalidation after a rename (`CharacterHandler.cpp:839`), and the cinematic camera's announce packet has an empty handler there (`MiscHandler.cpp:919`).
  - Stock FrameXML and GlueXML line citations are checked against the stock 1.12 files, and vmangos line citations against the pinned revision.
- **Deviation labels.** "Deviation:" now marks only a choice with its recorded reason. Declining to copy a reference defect is stated plainly, and so is an unbuilt behaviour or an untraced one.
- **Verification.** Every file strips to identical code, apart from test literals in `login/`, `death/` and `ui_script/death_tests.rs` that now use placeholder account and character names, and `cargo fmt --check` is clean. Six second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the stock files, vmangos and the reference.

A second commit corrects one comment in `benilla-ui` (`unit_is_grey`: the reference's exclusion is players only). A third lowers `ui_script_wall`'s ceiling from 45 to 40: five gameplay files named `UiScript` only in comments the rewrite removed, and the ratchet asks for the number to follow.

Behaviour differences the rewrite surfaced are filed as their own issues (#339 to #353 among them). String literals are untouched; they are #92.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 00:27:56 +07:00

137 lines
5.4 KiB
Rust

//! The read-back probe: load one addon as the survey does, drive the session start, then run
//! the caller's Lua, cursor moves and ticks against the VM left standing.
//!
//! The registry and installed set are built from the whole folder, as the survey builds them:
//! against a registry of one, AceAddon and AceLibrary find no dependencies. It reads the state
//! after `PLAYER_ENTERING_WORLD` and the ticks, before the render and use probes touch the VM.
//! An eval can mutate the VM, so a probe is a debugger; the survey is the measurement.
use std::collections::BTreeSet;
use std::path::Path;
use benilla_ui::script::UiScript;
use benilla_ui::toc::Toc;
/// One addon, loaded and asked.
#[derive(Debug, Clone, Default)]
pub struct ProbeOutcome {
pub name: String,
/// Load-time failures, verbatim, as [`super::AddonReport::errors`] carries them.
pub load_errors: Vec<String>,
/// What its handlers raised while the session start was driven.
pub session_errors: Vec<String>,
/// `(chunk, answer)` per step in order; an answer is `= <value>` or `ERROR: <message>`, and a
/// raise does not stop the steps after it.
pub answers: Vec<(String, String)>,
}
/// Wrap a chunk in `pcall` + `tostring` so a raise comes back as a value; only the first result
/// is returned, so the wrapper needs nothing beyond Lua 5.0.
fn wrapped(chunk: &str) -> String {
format!(
"local __ok, __v = pcall(function() {chunk} end)\n\
if __ok then return \"= \" .. tostring(__v) else return \"ERROR: \" .. tostring(__v) end"
)
}
/// One step of a probe run. Much addon state is hover-driven, so moves interleave with reads.
#[derive(Debug, Clone)]
pub enum Step {
/// Lua to evaluate against the VM as it stands.
Eval(String),
/// Move the cursor to `(x, y)` in UI units (y-up from the bottom-left), firing the real
/// `OnLeave`/`OnEnter` pair as [`UiScript::mouse_move`] does for the app.
Mouse(f32, f32),
/// Advance one `OnUpdate` frame of `secs`; the answer is what the tick raised, or `no errors`.
Tick(f32),
}
/// Load `name` out of `root` the way the survey does, drive the session start, then run each
/// [`Step`] against the VM that is left; `None` when the folder has no manifest.
pub fn probe(root: &Path, name: &str, steps: &[Step]) -> Option<ProbeOutcome> {
let toc_path = super::manifest_path(root, name)?;
// Decoded: a cp1252 manifest read as UTF-8 parses as an empty toc and a false clean pass.
let toc = Toc::parse(&benilla_ui::source::decode(
&std::fs::read(&toc_path).unwrap_or_default(),
));
let (_, installed, registry) = super::corpus(root);
let mut script = match UiScript::new() {
Ok(s) => s,
Err(e) => {
return Some(ProbeOutcome {
name: name.to_string(),
load_errors: vec![format!("VM: {e}")],
..Default::default()
})
}
};
script.set_instruction_budget(super::ADDON_INSTRUCTION_BUDGET);
script.set_screen_size(1024.0, 768.0);
// No saved-variable roots, so a probe never touches a player's saved variables; the AddOns
// root is passed as the survey passes it, so `LoadAddOn` resolves.
script.register_addons(registry, Some(root.to_path_buf()), None, None);
super::seat_a_session(&mut script);
let _ = crate::ui_script::load_default_ui(&script);
let mut dep_order: Vec<super::LoadedDep> = Vec::new();
super::load_dependencies(
&mut script,
root,
&toc,
&installed,
&mut BTreeSet::new(),
&mut dep_order,
);
let load_errors = super::load_addon_files(&script, root, name, &toc).errors;
// The registry must agree with the VM, or `IsAddOnLoaded` answers for another session; the
// dependency chain stamped itself as it loaded.
script.mark_addon_loaded(name);
let session_errors = super::drive_session_start(&mut script, name, &installed);
let answers = steps
.iter()
.map(|step| match step {
Step::Eval(chunk) => {
let answer = script
.eval::<String>(&wrapped(chunk))
// Only a syntax error in the chunk reaches here; `pcall` caught any raise.
.unwrap_or_else(|e| format!("SYNTAX: {e}"));
(chunk.clone(), answer)
}
Step::Tick(secs) => {
let before = script.errors().len();
script.tick(*secs);
let raised: Vec<String> = script.errors().into_iter().skip(before).collect();
(
format!("--tick {secs}"),
if raised.is_empty() {
"= no errors".to_string()
} else {
format!("= RAISED: {}", raised.join(" | "))
},
)
}
Step::Mouse(x, y) => {
// Resolve first: the hit-test reads resolved rects, which a new or moved frame
// lacks until then.
script.resolve();
script.mouse_move(*x, *y);
let focus = script
.hit_test_name(*x, *y)
.unwrap_or_else(|| "<nothing>".into());
(format!("--mouse {x},{y}"), format!("= over {focus}"))
}
})
.collect();
Some(ProbeOutcome {
name: name.to_string(),
load_errors,
session_errors,
answers,
})
}