using System; namespace IdleSpectator; public enum NarrativeInterruptClass { None, RelatedEscalation, StoryPayoff, WorldCritical, OrdinaryInteresting, TextureNoise } /// Explicit editorial interrupt classification for episode scheduling. public static class InterruptPolicy { public static NarrativeInterruptClass Classify( InterestCandidate candidate, EpisodePlan episode, NarrativeThread thread) { if (candidate == null) return NarrativeInterruptClass.None; NarrativeFunction function = NarrativeFunctionPolicy.Classify(candidate); if (function == NarrativeFunction.Noise || function == NarrativeFunction.CharacterTexture) { return NarrativeInterruptClass.TextureNoise; } if (IsRelated(candidate, episode, thread) && NarrativeFunctionPolicy.ChangesStoryState(function)) { return NarrativeInterruptClass.RelatedEscalation; } if (WorldCriticalPolicy.IsWorldCritical(candidate)) return NarrativeInterruptClass.WorldCritical; if (IsStoryPayoff(candidate)) return NarrativeInterruptClass.StoryPayoff; return NarrativeInterruptClass.OrdinaryInteresting; } public static bool MayInterrupt(NarrativeInterruptClass kind) { return kind == NarrativeInterruptClass.WorldCritical || kind == NarrativeInterruptClass.StoryPayoff || kind == NarrativeInterruptClass.RelatedEscalation; } public static bool IsRelated(InterestCandidate candidate, EpisodePlan episode, NarrativeThread thread) { if (candidate == null || episode == null) return false; if (Touches(candidate, episode.ProtagonistId)) return true; for (int i = 0; i < episode.EligibleCastIds.Count; i++) { if (Touches(candidate, episode.EligibleCastIds[i])) return true; } if (thread == null || string.IsNullOrEmpty(thread.AnchorKey)) return false; return EqualsKey(candidate.CorrelationKey, thread.AnchorKey) || EqualsKey(candidate.CityKey, thread.AnchorKey) || EqualsKey(candidate.KingdomKey, thread.AnchorKey) || (candidate.Key ?? "").IndexOf(thread.AnchorKey, StringComparison.OrdinalIgnoreCase) >= 0; } private static bool Touches(InterestCandidate c, long id) { if (id == 0) return false; if (c.SubjectId == id || c.RelatedId == id || c.PairOwnerId == id || c.PairPartnerId == id || c.TheaterLeadId == id || EventFeedUtil.SafeId(c.FollowUnit) == id) return true; for (int i = 0; i < c.ParticipantIds.Count; i++) { if (c.ParticipantIds[i] == id) return true; } return false; } private static bool IsStoryPayoff(InterestCandidate c) { return StoryReason.IsStoryAsset(c.AssetId) || (c.Source ?? "").IndexOf("story", StringComparison.OrdinalIgnoreCase) >= 0; } private static bool EqualsKey(string a, string b) => !string.IsNullOrEmpty(a) && !string.IsNullOrEmpty(b) && string.Equals(a, b, StringComparison.OrdinalIgnoreCase); }