ai-code-battle/cmd/acb-evolver/internal/mapelites/grid_test.go
jedarden ea04f4debb style: apply gofmt alignment fixes across codebase
Tab/space alignment consistency from running gofmt on all packages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:40:33 -04:00

272 lines
7.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package mapelites
import "testing"
func TestBehaviorToCell(t *testing.T) {
// 3×3×3×3 grid per §10.2
g := New(3)
cases := []struct {
agg, eco, expl, form float64
wantX, wantY, wantZ, wantW int
}{
{0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0},
{1.0, 1.0, 1.0, 1.0, 2, 2, 2, 2},
{0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1},
{0.15, 0.85, 0.33, 0.66, 0, 2, 0, 1},
{0.99, 0.01, 0.99, 0.01, 2, 0, 2, 0},
{0.09, 0.09, 0.09, 0.09, 0, 0, 0, 0},
{0.34, 0.67, 0.34, 0.67, 1, 2, 1, 2},
}
for _, tc := range cases {
x, y, z, w := g.BehaviorToCell(tc.agg, tc.eco, tc.expl, tc.form)
if x != tc.wantX || y != tc.wantY || z != tc.wantZ || w != tc.wantW {
t.Errorf("BehaviorToCell(%.2f, %.2f, %.2f, %.2f) = (%d,%d,%d,%d), want (%d,%d,%d,%d)",
tc.agg, tc.eco, tc.expl, tc.form, x, y, z, w, tc.wantX, tc.wantY, tc.wantZ, tc.wantW)
}
}
}
func TestTotalCells(t *testing.T) {
g := New(3)
if g.TotalCells() != 81 {
t.Errorf("3⁴ = 81 cells, got %d", g.TotalCells())
}
g10 := New(10)
if g10.TotalCells() != 10000 {
t.Errorf("10⁴ = 10000 cells, got %d", g10.TotalCells())
}
}
func TestTryPlace_EmptyCell(t *testing.T) {
g := New(3)
p, placed := g.TryPlace(1, 10.0, 0.1, 0.9, 0.5, 0.5)
if !placed {
t.Fatal("expected placement into empty cell")
}
if p.X != 0 || p.Y != 2 || p.Z != 1 || p.W != 1 {
t.Errorf("expected cell (0,2,1,1), got (%d,%d,%d,%d)", p.X, p.Y, p.Z, p.W)
}
cell := g.Get(0, 2, 1, 1)
if !cell.Occupied || cell.ProgramID != 1 || cell.Fitness != 10.0 {
t.Errorf("unexpected cell state: %+v", cell)
}
}
func TestTryPlace_LowerFitnessDoesNotReplace(t *testing.T) {
g := New(3)
g.TryPlace(1, 10.0, 0.5, 0.5, 0.5, 0.5)
_, placed := g.TryPlace(2, 5.0, 0.5, 0.5, 0.5, 0.5)
if placed {
t.Fatal("lower fitness should not replace incumbent")
}
if g.Get(1, 1, 1, 1).ProgramID != 1 {
t.Error("incumbent program 1 should still hold the cell")
}
}
func TestTryPlace_HigherFitnessReplaces(t *testing.T) {
g := New(3)
g.TryPlace(1, 10.0, 0.5, 0.5, 0.5, 0.5)
_, placed := g.TryPlace(2, 20.0, 0.5, 0.5, 0.5, 0.5)
if !placed {
t.Fatal("higher fitness should replace incumbent")
}
cell := g.Get(1, 1, 1, 1)
if cell.ProgramID != 2 || cell.Fitness != 20.0 {
t.Errorf("expected program 2 with fitness 20, got %+v", cell)
}
}
func TestTryPlace_EqualFitnessDoesNotReplace(t *testing.T) {
g := New(3)
g.TryPlace(1, 10.0, 0.5, 0.5, 0.5, 0.5)
_, placed := g.TryPlace(2, 10.0, 0.5, 0.5, 0.5, 0.5)
if placed {
t.Fatal("equal fitness should not replace incumbent")
}
}
func TestOccupiedCount(t *testing.T) {
g := New(3)
if g.OccupiedCount() != 0 {
t.Error("new grid should have 0 occupied cells")
}
g.TryPlace(1, 1.0, 0.1, 0.1, 0.1, 0.1)
g.TryPlace(2, 1.0, 0.9, 0.9, 0.9, 0.9)
g.TryPlace(3, 1.0, 0.5, 0.5, 0.5, 0.5)
if g.OccupiedCount() != 3 {
t.Errorf("expected 3 occupied cells, got %d", g.OccupiedCount())
}
// Same cell should not increase count
g.TryPlace(4, 99.0, 0.5, 0.5, 0.5, 0.5)
if g.OccupiedCount() != 3 {
t.Errorf("expected still 3 occupied cells after same-cell update, got %d", g.OccupiedCount())
}
}
func TestElite_EmptyGrid(t *testing.T) {
g := New(3)
_, found := g.Elite()
if found {
t.Fatal("empty grid should have no elite")
}
}
func TestElite(t *testing.T) {
g := New(3)
g.TryPlace(1, 5.0, 0.1, 0.1, 0.1, 0.1)
g.TryPlace(2, 15.0, 0.9, 0.9, 0.9, 0.9)
g.TryPlace(3, 10.0, 0.5, 0.5, 0.5, 0.5)
elite, found := g.Elite()
if !found {
t.Fatal("expected an elite in non-empty grid")
}
if elite.ProgramID != 2 || elite.Fitness != 15.0 {
t.Errorf("expected elite program 2 (fitness 15), got %+v", elite)
}
}
func TestAllElites(t *testing.T) {
g := New(3)
if len(g.AllElites()) != 0 {
t.Error("empty grid should return no elites")
}
g.TryPlace(1, 1.0, 0.0, 0.0, 0.0, 0.0)
g.TryPlace(2, 2.0, 0.5, 0.5, 0.5, 0.5)
g.TryPlace(3, 3.0, 1.0, 1.0, 1.0, 1.0)
elites := g.AllElites()
if len(elites) != 3 {
t.Errorf("expected 3 elites, got %d", len(elites))
}
}
func TestSeedBehaviorVectors(t *testing.T) {
// Verify that the 6 seed bots land in distinct grid cells on a 3×3×3×3 grid.
g := New(3)
bots := []struct {
id int64
name string
aggression, economy float64
exploration, formation float64
}{
{1, "gatherer", 0.1, 0.9, 0.3, 0.2},
{2, "guardian", 0.2, 0.6, 0.1, 0.8},
{3, "rusher", 0.9, 0.2, 0.5, 0.3},
{4, "swarm", 0.6, 0.5, 0.4, 0.9},
{5, "hunter", 0.7, 0.3, 0.8, 0.4},
{6, "random", 0.3, 0.4, 0.5, 0.5},
}
placed := 0
for _, b := range bots {
_, ok := g.TryPlace(b.id, 1.0, b.aggression, b.economy, b.exploration, b.formation)
if ok {
placed++
}
}
if placed != 6 {
t.Errorf("expected all 6 seed bots in distinct cells, but only %d placed", placed)
}
if g.OccupiedCount() != 6 {
t.Errorf("expected 6 occupied cells, got %d", g.OccupiedCount())
}
}
func TestSlice(t *testing.T) {
g := New(3)
// Place bots at known positions
g.TryPlace(1, 5.0, 0.1, 0.1, 0.1, 0.1) // (0,0,0,0)
g.TryPlace(2, 8.0, 0.9, 0.9, 0.1, 0.1) // (2,2,0,0)
g.TryPlace(3, 3.0, 0.5, 0.1, 0.1, 0.1) // (1,0,0,0)
// Slice: aggression×economy at z=0, w=0 (dims 2,3 fixed to 0)
slice := g.Slice(2, 0, 3, 0)
if len(slice) != 3 {
t.Fatalf("expected 3 rows in slice, got %d", len(slice))
}
if len(slice[0]) != 3 {
t.Fatalf("expected 3 cols in slice, got %d", len(slice[0]))
}
// Check (0,0) = program 1
c00 := slice[0][0]
if !c00.Occupied || c00.ProgramID != 1 {
t.Errorf("slice[0][0]: expected program 1, got %+v", c00)
}
// Check (2,2) = program 2
c22 := slice[2][2]
if !c22.Occupied || c22.ProgramID != 2 {
t.Errorf("slice[2][2]: expected program 2, got %+v", c22)
}
// Check (1,0) = program 3
c10 := slice[1][0]
if !c10.Occupied || c10.ProgramID != 3 {
t.Errorf("slice[1][0]: expected program 3, got %+v", c10)
}
}
func TestSnapshot(t *testing.T) {
g := New(3)
g.TryPlace(1, 5.0, 0.1, 0.1, 0.1, 0.1)
g.TryPlace(2, 8.0, 0.9, 0.9, 0.9, 0.9)
snap := g.Snapshot()
if snap.Size != 3 {
t.Errorf("expected size 3, got %d", snap.Size)
}
if len(snap.Cells) != 2 {
t.Errorf("expected 2 cells in snapshot, got %d", len(snap.Cells))
}
if snap.DimNames[0] != "aggression" || snap.DimNames[3] != "formation" {
t.Errorf("dim names: %v", snap.DimNames)
}
}
func TestMigration_2Dto4D(t *testing.T) {
// Simulate migrating a 2-D archive into a 4-D grid.
// Old programs had only aggression and economy.
// They should project into the 4-D grid at z=middle, w=middle.
g := New(3)
// Old 2-D program: aggression=0.5, economy=0.5
// Migrate to 4-D: exploration=0.5 (middle), formation=0.5 (middle)
middle := 0.5
g.TryPlace(1, 10.0, 0.5, 0.5, middle, middle)
cell := g.Get(1, 1, 1, 1)
if !cell.Occupied {
t.Error("migrated program should occupy (1,1,1,1)")
}
if cell.ProgramID != 1 {
t.Errorf("expected program 1, got %d", cell.ProgramID)
}
// A new 4-D program with different exploration/formation should go to a different cell
_, placed := g.TryPlace(2, 15.0, 0.5, 0.5, 0.9, 0.1)
if !placed {
t.Error("different 4-D coords should be a new cell")
}
cell2 := g.Get(1, 1, 2, 0)
if !cell2.Occupied || cell2.ProgramID != 2 {
t.Error("new program should be at (1,1,2,0)")
}
}
func TestPlacementKey(t *testing.T) {
p := Placement{X: 1, Y: 2, Z: 0, W: 2}
key := p.Key()
if key != [NumDims]int{1, 2, 0, 2} {
t.Errorf("expected [1 2 0 2], got %v", key)
}
}