ai-code-battle/cmd/acb-evolver/internal/mapelites/grid.go
jedarden 5669688984 Add validation pipeline, sandbox, and evolution DB layer (Phase 7)
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>
2026-03-26 22:45:13 -04:00

115 lines
2.9 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 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
}