using System.Collections.Generic; using UnityEngine; namespace IdleSpectator; public enum InterestLeadKind { EventLed = 0, CharacterLed = 1 } public enum InterestCompletionKind { /// One-shot narrative dwell (WorldLog, harness tips). FixedDwell = 0, /// Combat / hunt / fire while participants stay live. CombatActive = 1, /// Owning AI task / beat still live. ActivityActive = 2, /// Grief / family emotion with survivor + optional crying. HappinessGrief = 3, /// Status phase still present on subject. StatusPhase = 4, /// Character vignette until max watch or cold activity. CharacterVignette = 5, /// Harness-forced session; active until released or max cap. Manual = 6, /// Sticky war front while the war / kingdoms remain resolvable. WarFront = 7, /// Sticky plot cell while the plot / plotters remain resolvable. PlotActive = 8, /// Sticky family pack while members / alpha remain resolvable. FamilyPack = 9, /// Sticky status outbreak while clustered carriers remain resolvable. StatusOutbreak = 10 } /// /// Durable interest scene candidate. is the sole ranking signal; /// typed fields (lead, completion, combat) gate interrupt policy. /// Multi-actor sticky scoreboard lives on . /// public sealed class InterestCandidate { public string Key = ""; public InterestLeadKind LeadKind = InterestLeadKind.EventLed; public string Category = ""; public string Source = ""; public float EventStrength; public float CharacterSignificance; public float Novelty = 1f; public float VisualConfidence = 0.5f; public float TotalScore; /// Live fighters / participants in a cluster (battles, multi-unit events). public int ParticipantCount; /// Sticky multi-actor scoreboard (combat first; war/plot/family later). public readonly LiveSceneStickyState Sticky = new LiveSceneStickyState(); /// Notable (king/favorite/leader/high renown) participants in the cluster. public int NotableParticipantCount; public Vector3 Position; public Actor FollowUnit; public Actor RelatedUnit; public long SubjectId; public long RelatedId; /// /// Durable 1v1 ownership: first Follow stays camera owner while both pair ids live. /// Prevents Duel A↔B thrash across maintain ticks and registry upserts. /// public long PairOwnerId; public long PairPartnerId; public string Label = ""; public string AssetId = ""; public string SpeciesId = ""; public string Verb = ""; public string CityKey = ""; public string KingdomKey = ""; public string RegionKey = ""; public string HappinessEffectId = ""; public string StatusId = ""; public string CorrelationKey = ""; public float CreatedAt; public float LastSeenAt; public float ExpiresAt; public float MinWatch = 1.5f; public float MaxWatch = 45f; public InterestCompletionKind Completion = InterestCompletionKind.FixedDwell; public bool Resumable = true; public bool ForceActive; public bool Selected; public string ScoreDetail = ""; public readonly List ParticipantIds = new List(4); // Compat forwarders - existing combat maintain/harness code. public int CombatPeakParticipants { get => Sticky.PeakParticipants; set => Sticky.PeakParticipants = value; } public EnsembleFrame CombatSideFrame { get => Sticky.Frame; set => Sticky.Frame = value; } public string CombatSideAKey { get => Sticky.SideAKey; set => Sticky.SideAKey = value; } public string CombatSideADisplay { get => Sticky.SideADisplay; set => Sticky.SideADisplay = value; } public string CombatSideAKingdom { get => Sticky.SideAKingdom; set => Sticky.SideAKingdom = value; } public int CombatSideACount { get => Sticky.SideACount; set => Sticky.SideACount = value; } public string CombatSideBKey { get => Sticky.SideBKey; set => Sticky.SideBKey = value; } public string CombatSideBDisplay { get => Sticky.SideBDisplay; set => Sticky.SideBDisplay = value; } public string CombatSideBKingdom { get => Sticky.SideBKingdom; set => Sticky.SideBKingdom = value; } public int CombatSideBCount { get => Sticky.SideBCount; set => Sticky.SideBCount = value; } public List CombatSideAIds => Sticky.SideAIds; public List CombatSideBIds => Sticky.SideBIds; public bool HasFollowUnit => FollowUnit != null && FollowUnit.isAlive(); public bool HasValidPosition => HasFollowUnit || (Position != Vector3.zero && !float.IsNaN(Position.x)); public bool HasStickyCombatSides => Sticky.HasOpposingSides; public void ClearCombatSticky() => Sticky.Clear(); /// Lock durable 1v1 owner/partner ids (no-op when owner missing). public void StampCombatPair(Actor owner, Actor partner) { long oid = EventFeedUtil.SafeId(owner); if (oid == 0) { return; } if (PairOwnerId == 0) { PairOwnerId = oid; } long pid = EventFeedUtil.SafeId(partner); if (pid != 0 && pid != PairOwnerId) { PairPartnerId = pid; } } public bool HasCombatPairLock => PairOwnerId != 0; public InterestEvent ToInterestEvent() { return new InterestEvent { Score = TotalScore, Position = Position, FollowUnit = FollowUnit, RelatedUnit = RelatedUnit, Label = Label, CreatedAt = CreatedAt, AssetId = AssetId, ParticipantCount = ParticipantCount, NotableParticipantCount = NotableParticipantCount, CharacterSignificance = CharacterSignificance }; } public InterestCandidate CloneShallow() { var copy = new InterestCandidate { Key = Key, LeadKind = LeadKind, Category = Category, Source = Source, EventStrength = EventStrength, CharacterSignificance = CharacterSignificance, Novelty = Novelty, VisualConfidence = VisualConfidence, TotalScore = TotalScore, ParticipantCount = ParticipantCount, NotableParticipantCount = NotableParticipantCount, Position = Position, FollowUnit = FollowUnit, RelatedUnit = RelatedUnit, SubjectId = SubjectId, RelatedId = RelatedId, PairOwnerId = PairOwnerId, PairPartnerId = PairPartnerId, Label = Label, AssetId = AssetId, SpeciesId = SpeciesId, Verb = Verb, CityKey = CityKey, KingdomKey = KingdomKey, RegionKey = RegionKey, HappinessEffectId = HappinessEffectId, StatusId = StatusId, CorrelationKey = CorrelationKey, CreatedAt = CreatedAt, LastSeenAt = LastSeenAt, ExpiresAt = ExpiresAt, MinWatch = MinWatch, MaxWatch = MaxWatch, Completion = Completion, Resumable = Resumable, ForceActive = ForceActive, Selected = Selected, ScoreDetail = ScoreDetail }; Sticky.CopyTo(copy.Sticky); for (int i = 0; i < ParticipantIds.Count; i++) { copy.ParticipantIds.Add(ParticipantIds[i]); } return copy; } }