package game import ( "io" "log/slog" "testing" "time" "github.com/denisovdennis/autohero/internal/model" ) func TestResolveCombat_MatchesEngineOutcome(t *testing.T) { baseHero := &model.Hero{ ID: 1, Level: 5, MaxHP: 320, HP: 320, Attack: 25, Defense: 8, Speed: 1.0, Strength: 10, Constitution: 12, Agility: 8, Luck: 5, Potions: 0, State: model.StateWalking, } tmpl, ok := model.EnemyTemplates[model.EnemyWolf] if !ok { tmpl = model.Enemy{ Type: model.EnemyWolf, Name: "Forest Wolf", MaxHP: 40, HP: 40, Attack: 8, Defense: 2, Speed: 1.2, BaseLevel: 1, LevelVariance: 0.3, MaxHeroLevelDiff: 5, HPPerLevel: 5, AttackPerLevel: 1.5, DefensePerLevel: 1.0, } } enemy := ScaleEnemyTemplate(tmpl, baseHero.Level) logger := slog.New(slog.NewTextHandler(io.Discard, nil)) eventCh := make(chan model.CombatEvent, 8) engine := NewEngine(100*time.Millisecond, eventCh, logger) heroEngine := *baseHero enemyEngine := enemy engine.StartCombat(&heroEngine, &enemyEngine) now := time.Now() for i := 0; i < 20000; i++ { now = now.Add(100 * time.Millisecond) engine.processCombatTick(now) if _, ok := engine.GetCombat(heroEngine.ID); !ok { break } } heroResolve := *baseHero enemyResolve := enemy survived := ResolveCombatToEnd(&heroResolve, &enemyResolve, time.Now(), CombatSimOptions{ TickRate: 100 * time.Millisecond, }) engineSurvived := heroEngine.HP > 0 if survived != engineSurvived { t.Fatalf("survival mismatch: resolve=%v engine=%v", survived, engineSurvived) } // Final HP can differ: engine applies debuff ticks for all combats before the attack queue each tick, // while ResolveCombatToEnd interleaves tick vs attacks on one timeline. Survival is the parity signal. }