Skip to main content

wowlab_engine_ports/
game_bugs.rs

1//! Central registry of modeled live-game bugs.
2
3use crate::EngineError;
4
5/// One deliberately modeled live-game behavior that diverges from spell data.
6#[derive(Debug)]
7pub struct GameBug {
8    pub slug: &'static str,
9    pub description: &'static str,
10}
11
12inventory::collect!(&'static GameBug);
13
14/// Declare a modeled live-game bug and register it with `inventory`.
15#[macro_export]
16macro_rules! define_game_bug {
17    (
18        $(#[$meta:meta])*
19        $vis:vis static $name:ident = ($slug:literal, $description:literal $(,)?);
20    ) => {
21        $(#[$meta])*
22        $vis static $name: $crate::GameBug = $crate::GameBug {
23            slug: $slug,
24            description: $description,
25        };
26        inventory::submit! { &$name }
27    };
28}
29
30/// All registered bugs, sorted by slug.
31#[must_use]
32pub fn registered_game_bugs() -> Vec<&'static GameBug> {
33    let mut bugs: Vec<&'static GameBug> = inventory::iter::<&'static GameBug>
34        .into_iter()
35        .copied()
36        .collect();
37
38    bugs.sort_by_key(|bug| bug.slug);
39
40    bugs
41}
42
43/// Raw bug-toggle input parsed from sim-config settings, unvalidated.
44#[derive(Clone, Debug)]
45pub struct BugSettings {
46    pub default_enabled: bool,
47    pub overrides: Vec<(String, bool)>,
48}
49
50impl Default for BugSettings {
51    fn default() -> Self {
52        Self {
53            default_enabled: true,
54            overrides: Vec::new(),
55        }
56    }
57}
58
59/// Resolved per-run bug toggles consulted by combat code.
60#[derive(Clone, Debug)]
61pub struct BugToggles {
62    default_enabled: bool,
63    overrides: Vec<(&'static str, bool)>,
64}
65
66impl BugToggles {
67    /// Creates toggles with live-game behavior enabled by default.
68    #[must_use]
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    /// Whether the given bug is modeled in this run.
74    #[must_use]
75    pub fn enabled(&self, bug: &GameBug) -> bool {
76        self.overrides
77            .iter()
78            .find(|(slug, _)| *slug == bug.slug)
79            .map_or(self.default_enabled, |(_, enabled)| *enabled)
80    }
81}
82
83impl Default for BugToggles {
84    fn default() -> Self {
85        Self {
86            default_enabled: true,
87            overrides: Vec::new(),
88        }
89    }
90}
91
92/// Validate raw settings against the registry and freeze them for a run.
93/// # Errors
94/// Returns an error when a configured bug slug is not registered.
95pub fn resolve_bug_toggles(settings: &BugSettings) -> Result<BugToggles, EngineError> {
96    let registry = registered_game_bugs();
97
98    if let Some((unknown, _)) = settings
99        .overrides
100        .iter()
101        .find(|(slug, _)| !registry.iter().any(|bug| bug.slug == slug))
102    {
103        return Err(EngineError::intent_validation(format!(
104            "unknown game bug {unknown:?}; registered bugs: {}",
105            registry
106                .iter()
107                .map(|bug| bug.slug)
108                .collect::<Vec<_>>()
109                .join(", ")
110        )));
111    }
112
113    let overrides = settings
114        .overrides
115        .iter()
116        .filter_map(|(slug, enabled)| {
117            registry
118                .iter()
119                .find(|bug| bug.slug == slug)
120                .map(|bug| (bug.slug, *enabled))
121        })
122        .collect();
123
124    Ok(BugToggles {
125        default_enabled: settings.default_enabled,
126        overrides,
127    })
128}
129
130#[cfg(test)]
131mod tests {
132    use googletest::prelude::*;
133
134    use super::*;
135
136    crate::define_game_bug! {
137        static TEST_BUG = (
138            "engine_ports_test_bug",
139            "test-only modeled behavior",
140        );
141    }
142
143    #[gtest]
144    fn resolved_toggles_apply_default_and_named_override() -> Result<()> {
145        let toggles = resolve_bug_toggles(&BugSettings {
146            default_enabled: true,
147            overrides: vec![(TEST_BUG.slug.to_string(), false)],
148        })
149        .or_fail()?;
150
151        verify_true!(!toggles.enabled(&TEST_BUG))
152    }
153
154    #[gtest]
155    fn unknown_toggle_reports_registered_slugs() -> Result<()> {
156        let error = resolve_bug_toggles(&BugSettings {
157            default_enabled: true,
158            overrides: vec![("unknown_bug".to_string(), false)],
159        })
160        .err()
161        .or_fail()?;
162
163        verify_true!(error.is_intent_validation())?;
164        verify_that!(error.to_string(), contains_substring("unknown_bug"))?;
165
166        verify_that!(error.to_string(), contains_substring(TEST_BUG.slug))
167    }
168}