Skip to main content

forge/
talent_target_flag_summary.rs

1// #t(file: rust_alloc_in_loop) the bounded filtered CLI aggregation owns stable spec labels.
2
3//! Filtered cross-spec cast-target flag incidence reporting.
4
5use std::collections::{BTreeMap, BTreeSet};
6
7#[cfg(test)]
8use googletest::{Result as GtestResult, prelude::*};
9use wowlab_common::output;
10use wowlab_engine_domain::dbc::{
11    SemanticSupport, SpellCastTargetFlags, spell_cast_target_flag_semantic, spell_target_masks,
12};
13use wowlab_types::{data::SpellDataFlat, game::SpecId};
14
15#[derive(Default)]
16struct TargetFlagSummary {
17    observations: usize,
18    specs: BTreeSet<String>,
19}
20
21#[derive(Default)]
22pub(super) struct TargetFlagSummaries(BTreeMap<u8, TargetFlagSummary>);
23
24#[derive(tabled::Tabled)]
25struct TargetFlagSummaryRow {
26    #[tabled(rename = "Bit")]
27    bit: u8,
28    #[tabled(rename = "Name")]
29    name: &'static str,
30    #[tabled(rename = "Coverage")]
31    coverage: &'static str,
32    #[tabled(rename = "Observations")]
33    observations: usize,
34    #[tabled(rename = "Specs")]
35    specs: usize,
36    #[tabled(rename = "Handler / gap")]
37    handler: &'static str,
38}
39
40impl TargetFlagSummaries {
41    pub(super) fn observe(&mut self, spec: SpecId, spell: &SpellDataFlat, filter: &[u8]) {
42        let explicit = spell_target_masks(spell).explicit;
43
44        for bit in filter {
45            let flag = SpellCastTargetFlags::from_bits_retain(1_u32 << bit);
46
47            if explicit.contains(flag) {
48                let summary = self.0.entry(*bit).or_default();
49
50                summary.observations += 1;
51                summary.specs.insert(spec.slug().to_string());
52            }
53        }
54    }
55
56    pub(super) fn print(&self, filter: &[u8]) {
57        output::blank();
58        output::header("Cross-spec cast-target flag summary");
59        output::table(filter.iter().map(|bit| self.row(*bit)));
60    }
61
62    fn row(&self, bit: u8) -> TargetFlagSummaryRow {
63        let flag = SpellCastTargetFlags::from_bits_retain(1_u32 << bit);
64        let semantic = spell_cast_target_flag_semantic(flag);
65        let (name, coverage, handler) = semantic.map_or(
66            (
67                "Unregistered",
68                "unsupported",
69                "cast-target bit is absent from the canonical registry",
70            ),
71            |semantic| {
72                let coverage = match semantic.support {
73                    SemanticSupport::Generic => "generic",
74                    SemanticSupport::Partial | SemanticSupport::ContentRequired => "partial",
75                    SemanticSupport::Ignored => "ignored",
76                    _ => "unsupported",
77                };
78
79                (semantic.name, coverage, semantic.handler)
80            },
81        );
82        let summary = self.0.get(&bit);
83
84        TargetFlagSummaryRow {
85            bit,
86            name,
87            coverage,
88            observations: summary.map_or(0, |summary| summary.observations),
89            specs: summary.map_or(0, |summary| summary.specs.len()),
90            handler,
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[gtest]
100    fn counts_only_filtered_explicit_payload_bits() -> GtestResult<()> {
101        let mut summaries = TargetFlagSummaries::default();
102        let spell = SpellDataFlat {
103            target_flags: (1 << 5) | (1 << 7),
104            ..SpellDataFlat::default()
105        };
106
107        summaries.observe(SpecId::Fire, &spell, &[5, 6]);
108
109        let summary = &summaries.0[&5];
110
111        verify_that!(summary.observations, eq(1))?;
112        verify_that!(summary.specs.len(), eq(1))?;
113        verify_true!(!summaries.0.contains_key(&6))?;
114
115        Ok(())
116    }
117}