The slam-clip macro chain is sound, and now has a test that says so

His rotation macro never picks Slam. Five of our verbs and feeds stand
between `[noslamclip]` and an answer, and any one of them would read in play
exactly as he describes, so this drives all five on his own 92-addon
interface rather than reasoning about them:

  UnitAttackSpeed's (number, nil) shape, which is what isDualWield() asks;
  CHAT_MSG_COMBAT_SELF_HITS reaching a registered frame with arg1 as the
  composed sentence, which is the only thing SP_SwingTimer resets on;
  the GetSpellName spellbook walk;
  GameTooltip:SetSpell on a nil-parent scanning tooltip built from
  GameTooltipTemplate, read back through getglobal;
  and the <OnUpdate> delta arriving as seconds.

All five answer correctly. Measured, the conditional is true at the top of a
swing with a 44.4% window off a scanned 1.5 s cast time — so the cause is not
in this chain, and the test is here to keep that true.

The one asymmetry it does pin down is faithful: before any swing lands,
SP_SwingTimer's ADDON_LOADED arm latches st_timerMax to 0, because
UnitAttackSpeed answers 0 while the stats feed has not pushed and 0 is truthy
in Lua, so its own `if not st_timerMax` guard never fires again.
reference/1.12-shapes.tsv has UnitAttackSpeed returning exactly two values and
never none, so answering 0 is right; the first swing heals it, which the
second half of the test shows.

The scanning-tooltip leg is worth naming on its own: without it the addon
falls back to a 2.5 s constant, and against a 2.70 s swing that is a 7%
window instead of 44% — which would read as "Slam never fires" too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-29 08:44:25 -06:00
co-authored by Claude Opus 5
parent 0c698b8797
commit e17d7dff04
@@ -1495,3 +1495,168 @@ fn a_cursor_seated_sign_plate_prices_its_own_re_anchor() {
);
}
}
/// SuperCleveRoidMacros' `[noslamclip]` conditional, end to end, on grom's own interface.
///
/// He reports that `/cast [noslamclip, stance:1/3] Slam; [slamclip, stance:1/3] Heroic Strike`
/// never picks Slam. This drives the whole chain the conditional stands on, because every link in
/// it is a verb or a feed of ours and any one of them could be the cause:
///
/// * `UnitAttackSpeed("player")` — SP_SwingTimer's `GetWeaponSpeed`, and the `(number, nil)`
/// shape for a one-handed weapon that `isDualWield()` keys on.
/// * `CHAT_MSG_COMBAT_SELF_HITS` reaching a registered frame with `arg1` as the composed English
/// sentence. SP_SwingTimer resets its timer on nothing else; `ui_chat::frames::route` is the
/// one function that both tees a line to `WoWCombatLog.txt` and fires the event, so a line in
/// that file means this fire happened.
/// * The spellbook walk `GetSpellName(i, BOOKTYPE_SPELL)` finding Slam.
/// * `GameTooltip:SetSpell` on a **nil-parent scanning tooltip built from
/// `GameTooltipTemplate`**, whose `<name>TextLeft<i>` 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".
/// * The `<OnUpdate>` delta reaching `SP_ST_OnUpdate(arg1)` as seconds.
///
/// **All five answer correctly**, which is the finding: measured here the conditional is `true`
/// at the top of a swing, with a 44.4% window off a 1.5 s scanned cast time. So whatever he is
/// seeing is not one of these, and this test exists so that stays true.
///
/// The one real asymmetry it documents is the first bullet's edge: before any swing has landed,
/// `st_timerMax` is latched to `0` by SP_SwingTimer's `ADDON_LOADED` arm, because
/// `UnitAttackSpeed("player")` answers `0` while the stats feed has not pushed yet — and `0` is
/// truthy in Lua, so its own `if not st_timerMax` guard never re-fires. That is *faithful*
/// (`reference/1.12-shapes.tsv` has `UnitAttackSpeed` returning exactly 2 values, never none), and
/// it heals on the first swing, as the second half of this test shows.
#[test]
fn the_slam_clip_conditional_answers_on_the_players_own_interface() {
let _data = benilla_formats::wow_data_or_skip!();
let Some(root) = crate::ui_script::addons::root() else {
eprintln!("slam-clip probe: no benilla-config/AddOns — nothing to ask");
return;
};
let mut s = load_the_players_ui(&root);
// What the live feeds push and this loader does not: his own sheet (2.70 s mainhand, no
// offhand), Slam in the spellbook with its tooltip, and a target to be hitting.
s.set_player_combat_stats(Some(benilla_ui::script::UnitCombatStats {
main_attack_time_ms: 2700,
has_offhand: false,
min_damage: 41.0,
max_damage: 51.0,
..Default::default()
}));
s.set_spell_tooltip(
1464,
benilla_ui::script::SpellTooltipView {
name: "Slam".into(),
rank: Some("Rank 1".into()),
cost: Some("15 Rage".into()),
range: Some("Melee Range".into()),
cast_time: Some("1.5 sec cast".into()),
description: "Slams the opponent.".into(),
..Default::default()
},
);
s.set_spellbook(benilla_ui::script::SpellBookState {
tabs: Vec::new(),
slots: vec![benilla_ui::script::SpellSlotView {
spell_id: 1464,
name: "Slam".into(),
..Default::default()
}],
});
s.set_unit(
"target",
Some(UnitState {
exists: true,
has_object: true,
name: Some("Apprentice Training Dummy".into()),
health: 100,
max_health: 100,
level: 14,
guid: 0x7001,
..Default::default()
}),
);
let ask = |s: &mut UiScript, chunk: &str| -> String {
s.eval::<String>(&format!(
"local ok, v = pcall(function() return {chunk} end) return tostring(v)"
))
.unwrap_or_else(|e| format!("<{e}>"))
};
// The addon's own swing timer is registered for the event and answerable before any of it.
assert_eq!(
ask(
&mut s,
"SP_ST_Updater:IsEventRegistered('CHAT_MSG_COMBAT_SELF_HITS')"
),
"1",
"SP_SwingTimer's updater frame did not register the event it resets on"
);
assert_eq!(
ask(&mut s, "(UnitAttackSpeed('player'))"),
"2.7",
"the sheet's mainhand speed, which is the swing the window is a fraction of"
);
assert_eq!(
ask(&mut s, "select(2, UnitAttackSpeed('player'))"),
"nil",
"no offhand is `nil`, not 0 — `isDualWield()` is exactly this test"
);
// 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"
);
// The latch, before any swing: documented, faithful, and healed below.
assert_eq!(
ask(&mut s, "st_timerMax"),
"0",
"SP_SwingTimer's ADDON_LOADED arm latched the pre-feed `UnitAttackSpeed` answer"
);
// One auto-attack lands, exactly as `ui_chat::frames::route` fires it.
s.fire_event(
"CHAT_MSG_COMBAT_SELF_HITS",
vec![benilla_ui::script::ScriptValue::Str(
"You hit Apprentice Training Dummy for 46.".into(),
)],
);
assert_eq!(
ask(&mut s, "st_timerMax"),
"2.7",
"the swing reached SP_ST_OnEvent with a parsable `arg1` and reset the timer"
);
assert_eq!(
ask(&mut s, "CleveRoids.GetSwingPercentElapsed()"),
"0",
"at the top of a swing, none of it has elapsed"
);
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"
);
assert_eq!(
ask(&mut s, "CleveRoids.ValidateNoSlamClip()"),
"true",
"**the conditional itself**: at the top of a swing `[noslamclip]` is true, so the macro's \
first clause is the one that should fire"
);
// The `<OnUpdate>` delta: the timer has to run down in real seconds, or the window never
// closes and `[slamclip]` never takes over.
for _ in 0..6 {
s.tick(1.0 / 60.0);
}
let left: f64 = s.eval("return st_timer").expect("st_timer");
assert!(
(left - (2.7 - 6.0 / 60.0)).abs() < 1e-3,
"six frames of 1/60 s should leave {:.4} s on the swing, not {left:.4} — \
`SP_ST_OnUpdate(arg1)` is not getting the frame's elapsed seconds",
2.7 - 6.0 / 60.0,
);
}