worldbox-observer-mod/IdleSpectator/Narrative/InterruptPolicy.cs
DazedAnon 12e2ba0d01 feat(narrative): refine camera and narrative handling for civilized terminology
- Implement checks for civilized terminology in camera focus events
- Update event handling to suppress inappropriate pack language for civilized characters
- Enhance narrative presentation to differentiate between family and pack language
- Introduce new scenarios to validate significance of beats and relationships
- Improve documentation to clarify narrative structure and event significance
2026-07-24 14:06:23 -05:00

87 lines
3.2 KiB
C#

using System;
namespace IdleSpectator;
public enum NarrativeInterruptClass
{
None,
RelatedEscalation,
StoryPayoff,
WorldCritical,
OrdinaryInteresting,
TextureNoise
}
/// <summary>Explicit editorial interrupt classification for episode scheduling.</summary>
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);
}