Files
AetherForge d605ef4adb
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add Strain Hospice for graceful low-win strain retirement.
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.
2026-06-07 09:26:39 -07:00

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])
}