wowlab_sentinel/strategy/factorial/
interactions.rs1#![expect(
4 clippy::cast_possible_truncation,
5 reason = "factorial item indices are bounded by manifest slot sizes and serialized as u32"
6)]
7
8use super::types::{FactorialSlot, MainEffectEstimate};
9
10pub(crate) fn select_items_for_interaction(
11 slots: &[FactorialSlot],
12 main_effects: &[MainEffectEstimate],
13 top_k: usize,
14) -> Vec<Vec<u32>> {
15 let effect_lookup = |slot_index: u32, item_idx: u32| -> i32 {
16 main_effects
17 .iter()
18 .find(|me| me.slot_index == slot_index && me.item_idx == item_idx)
19 .map_or(0, |me| me.effect_dps_x10)
20 };
21
22 slots
23 .iter()
24 .map(|slot| {
25 let mut selected: Vec<u32> = Vec::new();
26
27 selected.push(0);
28
29 for (idx, item) in slot.items.iter().enumerate() {
30 if item.has_effect() && !selected.contains(&(idx as u32)) {
31 selected.push(idx as u32);
32 }
33 }
34
35 let mut ranked: Vec<(u32, i32)> = (0..slot.items.len() as u32)
36 .filter(|idx| !selected.contains(idx))
37 .map(|idx| (idx, effect_lookup(slot.slot_index, idx)))
38 .collect();
39
40 ranked.sort_by_key(|&(_, effect)| std::cmp::Reverse(effect));
41
42 for (idx, _) in ranked {
43 if selected.len() >= top_k.max(1) {
44 break;
45 }
46
47 selected.push(idx);
48 }
49
50 selected
51 })
52 .collect()
53}
54
55#[cfg(test)]
56mod tests {
57 use googletest::prelude::*;
58
59 use super::*;
60 use crate::strategy::factorial::types::FactorialItem;
61
62 fn slot_with(slot_index: u32, items: &[(u32, bool)]) -> FactorialSlot {
63 FactorialSlot {
64 slot_index,
65 items: items
66 .iter()
67 .map(|&(_item_id, has_effect)| FactorialItem::new(has_effect))
68 .collect(),
69 }
70 }
71
72 #[gtest]
73 fn effect_items_always_selected() -> Result<()> {
74 let slots = vec![slot_with(
75 0,
76 &[
77 (100, false),
78 (101, false),
79 (102, false),
80 (103, false),
81 (104, false),
82 (999, true),
83 ],
84 )];
85 let selected = select_items_for_interaction(&slots, &[], 2);
86
87 verify_true!(selected[0].contains(&5))?;
88 verify_true!(selected[0].contains(&0))?;
89
90 Ok(())
91 }
92
93 #[gtest]
94 fn top_k_limits_stat_sticks() -> Result<()> {
95 let slots = vec![slot_with(
96 0,
97 &[
98 (100, false),
99 (101, false),
100 (102, false),
101 (103, false),
102 (104, false),
103 ],
104 )];
105 let selected = select_items_for_interaction(&slots, &[], 3);
106
107 verify_eq!(selected[0].len(), 3)?;
108
109 Ok(())
110 }
111}