Skip to main content

wowlab_engine_combat/systems/buffs/
planned.rs

1// #t(file: rust_duplicate_strings) finalized binding and occupied-slot invariants intentionally use one stable diagnostic at every traversal entry point.
2// #t(file: rust_unchecked_indexing) all direct indexes consume coordinates compiled from the same immutable passive, aura, and effect registries.
3
4use wowlab_engine_domain::dbc::SpellEffectAttributes;
5
6use crate::{
7    context::ActorView,
8    state::{BuffEffect, EffectFamily},
9};
10
11pub(crate) fn dbc_effect_points(
12    program: &crate::state::CombatProgram,
13    _state: &crate::state::CombatInstance,
14    source: crate::state::program::LocalDamageEffectIdx,
15    stacks: f64,
16) -> f64 {
17    let attributes = SpellEffectAttributes::from_dbc(program.damage.effect_at(source).attributes);
18
19    if attributes.contains(SpellEffectAttributes::SUPPRESS_POINTS_STACKING) {
20        1.0
21    } else {
22        stacks
23    }
24}
25
26/// Visit only finalized aura/effect entries carrying the requested runtime family.
27pub(crate) fn for_each_planned_actor_effect(
28    view: ActorView<'_>,
29    family: EffectFamily,
30    mut visit: impl FnMut(f64, &BuffEffect),
31) {
32    if family.bits().is_power_of_two() {
33        if view.actor == wowlab_types::sim::ActorId::Player {
34            for &index in view.program.effects.passive_family_indexes(family) {
35                visit(1.0, &view.program.passive_driver_effects[index].effect);
36            }
37        }
38
39        for_each_planned_actor_aura_effect_single_family(
40            view,
41            family,
42            |_, _, _, stacks, effect| {
43                visit(stacks, effect);
44            },
45        );
46
47        return;
48    }
49
50    if view.actor == wowlab_types::sim::ActorId::Player {
51        for_each_planned_player_passive_effect(view.program, family, &mut visit);
52    }
53
54    for_each_planned_actor_aura_effect(view, family, |_, _, stacks, effect| {
55        visit(stacks, effect);
56    });
57}
58
59/// Visit effects together with the dense damage coordinates frozen for their exact slot.
60pub(crate) fn for_each_planned_actor_effect_damage_binding(
61    view: ActorView<'_>,
62    family: EffectFamily,
63    mut visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
64) {
65    if family.bits().is_power_of_two() {
66        if view.actor == wowlab_types::sim::ActorId::Player {
67            for &index in view.program.effects.passive_family_indexes(family) {
68                let effect = &view.program.passive_driver_effects[index].effect;
69                let binding = view
70                    .program
71                    .damage
72                    .passive_buff_binding(index)
73                    .expect("every finalized passive effect has a damage binding record");
74
75                visit(1.0, effect, binding);
76            }
77        }
78
79        for_each_planned_actor_aura_effect_single_family(
80            view,
81            family,
82            |_, local, position, stacks, effect| {
83                let binding = view
84                    .program
85                    .damage
86                    .aura_buff_binding(local, position)
87                    .expect("every finalized aura effect has a damage binding record");
88
89                visit(stacks, effect, binding);
90            },
91        );
92
93        return;
94    }
95
96    if view.actor == wowlab_types::sim::ActorId::Player {
97        for_each_planned_player_passive_effect_binding(view.program, family, &mut visit);
98    }
99
100    for_each_planned_actor_aura_effect_inner(view, family, |_, local, position, stacks, effect| {
101        let binding = view
102            .program
103            .damage
104            .aura_buff_binding(local, position)
105            .expect("every finalized aura effect has a damage binding record");
106
107        visit(stacks, effect, binding);
108    });
109}
110
111#[cfg(feature = "rotation-optimizations")]
112fn for_each_active_bound_action_effect(
113    view: ActorView<'_>,
114    passive_indexes: &[usize],
115    aura_plan: &crate::state::program::ActiveAuraEffectPlan,
116    mut visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
117) {
118    if view.actor == wowlab_types::sim::ActorId::Player {
119        for &index in passive_indexes {
120            let effect = &view.program.passive_driver_effects[index].effect;
121            let binding = view
122                .program
123                .damage
124                .passive_buff_binding(index)
125                .expect("every finalized passive effect has a damage binding record");
126
127            visit(1.0, effect, binding);
128        }
129    }
130
131    for_each_active_aura_effect_plan(view, aura_plan, aura_plan, &mut visit);
132}
133
134#[cfg(not(feature = "rotation-optimizations"))]
135fn for_each_active_bound_action_effect(
136    view: ActorView<'_>,
137    passive_indexes: &[usize],
138    aura_entries: &[crate::state::program::PlannedAuraEffect],
139    mut visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
140) {
141    if view.actor == wowlab_types::sim::ActorId::Player {
142        for &index in passive_indexes {
143            let effect = &view.program.passive_driver_effects[index].effect;
144            let binding = view
145                .program
146                .damage
147                .passive_buff_binding(index)
148                .expect("every finalized passive effect has a damage binding record");
149
150            visit(1.0, effect, binding);
151        }
152    }
153
154    for entry in aura_entries {
155        wowlab_types::bench_metrics::record_aura_slot_visit();
156        let slot = view.instance.buffer.aura_at(entry.handle);
157
158        if !slot.is_occupied() {
159            continue;
160        }
161
162        let effect = view.program.aura(entry.local).effects[usize::from(entry.position)]
163            .as_ref()
164            .expect("planned effect position remains occupied after finalization");
165        let binding = view
166            .program
167            .damage
168            .aura_buff_binding(entry.local, entry.position)
169            .expect("every finalized aura effect has a damage binding record");
170
171        visit(f64::from(slot.stacks), effect, binding);
172    }
173}
174
175#[cfg(feature = "rotation-optimizations")]
176fn for_each_active_bound_action_effect_union(
177    view: ActorView<'_>,
178    left_passives: &[usize],
179    right_passives: &[usize],
180    left_auras: &crate::state::program::ActiveAuraEffectPlan,
181    right_auras: &crate::state::program::ActiveAuraEffectPlan,
182    mut visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
183) {
184    if view.actor == wowlab_types::sim::ActorId::Player {
185        let mut left = 0;
186        let mut right = 0;
187
188        while left < left_passives.len() || right < right_passives.len() {
189            let index = match (left_passives.get(left), right_passives.get(right)) {
190                (Some(&left_index), Some(&right_index)) if left_index < right_index => {
191                    left += 1;
192
193                    left_index
194                }
195                (Some(&left_index), Some(&right_index)) if right_index < left_index => {
196                    right += 1;
197
198                    right_index
199                }
200                (Some(&index), Some(_)) => {
201                    left += 1;
202                    right += 1;
203
204                    index
205                }
206                (Some(&index), None) => {
207                    left += 1;
208
209                    index
210                }
211                (None, Some(&index)) => {
212                    right += 1;
213
214                    index
215                }
216                (None, None) => break,
217            };
218            let effect = &view.program.passive_driver_effects[index].effect;
219            let binding = view
220                .program
221                .damage
222                .passive_buff_binding(index)
223                .expect("every finalized passive effect has a damage binding record");
224
225            visit(1.0, effect, binding);
226        }
227    }
228
229    for_each_active_aura_effect_plan(view, left_auras, right_auras, &mut visit);
230}
231
232#[cfg(feature = "rotation-optimizations")]
233fn for_each_active_aura_effect_plan(
234    view: ActorView<'_>,
235    left: &crate::state::program::ActiveAuraEffectPlan,
236    right: &crate::state::program::ActiveAuraEffectPlan,
237    visit: &mut impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
238) {
239    let mut visit_binding = |local: crate::state::LocalAuraIdx,
240                             handle: wowlab_engine_domain::rotation::AuraSlotHandle,
241                             mut mask: u16| {
242        let slot = view.instance.buffer.aura_at(handle);
243
244        debug_assert!(
245            slot.is_occupied(),
246            "active aura intersection contains only occupied concrete bindings"
247        );
248
249        let aura = view.program.aura(local);
250
251        while mask != 0 {
252            let position =
253                u8::try_from(mask.trailing_zeros()).expect("u16 effect mask positions fit in u8");
254
255            mask &= mask - 1;
256            wowlab_types::bench_metrics::record_aura_slot_visit();
257            let effect = aura.effects[usize::from(position)]
258                .as_ref()
259                .expect("active-aura effect plan references an occupied effect");
260            let damage_binding = view
261                .program
262                .damage
263                .aura_buff_binding(local, position)
264                .expect("every finalized aura effect has a damage binding record");
265
266            visit(f64::from(slot.stacks), effect, damage_binding);
267        }
268    };
269
270    if std::ptr::eq(left, right) {
271        for &(local, mask) in left.masks() {
272            view.instance
273                .for_each_active_aura_local(view.actor, local, |binding| {
274                    visit_binding(local, binding.handle, mask);
275                });
276        }
277
278        return;
279    }
280
281    view.instance.for_each_active_aura_intersection(
282        view.actor,
283        (left.relevant_locals(), right.relevant_locals()),
284        |local, binding| {
285            visit_binding(local, binding.handle, left.mask(local) | right.mask(local));
286        },
287    );
288}
289
290pub(crate) fn for_each_active_spell_modifier_for_action(
291    view: ActorView<'_>,
292    action: crate::state::program::LocalDamageActionIdx,
293    property: wowlab_engine_domain::dbc::ModifierPropertyKind,
294    visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
295) {
296    debug_assert_eq!(
297        view.actor,
298        wowlab_types::sim::ActorId::Player,
299        "affected-spell property modifiers resolve through the canonical player owner"
300    );
301
302    #[cfg(feature = "rotation-optimizations")]
303    for_each_active_bound_action_effect(
304        view,
305        view.program
306            .effects
307            .spell_modifier_passive_indexes(action, property),
308        view.program
309            .effects
310            .spell_modifier_player_aura_plan(action, property),
311        visit,
312    );
313
314    #[cfg(not(feature = "rotation-optimizations"))]
315    for_each_active_bound_action_effect(
316        view,
317        view.program
318            .effects
319            .spell_modifier_passive_indexes(action, property),
320        view.program
321            .effects
322            .spell_modifier_player_auras(action, property),
323        visit,
324    );
325}
326
327pub(crate) fn for_each_active_scoped_spell_modifier_for_action(
328    view: ActorView<'_>,
329    action: crate::state::program::LocalDamageActionIdx,
330    visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
331) {
332    #[cfg(feature = "rotation-optimizations")]
333    for_each_active_bound_action_effect(
334        view,
335        view.program
336            .effects
337            .scoped_spell_modifier_passive_indexes(action),
338        view.program.effects.scoped_spell_modifier_aura_plan(action),
339        visit,
340    );
341
342    #[cfg(not(feature = "rotation-optimizations"))]
343    for_each_active_bound_action_effect(
344        view,
345        view.program
346            .effects
347            .scoped_spell_modifier_passive_indexes(action),
348        view.program
349            .effects
350            .scoped_spell_modifier_actor_auras(view.actor, action),
351        visit,
352    );
353}
354
355#[cfg(feature = "rotation-optimizations")]
356pub(crate) fn for_each_active_damage_modifier_for_actions(
357    view: ActorView<'_>,
358    profile_action: crate::state::program::LocalDamageActionIdx,
359    effect_action: crate::state::program::LocalDamageActionIdx,
360    visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
361) {
362    for_each_active_bound_action_effect_union(
363        view,
364        view.program
365            .effects
366            .damage_spell_modifier_passive_indexes(profile_action),
367        view.program
368            .effects
369            .damage_spell_modifier_passive_indexes(effect_action),
370        view.program
371            .effects
372            .damage_spell_modifier_aura_plan(profile_action),
373        view.program
374            .effects
375            .damage_spell_modifier_aura_plan(effect_action),
376        visit,
377    );
378}
379
380#[cfg(not(feature = "rotation-optimizations"))]
381pub(crate) fn for_each_active_damage_modifier_for_action(
382    view: ActorView<'_>,
383    action: crate::state::program::LocalDamageActionIdx,
384    visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
385) {
386    for_each_active_bound_action_effect(
387        view,
388        view.program
389            .effects
390            .damage_spell_modifier_passive_indexes(action),
391        view.program
392            .effects
393            .damage_spell_modifier_actor_auras(view.actor, action),
394        visit,
395    );
396}
397
398pub(crate) fn for_each_active_cost_modifier_for_action(
399    view: ActorView<'_>,
400    action: crate::state::program::LocalDamageActionIdx,
401    visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
402) {
403    #[cfg(feature = "rotation-optimizations")]
404    for_each_active_bound_action_effect(
405        view,
406        view.program.effects.cost_modifier_passive_indexes(action),
407        view.program.effects.cost_modifier_aura_plan(action),
408        visit,
409    );
410
411    #[cfg(not(feature = "rotation-optimizations"))]
412    for_each_active_bound_action_effect(
413        view,
414        view.program.effects.cost_modifier_passive_indexes(action),
415        view.program
416            .effects
417            .cost_modifier_actor_auras(view.actor, action),
418        visit,
419    );
420}
421
422fn for_each_planned_player_passive_effect_binding(
423    program: &crate::state::CombatProgram,
424    family: EffectFamily,
425    visit: &mut impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
426) {
427    let mut cursors = [0_usize; u128::BITS as usize];
428
429    loop {
430        let mut selected_entry = None::<(usize, usize)>;
431        let mut bits = family.bits();
432
433        while bits != 0 {
434            let bit = bits.trailing_zeros() as usize;
435            let selected = EffectFamily::from_bits_retain(1_u128 << bit);
436            let indexes = program.effects.passive_family_indexes(selected);
437            // BOUNDS: trailing_zeros on a nonzero u128 produces an index below u128::BITS.
438            let cursor = &mut cursors[bit];
439
440            bits &= bits - 1;
441
442            while let Some(&index) = indexes.get(*cursor) {
443                // BOUNDS: effect-plan family indexes are finalized from this passive registry.
444                let effect = &program.passive_driver_effects[index].effect;
445                let matching = effect.classification().families().intersection(family);
446
447                if matching.bits().trailing_zeros() as usize == bit {
448                    if selected_entry.is_none_or(|(current, _)| index < current) {
449                        selected_entry = Some((index, bit));
450                    }
451
452                    break;
453                }
454
455                *cursor += 1;
456            }
457        }
458
459        let Some((index, bit)) = selected_entry else {
460            break;
461        };
462
463        // BOUNDS: selected bits originate from the same bounded u128 family mask.
464
465        cursors[bit] += 1;
466        // BOUNDS: selected entries originate from finalized passive family index slices.
467        let effect = &program.passive_driver_effects[index].effect;
468        let binding = program
469            .damage
470            .passive_buff_binding(index)
471            .expect("every finalized passive effect has a damage binding record");
472
473        visit(1.0, effect, binding);
474    }
475}
476
477/// Visit finalized aura effects while retaining their exact fixed-slot coordinate.
478pub(crate) fn for_each_planned_actor_aura_effect_binding(
479    view: ActorView<'_>,
480    family: EffectFamily,
481    mut visit: impl FnMut(wowlab_types::sim::AuraKey, crate::state::LocalAuraIdx, u8, f64, &BuffEffect),
482) {
483    for_each_planned_actor_aura_effect_inner(
484        view,
485        family,
486        |key, local, position, stacks, effect| {
487            visit(key, local, position, stacks, effect);
488        },
489    );
490}
491
492fn for_each_planned_player_passive_effect(
493    program: &crate::state::CombatProgram,
494    family: EffectFamily,
495    visit: &mut impl FnMut(f64, &BuffEffect),
496) {
497    let mut cursors = [0_usize; u128::BITS as usize];
498
499    loop {
500        let mut selected_entry = None::<(usize, usize)>;
501        let mut bits = family.bits();
502
503        while bits != 0 {
504            let bit = bits.trailing_zeros() as usize;
505            let selected = EffectFamily::from_bits_retain(1_u128 << bit);
506            let indexes = program.effects.passive_family_indexes(selected);
507            // BOUNDS: EffectFamily bits and cursor storage share the u128 bit width.
508            let cursor = &mut cursors[bit];
509
510            bits &= bits - 1;
511
512            while let Some(&index) = indexes.get(*cursor) {
513                // BOUNDS: passive family indexes are compiled from this exact driver slice.
514                let effect = &program.passive_driver_effects[index].effect;
515                let matching = effect.classification().families().intersection(family);
516
517                if matching.bits().trailing_zeros() as usize == bit {
518                    if selected_entry.is_none_or(|(current, _)| index < current) {
519                        selected_entry = Some((index, bit));
520                    }
521
522                    break;
523                }
524
525                *cursor += 1;
526            }
527        }
528
529        let Some((index, bit)) = selected_entry else {
530            break;
531        };
532
533        // BOUNDS: selected bits come from the same u128 family mask as cursor storage.
534
535        cursors[bit] += 1;
536        // BOUNDS: passive family indexes are compiled from this exact driver slice.
537        visit(1.0, &program.passive_driver_effects[index].effect);
538    }
539}
540
541pub(crate) fn for_each_planned_actor_aura_effect(
542    view: ActorView<'_>,
543    family: EffectFamily,
544    mut visit: impl FnMut(wowlab_types::sim::AuraKey, crate::state::LocalAuraIdx, f64, &BuffEffect),
545) {
546    for_each_planned_actor_aura_effect_inner(view, family, |key, local, _, stacks, effect| {
547        visit(key, local, stacks, effect);
548    });
549}
550
551fn for_each_planned_actor_aura_effect_inner(
552    view: ActorView<'_>,
553    family: EffectFamily,
554    mut visit: impl FnMut(wowlab_types::sim::AuraKey, crate::state::LocalAuraIdx, u8, f64, &BuffEffect),
555) {
556    if family.bits().is_power_of_two() {
557        for_each_planned_actor_aura_effect_single_family(view, family, visit);
558
559        return;
560    }
561
562    let mut cursors = [0_usize; u128::BITS as usize];
563
564    loop {
565        let mut selected_entry = None::<(&crate::state::program::PlannedAuraEffect, usize)>;
566        let mut bits = family.bits();
567
568        while bits != 0 {
569            let bit = bits.trailing_zeros() as usize;
570            let selected = EffectFamily::from_bits_retain(1_u128 << bit);
571            let entries = view
572                .program
573                .effects
574                .actor_family_effects(view.actor, selected);
575            // BOUNDS: EffectFamily bits and cursor storage share the u128 bit width.
576            let cursor = &mut cursors[bit];
577
578            bits &= bits - 1;
579
580            while let Some(entry) = entries.get(*cursor) {
581                if entry.is_canonical_for(family, selected) {
582                    if selected_entry.is_none_or(|(current, _)| entry.order < current.order) {
583                        selected_entry = Some((entry, bit));
584                    }
585
586                    break;
587                }
588
589                *cursor += 1;
590            }
591        }
592
593        let Some((entry, bit)) = selected_entry else {
594            break;
595        };
596
597        // BOUNDS: selected bits come from the same u128 family mask as cursor storage.
598
599        cursors[bit] += 1;
600
601        wowlab_types::bench_metrics::record_aura_slot_visit();
602        let slot = view.instance.buffer.aura_at(entry.handle);
603
604        if !slot.is_occupied() {
605            continue;
606        }
607
608        // BOUNDS: exact effect positions are compiled from this aura's fixed array.
609
610        let effect = view.program.aura(entry.local).effects[usize::from(entry.position)]
611            .as_ref()
612            .expect("planned effect position remains occupied after finalization");
613
614        visit(
615            entry.key,
616            entry.local,
617            entry.position,
618            f64::from(slot.stacks),
619            effect,
620        );
621    }
622}
623
624fn for_each_planned_actor_aura_effect_single_family(
625    view: ActorView<'_>,
626    family: EffectFamily,
627    mut visit: impl FnMut(wowlab_types::sim::AuraKey, crate::state::LocalAuraIdx, u8, f64, &BuffEffect),
628) {
629    #[cfg(feature = "rotation-optimizations")]
630    {
631        let plan = view.program.effects.family_aura_plan(family);
632
633        view.instance.for_each_active_aura_intersection(
634            view.actor,
635            (plan.relevant_locals(), &[]),
636            |local, binding| {
637                let slot = view.instance.buffer.aura_at(binding.handle);
638                let aura = view.program.aura(local);
639                let mut mask = plan.mask(local);
640
641                debug_assert!(
642                    slot.is_occupied(),
643                    "active aura intersection contains only occupied concrete bindings"
644                );
645
646                while mask != 0 {
647                    let position = u8::try_from(mask.trailing_zeros())
648                        .expect("u16 effect mask positions fit in u8");
649
650                    mask &= mask - 1;
651                    wowlab_types::bench_metrics::record_aura_slot_visit();
652                    let effect = aura.effects[usize::from(position)]
653                        .as_ref()
654                        .expect("family aura plan references an occupied effect");
655
656                    visit(binding.key, local, position, f64::from(slot.stacks), effect);
657                }
658            },
659        );
660    }
661
662    #[cfg(not(feature = "rotation-optimizations"))]
663    for entry in view
664        .program
665        .effects
666        .actor_family_effects(view.actor, family)
667    {
668        wowlab_types::bench_metrics::record_aura_slot_visit();
669        let slot = view.instance.buffer.aura_at(entry.handle);
670
671        if !slot.is_occupied() {
672            continue;
673        }
674
675        // BOUNDS: exact effect positions are compiled from this aura's fixed array.
676
677        let effect = view.program.aura(entry.local).effects[usize::from(entry.position)]
678            .as_ref()
679            .expect("planned effect position remains occupied after finalization");
680
681        visit(
682            entry.key,
683            entry.local,
684            entry.position,
685            f64::from(slot.stacks),
686            effect,
687        );
688    }
689}
690
691pub(crate) fn for_each_planned_player_effect(
692    program: &crate::state::CombatProgram,
693    instance: &crate::state::CombatInstance,
694    family: EffectFamily,
695    visit: impl FnMut(f64, &BuffEffect),
696) {
697    for_each_planned_actor_effect(
698        ActorView::new(program, instance, wowlab_types::sim::ActorId::Player),
699        family,
700        visit,
701    );
702}
703
704pub(crate) fn for_each_planned_target_effect(
705    program: &crate::state::CombatProgram,
706    instance: &crate::state::CombatInstance,
707    target: wowlab_types::sim::EnemyIdx,
708    family: EffectFamily,
709    visit: impl FnMut(f64, &BuffEffect),
710) {
711    for_each_planned_actor_effect(
712        ActorView::new(program, instance, wowlab_types::sim::ActorId::Enemy(target)),
713        family,
714        visit,
715    );
716}