feat(§15.2): generate and stream static meta JSON files to R2
- Add data/meta/rivalries.json to R2 upload list in uploadMetaJSONToR2 - Add attachCommunityHints() to narrative.go to enrich story arcs with highest-upvote community tactical hints (upvotes >= 3, idea/mistake types) - Fix detectRivalryArcs() key separator from "-" to "|" to avoid UUID hyphen collisions when parsing bot ID pairs - Fix partitionBots() call sites in bot_strategies_phase13.go to use struct field access (.friendly, .enemy) matching updated return type generator.go already contains generateArchetypes, generateCommunityHints, and generateMatchFeedback (all called from generateAllIndexes). main.go uploads all four outputs to R2 on every build cycle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
60b83a02d9
commit
7978ebbab3
3 changed files with 76 additions and 15 deletions
|
|
@ -134,6 +134,7 @@ func uploadMetaJSONToR2(ctx context.Context, cfg *Config, outputDir string, data
|
|||
|
||||
static := []string{
|
||||
"data/meta/archetypes.json",
|
||||
"data/meta/rivalries.json",
|
||||
"data/evolution/community_hints.json",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ type KeyMatch struct {
|
|||
TurnCount int `json:"turn_count"`
|
||||
Won bool `json:"won"`
|
||||
EndCondition string `json:"end_condition,omitempty"`
|
||||
CriticalMoment string `json:"critical_moment,omitempty"` // §13.2 turning point summary
|
||||
}
|
||||
|
||||
// HeadToHeadRecord represents the head-to-head record between two bots
|
||||
|
|
@ -466,6 +467,55 @@ func detectStoryArcs(data *IndexData) []StoryArc {
|
|||
// Comeback: Bot recovered >=150 rating after decline
|
||||
arcs = append(arcs, detectComebackArcs(data)...)
|
||||
|
||||
// Enrich arcs with community tactical hints where available.
|
||||
arcs = attachCommunityHints(arcs, data)
|
||||
|
||||
return arcs
|
||||
}
|
||||
|
||||
// attachCommunityHints enriches detected story arcs with the highest-upvote
|
||||
// community tactical hint associated with the primary bot in each arc.
|
||||
// Feedback is expected to be sorted by upvotes DESC (as fetched from the DB).
|
||||
func attachCommunityHints(arcs []StoryArc, data *IndexData) []StoryArc {
|
||||
if len(data.Feedback) == 0 {
|
||||
return arcs
|
||||
}
|
||||
|
||||
// Build matchID → participant botIDs map.
|
||||
matchBots := make(map[string][]string, len(data.Matches))
|
||||
for _, m := range data.Matches {
|
||||
ids := make([]string, 0, len(m.Participants))
|
||||
for _, p := range m.Participants {
|
||||
ids = append(ids, p.BotID)
|
||||
}
|
||||
matchBots[m.ID] = ids
|
||||
}
|
||||
|
||||
// Assign the first (highest-upvote) eligible hint per bot.
|
||||
const minHintUpvotes = 3
|
||||
botHint := make(map[string]string)
|
||||
for _, f := range data.Feedback {
|
||||
if f.Type != "idea" && f.Type != "mistake" {
|
||||
continue
|
||||
}
|
||||
if f.Upvotes < minHintUpvotes {
|
||||
break // Sorted DESC; no higher-upvote entries remain.
|
||||
}
|
||||
for _, botID := range matchBots[f.MatchID] {
|
||||
if _, seen := botHint[botID]; !seen {
|
||||
botHint[botID] = f.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range arcs {
|
||||
if arcs[i].CommunityHint != "" {
|
||||
continue
|
||||
}
|
||||
if hint, ok := botHint[arcs[i].BotID]; ok {
|
||||
arcs[i].CommunityHint = hint
|
||||
}
|
||||
}
|
||||
return arcs
|
||||
}
|
||||
|
||||
|
|
@ -575,7 +625,7 @@ func detectRivalryArcs(data *IndexData) []StoryArc {
|
|||
|
||||
for i, p1 := range m.Participants {
|
||||
for _, p2 := range m.Participants[i+1:] {
|
||||
key := fmt.Sprintf("%s-%s", minStr(p1.BotID, p2.BotID), maxStr(p1.BotID, p2.BotID))
|
||||
key := fmt.Sprintf("%s|%s", minStr(p1.BotID, p2.BotID), maxStr(p1.BotID, p2.BotID))
|
||||
pairMatches[key] = append(pairMatches[key], m)
|
||||
}
|
||||
}
|
||||
|
|
@ -587,8 +637,8 @@ func detectRivalryArcs(data *IndexData) []StoryArc {
|
|||
continue
|
||||
}
|
||||
|
||||
// Parse bot IDs from key
|
||||
parts := strings.Split(key, "-")
|
||||
// Parse bot IDs from key (separator is "|" to avoid conflicts with UUID hyphens).
|
||||
parts := strings.SplitN(key, "|", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ func (b *DefenderBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -105,7 +106,8 @@ func (b *ScoutBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -143,7 +145,7 @@ func (b *ScoutBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
}
|
||||
}
|
||||
|
||||
dir := b.bestExploreDir(bot.Position, config, claimed, wallSet)
|
||||
dir := b.bestExploreDir(bot.Position, config, state.Turn, claimed, wallSet)
|
||||
if dir == DirNone {
|
||||
dir = randDirection(b.rng)
|
||||
}
|
||||
|
|
@ -160,7 +162,7 @@ func (b *ScoutBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
return moves, nil
|
||||
}
|
||||
|
||||
func (b *ScoutBot) bestExploreDir(pos Position, config Config, claimed, wallSet map[Position]bool) Direction {
|
||||
func (b *ScoutBot) bestExploreDir(pos Position, config Config, turn int, claimed, wallSet map[Position]bool) Direction {
|
||||
bestDir := DirNone
|
||||
bestScore := -1
|
||||
|
||||
|
|
@ -208,7 +210,8 @@ func (b *FarmerBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -283,7 +286,8 @@ func (b *PacifistBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -354,7 +358,8 @@ func (b *PhalanxBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -432,7 +437,8 @@ func (b *RaiderBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -525,7 +531,8 @@ func (b *NomadBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -612,7 +619,8 @@ func (b *OpportunistBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -734,7 +742,8 @@ func (b *AssassinBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, _ := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots := part.friendly
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -800,7 +809,8 @@ func (b *KamikazeBot) GetMoves(state *VisibleState) ([]Move, error) {
|
|||
myID := state.You.ID
|
||||
config := state.Config
|
||||
|
||||
myBots, enemyBots := partitionBots(state.Bots, myID)
|
||||
part := partitionBots(state.Bots, myID)
|
||||
myBots, enemyBots := part.friendly, part.enemy
|
||||
if len(myBots) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue