feat(narrative): add critical moment summaries to key match extraction
Generates contextual turning-point descriptions for matches used in blog narratives and rivalry chronicles (§13.2). Summarizes close scores, ELO upsets, non-standard end conditions, and marathon matches. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
fddcc0ba34
commit
ae8eb0465e
2 changed files with 44 additions and 1 deletions
|
|
@ -1 +1 @@
|
|||
17dbef092717f4f1f81dc870be3f8025466740a0
|
||||
fddcc0ba34f2ce3a57a17122826ef7963f8bf7d3
|
||||
|
|
|
|||
|
|
@ -874,6 +874,7 @@ func extractKeyMatches(botID string, data *IndexData) []KeyMatch {
|
|||
TurnCount: m.TurnCount,
|
||||
Won: botPart.Won,
|
||||
EndCondition: m.EndCondition,
|
||||
CriticalMoment: summarizeCriticalMoment(m, botPart, oppPart),
|
||||
})
|
||||
|
||||
if len(matches) >= 3 {
|
||||
|
|
@ -911,6 +912,7 @@ func extractRivalryMatches(botAID, botBID string, data *IndexData) []KeyMatch {
|
|||
Score: fmt.Sprintf("%d-%d", botAPart.Score, botBPart.Score),
|
||||
TurnCount: m.TurnCount,
|
||||
Won: botAPart.Won,
|
||||
CriticalMoment: summarizeCriticalMoment(m, botAPart, botBPart),
|
||||
})
|
||||
|
||||
if len(matches) >= 5 {
|
||||
|
|
@ -921,6 +923,47 @@ func extractRivalryMatches(botAID, botBID string, data *IndexData) []KeyMatch {
|
|||
return matches
|
||||
}
|
||||
|
||||
// summarizeCriticalMoment generates a brief turning-point description from
|
||||
// match data per plan §13.2. The index builder does not have access to the
|
||||
// full replay JSON (stored on B2/R2), so this uses the score, turn count,
|
||||
// end condition, and pre-match ELO to synthesize a contextual summary.
|
||||
func summarizeCriticalMoment(m MatchData, winner, loser *ParticipantData) string {
|
||||
scoreDelta := winner.Score - loser.Score
|
||||
if scoreDelta < 0 {
|
||||
scoreDelta = -scoreDelta
|
||||
}
|
||||
|
||||
parts := make([]string, 0, 3)
|
||||
|
||||
// Close match indicator
|
||||
if scoreDelta <= 1 {
|
||||
parts = append(parts, "decided by a single point")
|
||||
}
|
||||
|
||||
// ELO upset indicator
|
||||
if winner.PreMatchRating > 0 && loser.PreMatchRating > 0 {
|
||||
eloDelta := loser.PreMatchRating - winner.PreMatchRating
|
||||
if eloDelta >= 150 {
|
||||
parts = append(parts, fmt.Sprintf("upset by %.0f ELO points", eloDelta))
|
||||
}
|
||||
}
|
||||
|
||||
// End condition context
|
||||
if m.EndCondition != "" && m.EndCondition != "turn_limit" {
|
||||
parts = append(parts, m.EndCondition)
|
||||
}
|
||||
|
||||
// Late-game drama
|
||||
if m.TurnCount >= 400 {
|
||||
parts = append(parts, "marathon match")
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
func getBotRank(botID string, data *IndexData) int {
|
||||
for i, bot := range data.Bots {
|
||||
if bot.ID == botID {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue