diff --git a/crates/save/src/lib.rs b/crates/save/src/lib.rs
index eb6152f..a59cb26 100644
--- a/crates/save/src/lib.rs
+++ b/crates/save/src/lib.rs
@@ -2,12 +2,12 @@ mod companion;
 mod glyphs;
 mod level;
 mod murals;
+mod robe;
 mod scarf;
 mod symbol;
 mod test;
 
 
-use core::fmt;
 use std::fs::File;
 use std::io::{self, Read, Write};
 use std::path::{Path, PathBuf};
@@ -15,6 +15,7 @@ use std::path::{Path, PathBuf};
 use binrw::{until_eof, BinRead, BinReaderExt, BinWriterExt};
 use chrono::{DateTime, NaiveDateTime, Utc};
 use level::Level;
+use robe::Robe;
 use scarf::Scarf;
 use symbol::Symbol;
 
@@ -22,6 +23,7 @@ use crate::companion::{CompanionSymbols, CompanionWithId, Companions};
 use crate::glyphs::Glyphs;
 pub use crate::level::NAMES as LEVEL_NAMES;
 use crate::murals::Murals;
+pub use crate::robe::Color as RobeColor;
 
 
 pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -53,27 +55,14 @@ pub enum Error {
     #[error("Symbol id is out of range")]
     SymbolIdOutOfRange,
 
+    #[error(transparent)]
+    RobeChange(robe::Error),
+
     #[error("Failed to read file")]
     FileReadingFailed(io::Error),
 }
 
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum RobeColor {
-    Red,
-    White,
-}
-
-impl fmt::Display for RobeColor {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::Red => write!(f, "Red"),
-            Self::White => write!(f, "White"),
-        }
-    }
-}
-
-
 #[binrw::binrw]
 #[derive(Debug, Clone)]
 #[brw(little)]
@@ -84,7 +73,7 @@ pub struct Savefile {
     #[br(count = 8)]
     _unknown0: Vec<u8>,
 
-    robe: u32,
+    pub robe: Robe,
 
     pub symbol: Symbol,
 
@@ -192,42 +181,6 @@ impl Savefile {
                 }
             })
     }
-
-    pub fn robe_color(&self) -> RobeColor {
-        if self.robe > 3 {
-            RobeColor::White
-        } else {
-            RobeColor::Red
-        }
-    }
-
-    pub fn set_robe_color(&mut self, color: RobeColor) {
-        self.robe = match (self.robe_color(), color) {
-            (RobeColor::Red, RobeColor::White) => self.robe + 4,
-            (RobeColor::White, RobeColor::Red) => self.robe - 4,
-            _ => return,
-        }
-    }
-
-    pub fn robe_tier(&self) -> u32 {
-        match self.robe_color() {
-            RobeColor::Red => self.robe + 1,
-            RobeColor::White => self.robe - 2,
-        }
-    }
-
-    pub fn set_robe_tier(&mut self, tier: u32) {
-        if tier < 1 || tier > 4 {
-            return;
-        }
-
-        self.robe = match self.robe_color() {
-            RobeColor::Red => tier - 1,
-            // There can't be a tier 1 white robe, setting it to the lowers possible tier
-            RobeColor::White if tier == 1 => 4,
-            RobeColor::White => tier + 2,
-        }
-    }
 }
 
 #[binrw::parser(reader, endian)]
diff --git a/crates/save/src/robe.rs b/crates/save/src/robe.rs
new file mode 100644
index 0000000..b00aa70
--- /dev/null
+++ b/crates/save/src/robe.rs
@@ -0,0 +1,192 @@
+use core::fmt;
+use std::str::FromStr;
+
+use binrw::{BinRead, BinWrite};
+
+use crate::Result;
+
+
+const MIN_TIER: u32 = 1;
+const MAX_TIER: u32 = 4;
+const MAX_RED_TIER_ID: u32 = 3;
+
+
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
+pub enum Error {
+    #[error("Tier must be in range from 1 to 4")]
+    TierOutOfRange,
+
+    #[error("White tier can not be lower than 2")]
+    WhiteTierMinimum,
+
+    #[error("Invalid color, expected red or white")]
+    InvalidColor,
+}
+
+
+#[derive(Debug, Clone, BinRead, BinWrite)]
+pub struct Robe {
+    value: u32,
+}
+
+impl Robe {
+    pub fn color(&self) -> Color {
+        if self.value > MAX_RED_TIER_ID {
+            Color::White
+        } else {
+            Color::Red
+        }
+    }
+
+    pub fn set_color(&mut self, color: Color) {
+        self.value = match (self.color(), color, self.value) {
+            (Color::Red, Color::White, 0) => MAX_RED_TIER_ID + 1,
+            (Color::Red, Color::White, val) => val + MAX_RED_TIER_ID,
+            (Color::White, Color::Red, val) => val - MAX_RED_TIER_ID,
+            _ => return,
+        }
+    }
+
+    pub fn swap_colors(&mut self) {
+        let new_color = match self.color() {
+            Color::Red => Color::White,
+            Color::White => Color::Red,
+        };
+
+        self.set_color(new_color);
+    }
+
+    pub fn tier(&self) -> u32 {
+        match self.color() {
+            Color::Red => self.value + 1,
+            Color::White => self.value - MAX_RED_TIER_ID + 1,
+        }
+    }
+
+    pub fn set_tier(&mut self, tier: u32) -> Result<(), Error> {
+        if tier < MIN_TIER || tier > MAX_TIER {
+            return Err(Error::TierOutOfRange);
+        }
+
+        self.value = match self.color() {
+            Color::Red => tier - 1,
+            Color::White if tier == MIN_TIER => {
+                return Err(Error::WhiteTierMinimum);
+            }
+            Color::White => MAX_RED_TIER_ID + tier - 1,
+        };
+
+        Ok(())
+    }
+
+    pub fn increase_tier(&mut self) {
+        let _ = self.set_tier(self.tier() + 1);
+    }
+
+    pub fn decrease_tier(&mut self) {
+        let _ = self.set_tier(self.tier() - 1);
+    }
+}
+
+impl fmt::Display for Robe {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.value)
+    }
+}
+
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Color {
+    Red,
+    White,
+}
+
+impl FromStr for Color {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "Red" | "red" => Ok(Self::Red),
+            "White" | "white" => Ok(Self::White),
+            _ => Err(Error::InvalidColor),
+        }
+    }
+}
+
+impl fmt::Display for Color {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Red => write!(f, "Red"),
+            Self::White => write!(f, "White"),
+        }
+    }
+}
+
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn change_robe_color() {
+        // lowest red tier
+        let mut robe = Robe { value: 0 };
+
+        assert_eq!(robe.color(), Color::Red);
+        assert_eq!(robe.tier(), 1);
+
+        robe.set_color(Color::White);
+
+        assert_eq!(robe.color(), Color::White);
+        assert_eq!(robe.tier(), 2);
+
+        robe.set_color(Color::Red);
+
+        assert_eq!(robe.color(), Color::Red);
+        assert_eq!(robe.tier(), 2);
+
+        // highest red tier
+        let mut robe = Robe {
+            value: MAX_RED_TIER_ID,
+        };
+
+        assert_eq!(robe.color(), Color::Red);
+        assert_eq!(robe.tier(), 4);
+
+        robe.set_color(Color::White);
+
+        assert_eq!(robe.color(), Color::White);
+        assert_eq!(robe.tier(), 4);
+
+        robe.set_color(Color::Red);
+
+        assert_eq!(robe.color(), Color::Red);
+        assert_eq!(robe.tier(), 4);
+    }
+
+
+    #[test]
+    fn change_red_robe_tier() {
+        let mut robe = Robe { value: 0 };
+
+        for tier in 1..=4 {
+            robe.set_tier(tier).unwrap();
+            assert_eq!(robe.tier(), tier, "unexpected tier");
+            assert_eq!(robe.color(), Color::Red, "unexpected color");
+        }
+    }
+
+    #[test]
+    fn change_white_robe_tier() {
+        let mut robe = Robe { value: 4 };
+
+        let result = robe.set_tier(1);
+        assert_eq!(result, Err(Error::WhiteTierMinimum));
+
+        for (tier, expected) in (2..=4).zip([2, 3, 4]) {
+            robe.set_tier(tier).unwrap();
+            assert_eq!(robe.tier(), expected, "unexpected tier");
+            assert_eq!(robe.color(), Color::White, "unexpected color");
+        }
+    }
+}
diff --git a/crates/save/src/test.rs b/crates/save/src/test.rs
index 7da8bef..5099e10 100644
--- a/crates/save/src/test.rs
+++ b/crates/save/src/test.rs
@@ -12,9 +12,8 @@ use crate::*;
 fn general_info() {
     let savefile = savefile();
 
-    assert_eq!(savefile.robe, 3);
-    assert_eq!(savefile.robe_color(), RobeColor::Red);
-    assert_eq!(savefile.robe_tier(), 4);
+    assert_eq!(savefile.robe.color(), RobeColor::Red);
+    assert_eq!(savefile.robe.tier(), 4);
 
     assert_eq!(savefile.symbol.as_ref(), &7);
     assert_eq!(savefile.scarf_length.as_ref(), &27);
@@ -149,58 +148,6 @@ fn murals() {
 }
 
 
-#[test]
-fn change_robe_color() {
-    let mut savefile = savefile();
-
-    // lowest tier
-    savefile.robe = 0;
-
-    savefile.set_robe_color(RobeColor::White);
-    assert_eq!(savefile.robe_color(), RobeColor::White);
-
-    savefile.set_robe_color(RobeColor::Red);
-    assert_eq!(savefile.robe_color(), RobeColor::Red);
-
-    // highest tier
-    savefile.robe = 3;
-
-    savefile.set_robe_color(RobeColor::White);
-    assert_eq!(savefile.robe_color(), RobeColor::White);
-
-    savefile.set_robe_color(RobeColor::Red);
-    assert_eq!(savefile.robe_color(), RobeColor::Red);
-}
-
-
-#[test]
-fn change_robe_tier() {
-    let mut savefile = savefile();
-
-    savefile.set_robe_tier(1);
-    assert_eq!(savefile.robe_tier(), 1);
-    assert_eq!(savefile.robe_color(), RobeColor::Red);
-
-    savefile.set_robe_tier(4);
-    assert_eq!(savefile.robe_tier(), 4);
-    assert_eq!(savefile.robe_color(), RobeColor::Red);
-
-    savefile.set_robe_color(RobeColor::White);
-
-    savefile.set_robe_tier(2);
-    assert_eq!(savefile.robe_tier(), 2);
-    assert_eq!(savefile.robe_color(), RobeColor::White);
-
-    savefile.set_robe_tier(1);
-    assert_eq!(savefile.robe_tier(), 2);
-    assert_eq!(savefile.robe_color(), RobeColor::White);
-
-    savefile.set_robe_tier(4);
-    assert_eq!(savefile.robe_tier(), 4);
-    assert_eq!(savefile.robe_color(), RobeColor::White);
-}
-
-
 fn savefile() -> Savefile {
     const TEST_FILE: &[u8] = include_bytes!("../test.bin");
     let mut savefile = Cursor::new(TEST_FILE);
diff --git a/crates/wayfarer/src/edit.rs b/crates/wayfarer/src/edit.rs
index 223b852..5a32335 100644
--- a/crates/wayfarer/src/edit.rs
+++ b/crates/wayfarer/src/edit.rs
@@ -68,14 +68,14 @@ fn edit_file(cur_savefile: &Savefile, args: &Args) -> Result<Savefile> {
 
     if let Some(color) = &args.robe_color {
         match color.as_ref() {
-            "red" => savefile.set_robe_color(RobeColor::Red),
-            "white" => savefile.set_robe_color(RobeColor::White),
+            "red" => savefile.robe.set_color(RobeColor::Red),
+            "white" => savefile.robe.set_color(RobeColor::White),
             _ => (),
         }
     }
 
     if let Some(tier) = args.robe_tier {
-        savefile.set_robe_tier(tier);
+        savefile.robe.set_tier(tier)?;
     }
 
     Ok(savefile)
diff --git a/crates/wayfarer/src/tui/state.rs b/crates/wayfarer/src/tui/state.rs
index 3a94414..ef4e022 100644
--- a/crates/wayfarer/src/tui/state.rs
+++ b/crates/wayfarer/src/tui/state.rs
@@ -6,7 +6,7 @@ use std::path::Path;
 use std::time::{Duration, Instant};
 
 use anyhow::{bail, Context, Result};
-use jrny_save::{RobeColor, Savefile};
+use jrny_save::Savefile;
 use ratatui::widgets::TableState;
 use tracing::{debug, error};
 use tui_input::Input;
@@ -175,15 +175,8 @@ impl State {
             4 => savefile.companions_met = value.parse()?,
             5 => savefile.scarf_length.set_length(value.parse()?)?,
             6 => savefile.symbol.set_by_id(value.parse()?)?,
-            7 => {
-                let new_color = match value {
-                    "Red" | "red" => RobeColor::Red,
-                    "White" | "white" => RobeColor::White,
-                    _ => bail!("invalid robe color"),
-                };
-                savefile.set_robe_color(new_color);
-            }
-            8 => savefile.set_robe_tier(value.parse()?),
+            7 => savefile.robe.set_color(value.parse()?),
+            8 => savefile.robe.set_tier(value.parse()?)?,
             9 => {}
             idx => debug!("unknown index {:?}", idx),
         }
@@ -213,16 +206,8 @@ impl State {
             4 => savefile.companions_met += 1,
             5 => savefile.scarf_length.increase_length()?,
             6 => savefile.symbol = savefile.symbol.wrapping_next(),
-            7 => {
-                savefile.set_robe_color(match savefile.robe_color() {
-                    RobeColor::Red => RobeColor::White,
-                    RobeColor::White => RobeColor::Red,
-                });
-            }
-            8 => {
-                let next_tier = savefile.robe_tier() + 1;
-                savefile.set_robe_tier(next_tier);
-            }
+            7 => savefile.robe.swap_colors(),
+            8 => savefile.robe.increase_tier(),
             9 => {}
             idx => debug!("unknown index {:?}", idx),
         }
@@ -255,16 +240,8 @@ impl State {
             4 => savefile.companions_met = savefile.companions_met.saturating_sub(1),
             5 => savefile.scarf_length.decrease_length()?,
             6 => savefile.symbol = savefile.symbol.wrapping_previous(),
-            7 => {
-                savefile.set_robe_color(match savefile.robe_color() {
-                    RobeColor::Red => RobeColor::White,
-                    RobeColor::White => RobeColor::Red,
-                });
-            }
-            8 => {
-                let next_tier = savefile.robe_tier().saturating_sub(1);
-                savefile.set_robe_tier(next_tier);
-            }
+            7 => savefile.robe.swap_colors(),
+            8 => savefile.robe.decrease_tier(),
             9 => {}
             idx => debug!("unknown index {:?}", idx),
         }
diff --git a/crates/wayfarer/src/tui/view/info/stats.rs b/crates/wayfarer/src/tui/view/info/stats.rs
index aef85ee..6e13755 100644
--- a/crates/wayfarer/src/tui/view/info/stats.rs
+++ b/crates/wayfarer/src/tui/view/info/stats.rs
@@ -56,8 +56,8 @@ pub(super) fn render<'a>(state: &mut State, frame: &mut Frame, area: Rect) {
         ("Companions Met", savefile.companions_met.to_string()),
         ("Scarf Length", savefile.scarf_length.to_string()),
         ("Symbol Number", savefile.symbol.as_ref().to_string()),
-        ("Robe Color", savefile.robe_color().to_string()),
-        ("Robe Tier", savefile.robe_tier().to_string()),
+        ("Robe Color", savefile.robe.color().to_string()),
+        ("Robe Tier", savefile.robe.tier().to_string()),
         ("Last Played", savefile.last_played.to_string()),
     ]
     .into_iter()