Skill tree integration + tons of random fixes

This commit is contained in:
2026-02-17 04:00:58 +00:00
parent edd2f7e6b5
commit a61ed77ed6
17 changed files with 2610 additions and 710 deletions

View File

@@ -28,6 +28,7 @@ impl BranchId {
}
}
#[allow(dead_code)]
pub fn from_key(key: &str) -> Option<Self> {
match key {
"lowercase" => Some(BranchId::Lowercase),
@@ -615,6 +616,7 @@ impl SkillTree {
}
/// Get all branch definitions with their current progress (for UI).
#[allow(dead_code)]
pub fn all_branches_with_progress(&self) -> Vec<(&'static BranchDefinition, &BranchProgress)> {
ALL_BRANCHES
.iter()
@@ -622,12 +624,49 @@ impl SkillTree {
.collect()
}
/// Number of unlocked keys in a branch.
pub fn branch_unlocked_count(&self, id: BranchId) -> usize {
let def = get_branch_definition(id);
let bp = self.branch_progress(id);
match bp.status {
BranchStatus::Complete => def.levels.iter().map(|l| l.keys.len()).sum(),
BranchStatus::InProgress => {
if id == BranchId::Lowercase {
self.lowercase_unlocked_count()
} else {
def.levels
.iter()
.enumerate()
.filter(|(i, _)| *i <= bp.current_level)
.map(|(_, l)| l.keys.len())
.sum()
}
}
_ => 0,
}
}
/// Total keys defined in a branch (across all levels).
pub fn branch_total_keys(id: BranchId) -> usize {
let def = get_branch_definition(id);
def.levels.iter().map(|l| l.keys.len()).sum()
}
/// Count of unique confident keys across all branches.
pub fn total_confident_keys(&self, stats: &KeyStatsStore) -> usize {
let mut keys: HashSet<char> = HashSet::new();
for branch_def in ALL_BRANCHES {
for level in branch_def.levels {
for &ch in level.keys {
if stats.get_confidence(ch) >= 1.0 {
keys.insert(ch);
}
}
}
}
keys.len()
}
/// Count of confident keys in a branch.
pub fn branch_confident_keys(&self, id: BranchId, stats: &KeyStatsStore) -> usize {
let def = get_branch_definition(id);
@@ -881,4 +920,52 @@ mod tests {
assert!(keys.contains(&'J')); // Capitals L2 (current level)
assert!(!keys.contains(&'O')); // Capitals L3 (locked)
}
#[test]
fn test_branch_unlocked_count() {
let tree = SkillTree::default();
// Lowercase starts InProgress with LOWERCASE_MIN_KEYS
assert_eq!(
tree.branch_unlocked_count(BranchId::Lowercase),
LOWERCASE_MIN_KEYS
);
// Locked branches return 0
assert_eq!(tree.branch_unlocked_count(BranchId::Capitals), 0);
assert_eq!(tree.branch_unlocked_count(BranchId::Numbers), 0);
// InProgress non-lowercase branch
let mut tree2 = SkillTree::default();
let bp = tree2.branch_progress_mut(BranchId::Capitals);
bp.status = BranchStatus::InProgress;
bp.current_level = 1;
// Level 0 (8 keys) + Level 1 (10 keys)
assert_eq!(tree2.branch_unlocked_count(BranchId::Capitals), 18);
// Complete branch returns all keys
let mut tree3 = SkillTree::default();
tree3.branch_progress_mut(BranchId::Numbers).status = BranchStatus::Complete;
assert_eq!(tree3.branch_unlocked_count(BranchId::Numbers), 10);
}
#[test]
fn test_selectable_branches_bounds() {
use crate::ui::components::skill_tree::selectable_branches;
let branches = selectable_branches();
assert!(!branches.is_empty());
assert_eq!(branches[0], BranchId::Lowercase);
let tree = SkillTree::default();
// Accessing branch_progress for every selectable branch should not panic
for &branch_id in &branches {
let _ = tree.branch_progress(branch_id);
let _ = SkillTree::branch_total_keys(branch_id);
let _ = tree.branch_unlocked_count(branch_id);
}
// Selection at 0 and at max index should be valid
assert!(0 < branches.len());
assert!(branches.len() - 1 < branches.len());
}
}