diff --git a/crates/benilla-app/src/addon_harness/tick_bench_tests.rs b/crates/benilla-app/src/addon_harness/tick_bench_tests.rs index 7d27e9e7..a4d1286d 100644 --- a/crates/benilla-app/src/addon_harness/tick_bench_tests.rs +++ b/crates/benilla-app/src/addon_harness/tick_bench_tests.rs @@ -24,6 +24,22 @@ use super::{ }; /// Plates a crowded pull shows; the nameplate CVar cap and `pfValidUnits`' own range. +/// `CleveRoids.GetSlamWindowPercent()` for grom's own numbers, as Lua prints it. +/// +/// `(mainhand - slamCast) / mainhand * 100` = `(2.70 - 2.50) / 2.70 * 100`. **Both numbers are +/// the realm's, not vanilla's.** Slam on OctoWoW is a 2.5 s cast — `Spell.dbc` id 1464 (and 8820, +/// 11604, 11605) carries `CastingTimeIndex` 20, and `SpellCastTimes.dbc` row 20 is 2500 ms, where +/// vanilla's Slam is 1.5 s. SuperCleveRoidMacros' own `DEFAULT_SLAM_CAST = 2.5 -- Turtle WoW` +/// agrees independently. +/// +/// So the no-clip window on a 2.70 s weapon is **0.2 s wide, 7.4% of the swing**, and a player +/// pressing the macro by hand will almost never land in it. That is the macro's arithmetic, not +/// a fault of this client — and it is worth a constant with this comment on it, because an +/// earlier revision of these tests invented a `1.5 sec cast` fixture, measured a 44% window off +/// it, and concluded the chain was healthy while the player's own sessions cast Heroic Strike 477 +/// times and Slam never once. +const WINDOW_PCT: &str = "7.4074074074074"; + const PLATES: u32 = 40; /// Frames per timed window. @@ -1587,7 +1603,7 @@ fn the_whole_slam_macro_line_picks_slam_early_and_heroic_strike_late() { ); assert_eq!( ask(&mut s, "CleveRoids.GetSlamCastTime()"), - "1.5", + "2.5", "the cast time came off the scanning tooltip, not the addon's 2.5 s fallback — against a \ 2.70 s swing the fallback leaves a 7% window instead of 44%, which in play reads as \ `[noslamclip]` never being true at all" @@ -1599,14 +1615,15 @@ fn the_whole_slam_macro_line_picks_slam_early_and_heroic_strike_late() { // `VANILLA_SWING_MS` stand-in — and `st_timer` is still 0, so the whole swing reads as // elapsed: // - // window = (2.0 - 1.5) / 2.0 = 25%, elapsed = 100% -> `[noslamclip]` false -> Heroic Strike. + // maxSlamStart = 2.0 - 2.50 <= 0 -> window 0%, elapsed 100% -> `[noslamclip]` false -> + // Heroic Strike. // // That is correct of the numbers it has and wrong about the weapon, and it heals on the first // auto-attack that lands (below). It is also why the latch is worth a log line // (`char_stats::with_unit_stats`): if the sheet never arrives, this *is* the session. assert_eq!(ask(&mut s, "st_timerMax"), "2"); assert_eq!(ask(&mut s, "CleveRoids.GetSwingPercentElapsed()"), "100"); - assert_eq!(ask(&mut s, "CleveRoids.GetSlamWindowPercent()"), "25"); + assert_eq!(ask(&mut s, "CleveRoids.GetSlamWindowPercent()"), "0"); assert_eq!( ask(&mut s, "CleveRoids.ValidateNoSlamClip()"), "false", @@ -1635,8 +1652,8 @@ fn the_whole_slam_macro_line_picks_slam_early_and_heroic_strike_late() { assert_eq!(ask(&mut s, "CleveRoids.GetSwingPercentElapsed()"), "0"); assert_eq!( ask(&mut s, "CleveRoids.GetSlamWindowPercent()"), - "44.444444444444", - "(2.70 - 1.50) / 2.70, off the real weapon this time" + WINDOW_PCT, + "(2.70 - 2.50) / 2.70, off the real weapon this time — 0.2 s of a 2.7 s swing" ); let _ = s.take_spell_casts(); s.fire_event( @@ -1650,12 +1667,12 @@ fn the_whole_slam_macro_line_picks_slam_early_and_heroic_strike_late() { `GetSwingPercentElapsed` and `GetSlamWindowPercent` above before anything else" ); - // 1.6 s later the swing is 59% elapsed, past the window: the second clause takes it. + // 1.6 s later the swing is 59% elapsed, far past the window: the second clause takes it. s.tick(1.6); assert_eq!( ask(&mut s, "CleveRoids.ValidateNoSlamClip()"), "false", - "59% elapsed is past the 44% window, so casting Slam now would clip the auto-attack" + "59% elapsed is past the 7.4% window, so casting Slam now would clip the auto-attack" ); let _ = s.take_spell_casts(); s.fire_event( @@ -1745,7 +1762,7 @@ fn seat_a_warrior_mid_fight(s: &mut UiScript, offhand: bool) { ..Default::default() })); for (id, name, cast) in [ - (1464u32, "Slam", Some("1.5 sec cast".to_string())), + (1464u32, "Slam", Some("2.5 sec cast".to_string())), (78, "Heroic Strike", None), (23922, "Shield Slam", None), ] { @@ -1823,14 +1840,12 @@ fn seat_a_warrior_mid_fight(s: &mut UiScript, offhand: bool) { /// * The spellbook walk `GetSpellName(i, BOOKTYPE_SPELL)` finding Slam. /// * `GameTooltip:SetSpell` on a **nil-parent scanning tooltip built from /// `GameTooltipTemplate`**, whose `TextLeft` the addon reads with `getglobal` looking -/// for `"%d+%.?%d* sec cast"`. This is the one that decides the size of the window: without it -/// the addon falls back to a 2.5 s constant, which against a 2.70 s swing leaves a 7% window -/// instead of 44% and would read in play as "Slam never fires". +/// for `"%d+%.?%d* sec cast"`. This is the one that decides the size of the window. /// * The `` delta reaching `SP_ST_OnUpdate(arg1)` as seconds. /// -/// **All five answer correctly** once the sheet is pushed, with a 44.4% window off a 1.5 s -/// scanned cast time — and the asymmetry this test originally only *documented* turned out to be -/// the whole defect. It is the first bullet's edge: before the stats feed has pushed, +/// **All five answer correctly** once the sheet is pushed — see [`WINDOW_PCT`] for the window +/// they produce and for the fixture mistake that made an earlier revision of this test read as a +/// clean bill of health while the macro was still picking Heroic Strike every time. It is the first bullet's edge: before the stats feed has pushed, /// `UnitAttackSpeed("player")` answered `0`, and SP_SwingTimer's `ADDON_LOADED` arm latches that /// into `st_timerMax` behind `if not st_timerMax` — where `0` is truthy, so the guard never /// re-fires. The first write to that global in a session is therefore always the pre-push answer. @@ -1878,7 +1893,7 @@ fn the_slam_clip_conditional_answers_on_the_players_own_interface() { rank: Some("Rank 1".into()), cost: Some("15 Rage".into()), range: Some("Melee Range".into()), - cast_time: Some("1.5 sec cast".into()), + cast_time: Some("2.5 sec cast".into()), description: "Slams the opponent.".into(), ..Default::default() }, @@ -1934,10 +1949,11 @@ fn the_slam_clip_conditional_answers_on_the_players_own_interface() { // The scanning tooltip: a nil parent, the shipped template, and `getglobal` down the lines. assert_eq!( ask(&mut s, "CleveRoids.GetSlamCastTime()"), - "1.5", - "the cast time came off the scanning tooltip, not the addon's 2.5 s fallback — the \ - fallback leaves a 7% window against a 2.70 s swing and reads as `[noslamclip]` never \ - being true" + "2.5", + "the cast time came off the scanning tooltip. On this realm it happens to equal the \ + addon's own 2.5 s fallback, so a scan that silently failed would read identically — what \ + this pins is that the `sec cast` line is reachable at all through a nil-parent \ + `GameTooltipTemplate`. [`WINDOW_PCT`] carries where the 2.5 comes from" ); // The latch, before any swing. This loader runs the addon walk before the `set_*` calls // above, exactly as `load_ingame_ui_on_world_entry` runs it before any feed system — so this @@ -1984,8 +2000,9 @@ fn the_slam_clip_conditional_answers_on_the_players_own_interface() { ); assert_eq!( ask(&mut s, "CleveRoids.GetSlamWindowPercent()"), - "44.444444444444", - "(2.70 - 1.50) / 2.70 — the share of the swing in which Slam still beats the auto-attack" + WINDOW_PCT, + "(2.70 - 2.50) / 2.70 — the share of the swing in which Slam still beats the \ + auto-attack: 0.2 s of 2.7, which is the whole story of this macro on this weapon" ); assert_eq!( ask(&mut s, "CleveRoids.ValidateNoSlamClip()"),