Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Archive failed epidemiology strains to museum hospice with SQLite persistence, operator/AI/court triggers, breeding and graft guards, topology museum nodes, and Seer plus oath ledger accountability.
95 lines
2.4 KiB
Go
95 lines
2.4 KiB
Go
package strategy
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// DefaultHospiceWinRateThreshold retires strains below 15% win rate.
|
|
DefaultHospiceWinRateThreshold = 0.15
|
|
// DefaultHospiceMinAttempts requires enough spread outcomes before auto-retire.
|
|
DefaultHospiceMinAttempts = 5
|
|
)
|
|
|
|
// StrainRetiredBy identifies who sent a strain to hospice.
|
|
type StrainRetiredBy string
|
|
|
|
const (
|
|
StrainRetiredByOperator StrainRetiredBy = "operator"
|
|
StrainRetiredByAI StrainRetiredBy = "ai"
|
|
StrainRetiredByCourt StrainRetiredBy = "court"
|
|
)
|
|
|
|
// StrainSpreadStats aggregates epidemiology wins/losses for one spread strain.
|
|
type StrainSpreadStats struct {
|
|
StrainID string
|
|
Wins int
|
|
Losses int
|
|
}
|
|
|
|
// NormalizeStrainID lowercases #RRGGBB strain identifiers.
|
|
func NormalizeStrainID(strain string) string {
|
|
s := strings.TrimSpace(strings.ToLower(strain))
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
if !strings.HasPrefix(s, "#") && len(s) == 6 {
|
|
s = "#" + s
|
|
}
|
|
return s
|
|
}
|
|
|
|
// StrainFromSpreadLane maps a join/spread lane to its stable strain color id.
|
|
func StrainFromSpreadLane(lane string) string {
|
|
if s := NormalizeStrainID(SpreadStrainFromJoinLane(lane)); s != "" {
|
|
return s
|
|
}
|
|
return NormalizeStrainID(lane)
|
|
}
|
|
|
|
// LaneInHospice reports whether a spread/join lane's strain is retired.
|
|
func LaneInHospice(lane string, hospice map[string]bool) bool {
|
|
if len(hospice) == 0 {
|
|
return false
|
|
}
|
|
return hospice[StrainFromSpreadLane(lane)]
|
|
}
|
|
|
|
// StrainInHospice reports whether a strain id is in the hospice set.
|
|
func StrainInHospice(strain string, hospice map[string]bool) bool {
|
|
if len(hospice) == 0 {
|
|
return false
|
|
}
|
|
return hospice[NormalizeStrainID(strain)]
|
|
}
|
|
|
|
// StrainWinRate returns wins / (wins + losses); 1.0 when no attempts recorded.
|
|
func StrainWinRate(wins, losses int) float64 {
|
|
total := wins + losses
|
|
if total == 0 {
|
|
return 1.0
|
|
}
|
|
return float64(wins) / float64(total)
|
|
}
|
|
|
|
// ShouldAutoRetireStrain is true when AI hospice policy applies to low performers.
|
|
func ShouldAutoRetireStrain(wins, losses int, threshold float64, minAttempts int) bool {
|
|
total := wins + losses
|
|
if total < minAttempts || minAttempts <= 0 {
|
|
return false
|
|
}
|
|
if threshold <= 0 {
|
|
threshold = DefaultHospiceWinRateThreshold
|
|
}
|
|
return StrainWinRate(wins, losses) < threshold
|
|
}
|
|
|
|
// PersonaPrimaryLane returns the first spread lane for a persona preset.
|
|
func PersonaPrimaryLane(persona string) string {
|
|
order := PersonaSpreadTierOrder(persona)
|
|
if len(order) == 0 {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(order[0])
|
|
}
|