forge/manifest_ledger/collect/
rows.rs1use 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#[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#[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 "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}