Three-stage fail-fast validator for LLM-generated bot candidates: - syntax.go: language-aware parse (go/parser for Go; py_compile, rustfmt, tsc, javac, php -l for others; brace-balance fallback) - schema.go: regex detection of /health + /turn endpoints and "moves" field - sandbox.go: nsjail-isolated smoke test — builds bot, polls /health, sends 5 signed /turn requests, verifies JSON moves responses - validator.go: orchestrates stages with fail-fast short-circuit DB layer: - programs table + CRUD (create, get, list, updateFitness, setPromoted) - validation_log table with RecordValidation, IslandPassRates, IslandValidationStats for per-island pass-rate tracking - seed.go: 6 generation-0 bots across alpha/beta/gamma/delta islands MAP-Elites grid (mapelites/grid.go): 2-D behavior grid on aggression×economy axes; TryPlace keeps the fittest occupant per niche. acb-evolver CLI gains two new subcommands: validate <file> -lang <lang> [-island <island>] [-nsjail] [-nolog] validation-stats (tabular per-island pass-rate breakdown) cmd/acb-api/db.go: add programs table to API schema so the API can query promoted evolved bots. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
115 lines
2.9 KiB
Go
115 lines
2.9 KiB
Go
// Package mapelites implements a 2-D MAP-Elites behavior grid for diversity
|
||
// maintenance in the evolution pipeline.
|
||
//
|
||
// The two behavior dimensions are:
|
||
//
|
||
// X axis – aggression (0.0 = pacifist … 1.0 = full aggressor)
|
||
// Y axis – economy (0.0 = ignores energy … 1.0 = perfect economy)
|
||
//
|
||
// Each cell in the Size×Size grid holds the ID and fitness of the single best
|
||
// program discovered in that behavioral niche.
|
||
package mapelites
|
||
|
||
import "math"
|
||
|
||
// Grid is a 2-D MAP-Elites behavior grid.
|
||
type Grid struct {
|
||
size int
|
||
cells [][]Cell
|
||
}
|
||
|
||
// Cell is a single niche in the grid.
|
||
type Cell struct {
|
||
ProgramID int64
|
||
Fitness float64
|
||
Occupied bool
|
||
}
|
||
|
||
// Placement records which grid cell a program was placed into.
|
||
type Placement struct {
|
||
X, Y int
|
||
}
|
||
|
||
// New creates an empty Grid with the given side length.
|
||
func New(size int) *Grid {
|
||
cells := make([][]Cell, size)
|
||
for i := range cells {
|
||
cells[i] = make([]Cell, size)
|
||
}
|
||
return &Grid{size: size, cells: cells}
|
||
}
|
||
|
||
// BehaviorToCell converts continuous behavior values (each in [0, 1]) to
|
||
// discrete grid coordinates clamped to [0, size-1].
|
||
func (g *Grid) BehaviorToCell(aggression, economy float64) (x, y int) {
|
||
x = int(math.Min(math.Floor(aggression*float64(g.size)), float64(g.size-1)))
|
||
y = int(math.Min(math.Floor(economy*float64(g.size)), float64(g.size-1)))
|
||
return
|
||
}
|
||
|
||
// TryPlace attempts to place a program in the cell determined by its behavior
|
||
// vector. The cell is updated only when it is empty or the new program has
|
||
// strictly higher fitness than the incumbent.
|
||
// Returns the target cell coordinates and whether the cell was updated.
|
||
func (g *Grid) TryPlace(id int64, fitness, aggression, economy float64) (Placement, bool) {
|
||
x, y := g.BehaviorToCell(aggression, economy)
|
||
cell := &g.cells[x][y]
|
||
|
||
if !cell.Occupied || fitness > cell.Fitness {
|
||
*cell = Cell{ProgramID: id, Fitness: fitness, Occupied: true}
|
||
return Placement{X: x, Y: y}, true
|
||
}
|
||
return Placement{X: x, Y: y}, false
|
||
}
|
||
|
||
// Get returns the cell at grid coordinates (x, y).
|
||
func (g *Grid) Get(x, y int) Cell {
|
||
return g.cells[x][y]
|
||
}
|
||
|
||
// Size returns the side length of the grid.
|
||
func (g *Grid) Size() int {
|
||
return g.size
|
||
}
|
||
|
||
// OccupiedCount returns the number of filled cells.
|
||
func (g *Grid) OccupiedCount() int {
|
||
n := 0
|
||
for _, row := range g.cells {
|
||
for _, c := range row {
|
||
if c.Occupied {
|
||
n++
|
||
}
|
||
}
|
||
}
|
||
return n
|
||
}
|
||
|
||
// Elite returns the cell with the highest fitness in the grid.
|
||
// Returns (zero Cell, false) when the grid is empty.
|
||
func (g *Grid) Elite() (Cell, bool) {
|
||
var best Cell
|
||
found := false
|
||
for _, row := range g.cells {
|
||
for _, c := range row {
|
||
if c.Occupied && (!found || c.Fitness > best.Fitness) {
|
||
best = c
|
||
found = true
|
||
}
|
||
}
|
||
}
|
||
return best, found
|
||
}
|
||
|
||
// AllElites returns a flat slice of every occupied cell.
|
||
func (g *Grid) AllElites() []Cell {
|
||
var out []Cell
|
||
for _, row := range g.cells {
|
||
for _, c := range row {
|
||
if c.Occupied {
|
||
out = append(out, c)
|
||
}
|
||
}
|
||
}
|
||
return out
|
||
}
|