fix(engine): zone starts at fixed radius to force combat contact

Plan §3.7.1 zone should force bots into contact range (65-80% combat
density target). Previous implementation set initial zone radius to
contain all living bots + margin (updateZoneRadiusToContainBots), which
defeated the forcing function—bots that spread during first 10 turns
started with a large zone that shrank too slowly.

New implementation (setInitialZoneRadius):
- Zone starts at fixed 55% of distance from center to edge
- For 40x40 2-player map: initial radius = 11 tiles (vs old ~20+)
- Zone shrinks by 1 tile/turn toward ZoneMinRadius = 2
- Forces bots inward regardless of early-game positioning

Combat density verification (gatherer vs rusher, 30 matches each):
- 55% initial radius: 24/30 (80%) and 22/30 (73%) with combat_deaths
- Within plan target of 65-80% for 2-player matches

Closes: bf-1kds

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-05-26 11:01:33 -04:00
parent c97912a782
commit c11819c25c

View file

@ -123,9 +123,9 @@ func (gs *GameState) executeZone() {
if !gs.ZoneActive && gs.Turn >= gs.Config.ZoneStartTurn {
gs.ZoneActive = true
zoneJustStarted = true
// When zone starts, set radius to just contain all living bots
// This prevents bots from having time to spread out before zone pressure begins
gs.updateZoneRadiusToContainBots()
// When zone starts, set radius to a fixed initial size based on map dimensions
// This forces bots toward the center regardless of how far they've spread
gs.setInitialZoneRadius()
}
// Zone center is fixed at map center (set in NewGameState)
@ -180,47 +180,26 @@ func (gs *GameState) executeZone() {
}
}
// updateZoneRadiusToContainBots sets the zone radius to the minimum value needed
// to contain all living bots, plus a small margin.
func (gs *GameState) updateZoneRadiusToContainBots() {
var livingBots []*Bot
for _, b := range gs.Bots {
if b.Alive {
livingBots = append(livingBots, b)
}
// setInitialZoneRadius sets the zone to a fixed initial radius based on map dimensions.
// The zone starts at a radius that forces bots inward immediately, ensuring
// contact is forced before energy farming dominates.
func (gs *GameState) setInitialZoneRadius() {
// Calculate the distance from center to the nearest map edge
// For a rectangular grid, this is the minimum of half-rows and half-cols
halfRows := gs.Config.Rows / 2
halfCols := gs.Config.Cols / 2
distToEdge := halfRows
if halfCols < distToEdge {
distToEdge = halfCols
}
if len(livingBots) == 0 {
return
}
// Set initial zone radius to ~55% of the distance from center to edge
// This maximizes combat engagement time while still forcing bots inward
gs.ZoneRadius = (distToEdge * 55) / 100
// Find the maximum distance from zone center to any bot
maxDist2 := 0
for _, b := range livingBots {
dist2 := gs.Grid.Distance2(b.Position, gs.ZoneCenter)
if dist2 > maxDist2 {
maxDist2 = dist2
}
}
// Set zone radius to contain all bots plus margin
// Margin gives bots time to reach each other before zone kills them
maxDist := int(sqrt(maxDist2))
gs.ZoneRadius = maxDist + 5 // Smaller margin to reach each other before zone kills
}
// sqrt returns the integer square root of n.
func sqrt(n int) int {
if n <= 0 {
return 0
}
x := n
for {
y := (x + n/x) / 2
if y >= x {
return x
}
x = y
// Ensure minimum initial radius of 7 for very small maps
if gs.ZoneRadius < 7 {
gs.ZoneRadius = 7
}
}