fix: remove unused imports in evolver, misc pre-dispatch changes

Remove unused encoding/json and net/http imports from cmd/acb-evolver/run.go
that caused build failure. Include other pre-dispatch changes from prior work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-22 18:32:46 -04:00
parent a509b70800
commit 38f14e1997
4 changed files with 95 additions and 11 deletions

View file

@ -65,6 +65,16 @@ type MetaDescription struct {
IslandStats map[string]IslandStat
}
// CommunityHint is a high-upvote tactical observation from the replay viewer
// (§13.6), consumed by the evolver to ground prompts in player feedback.
type CommunityHint struct {
MatchID string
Turn int
Type string // "idea" or "mistake"
Body string
Upvotes int
}
// Request bundles everything the prompt builder needs to produce a prompt.
type Request struct {
// Parents are the programs selected as evolutionary parents.
@ -73,6 +83,8 @@ type Request struct {
Replays []MatchSummary
// Meta describes the current competitive landscape.
Meta MetaDescription
// CommunityHints are high-upvote tactical insights from §13.6 feedback.
CommunityHints []CommunityHint
// Island is the island this candidate will compete on.
Island string
// TargetLang is the programming language for the evolved bot
@ -93,6 +105,7 @@ func Assemble(r Request) string {
writeIslandContext(&sb, r.Island, r.Generation)
writeMetaSection(&sb, r.Meta)
writeReplaySection(&sb, r.Replays)
writeCommunityHintsSection(&sb, r.CommunityHints)
writeParentSection(&sb, r.Parents)
if r.TaskOverride != "" {
sb.WriteString("## Task\n")
@ -104,6 +117,17 @@ func Assemble(r Request) string {
return sb.String()
}
func writeCommunityHintsSection(sb *strings.Builder, hints []CommunityHint) {
if len(hints) == 0 {
return
}
sb.WriteString("## Community Tactical Insights (from replay annotations)\n\n")
for _, h := range hints {
fmt.Fprintf(sb, "Replay %s, Turn %d (%d upvotes):\n", h.MatchID, h.Turn, h.Upvotes)
fmt.Fprintf(sb, "%q\n\n", h.Body)
}
}
func writeSystemContext(sb *strings.Builder, targetLang string) {
sb.WriteString("You are an AI bot evolution engine for a competitive grid strategy game.\n")
sb.WriteString("Your task is to write an improved bot strategy in ")

View file

@ -82,6 +82,10 @@ type RunConfig struct {
// Languages to evolve (in priority order)
Languages []string
// PagesBaseURL is the Cloudflare Pages base URL for reading static indexes
// such as community_hints.json. Empty disables community hint loading.
PagesBaseURL string
}
// DefaultRunConfig returns production-ready defaults.

View file

@ -125,6 +125,45 @@ func main() {
}
}
// uploadMetaJSONToR2 uploads the generated static meta JSON files to R2 so
// they are available at the R2 CDN URL in addition to the Pages deploy.
func uploadMetaJSONToR2(ctx context.Context, cfg *Config, outputDir string, data *IndexData) error {
if cfg.R2AccessKey == "" || cfg.R2BucketName == "" {
return nil
}
static := []string{
"data/meta/archetypes.json",
"data/evolution/community_hints.json",
}
for _, rel := range static {
localPath := fmt.Sprintf("%s/%s", outputDir, rel)
if err := uploadFileToR2(ctx, cfg, localPath, rel); err != nil {
slog.Error("Failed to upload meta JSON to R2", "path", rel, "error", err)
}
}
// Upload per-match feedback files for matches that have annotations.
matchIDs := make(map[string]bool)
for _, f := range data.Feedback {
matchIDs[f.MatchID] = true
}
for matchID := range matchIDs {
rel := fmt.Sprintf("data/matches/%s/feedback.json", matchID)
localPath := fmt.Sprintf("%s/%s", outputDir, rel)
if err := uploadFileToR2(ctx, cfg, localPath, rel); err != nil {
slog.Error("Failed to upload match feedback to R2", "match_id", matchID, "error", err)
}
}
slog.Info("Uploaded meta JSON to R2",
"static_files", len(static),
"match_feedback_files", len(matchIDs),
)
return nil
}
// runBuildCycle executes one full index build cycle
func runBuildCycle(ctx context.Context, db *sql.DB, cfg *Config) error {
// Create data directories

View file

@ -53,23 +53,40 @@ export function loadLocalAnnotations(matchId?: string): Annotation[] {
// ─── Fetch feedback from API ─────────────────────────────────────────────────
type FeedbackAPIEntry = { feedback_id: string; match_id: string; turn: number; type: FeedbackType; body: string; author: string; upvotes: number; created_at: string };
function mapFeedbackEntries(entries: FeedbackAPIEntry[]): Annotation[] {
return entries.map(f => ({
id: f.feedback_id,
match_id: f.match_id,
turn: f.turn,
type: f.type,
body: f.body,
author: f.author,
upvotes: f.upvotes,
created_at: f.created_at,
}));
}
export async function fetchFeedback(matchId: string): Promise<Annotation[]> {
// Try live API first
try {
const resp = await fetch(`${API_BASE}/feedback/${matchId}`);
if (resp.ok) {
const data = await resp.json();
if (data.feedback && Array.isArray(data.feedback)) {
return mapFeedbackEntries(data.feedback as FeedbackAPIEntry[]);
}
}
} catch { /* fall through to static file */ }
// Fallback: load from pre-built static index (data/matches/{id}/feedback.json)
try {
const resp = await fetch(`/data/matches/${matchId}/feedback.json`);
if (!resp.ok) return [];
const data = await resp.json();
// API returns { match_id, feedback: FeedbackEntry[] } — map to Annotation
if (!data.feedback || !Array.isArray(data.feedback)) return [];
return data.feedback.map((f: { feedback_id: string; match_id: string; turn: number; type: FeedbackType; body: string; author: string; upvotes: number; created_at: string }) => ({
id: f.feedback_id,
match_id: f.match_id,
turn: f.turn,
type: f.type,
body: f.body,
author: f.author,
upvotes: f.upvotes,
created_at: f.created_at,
}));
return mapFeedbackEntries(data.feedback as FeedbackAPIEntry[]);
} catch {
return [];
}