- Phase 4 (Decision Engine): ActionQueue with 7 priority tiers, 4 behavior states (Idle/Explore/Combat/Loot), CombatAI with skill rotation and flee logic, GoalManager with survival override, DecisionEngine state machine integrated into the main game loop - Phase 3 (Automapper): Complete hybrid SQLite/Memvid architecture with WorldMapper orchestrator, room fingerprinting (SHA-256), bidirectional exit linking, exploration queue, A* pathfinding, HistoricalStorage with JSON fallback, SemanticSearch with keyword fallback, SyncManager with configurable triggers - Character Creator: State machine for automated character creation with stat rolling/optimization per class weights, race/class/gender selection, reroll logic with configurable thresholds - Quest System: Quest lifecycle tracking (discover->accept->complete), NLP-based objective extraction (kill/fetch/talk/explore), EXP/time prioritization, pending kill target tracking - Progression Optimizer: Grinding spot finder (EXP/hour), equipment comparison by weighted stats, skill rotation optimizer (DPS vs mana efficiency), session summary statistics - LLM Integration (hybrid): Async Ollama client with retry/backoff, LLMBrain coordinator for quest parsing, social AI, decision advice, and ambiguous text parsing fallback. SocialAI for detecting and responding to player messages (say/tell/shout/group/trade). Graceful degradation when Ollama is unavailable. All 214 tests passing.
117 lines
4.2 KiB
Python
117 lines
4.2 KiB
Python
"""Tests for the ProgressionOptimizer module."""
|
|
|
|
import pytest
|
|
from modules.progression.optimizer import (
|
|
ProgressionOptimizer, GrindingSpot, EquipmentPiece, SkillInfo,
|
|
)
|
|
|
|
|
|
class TestGrindingSpot:
|
|
def test_exp_per_hour(self):
|
|
spot = GrindingSpot(room_id=1, room_title="Forest", kills=10,
|
|
total_exp=1000, total_time_seconds=3600)
|
|
assert spot.exp_per_hour == 1000.0
|
|
|
|
def test_survival_rate(self):
|
|
spot = GrindingSpot(room_id=1, room_title="Dungeon",
|
|
kills=8, deaths=2)
|
|
assert spot.survival_rate == 0.8
|
|
|
|
def test_zero_time(self):
|
|
spot = GrindingSpot(room_id=1, room_title="Room")
|
|
assert spot.exp_per_hour == 0.0
|
|
assert spot.kills_per_hour == 0.0
|
|
|
|
|
|
class TestEquipmentPiece:
|
|
def test_weighted_score(self):
|
|
eq = EquipmentPiece(name="Iron Sword", slot="weapon",
|
|
stat_bonuses={"str": 5, "dex": 2})
|
|
score = eq.weighted_score({"str": 2.0, "dex": 1.0})
|
|
assert score == 12.0
|
|
|
|
def test_default_weights(self):
|
|
eq = EquipmentPiece(name="Shield", slot="armor",
|
|
stat_bonuses={"con": 3})
|
|
score = eq.weighted_score()
|
|
assert score == 3.0
|
|
|
|
|
|
class TestSkillInfo:
|
|
def test_damage_per_mana(self):
|
|
skill = SkillInfo(name="Fireball", command="cast fireball",
|
|
damage=100, mana_cost=20)
|
|
assert skill.damage_per_mana == 5.0
|
|
|
|
def test_dps(self):
|
|
skill = SkillInfo(name="Slash", command="slash",
|
|
damage=50, cooldown_seconds=2.0)
|
|
assert skill.dps == 25.0
|
|
|
|
def test_zero_mana_cost(self):
|
|
skill = SkillInfo(name="Punch", command="punch",
|
|
damage=10, mana_cost=0)
|
|
assert skill.damage_per_mana == float("inf")
|
|
|
|
|
|
class TestProgressionOptimizer:
|
|
def test_record_kill(self):
|
|
opt = ProgressionOptimizer()
|
|
opt.record_kill(1, "Forest", exp_gained=50)
|
|
opt.record_kill(1, "Forest", exp_gained=50)
|
|
spots = opt.get_best_grinding_spots()
|
|
# Need at least 3 kills for statistical relevance
|
|
assert len(spots) == 0
|
|
opt.record_kill(1, "Forest", exp_gained=50)
|
|
spots = opt.get_best_grinding_spots()
|
|
assert len(spots) == 1
|
|
assert spots[0].kills == 3
|
|
assert spots[0].total_exp == 150
|
|
|
|
def test_record_death(self):
|
|
opt = ProgressionOptimizer()
|
|
opt.record_death(1, "Dungeon")
|
|
opt.record_kill(1, "Dungeon", 100)
|
|
# Spot exists but deaths affect survival rate
|
|
# (< 3 kills so won't appear in best spots)
|
|
assert 1 in opt._spots
|
|
assert opt._spots[1].deaths == 1
|
|
|
|
def test_add_and_compare_equipment(self):
|
|
opt = ProgressionOptimizer()
|
|
opt.add_equipment(EquipmentPiece(
|
|
name="Iron Sword", slot="weapon",
|
|
stat_bonuses={"str": 5}))
|
|
opt.add_equipment(EquipmentPiece(
|
|
name="Steel Sword", slot="weapon",
|
|
stat_bonuses={"str": 8}))
|
|
|
|
best = opt.get_best_equipment("weapon")
|
|
assert best is not None
|
|
assert best.name == "Steel Sword"
|
|
|
|
def test_optimal_rotation(self):
|
|
opt = ProgressionOptimizer()
|
|
opt.add_skill(SkillInfo("Slash", "slash", damage=30, mana_cost=10, cooldown_seconds=1))
|
|
opt.add_skill(SkillInfo("Fireball", "fireball", damage=100, mana_cost=50, cooldown_seconds=3))
|
|
|
|
rotation = opt.get_optimal_rotation(available_mana=40)
|
|
# Only Slash fits within 40 mana
|
|
assert len(rotation) == 1
|
|
assert rotation[0].name == "Slash"
|
|
|
|
def test_efficient_rotation(self):
|
|
opt = ProgressionOptimizer()
|
|
opt.add_skill(SkillInfo("Slash", "slash", damage=30, mana_cost=10))
|
|
opt.add_skill(SkillInfo("Fireball", "fireball", damage=100, mana_cost=50))
|
|
efficient = opt.get_efficient_rotation()
|
|
# Slash = 3.0 dmg/mana, Fireball = 2.0 dmg/mana
|
|
assert efficient[0].name == "Slash"
|
|
|
|
def test_session_summary(self):
|
|
opt = ProgressionOptimizer()
|
|
summary = opt.session_summary()
|
|
assert "total_kills" in summary
|
|
assert "total_exp" in summary
|
|
assert summary["total_kills"] == 0
|