using System; namespace IdleSpectator; /// /// Editorial meaning of a camera candidate. This is deliberately separate from numeric /// spectacle strength: a loud event can still be context, while a quiet state change can /// advance a life story. /// public static class NarrativeFunctionPolicy { public static NarrativeFunction Classify(InterestCandidate candidate) { if (candidate == null) return NarrativeFunction.Noise; if (candidate.HasNarrativeFunction) return candidate.NarrativeFunction; string asset = (candidate.AssetId ?? "").ToLowerInvariant(); string category = (candidate.Category ?? "").ToLowerInvariant(); string source = (candidate.Source ?? "").ToLowerInvariant(); string verb = (candidate.Verb ?? "").ToLowerInvariant(); string text = asset + " " + category + " " + source + " " + verb; if (ContainsAny(text, "add_child", "child_born", "new_child", "became_parent")) return NarrativeFunction.Consequence; if (ContainsAny(text, "clear_lover", "lover_died", "partner_died", "grief")) return NarrativeFunction.TurningPoint; if (ContainsAny(text, "set_lover", "new_family", "married", "bond_formed")) return NarrativeFunction.ThreadOpener; if (ContainsAny(text, "war_ended", "plot_ended", "plot_resolved", "recovered")) return NarrativeFunction.Resolution; if (ContainsAny(text, "death", "died", "killed", "destroyed", "role_lost", "lost_home")) return NarrativeFunction.Consequence; if (ContainsAny(text, "become_king", "new_king", "founded", "new_city", "role_gained")) return NarrativeFunction.TurningPoint; if (ContainsAny(text, "war_started", "joined_war", "plot_joined", "betray", "rival")) return NarrativeFunction.Escalation; switch (candidate.Completion) { case InterestCompletionKind.CombatActive: case InterestCompletionKind.WarFront: case InterestCompletionKind.PlotActive: case InterestCompletionKind.StatusOutbreak: return NarrativeFunction.Escalation; case InterestCompletionKind.HappinessGrief: return NarrativeFunction.TurningPoint; case InterestCompletionKind.EarthquakeActive: return NarrativeFunction.WorldContext; case InterestCompletionKind.FamilyPack: return NarrativeFunction.Development; case InterestCompletionKind.ActivityActive: return candidate.LeadKind == InterestLeadKind.EventLed ? NarrativeFunction.Development : NarrativeFunction.CharacterTexture; case InterestCompletionKind.CharacterVignette: case InterestCompletionKind.StatusPhase: return NarrativeFunction.CharacterTexture; } if (StoryReason.IsStoryAsset(candidate.AssetId) || source.IndexOf("story", StringComparison.Ordinal) >= 0) { return NarrativeFunction.Development; } if (candidate.LeadKind == InterestLeadKind.CharacterLed) return NarrativeFunction.CharacterTexture; if (candidate.EventStrength >= 95f) return NarrativeFunction.WorldContext; if (candidate.EventStrength >= 70f) return NarrativeFunction.Development; return candidate.EventStrength < 35f ? NarrativeFunction.Noise : NarrativeFunction.CharacterTexture; } public static bool ChangesStoryState(NarrativeFunction function) { return function == NarrativeFunction.ThreadOpener || function == NarrativeFunction.Development || function == NarrativeFunction.Escalation || function == NarrativeFunction.TurningPoint || function == NarrativeFunction.Resolution || function == NarrativeFunction.Consequence; } public static float EditorialBonus(NarrativeFunction function) { switch (function) { case NarrativeFunction.ThreadOpener: return 8f; case NarrativeFunction.Development: return 12f; case NarrativeFunction.Escalation: return 18f; case NarrativeFunction.TurningPoint: return 28f; case NarrativeFunction.Resolution: return 32f; case NarrativeFunction.Consequence: return 24f; case NarrativeFunction.WorldContext: return 10f; case NarrativeFunction.CharacterTexture: return -15f; default: return -35f; } } private static bool ContainsAny(string text, params string[] needles) { for (int i = 0; i < needles.Length; i++) { if (text.IndexOf(needles[i], StringComparison.Ordinal) >= 0) return true; } return false; } }