- Add NashMixture and MetaWeaknesses fields to meta.Description and compute them from island population proportions (§10.2 PSRO) - Update behaviorDistance to support N-D vectors for 4-D MAP-Elites grid (aggression, economy, exploration, formation) - Wire NashMixture/MetaWeaknesses through FromMetaDescription converter so they actually reach the LLM prompt (was dead code before) - Align LLM prompt with plan §15.1/§15.5: correct combat rules (focus-fire), fog of war, HTTP protocol section, Nash mixture target - Fix diversity normalization from sqrt(2) (2-D) to 2.0 (4-D max) - Rename handleUIFeedback to handleCreateFeedback (§13.6 naming) - Update tests for new fields and corrected prompt text Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
238 lines
6.7 KiB
Go
238 lines
6.7 KiB
Go
package meta
|
|
|
|
import (
|
|
"testing"
|
|
|
|
evolverdb "github.com/aicodebattle/acb/cmd/acb-evolver/internal/db"
|
|
)
|
|
|
|
func TestBuildSimple_Basic(t *testing.T) {
|
|
topBots := []BotInfo{
|
|
{Name: "TopBot1", Rating: 1600, Island: evolverdb.IslandAlpha, Evolved: true},
|
|
{Name: "TopBot2", Rating: 1550, Island: evolverdb.IslandBeta, Evolved: true},
|
|
{Name: "TopBot3", Rating: 1500, Island: evolverdb.IslandAlpha, Evolved: true},
|
|
}
|
|
|
|
islandStats := map[string]IslandStats{
|
|
evolverdb.IslandAlpha: {Count: 10, AvgFitness: 0.5, TopFitness: 0.9, Diversity: 0.7},
|
|
evolverdb.IslandBeta: {Count: 8, AvgFitness: 0.4, TopFitness: 0.8, Diversity: 0.6},
|
|
}
|
|
|
|
got := BuildSimple(20, topBots, islandStats)
|
|
|
|
if got.TotalBots != 20 {
|
|
t.Errorf("expected TotalBots 20, got %d", got.TotalBots)
|
|
}
|
|
|
|
if len(got.TopBots) != 3 {
|
|
t.Errorf("expected 3 TopBots, got %d", len(got.TopBots))
|
|
}
|
|
|
|
if got.TopBots[0].Name != "TopBot1" {
|
|
t.Errorf("expected TopBot1 as first, got %s", got.TopBots[0].Name)
|
|
}
|
|
|
|
if got.DominantStrategy == "" {
|
|
t.Error("expected non-empty DominantStrategy")
|
|
}
|
|
|
|
if len(got.IslandStats) != 2 {
|
|
t.Errorf("expected 2 IslandStats, got %d", len(got.IslandStats))
|
|
}
|
|
}
|
|
|
|
func TestBuildSimple_EmptyBots(t *testing.T) {
|
|
got := BuildSimple(0, nil, nil)
|
|
|
|
if got.TotalBots != 0 {
|
|
t.Errorf("expected TotalBots 0, got %d", got.TotalBots)
|
|
}
|
|
|
|
if len(got.TopBots) != 0 {
|
|
t.Errorf("expected 0 TopBots, got %d", len(got.TopBots))
|
|
}
|
|
|
|
if got.DominantStrategy != "unknown (no promoted bots)" {
|
|
t.Errorf("expected unknown meta message, got %q", got.DominantStrategy)
|
|
}
|
|
}
|
|
|
|
func TestBuildSimple_DominantStrategy_Alpha(t *testing.T) {
|
|
// Alpha (aggressive) has most top bots
|
|
topBots := []BotInfo{
|
|
{Name: "A1", Rating: 1600, Island: evolverdb.IslandAlpha, Evolved: true},
|
|
{Name: "A2", Rating: 1550, Island: evolverdb.IslandAlpha, Evolved: true},
|
|
{Name: "B1", Rating: 1500, Island: evolverdb.IslandBeta, Evolved: true},
|
|
}
|
|
|
|
got := BuildSimple(10, topBots, nil)
|
|
|
|
if got.DominantStrategy != "aggressive core-rushing" {
|
|
t.Errorf("expected 'aggressive core-rushing', got %q", got.DominantStrategy)
|
|
}
|
|
}
|
|
|
|
func TestBuildSimple_DominantStrategy_Beta(t *testing.T) {
|
|
// Beta (economic) has most top bots
|
|
topBots := []BotInfo{
|
|
{Name: "B1", Rating: 1600, Island: evolverdb.IslandBeta, Evolved: true},
|
|
{Name: "B2", Rating: 1550, Island: evolverdb.IslandBeta, Evolved: true},
|
|
{Name: "A1", Rating: 1500, Island: evolverdb.IslandAlpha, Evolved: true},
|
|
}
|
|
|
|
got := BuildSimple(10, topBots, nil)
|
|
|
|
if got.DominantStrategy != "energy-focused economy" {
|
|
t.Errorf("expected 'energy-focused economy', got %q", got.DominantStrategy)
|
|
}
|
|
}
|
|
|
|
func TestBuildSimple_DominantStrategy_Gamma(t *testing.T) {
|
|
topBots := []BotInfo{
|
|
{Name: "G1", Rating: 1600, Island: evolverdb.IslandGamma, Evolved: true},
|
|
{Name: "G2", Rating: 1550, Island: evolverdb.IslandGamma, Evolved: true},
|
|
}
|
|
|
|
got := BuildSimple(10, topBots, nil)
|
|
|
|
if got.DominantStrategy != "defensive adaptation" {
|
|
t.Errorf("expected 'defensive adaptation', got %q", got.DominantStrategy)
|
|
}
|
|
}
|
|
|
|
func TestBuildSimple_DominantStrategy_Delta(t *testing.T) {
|
|
topBots := []BotInfo{
|
|
{Name: "D1", Rating: 1600, Island: evolverdb.IslandDelta, Evolved: true},
|
|
{Name: "D2", Rating: 1550, Island: evolverdb.IslandDelta, Evolved: true},
|
|
}
|
|
|
|
got := BuildSimple(10, topBots, nil)
|
|
|
|
if got.DominantStrategy != "experimental mixed" {
|
|
t.Errorf("expected 'experimental mixed', got %q", got.DominantStrategy)
|
|
}
|
|
}
|
|
|
|
func TestCalculateDiversity_SingleProgram(t *testing.T) {
|
|
programs := []*evolverdb.Program{
|
|
{ID: 1, BehaviorVector: []float64{0.5, 0.5}},
|
|
}
|
|
|
|
got := calculateDiversity(programs)
|
|
|
|
if got != 0 {
|
|
t.Errorf("expected diversity 0 for single program, got %f", got)
|
|
}
|
|
}
|
|
|
|
func TestCalculateDiversity_IdenticalPrograms(t *testing.T) {
|
|
programs := []*evolverdb.Program{
|
|
{ID: 1, BehaviorVector: []float64{0.5, 0.5}},
|
|
{ID: 2, BehaviorVector: []float64{0.5, 0.5}},
|
|
{ID: 3, BehaviorVector: []float64{0.5, 0.5}},
|
|
}
|
|
|
|
got := calculateDiversity(programs)
|
|
|
|
if got != 0 {
|
|
t.Errorf("expected diversity 0 for identical programs, got %f", got)
|
|
}
|
|
}
|
|
|
|
func TestCalculateDiversity_DiversePrograms(t *testing.T) {
|
|
programs := []*evolverdb.Program{
|
|
{ID: 1, BehaviorVector: []float64{0.0, 0.0, 0.0, 0.0}},
|
|
{ID: 2, BehaviorVector: []float64{1.0, 1.0, 1.0, 1.0}},
|
|
}
|
|
|
|
got := calculateDiversity(programs)
|
|
|
|
// Distance between (0,0,0,0) and (1,1,1,1) is sqrt(4) = 2.0
|
|
// Normalized by 2.0 (max distance in 4D unit hypercube is sqrt(4) = 2.0)
|
|
// Expected: 2.0 / 2.0 = 1.0
|
|
if got < 0.9 || got > 1.1 {
|
|
t.Errorf("expected diversity close to 1.0 for maximally diverse programs, got %f", got)
|
|
}
|
|
}
|
|
|
|
func TestCalculateDiversity_EmptyPrograms(t *testing.T) {
|
|
got := calculateDiversity(nil)
|
|
|
|
if got != 0 {
|
|
t.Errorf("expected diversity 0 for nil programs, got %f", got)
|
|
}
|
|
}
|
|
|
|
func TestCalculateDiversity_NoBehaviorVector(t *testing.T) {
|
|
programs := []*evolverdb.Program{
|
|
{ID: 1, BehaviorVector: nil},
|
|
{ID: 2, BehaviorVector: []float64{}},
|
|
}
|
|
|
|
got := calculateDiversity(programs)
|
|
|
|
if got != 0 {
|
|
t.Errorf("expected diversity 0 for programs without behavior vectors, got %f", got)
|
|
}
|
|
}
|
|
|
|
func TestBehaviorDistance(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
a, b []float64
|
|
expected float64
|
|
}{
|
|
{"same point", []float64{0.5, 0.5}, []float64{0.5, 0.5}, 0},
|
|
{"unit apart x", []float64{0.0, 0.0}, []float64{1.0, 0.0}, 1},
|
|
{"unit apart y", []float64{0.0, 0.0}, []float64{0.0, 1.0}, 1},
|
|
{"diagonal", []float64{0.0, 0.0}, []float64{1.0, 1.0}, 1.414214},
|
|
{"nil vector a", nil, []float64{0.5, 0.5}, 0},
|
|
{"nil vector b", []float64{0.5, 0.5}, nil, 0},
|
|
{"short vector a", []float64{0.5}, []float64{0.5, 0.5}, 0},
|
|
{"short vector b", []float64{0.5, 0.5}, []float64{0.5}, 0},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := behaviorDistance(tc.a, tc.b)
|
|
// Use approximate comparison for floating point
|
|
const epsilon = 0.0001
|
|
diff := got - tc.expected
|
|
if diff < 0 {
|
|
diff = -diff
|
|
}
|
|
if diff > epsilon {
|
|
t.Errorf("expected distance %f, got %f", tc.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIslandStats_Values(t *testing.T) {
|
|
islandStats := map[string]IslandStats{
|
|
evolverdb.IslandAlpha: {Count: 5, AvgFitness: 0.75, TopFitness: 0.95, Diversity: 0.8},
|
|
}
|
|
|
|
got := BuildSimple(5, nil, islandStats)
|
|
|
|
alphaStats, ok := got.IslandStats[evolverdb.IslandAlpha]
|
|
if !ok {
|
|
t.Fatal("expected alpha island stats")
|
|
}
|
|
|
|
if alphaStats.Count != 5 {
|
|
t.Errorf("expected Count 5, got %d", alphaStats.Count)
|
|
}
|
|
|
|
if alphaStats.AvgFitness != 0.75 {
|
|
t.Errorf("expected AvgFitness 0.75, got %f", alphaStats.AvgFitness)
|
|
}
|
|
|
|
if alphaStats.TopFitness != 0.95 {
|
|
t.Errorf("expected TopFitness 0.95, got %f", alphaStats.TopFitness)
|
|
}
|
|
|
|
if alphaStats.Diversity != 0.8 {
|
|
t.Errorf("expected Diversity 0.8, got %f", alphaStats.Diversity)
|
|
}
|
|
}
|