Skip to main content

forge/manifest_ledger/collect/
rows.rs

1use wowlab_manifest_schema::{ManifestDamageDef, ScalarRef, SpellScalarField};
2
3use crate::manifest_ledger::types::{
4    LedgerDisposition, LedgerFinding, LedgerRow, LedgerSource, OperationCategory,
5};
6
7pub(super) fn damage_row(
8    owner: &str,
9    path: &str,
10    key: String,
11    spell_id: u32,
12    damage: &ManifestDamageDef,
13) -> LedgerRow {
14    match damage {
15        ManifestDamageDef::EffectRef {
16            spell_id: effect_spell,
17            effect,
18            ..
19        } => base_row(
20            owner,
21            path,
22            key,
23            OperationCategory::Damage,
24            LedgerDisposition::GenericGap,
25            "authored damage payload binds directly to DBC",
26        )
27        .owner(spell_id)
28        .effect(*effect_spell, *effect),
29        ManifestDamageDef::Ap { .. }
30        | ManifestDamageDef::Sp { .. }
31        | ManifestDamageDef::Flat { .. } => base_row(
32            owner,
33            path,
34            key,
35            OperationCategory::Damage,
36            LedgerDisposition::DataAbsent,
37            "authored literal damage payload requires spell-specific evidence",
38        )
39        .owner(spell_id),
40    }
41}
42
43// #t(fn: rust_large_fn_params) row provenance is intentionally explicit at each scalar call site.
44#[expect(
45    clippy::too_many_arguments,
46    reason = "ledger provenance remains explicit at scalar collection call sites"
47)]
48pub(super) fn scalar_option(
49    rows: &mut Vec<LedgerRow>,
50    owner: &str,
51    path: &str,
52    key: &str,
53    spell_id: u32,
54    scalar: Option<&ScalarRef>,
55    category: OperationCategory,
56    core_property: bool,
57) {
58    let Some(scalar) = scalar else {
59        return;
60    };
61    let disposition = if core_property && is_identity_core_property(key, spell_id, scalar) {
62        LedgerDisposition::RedundantGeneric
63    } else if matches!(scalar, ScalarRef::Literal(_)) {
64        LedgerDisposition::DataAbsent
65    } else {
66        LedgerDisposition::GenericGap
67    };
68
69    rows.push(scalar_row(
70        owner,
71        path,
72        key.to_string(),
73        spell_id,
74        scalar,
75        category,
76        disposition,
77        "authored scalar behavior",
78    ));
79}
80
81pub(super) fn is_identity_core_property(key: &str, spell_id: u32, scalar: &ScalarRef) -> bool {
82    let ScalarRef::SpellRef {
83        spell_id: source,
84        field,
85        multiplier: None,
86        offset: None,
87        round_decimals: None,
88    } = scalar
89    else {
90        return false;
91    };
92
93    if *source != spell_id {
94        return false;
95    }
96
97    matches!(
98        (key.rsplit('.').next(), field),
99        (Some("duration_ms"), SpellScalarField::DurationMs)
100            | (Some("max_stacks"), SpellScalarField::MaxStacks)
101            | (Some("cooldown"), SpellScalarField::Cooldown)
102            | (Some("cost"), SpellScalarField::Cost)
103            | (Some("cast_time_ms"), SpellScalarField::CastTimeMs)
104            | (Some("gcd_ms"), SpellScalarField::GcdMs)
105            | (Some("charges"), SpellScalarField::Charges)
106            | (Some("charge_cd"), SpellScalarField::ChargeCd)
107    )
108}
109
110// #t(fn: rust_large_fn_params) row provenance is intentionally explicit at each scalar call site.
111#[expect(
112    clippy::too_many_arguments,
113    reason = "ledger provenance remains explicit at scalar row construction call sites"
114)]
115pub(super) fn scalar_row(
116    owner: &str,
117    path: &str,
118    key: String,
119    spell_id: u32,
120    scalar: &ScalarRef,
121    category: OperationCategory,
122    disposition: LedgerDisposition,
123    evidence: &str,
124) -> LedgerRow {
125    let mut row = base_row(owner, path, key, category, disposition, evidence).owner(spell_id);
126
127    if let Some((effect_spell, effect)) = effect_coordinate(scalar) {
128        row = row.effect(effect_spell, effect);
129    }
130
131    row
132}
133
134pub(super) fn effect_coordinate(scalar: &ScalarRef) -> Option<(u32, u8)> {
135    match scalar {
136        ScalarRef::EffectRef {
137            spell_id, effect, ..
138        } => Some((*spell_id, *effect)),
139        ScalarRef::SpellRef { .. } | ScalarRef::Literal(_) => None,
140    }
141}
142
143pub(super) fn hook_placeholder(
144    rows: &mut Vec<LedgerRow>,
145    owner: &str,
146    path: &str,
147    key: &str,
148    spell_id: u32,
149    category: OperationCategory,
150) {
151    rows.push(
152        base_row(
153            owner,
154            path,
155            key,
156            category,
157            LedgerDisposition::Unresolved,
158            // #t(rust_duplicate_strings) Each ledger stage owns explicit placeholder evidence without coupling its private modules.
159            "hook body requires operation-level source inventory",
160        )
161        .owner(spell_id),
162    );
163}
164
165pub(super) fn base_row(
166    owner: &str,
167    path: &str,
168    key: impl Into<String>,
169    category: OperationCategory,
170    disposition: LedgerDisposition,
171    evidence: impl Into<String>,
172) -> LedgerRow {
173    LedgerRow::new(
174        LedgerSource::new(owner, path, key),
175        LedgerFinding::new(category, disposition, evidence),
176    )
177}