diff --git a/assets/common/items/crafting_ing/tin_ingot.ron b/assets/common/items/crafting_ing/tin_ingot.ron
index 8995168457..1d404b0317 100644
--- a/assets/common/items/crafting_ing/tin_ingot.ron
+++ b/assets/common/items/crafting_ing/tin_ingot.ron
@@ -5,5 +5,5 @@ ItemDef(
         kind: "TinIngot",
     ),
     quality: Common,
-    tags: [MetalIngot(0.25)],
+    tags: [MetalIngot],
 )
diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron
index 9eed04e03f..39bee54c22 100644
--- a/assets/common/recipe_book.ron
+++ b/assets/common/recipe_book.ron
@@ -363,11 +363,11 @@
             (Item("common.items.crafting_tools.sewing_set"), 0),
         ],
     ),
-    "metal_blade": (
-        ("common.items.crafting_ing.modular.damage.sword.metal_blade", 1),
-        [
-            (Tag(MetalIngot), 5),
-            (Item("common.items.crafting_tools.craftsman_hammer"), 0),
-        ],
-    ),
+    //"metal_blade": (
+    //    ("common.items.crafting_ing.modular.damage.sword.metal_blade", 1),
+    //    [
+    //        (Tag(MetalIngot), 5),
+    //        (Item("common.items.crafting_tools.craftsman_hammer"), 0),
+    //    ],
+    //),
 }
diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs
index 80f928f28e..11dad4f3a9 100644
--- a/common/src/comp/inventory/item/tool.rs
+++ b/common/src/comp/inventory/item/tool.rs
@@ -72,6 +72,13 @@ impl Stats {
             speed: 0.0,
         }
     }
+
+    pub fn clamp_speed(mut self) -> Stats {
+        // if a tool has 0.0 speed, that panics due to being infinite duration, so
+        // enforce speed >= 0.1 on the final product (but not the intermediates)
+        self.speed = self.speed.max(0.1);
+        self
+    }
 }
 
 impl Asset for Stats {
@@ -112,7 +119,7 @@ impl DivAssign<usize> for Stats {
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct MaterialStatManifest(HashMap<String, Stats>);
+pub struct MaterialStatManifest(pub HashMap<String, Stats>);
 
 // This could be a Compound that also loads the keys, but the RecipeBook
 // Compound impl already does that, so checking for existence here is redundant.
@@ -166,16 +173,13 @@ impl StatKind {
             average_mult /= multipliers.len();
             stats *= average_mult;
         }
-        // if an item has 0.0 speed, that panics due to being infinite duration, so
-        // enforce speed >= 0.1
-        stats.speed = stats.speed.max(0.1);
         stats
     }
 }
 
 impl From<(&MaterialStatManifest, &[Item], &Tool)> for Stats {
     fn from((msm, components, tool): (&MaterialStatManifest, &[Item], &Tool)) -> Self {
-        let raw_stats = tool.stats.resolve_stats(msm, components);
+        let raw_stats = tool.stats.resolve_stats(msm, components).clamp_speed();
         let (power, speed) = match tool.hands {
             Hands::One => (0.67, 1.33),
             // TODO: Restore this when one-handed weapons are made accessible
@@ -245,7 +249,10 @@ impl Tool {
     }
 
     pub fn base_speed(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 {
-        self.stats.resolve_stats(msm, components).speed
+        self.stats
+            .resolve_stats(msm, components)
+            .clamp_speed()
+            .speed
     }
 
     pub fn equip_time(&self, msm: &MaterialStatManifest, components: &[Item]) -> Duration {
diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs
index 7460a3bb74..f5a5db77ba 100644
--- a/voxygen/src/hud/bag.rs
+++ b/voxygen/src/hud/bag.rs
@@ -509,6 +509,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Head)),
                 || (i18n.get("hud.bag.head"), ""),
+                &self.msm,
             );
             let head_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Head))
@@ -531,6 +532,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Neck)),
                 || (i18n.get("hud.bag.neck"), ""),
+                &self.msm,
             );
             let neck_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Neck))
@@ -554,6 +556,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Chest)),
                 || (i18n.get("hud.bag.chest"), ""),
+                &self.msm,
             );
             let chest_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Chest))
@@ -576,6 +579,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Shoulders)),
                 || (i18n.get("hud.bag.shoulders"), ""),
+                &self.msm,
             );
             let shoulder_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Shoulders))
@@ -598,6 +602,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Hands)),
                 || (i18n.get("hud.bag.hands"), ""),
+                &self.msm,
             );
             let chest_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Hands))
@@ -620,6 +625,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Belt)),
                 || (i18n.get("hud.bag.belt"), ""),
+                &self.msm,
             );
             let belt_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Belt))
@@ -642,6 +648,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Legs)),
                 || (i18n.get("hud.bag.legs"), ""),
+                &self.msm,
             );
             let legs_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Legs))
@@ -664,6 +671,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Ring1)),
                 || (i18n.get("hud.bag.ring"), ""),
+                &self.msm,
             );
             let ring_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Ring1))
@@ -686,6 +694,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Ring2)),
                 || (i18n.get("hud.bag.ring"), ""),
+                &self.msm,
             );
             let ring2_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Ring2))
@@ -708,6 +717,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Back)),
                 || (i18n.get("hud.bag.back"), ""),
+                &self.msm,
             );
             let back_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Back))
@@ -730,6 +740,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Feet)),
                 || (i18n.get("hud.bag.feet"), ""),
+                &self.msm,
             );
             let foot_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Feet))
@@ -749,9 +760,11 @@ impl<'a> Widget for Bag<'a> {
                 )
                 .set(state.ids.feet_slot, ui);
             // Lantern
-            let (title, desc) = loadout_slot_text(inventory.equipped(EquipSlot::Lantern), || {
-                (i18n.get("hud.bag.lantern"), "")
-            });
+            let (title, desc) = loadout_slot_text(
+                inventory.equipped(EquipSlot::Lantern),
+                || (i18n.get("hud.bag.lantern"), ""),
+                &self.msm,
+            );
             let lantern_q_col = inventory
                 .equipped(EquipSlot::Lantern)
                 .map(|item| get_quality_col(item))
@@ -770,9 +783,11 @@ impl<'a> Widget for Bag<'a> {
                 )
                 .set(state.ids.lantern_slot, ui);
             // Glider
-            let (title, desc) = loadout_slot_text(inventory.equipped(EquipSlot::Glider), || {
-                (i18n.get("hud.bag.glider"), "")
-            });
+            let (title, desc) = loadout_slot_text(
+                inventory.equipped(EquipSlot::Glider),
+                || (i18n.get("hud.bag.glider"), ""),
+                &self.msm,
+            );
             let glider_q_col = inventory
                 .equipped(EquipSlot::Glider)
                 .map(|item| get_quality_col(item))
@@ -794,6 +809,7 @@ impl<'a> Widget for Bag<'a> {
             let (title, desc) = loadout_slot_text(
                 inventory.equipped(EquipSlot::Armor(ArmorSlot::Tabard)),
                 || (i18n.get("hud.bag.tabard"), ""),
+                &self.msm,
             );
             let tabard_q_col = inventory
                 .equipped(EquipSlot::Armor(ArmorSlot::Tabard))
@@ -813,9 +829,11 @@ impl<'a> Widget for Bag<'a> {
                 )
                 .set(state.ids.tabard_slot, ui);
             // Mainhand/Left-Slot
-            let (title, desc) = loadout_slot_text(inventory.equipped(EquipSlot::Mainhand), || {
-                (i18n.get("hud.bag.mainhand"), "")
-            });
+            let (title, desc) = loadout_slot_text(
+                inventory.equipped(EquipSlot::Mainhand),
+                || (i18n.get("hud.bag.mainhand"), ""),
+                &self.msm,
+            );
             let mainhand_q_col = inventory
                 .equipped(EquipSlot::Mainhand)
                 .map(|item| get_quality_col(item))
@@ -834,9 +852,11 @@ impl<'a> Widget for Bag<'a> {
                 )
                 .set(state.ids.mainhand_slot, ui);
             // Offhand/Right-Slot
-            let (title, desc) = loadout_slot_text(inventory.equipped(EquipSlot::Offhand), || {
-                (i18n.get("hud.bag.offhand"), "")
-            });
+            let (title, desc) = loadout_slot_text(
+                inventory.equipped(EquipSlot::Offhand),
+                || (i18n.get("hud.bag.offhand"), ""),
+                &self.msm,
+            );
             let offhand_q_col = inventory
                 .equipped(EquipSlot::Offhand)
                 .map(|item| get_quality_col(item))
@@ -859,6 +879,7 @@ impl<'a> Widget for Bag<'a> {
         let (title, desc) = loadout_slot_text(
             inventory.equipped(EquipSlot::Armor(ArmorSlot::Bag1)),
             || (i18n.get("hud.bag.bag"), ""),
+            &self.msm,
         );
         let bag1_q_col = inventory
             .equipped(EquipSlot::Armor(ArmorSlot::Bag1))
@@ -885,6 +906,7 @@ impl<'a> Widget for Bag<'a> {
         let (title, desc) = loadout_slot_text(
             inventory.equipped(EquipSlot::Armor(ArmorSlot::Bag2)),
             || (i18n.get("hud.bag.bag"), ""),
+            &self.msm,
         );
         let bag2_q_col = inventory
             .equipped(EquipSlot::Armor(ArmorSlot::Bag2))
@@ -907,6 +929,7 @@ impl<'a> Widget for Bag<'a> {
         let (title, desc) = loadout_slot_text(
             inventory.equipped(EquipSlot::Armor(ArmorSlot::Bag3)),
             || (i18n.get("hud.bag.bag"), ""),
+            &self.msm,
         );
         let bag3_q_col = inventory
             .equipped(EquipSlot::Armor(ArmorSlot::Bag3))
@@ -929,6 +952,7 @@ impl<'a> Widget for Bag<'a> {
         let (title, desc) = loadout_slot_text(
             inventory.equipped(EquipSlot::Armor(ArmorSlot::Bag4)),
             || (i18n.get("hud.bag.bag"), ""),
+            &self.msm,
         );
         let bag4_q_col = inventory
             .equipped(EquipSlot::Armor(ArmorSlot::Bag4))
@@ -1011,7 +1035,7 @@ impl<'a> Widget for Bag<'a> {
             }
 
             if let Some(item) = item {
-                let (title, desc) = super::util::item_text(item);
+                let (title, desc) = super::util::item_text(item, &self.msm);
                 let quality_col = get_quality_col(item);
                 let quality_col_img = match item.quality() {
                     Quality::Low => self.imgs.inv_slot_grey,
diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs
index f071951e58..97aa47706b 100644
--- a/voxygen/src/hud/crafting.rs
+++ b/voxygen/src/hud/crafting.rs
@@ -12,7 +12,7 @@ use client::{self, Client};
 use common::{
     assets::AssetExt,
     comp::{
-        item::{ItemDef, ItemDesc, Quality, TagExampleInfo},
+        item::{ItemDef, ItemDesc, MaterialStatManifest, Quality, TagExampleInfo},
         Inventory,
     },
     recipe::RecipeInput,
@@ -68,6 +68,7 @@ pub struct Crafting<'a> {
     tooltip_manager: &'a mut TooltipManager,
     item_imgs: &'a ItemImgs,
     inventory: &'a Inventory,
+    msm: &'a MaterialStatManifest,
     #[conrod(common_builder)]
     common: widget::CommonBuilder,
 }
@@ -83,6 +84,7 @@ impl<'a> Crafting<'a> {
         tooltip_manager: &'a mut TooltipManager,
         item_imgs: &'a ItemImgs,
         inventory: &'a Inventory,
+        msm: &'a MaterialStatManifest,
     ) -> Self {
         Self {
             client,
@@ -94,6 +96,7 @@ impl<'a> Crafting<'a> {
             tooltip_manager,
             item_imgs,
             inventory,
+            msm,
             common: widget::CommonBuilder::default(),
         }
     }
@@ -297,7 +300,7 @@ impl<'a> Widget for Crafting<'a> {
                 {
                     let output_text = format!("x{}", &recipe.output.1.to_string());
                     // Output Image
-                    let (title, desc) = super::util::item_text(&*recipe.output.0);
+                    let (title, desc) = super::util::item_text(&*recipe.output.0, self.msm);
                     let quality_col = get_quality_col(&*recipe.output.0);
                     Button::image(animate_by_pulse(
                         &self
@@ -488,7 +491,7 @@ impl<'a> Widget for Crafting<'a> {
                 };
                 frame.set(state.ids.ingredient_frame[i], ui);
                 //Item Image
-                let (title, desc) = super::util::item_text(&*item_def);
+                let (title, desc) = super::util::item_text(&*item_def, self.msm);
                 Button::image(animate_by_pulse(
                     &self.item_imgs.img_ids_or_not_found_img((&*item_def).into()),
                     self.pulse,
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index 003248e1d3..01f13e6687 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -2143,6 +2143,7 @@ impl Hud {
                 tooltip_manager,
                 &mut self.slot_manager,
                 i18n,
+                &msm,
                 self.pulse,
             )
             .set(self.ids.trade, ui_widgets)
@@ -2207,6 +2208,7 @@ impl Hud {
                     tooltip_manager,
                     &self.item_imgs,
                     &inventory,
+                    &msm,
                 )
                 .set(self.ids.crafting_window, ui_widgets)
                 {
diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs
index f11aea24f9..4bfeddf724 100644
--- a/voxygen/src/hud/skillbar.rs
+++ b/voxygen/src/hud/skillbar.rs
@@ -400,7 +400,13 @@ impl<'a> Widget for Skillbar<'a> {
                 .set(state.ids.stamina_txt, ui);
         }
         // Slots
-        let content_source = (self.hotbar, self.inventory, self.energy, self.ability_map); // TODO: avoid this
+        let content_source = (
+            self.hotbar,
+            self.inventory,
+            self.energy,
+            self.ability_map,
+            self.msm,
+        ); // TODO: avoid this
         let image_source = (self.item_imgs, self.imgs);
         let mut slot_maker = SlotMaker {
             // TODO: is a separate image needed for the frame?
diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs
index 7dd43f47b6..bd8e4446d9 100644
--- a/voxygen/src/hud/slots.rs
+++ b/voxygen/src/hud/slots.rs
@@ -2,13 +2,12 @@ use super::{
     hotbar::{self, Slot as HotbarSlot},
     img_ids,
     item_imgs::{ItemImgs, ItemKey},
-    util::MATERIAL_STATS_MANIFEST,
 };
 use crate::ui::slot::{self, SlotKey, SumSlot};
 use common::comp::{
     item::{
         tool::{AbilityMap, Hands, ToolKind},
-        ItemKind,
+        ItemKind, MaterialStatManifest,
     },
     slot::InvSlotId,
     Energy, Inventory,
@@ -102,7 +101,13 @@ pub enum HotbarImage {
     BowJumpBurst,
 }
 
-type HotbarSource<'a> = (&'a hotbar::State, &'a Inventory, &'a Energy, &'a AbilityMap);
+type HotbarSource<'a> = (
+    &'a hotbar::State,
+    &'a Inventory,
+    &'a Energy,
+    &'a AbilityMap,
+    &'a MaterialStatManifest,
+);
 type HotbarImageSource<'a> = (&'a ItemImgs, &'a img_ids::Imgs);
 
 impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
@@ -110,7 +115,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
 
     fn image_key(
         &self,
-        (hotbar, inventory, energy, ability_map): &HotbarSource<'a>,
+        (hotbar, inventory, energy, ability_map, msm): &HotbarSource<'a>,
     ) -> Option<(Self::ImageKey, Option<Color>)> {
         hotbar.get(*self).and_then(|contents| match contents {
             hotbar::SlotContents::Inventory(idx) => inventory
@@ -131,11 +136,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
                         (
                             i,
                             if let Some(skill) = tool
-                                .get_abilities(
-                                    &MATERIAL_STATS_MANIFEST,
-                                    item.components(),
-                                    ability_map,
-                                )
+                                .get_abilities(&msm, item.components(), ability_map)
                                 .abilities
                                 .get(0)
                             {
@@ -178,11 +179,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
                         (
                             i,
                             if let Some(skill) = tool
-                                .get_abilities(
-                                    &MATERIAL_STATS_MANIFEST,
-                                    item.components(),
-                                    ability_map,
-                                )
+                                .get_abilities(&msm, item.components(), ability_map)
                                 .abilities
                                 .get(skill_index)
                             {
@@ -201,7 +198,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
         })
     }
 
-    fn amount(&self, (hotbar, inventory, _, _): &HotbarSource<'a>) -> Option<u32> {
+    fn amount(&self, (hotbar, inventory, _, _, _): &HotbarSource<'a>) -> Option<u32> {
         hotbar
             .get(*self)
             .and_then(|content| match content {
diff --git a/voxygen/src/hud/trade.rs b/voxygen/src/hud/trade.rs
index d5312e3ced..150082e625 100644
--- a/voxygen/src/hud/trade.rs
+++ b/voxygen/src/hud/trade.rs
@@ -15,7 +15,10 @@ use crate::{
 };
 use client::Client;
 use common::{
-    comp::{inventory::item::Quality, Inventory},
+    comp::{
+        inventory::item::{MaterialStatManifest, Quality},
+        Inventory,
+    },
     trade::{PendingTrade, TradeAction, TradePhase},
 };
 use common_net::sync::WorldSyncExt;
@@ -61,6 +64,7 @@ pub struct Trade<'a> {
     common: widget::CommonBuilder,
     slot_manager: &'a mut SlotManager,
     localized_strings: &'a Localization,
+    msm: &'a MaterialStatManifest,
     pulse: f32,
 }
 
@@ -74,6 +78,7 @@ impl<'a> Trade<'a> {
         tooltip_manager: &'a mut TooltipManager,
         slot_manager: &'a mut SlotManager,
         localized_strings: &'a Localization,
+        msm: &'a MaterialStatManifest,
         pulse: f32,
     ) -> Self {
         Self {
@@ -84,9 +89,9 @@ impl<'a> Trade<'a> {
             rot_imgs,
             tooltip_manager,
             common: widget::CommonBuilder::default(),
-            //tooltip_manager,
             slot_manager,
             localized_strings,
+            msm,
             pulse,
         }
     }
@@ -295,7 +300,7 @@ impl<'a> Trade<'a> {
                 );
             let slot_id = state.ids.inv_slots[i + who * MAX_TRADE_SLOTS];
             if let Some(Some(item)) = slot.invslot.and_then(|slotid| inventory.slot(slotid)) {
-                let (title, desc) = super::util::item_text(item);
+                let (title, desc) = super::util::item_text(item, self.msm);
                 let quality_col = get_quality_col(item);
                 let quality_col_img = match item.quality() {
                     Quality::Low => self.imgs.inv_slot_grey,
diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs
index ad6c324874..cde747dfe4 100644
--- a/voxygen/src/hud/util.rs
+++ b/voxygen/src/hud/util.rs
@@ -1,30 +1,28 @@
 use common::comp::item::{
     armor::{Armor, ArmorKind, Protection},
-    tool::{Hands, StatKind, Tool, ToolKind},
+    tool::{Hands, StatKind, Stats, Tool, ToolKind},
     Item, ItemDesc, ItemKind, MaterialStatManifest, ModularComponent,
 };
-use lazy_static::lazy_static;
 use std::{borrow::Cow, fmt::Write};
 
-lazy_static! {
-    // TODO: even more plumbing
-    pub static ref MATERIAL_STATS_MANIFEST: MaterialStatManifest = MaterialStatManifest::default();
-}
-
 pub fn loadout_slot_text<'a>(
     item: Option<&'a impl ItemDesc>,
     mut empty: impl FnMut() -> (&'a str, &'a str),
+    msm: &'a MaterialStatManifest,
 ) -> (&'a str, Cow<'a, str>) {
     item.map_or_else(
         || {
             let (title, desc) = empty();
             (title, Cow::Borrowed(desc))
         },
-        item_text,
+        |item| item_text(item, msm),
     )
 }
 
-pub fn item_text<'a>(item: &'a impl ItemDesc) -> (&'_ str, Cow<'a, str>) {
+pub fn item_text<'a>(
+    item: &'a impl ItemDesc,
+    msm: &'a MaterialStatManifest,
+) -> (&'a str, Cow<'a, str>) {
     let desc: Cow<str> = match item.kind() {
         ItemKind::Armor(armor) => {
             Cow::Owned(armor_desc(armor, item.description(), item.num_slots()))
@@ -32,20 +30,24 @@ pub fn item_text<'a>(item: &'a impl ItemDesc) -> (&'_ str, Cow<'a, str>) {
         ItemKind::Tool(tool) => Cow::Owned(tool_desc(
             &tool,
             item.components(),
-            &MATERIAL_STATS_MANIFEST,
+            &msm,
             item.description(),
         )),
         ItemKind::ModularComponent(mc) => Cow::Owned(modular_component_desc(
             mc,
             item.components(),
-            &MATERIAL_STATS_MANIFEST,
+            &msm,
             item.description(),
         )),
         ItemKind::Glider(_glider) => Cow::Owned(glider_desc(item.description())),
         ItemKind::Consumable { .. } => Cow::Owned(consumable_desc(item.description())),
         ItemKind::Throwable { .. } => Cow::Owned(throwable_desc(item.description())),
         ItemKind::Utility { .. } => Cow::Owned(utility_desc(item.description())),
-        ItemKind::Ingredient { .. } => Cow::Owned(ingredient_desc(item.description())),
+        ItemKind::Ingredient { .. } => Cow::Owned(ingredient_desc(
+            item.description(),
+            item.item_definition_id(),
+            msm,
+        )),
         ItemKind::Lantern { .. } => Cow::Owned(lantern_desc(item.description())),
         ItemKind::TagExamples { .. } => Cow::Borrowed(item.description()),
         //_ => Cow::Borrowed(item.description()),
@@ -61,13 +63,11 @@ fn modular_component_desc(
     msm: &MaterialStatManifest,
     description: &str,
 ) -> String {
-    let mut result = format!(
-        "Modular Component\n\n{:?}\n\n{}",
-        StatKind::Direct(mc.stats).resolve_stats(msm, components),
-        description
-    );
+    let stats = StatKind::Direct(mc.stats).resolve_stats(msm, components);
+    let statblock = statblock_desc(&stats);
+    let mut result = format!("Modular Component\n\n{}\n\n{}", statblock, description);
     if !components.is_empty() {
-        result += "Made from:\n";
+        result += "\n\nMade from:\n";
         for component in components {
             result += component.name();
             result += "\n"
@@ -88,7 +88,14 @@ fn throwable_desc(desc: &str) -> String {
 
 fn utility_desc(desc: &str) -> String { format!("{}\n\n<Right-Click to use>", desc) }
 
-fn ingredient_desc(desc: &str) -> String { format!("Crafting Ingredient\n\n{}", desc) }
+fn ingredient_desc(desc: &str, item_id: &str, msm: &MaterialStatManifest) -> String {
+    let mut result = format!("Crafting Ingredient\n\n{}", desc);
+    if let Some(stats) = msm.0.get(item_id) {
+        result += "\n\nStat multipliers:\n";
+        result += &statblock_desc(stats);
+    }
+    result
+}
 
 fn lantern_desc(desc: &str) -> String { format!("Lantern\n\n{}\n\n<Right-Click to use>", desc) }
 
@@ -153,26 +160,16 @@ fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc:
     };
 
     // Get tool stats
-    let power = tool.base_power(msm, components);
+    let stats = tool.stats.resolve_stats(msm, components).clamp_speed();
+
     //let poise_strength = tool.base_poise_strength();
     let hands = match tool.hands {
         Hands::One => "One",
         Hands::Two => "Two",
     };
-    let speed = tool.base_speed(msm, components);
 
-    let mut result = format!(
-        "{}-Handed {}\n\nDPS: {:0.1}\n\nPower: {:0.1}\n\nSpeed: {:0.1}\n\n",
-        // add back when ready for poise
-        //"{}\n\nDPS: {:0.1}\n\nPower: {:0.1}\n\nPoise Strength: {:0.1}\n\nSpeed: \
-        // {:0.1}\n\n{}\n\n<Right-Click to use>",
-        hands,
-        kind,
-        speed * power * 10.0, // Damage per second
-        power * 10.0,
-        //poise_strength * 10.0,
-        speed
-    );
+    let mut result = format!("{}-Handed {}\n\n", hands, kind);
+    result += &statblock_desc(&stats);
     if !components.is_empty() {
         result += "Made from:\n";
         for component in components {
@@ -188,6 +185,19 @@ fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc:
     result
 }
 
+fn statblock_desc(stats: &Stats) -> String {
+    format!(
+        "DPS: {:0.1}\n\nPower: {:0.1}\n\nSpeed: {:0.1}\n\n",
+        // add back when ready for poise
+        //"{}\n\nDPS: {:0.1}\n\nPower: {:0.1}\n\nPoise Strength: {:0.1}\n\nSpeed: \
+        // {:0.1}\n\n{}\n\n<Right-Click to use>",
+        stats.speed * stats.power * 10.0, // Damage per second
+        stats.power * 10.0,
+        //stats.poise_strength * 10.0,
+        stats.speed
+    )
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -234,11 +244,29 @@ mod tests {
 
     #[test]
     fn test_ingredient_desc() {
-        let item_description = "mushrooms";
+        let mut testmsm = MaterialStatManifest(hashbrown::HashMap::new());
+        testmsm.0.insert(
+            "common.items.crafting_ing.bronze_ingot".to_string(),
+            Stats {
+                equip_time_millis: 0,
+                power: 3.0,
+                poise_strength: 5.0,
+                speed: 7.0,
+            },
+        );
 
         assert_eq!(
             "Crafting Ingredient\n\nmushrooms",
-            ingredient_desc(item_description)
+            ingredient_desc("mushrooms", "common.items.food.mushroom", &testmsm)
+        );
+        assert_eq!(
+            "Crafting Ingredient\n\nA bronze ingot.\n\nStat multipliers:\nDPS: 210.0\n\nPower: \
+             30.0\n\nSpeed: 7.0\n\n",
+            ingredient_desc(
+                "A bronze ingot.",
+                "common.items.crafting_ing.bronze_ingot",
+                &testmsm
+            )
         );
     }