178 lines
6.1 KiB
Go
178 lines
6.1 KiB
Go
package persona
|
|
|
|
import "fmt"
|
|
|
|
// ConsistencyIssue represents an internally inconsistent attribute combination.
|
|
// Unlike plausibility violations (which compare against ethnicity expectations),
|
|
// consistency issues catch impossible or contradictory combinations regardless
|
|
// of ethnicity.
|
|
type ConsistencyIssue struct {
|
|
Field1 string // First field involved (e.g., "identity.age")
|
|
Value1 interface{} // Value of first field
|
|
Field2 string // Second field involved (e.g., "face.hair_color")
|
|
Value2 interface{} // Value of second field
|
|
Severity string // "warning" or "error"
|
|
Description string // Human-readable explanation
|
|
}
|
|
|
|
// ConsistencyResult contains cross-attribute validation results.
|
|
type ConsistencyResult struct {
|
|
Valid bool // True if no errors (warnings are OK)
|
|
Issues []ConsistencyIssue // All issues found
|
|
}
|
|
|
|
// ValidateConsistency checks for internally inconsistent attribute combinations
|
|
// that are implausible regardless of ethnicity.
|
|
//
|
|
// Examples of inconsistencies:
|
|
// - Young age (< 25) with gray hair (without dye indication)
|
|
// - Mature skin texture with young age
|
|
// - Height category mismatched with height CM
|
|
// - Muscle definition that contradicts build type
|
|
func ValidateConsistency(dna *DNA) *ConsistencyResult {
|
|
if dna == nil {
|
|
return &ConsistencyResult{Valid: true}
|
|
}
|
|
|
|
result := &ConsistencyResult{
|
|
Valid: true,
|
|
Issues: []ConsistencyIssue{},
|
|
}
|
|
|
|
// Age vs Hair Color consistency
|
|
if dna.Identity.Age > 0 && dna.Face.HairColor != "" {
|
|
if dna.Identity.Age < 30 && dna.Face.HairColor == HairColorGray {
|
|
result.Issues = append(result.Issues, ConsistencyIssue{
|
|
Field1: "identity.age",
|
|
Value1: dna.Identity.Age,
|
|
Field2: "face.hair_color",
|
|
Value2: dna.Face.HairColor,
|
|
Severity: "warning",
|
|
Description: fmt.Sprintf("Gray hair at age %d is unusual without premature graying or dye", dna.Identity.Age),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Age vs Skin Texture consistency
|
|
if dna.Identity.Age > 0 && dna.Face.SkinTexture != "" {
|
|
if dna.Identity.Age < 35 && dna.Face.SkinTexture == SkinTextureMature {
|
|
result.Issues = append(result.Issues, ConsistencyIssue{
|
|
Field1: "identity.age",
|
|
Value1: dna.Identity.Age,
|
|
Field2: "face.skin_texture",
|
|
Value2: dna.Face.SkinTexture,
|
|
Severity: "warning",
|
|
Description: fmt.Sprintf("Mature skin texture at age %d is inconsistent", dna.Identity.Age),
|
|
})
|
|
}
|
|
if dna.Identity.Age > 55 && dna.Face.SkinTexture == SkinTextureSmooth {
|
|
result.Issues = append(result.Issues, ConsistencyIssue{
|
|
Field1: "identity.age",
|
|
Value1: dna.Identity.Age,
|
|
Field2: "face.skin_texture",
|
|
Value2: dna.Face.SkinTexture,
|
|
Severity: "warning",
|
|
Description: fmt.Sprintf("Smooth skin texture at age %d is unusual", dna.Identity.Age),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Height Category vs Height CM consistency
|
|
if dna.Body.HeightCM > 0 && dna.Body.Height != "" {
|
|
if issue := validateHeightCMConsistency(dna.Body.HeightCM, dna.Body.Height); issue != nil {
|
|
result.Issues = append(result.Issues, *issue)
|
|
if issue.Severity == "error" {
|
|
result.Valid = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Muscle Definition vs Build consistency
|
|
if dna.Body.MuscleDefinition != "" && dna.Body.Build != "" {
|
|
// High muscle definition with slender build is contradictory
|
|
if dna.Body.MuscleDefinition == MuscleDefinitionDefined || dna.Body.MuscleDefinition == MuscleDefinitionRipped {
|
|
if dna.Body.Build == BodyBuildSlender || dna.Body.Build == BodyBuildPetite {
|
|
result.Issues = append(result.Issues, ConsistencyIssue{
|
|
Field1: "body.muscle_definition",
|
|
Value1: dna.Body.MuscleDefinition,
|
|
Field2: "body.build",
|
|
Value2: dna.Body.Build,
|
|
Severity: "warning",
|
|
Description: fmt.Sprintf("High muscle definition (%s) is unusual with %s build", dna.Body.MuscleDefinition, dna.Body.Build),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Body Fat Percent vs Build consistency
|
|
if dna.Body.BodyFatPercent > 0 && dna.Body.Build != "" {
|
|
// Very low body fat (< 15%) with curvy/plus_curvy build is contradictory
|
|
if dna.Body.BodyFatPercent < 15 {
|
|
if dna.Body.Build == BodyBuildCurvy || dna.Body.Build == BodyBuildPlusCurvy {
|
|
result.Issues = append(result.Issues, ConsistencyIssue{
|
|
Field1: "body.body_fat_percent",
|
|
Value1: dna.Body.BodyFatPercent,
|
|
Field2: "body.build",
|
|
Value2: dna.Body.Build,
|
|
Severity: "error",
|
|
Description: fmt.Sprintf("Very low body fat (%d%%) is incompatible with %s build", dna.Body.BodyFatPercent, dna.Body.Build),
|
|
})
|
|
result.Valid = false
|
|
}
|
|
}
|
|
// High body fat (> 30%) with athletic/muscular build is contradictory
|
|
if dna.Body.BodyFatPercent > 30 {
|
|
if dna.Body.Build == BodyBuildAthletic || dna.Body.Build == BodyBuildMuscular {
|
|
result.Issues = append(result.Issues, ConsistencyIssue{
|
|
Field1: "body.body_fat_percent",
|
|
Value1: dna.Body.BodyFatPercent,
|
|
Field2: "body.build",
|
|
Value2: dna.Body.Build,
|
|
Severity: "error",
|
|
Description: fmt.Sprintf("High body fat (%d%%) is incompatible with %s build", dna.Body.BodyFatPercent, dna.Body.Build),
|
|
})
|
|
result.Valid = false
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// validateHeightCMConsistency checks if height CM matches height category.
|
|
func validateHeightCMConsistency(heightCM int, heightCat HeightCategory) *ConsistencyIssue {
|
|
expectedCat := HeightCategoryFromCM(heightCM)
|
|
|
|
if heightCat != expectedCat {
|
|
return &ConsistencyIssue{
|
|
Field1: "body.height_cm",
|
|
Value1: heightCM,
|
|
Field2: "body.height",
|
|
Value2: heightCat,
|
|
Severity: "error",
|
|
Description: fmt.Sprintf("Height %dcm should be category %s, not %s", heightCM, expectedCat, heightCat),
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HasErrors returns true if there are any error-level issues.
|
|
func (r *ConsistencyResult) HasErrors() bool {
|
|
for _, issue := range r.Issues {
|
|
if issue.Severity == "error" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasWarnings returns true if there are any warning-level issues.
|
|
func (r *ConsistencyResult) HasWarnings() bool {
|
|
for _, issue := range r.Issues {
|
|
if issue.Severity == "warning" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|