worldbox-observer-mod/IdleSpectator/Narrative/NarrativePresentation.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

812 lines
29 KiB
C#

using System;
using System.Text;
namespace IdleSpectator;
/// <summary>
/// Semantic presentation kind. These values describe observed meaning, never final player-facing
/// English.
/// </summary>
public enum NarrativePresentationKind
{
Unknown,
BondFormed,
BondBroken,
SeekingLover,
FriendFormed,
ParentWelcomesChild,
ChildBorn,
FamilyFormed,
FamilyEnded,
FamilyPackFormed,
FamilyPackJoined,
FamilyPackLeft,
GiftGiven,
GiftReceived,
SharedIntimacy,
Conversation,
Gossip,
GrievesChild,
GrievesLover,
GrievesFriend,
GrievesKin,
AuthoredObservation,
EnsembleObservation,
StatusObservation,
WorldObservation
}
public enum NarrativeRelationRole
{
None,
Lover,
FormerLover,
Friend,
Parent,
Child,
Kin,
GiftPartner,
ConversationPartner
}
/// <summary>Complete semantic input for one orange Beat. Contains no final sentence.</summary>
public sealed class NarrativePresentationModel
{
public string EventId = "";
public string DevelopmentId = "";
public NarrativePresentationKind Kind;
public NarrativeDevelopmentKind DevelopmentKind;
public long PerspectiveId;
public LifeSagaIdentity Perspective;
public long OtherId;
public LifeSagaIdentity Other;
public NarrativeRelationRole RelationRole;
public int Ordinal;
public int Count;
public string Change = "";
public string Outcome = "";
/// <summary>Catalog-authored observation clause used only by the safe typed fallback.</summary>
public string AuthoredClause = "";
public string ActionId = "";
public string Phase = "";
public string EvidenceSource = "";
public float Confidence = 1f;
public NarrativePresentationModel Clone()
{
return new NarrativePresentationModel
{
EventId = EventId,
DevelopmentId = DevelopmentId,
Kind = Kind,
DevelopmentKind = DevelopmentKind,
PerspectiveId = PerspectiveId,
Perspective = Perspective,
OtherId = OtherId,
Other = Other,
RelationRole = RelationRole,
Ordinal = Ordinal,
Count = Count,
Change = Change,
Outcome = Outcome,
AuthoredClause = AuthoredClause,
ActionId = ActionId,
Phase = Phase,
EvidenceSource = EvidenceSource,
Confidence = Confidence
};
}
}
public static class NarrativePresentationFactory
{
public static bool HarnessProbeNamedParticipantFallback(out string detail)
{
var occurrence = new HappinessOccurrence
{
EffectId = "fallen_in_love",
SubjectId = 11,
SubjectName = "Joon",
RelatedId = 0,
RelatedName = "Omyahel",
RelatedSpecies = "human"
};
LifeSagaIdentity other = OccurrenceOtherIdentity(occurrence, default(LifeSagaIdentity));
var model = new NarrativePresentationModel
{
Kind = NarrativePresentationKind.BondFormed,
PerspectiveId = occurrence.SubjectId,
Perspective = new LifeSagaIdentity { Id = 11, Name = "Joon" },
OtherId = other.Id,
Other = other
};
string beat = NarrativeRenderer.Beat(model);
bool ok = other.Id == 0
&& other.Name == "Omyahel"
&& beat == "Finds love with Omyahel";
detail = "other=" + other.Id + ":" + other.Name
+ " beat='" + beat + "' pass=" + ok;
return ok;
}
public static NarrativePresentationModel Relationship(string eventId, Actor subject, Actor related)
{
if (subject == null)
{
return null;
}
string id = (eventId ?? "").Trim().ToLowerInvariant();
NarrativePresentationKind presentationKind;
NarrativeDevelopmentKind developmentKind = NarrativeDevelopmentKind.Unknown;
NarrativeRelationRole relation = NarrativeRelationRole.None;
switch (id)
{
case "set_lover":
presentationKind = NarrativePresentationKind.BondFormed;
developmentKind = NarrativeDevelopmentKind.BondFormed;
relation = NarrativeRelationRole.Lover;
break;
case "clear_lover":
presentationKind = NarrativePresentationKind.BondBroken;
developmentKind = NarrativeDevelopmentKind.BondBroken;
relation = NarrativeRelationRole.FormerLover;
break;
case "find_lover":
presentationKind = NarrativePresentationKind.SeekingLover;
break;
case "add_child":
presentationKind = NarrativePresentationKind.ParentWelcomesChild;
developmentKind = NarrativeDevelopmentKind.ChildBorn;
relation = NarrativeRelationRole.Child;
break;
case "baby_created":
presentationKind = NarrativePresentationKind.ChildBorn;
developmentKind = NarrativeDevelopmentKind.ChildBorn;
relation = NarrativeRelationRole.Parent;
if (related == null)
{
related = FirstParent(subject);
}
break;
case "new_family":
presentationKind = NarrativePresentationKind.FamilyFormed;
break;
case "family_removed":
presentationKind = NarrativePresentationKind.FamilyEnded;
break;
case "family_group_new":
presentationKind = NarrativePresentationKind.FamilyPackFormed;
relation = NarrativeRelationRole.Kin;
break;
case "family_group_join":
presentationKind = NarrativePresentationKind.FamilyPackJoined;
relation = NarrativeRelationRole.Kin;
break;
case "family_group_leave":
presentationKind = NarrativePresentationKind.FamilyPackLeft;
relation = NarrativeRelationRole.Kin;
break;
default:
return null;
}
long perspectiveId = EventFeedUtil.SafeId(subject);
LifeSagaIdentity otherSnapshot = LifeSagaMemory.Snapshot(related);
NarrativeDevelopment development = developmentKind == NarrativeDevelopmentKind.Unknown
? null
: MatchingDevelopment(perspectiveId, developmentKind, otherSnapshot.Id);
if (otherSnapshot.Id == 0 && development != null && development.OtherId != 0)
{
otherSnapshot = development.Other;
}
int ordinal = 0;
if (presentationKind == NarrativePresentationKind.ParentWelcomesChild)
{
ordinal = NarrativeStoryStore.CountConsequences(
perspectiveId, CharacterConsequenceKind.BecameParent);
}
string developmentId = development?.Id ?? "";
return new NarrativePresentationModel
{
EventId = string.IsNullOrEmpty(developmentId)
? "observed:" + id + ":" + perspectiveId + ":" + otherSnapshot.Id
: developmentId,
DevelopmentId = developmentId,
Kind = presentationKind,
DevelopmentKind = developmentKind,
PerspectiveId = perspectiveId,
Perspective = LifeSagaMemory.Snapshot(subject),
OtherId = otherSnapshot.Id,
Other = otherSnapshot,
RelationRole = relation,
Ordinal = ordinal,
EvidenceSource = "relationship:" + id,
Confidence = 1f
};
}
/// <summary>
/// Typed presentation for relationship-bearing happiness signals. Ambient effects deliberately
/// stay on the compatibility path until their semantic catalog migration.
/// </summary>
public static NarrativePresentationModel Happiness(
HappinessOccurrence occurrence,
Actor subject,
Actor related)
{
if (occurrence == null || subject == null)
{
return null;
}
string id = (occurrence.EffectId ?? "").Trim().ToLowerInvariant();
NarrativePresentationKind presentationKind;
NarrativeDevelopmentKind developmentKind = NarrativeDevelopmentKind.Unknown;
NarrativeRelationRole relation = NarrativeRelationRole.None;
switch (id)
{
case "fallen_in_love":
presentationKind = NarrativePresentationKind.BondFormed;
developmentKind = NarrativeDevelopmentKind.BondFormed;
relation = NarrativeRelationRole.Lover;
break;
case "just_made_friend":
presentationKind = NarrativePresentationKind.FriendFormed;
developmentKind = NarrativeDevelopmentKind.FriendFormed;
relation = NarrativeRelationRole.Friend;
break;
case "just_had_child":
presentationKind = NarrativePresentationKind.ParentWelcomesChild;
developmentKind = NarrativeDevelopmentKind.ChildBorn;
relation = NarrativeRelationRole.Child;
break;
case "just_gave_gift":
presentationKind = NarrativePresentationKind.GiftGiven;
relation = NarrativeRelationRole.GiftPartner;
break;
case "just_received_gift":
presentationKind = NarrativePresentationKind.GiftReceived;
relation = NarrativeRelationRole.GiftPartner;
break;
case "just_kissed":
presentationKind = NarrativePresentationKind.SharedIntimacy;
relation = NarrativeRelationRole.Lover;
break;
case "just_talked":
presentationKind = NarrativePresentationKind.Conversation;
relation = NarrativeRelationRole.ConversationPartner;
break;
case "just_talked_gossip":
presentationKind = NarrativePresentationKind.Gossip;
relation = NarrativeRelationRole.ConversationPartner;
break;
case "death_child":
presentationKind = NarrativePresentationKind.GrievesChild;
relation = NarrativeRelationRole.Child;
break;
case "death_lover":
presentationKind = NarrativePresentationKind.GrievesLover;
relation = NarrativeRelationRole.FormerLover;
break;
case "death_best_friend":
presentationKind = NarrativePresentationKind.GrievesFriend;
relation = NarrativeRelationRole.Friend;
break;
case "death_family_member":
presentationKind = NarrativePresentationKind.GrievesKin;
relation = NarrativeRelationRole.Kin;
break;
default:
return null;
}
long perspectiveId = EventFeedUtil.SafeId(subject);
LifeSagaIdentity otherSnapshot =
OccurrenceOtherIdentity(occurrence, LifeSagaMemory.Snapshot(related));
NarrativeDevelopment development = developmentKind == NarrativeDevelopmentKind.Unknown
? null
: MatchingDevelopment(perspectiveId, developmentKind, otherSnapshot.Id);
if (otherSnapshot.Id == 0 && development != null && development.OtherId != 0)
{
otherSnapshot = development.Other;
}
int ordinal = presentationKind == NarrativePresentationKind.ParentWelcomesChild
? NarrativeStoryStore.CountConsequences(
perspectiveId, CharacterConsequenceKind.BecameParent)
: 0;
string developmentId = development?.Id ?? "";
string eventId = !string.IsNullOrEmpty(developmentId)
? developmentId
: "happiness:" + id + ":" + perspectiveId + ":" + otherSnapshot.Id
+ ":" + (occurrence.CorrelationKey ?? "");
return new NarrativePresentationModel
{
EventId = eventId,
DevelopmentId = developmentId,
Kind = presentationKind,
DevelopmentKind = developmentKind,
PerspectiveId = perspectiveId,
Perspective = LifeSagaMemory.Snapshot(subject),
OtherId = otherSnapshot.Id,
Other = otherSnapshot,
RelationRole = relation,
Ordinal = ordinal,
EvidenceSource = "happiness:" + id,
Confidence = 1f
};
}
/// <summary>
/// Safe typed envelope for catalog/ensemble events that do not create a durable character
/// development. The renderer owns subject removal and final HUD hygiene.
/// </summary>
public static NarrativePresentationModel Candidate(
string key,
string source,
string actionId,
string authoredClause,
Actor subject,
Actor related)
{
string normalizedSource = (source ?? "").Trim().ToLowerInvariant();
NarrativePresentationKind kind;
if (normalizedSource == "status")
{
kind = NarrativePresentationKind.StatusObservation;
}
else if (normalizedSource == "worldlog"
|| normalizedSource == "era"
|| normalizedSource == "worldlaw"
|| normalizedSource == "power"
|| normalizedSource == "biome"
|| normalizedSource == "earthquake")
{
kind = NarrativePresentationKind.WorldObservation;
}
else if ((actionId ?? "").StartsWith("live_", StringComparison.OrdinalIgnoreCase)
|| normalizedSource == "combat"
|| normalizedSource == "war"
|| normalizedSource == "plot")
{
kind = NarrativePresentationKind.EnsembleObservation;
}
else
{
kind = NarrativePresentationKind.AuthoredObservation;
}
return new NarrativePresentationModel
{
EventId = string.IsNullOrEmpty(key) ? "observed:" + normalizedSource : key,
Kind = kind,
PerspectiveId = EventFeedUtil.SafeId(subject),
Perspective = LifeSagaMemory.Snapshot(subject),
OtherId = EventFeedUtil.SafeId(related),
Other = LifeSagaMemory.Snapshot(related),
AuthoredClause = authoredClause ?? "",
ActionId = actionId ?? "",
Phase = normalizedSource,
EvidenceSource = "catalog:" + normalizedSource,
Confidence = 1f
};
}
private static NarrativeDevelopment MatchingDevelopment(
long perspectiveId,
NarrativeDevelopmentKind kind,
long otherId)
{
NarrativeDevelopment development = NarrativeStoryStore.LatestFor(perspectiveId, kind, 3f);
if (development == null)
{
return null;
}
if (otherId != 0 && development.OtherId != 0 && development.OtherId != otherId)
{
return null;
}
return development;
}
private static LifeSagaIdentity OccurrenceOtherIdentity(
HappinessOccurrence occurrence,
LifeSagaIdentity live)
{
if (live.Id != 0 || occurrence == null)
{
return live;
}
if (occurrence.RelatedId == 0 && string.IsNullOrEmpty(occurrence.RelatedName))
{
return live;
}
return new LifeSagaIdentity
{
Id = occurrence.RelatedId,
Name = occurrence.RelatedName ?? "",
SpeciesId = occurrence.RelatedSpecies ?? ""
};
}
private static Actor FirstParent(Actor child)
{
foreach (Actor parent in ActorRelation.EnumerateParents(child, 1))
{
if (parent != null)
{
return parent;
}
}
return null;
}
}
/// <summary>Single English owner for typed narrative presentation.</summary>
public static class NarrativeRenderer
{
public static string Beat(NarrativePresentationModel model)
{
if (model == null || model.Confidence < 0.75f)
{
return "";
}
string other = CleanName(model.Other.Name);
switch (model.Kind)
{
case NarrativePresentationKind.BondFormed:
return string.IsNullOrEmpty(other) ? "Finds love" : "Finds love with " + other;
case NarrativePresentationKind.BondBroken:
return string.IsNullOrEmpty(other) ? "Parts from a lover" : "Parts from " + other;
case NarrativePresentationKind.SeekingLover:
return "Seeks a lover";
case NarrativePresentationKind.FriendFormed:
return string.IsNullOrEmpty(other) ? "Makes a true friend" : "Befriends " + other;
case NarrativePresentationKind.ParentWelcomesChild:
{
string ordinal = model.Ordinal == 1 ? " first" : "";
return string.IsNullOrEmpty(other)
? "Welcomes a" + ordinal + " child"
: "Welcomes" + ordinal + " child " + other;
}
case NarrativePresentationKind.ChildBorn:
return string.IsNullOrEmpty(other) ? "Is born" : "Is born to " + other;
case NarrativePresentationKind.FamilyFormed:
return "Founds a family";
case NarrativePresentationKind.FamilyEnded:
return "Their family line ends";
case NarrativePresentationKind.FamilyPackFormed:
{
bool pack = LifeSagaRoster.UsesPackLanguage(
EventFeedUtil.FindAliveById(model.PerspectiveId));
string group = pack ? "a family pack" : "a family";
return string.IsNullOrEmpty(other)
? "Forms " + group
: "Forms " + group + " with " + other;
}
case NarrativePresentationKind.FamilyPackJoined:
{
bool pack = LifeSagaRoster.UsesPackLanguage(
EventFeedUtil.FindAliveById(model.PerspectiveId));
string group = pack ? "a family pack" : "a family";
return string.IsNullOrEmpty(other)
? "Joins " + group
: "Joins " + group + " with " + other;
}
case NarrativePresentationKind.FamilyPackLeft:
{
bool pack = LifeSagaRoster.UsesPackLanguage(
EventFeedUtil.FindAliveById(model.PerspectiveId));
string group = pack ? "a family pack" : "a family";
return string.IsNullOrEmpty(other)
? "Leaves " + group
: "Leaves " + group + " shared with " + other;
}
case NarrativePresentationKind.GiftGiven:
return string.IsNullOrEmpty(other) ? "Gives a gift" : "Gives " + other + " a gift";
case NarrativePresentationKind.GiftReceived:
return string.IsNullOrEmpty(other)
? "Receives a gift"
: "Receives a gift from " + other;
case NarrativePresentationKind.SharedIntimacy:
return string.IsNullOrEmpty(other)
? "Shares intimacy with their lover"
: "Shares intimacy with " + other;
case NarrativePresentationKind.Conversation:
return string.IsNullOrEmpty(other)
? "Finishes a conversation"
: "Finishes a conversation with " + other;
case NarrativePresentationKind.Gossip:
return string.IsNullOrEmpty(other)
? "Shares gossip"
: "Shares gossip with " + other;
case NarrativePresentationKind.GrievesChild:
return string.IsNullOrEmpty(other) ? "Mourns a child" : "Mourns child " + other;
case NarrativePresentationKind.GrievesLover:
return string.IsNullOrEmpty(other) ? "Mourns a lost lover" : "Mourns lover " + other;
case NarrativePresentationKind.GrievesFriend:
return string.IsNullOrEmpty(other) ? "Mourns a lost friend" : "Mourns friend " + other;
case NarrativePresentationKind.GrievesKin:
return string.IsNullOrEmpty(other) ? "Mourns a family member" : "Mourns kin " + other;
case NarrativePresentationKind.AuthoredObservation:
case NarrativePresentationKind.EnsembleObservation:
case NarrativePresentationKind.StatusObservation:
case NarrativePresentationKind.WorldObservation:
return SafeAuthoredBeat(model);
default:
return "";
}
}
/// <summary>
/// Safety audit only. Semantic models prevent duplication; this detects regressions in rendered
/// output and compatibility fallbacks without rewriting their prose.
/// </summary>
public static bool HasSemanticRedundancy(string beat, NarrativePresentationModel model = null)
{
if (string.IsNullOrEmpty(beat))
{
return false;
}
string plain = StripRichText(beat).ToLowerInvariant();
string[] clauses = plain.Split(new[] { " · " }, StringSplitOptions.None);
if (clauses.Length > 1)
{
string left = clauses[0];
for (int i = 1; i < clauses.Length; i++)
{
string right = clauses[i];
if (RepeatsConcept(left, right, "child")
|| RepeatsConcept(left, right, "lover")
|| RepeatsConcept(left, right, "friend")
|| RepeatsConcept(left, right, "parent")
|| RepeatsConcept(left, right, "family pack"))
{
return true;
}
}
}
string other = CleanName(model?.Other.Name);
return !string.IsNullOrEmpty(other) && CountOccurrences(plain, other.ToLowerInvariant()) > 1;
}
public static bool HarnessProbeRelationshipProse(out string detail)
{
var parent = new LifeSagaIdentity { Id = 1, Name = "Joon", SpeciesId = "human" };
var child = new LifeSagaIdentity { Id = 2, Name = "Omyahel", SpeciesId = "human" };
var parentBeat = new NarrativePresentationModel
{
EventId = "event:child:1:2",
DevelopmentId = "child:1:2",
Kind = NarrativePresentationKind.ParentWelcomesChild,
DevelopmentKind = NarrativeDevelopmentKind.ChildBorn,
PerspectiveId = parent.Id,
Perspective = parent,
OtherId = child.Id,
Other = child,
RelationRole = NarrativeRelationRole.Child,
Ordinal = 1
};
var childBeat = new NarrativePresentationModel
{
EventId = parentBeat.EventId,
DevelopmentId = parentBeat.DevelopmentId,
Kind = NarrativePresentationKind.ChildBorn,
DevelopmentKind = NarrativeDevelopmentKind.ChildBorn,
PerspectiveId = child.Id,
Perspective = child,
OtherId = parent.Id,
Other = parent,
RelationRole = NarrativeRelationRole.Parent
};
var loverBeat = new NarrativePresentationModel
{
Kind = NarrativePresentationKind.BondFormed,
PerspectiveId = parent.Id,
Perspective = parent,
OtherId = child.Id,
Other = child,
RelationRole = NarrativeRelationRole.Lover
};
string parentLine = Beat(parentBeat);
string childLine = Beat(childBeat);
string loverLine = Beat(loverBeat);
bool oldDetected = HasSemanticRedundancy(
"Gains a child, Omyahel · their child", parentBeat);
bool ok = parentLine == "Welcomes first child Omyahel"
&& childLine == "Is born to Joon"
&& loverLine == "Finds love with Omyahel"
&& !HasSemanticRedundancy(parentLine, parentBeat)
&& !HasSemanticRedundancy(childLine, childBeat)
&& !HasSemanticRedundancy(loverLine, loverBeat)
&& oldDetected;
detail = "parent='" + parentLine
+ "' child='" + childLine
+ "' lover='" + loverLine
+ "' oldDetected=" + oldDetected
+ " pass=" + ok;
return ok;
}
private static bool RepeatsConcept(string left, string right, string concept)
{
return left.IndexOf(concept, StringComparison.Ordinal) >= 0
&& right.IndexOf(concept, StringComparison.Ordinal) >= 0;
}
private static int CountOccurrences(string value, string needle)
{
if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(needle))
{
return 0;
}
int count = 0;
int at = 0;
while ((at = value.IndexOf(needle, at, StringComparison.Ordinal)) >= 0)
{
count++;
at += needle.Length;
}
return count;
}
private static string CleanName(string value) => (value ?? "").Trim();
private static string SafeAuthoredBeat(NarrativePresentationModel model)
{
string beat = StripRichText(model?.AuthoredClause ?? "").Trim();
string subject = CleanName(model?.Perspective.Name);
if (!string.IsNullOrEmpty(subject)
&& beat.StartsWith(subject, StringComparison.OrdinalIgnoreCase))
{
beat = beat.Substring(subject.Length).TrimStart(' ', '·', '-', ':');
}
if (string.IsNullOrEmpty(beat))
{
beat = EventReason.HumanizeId(model?.ActionId ?? "");
}
if (beat.IndexOf('{') >= 0 || beat.IndexOf('}') >= 0)
{
beat = beat.Replace("{a}", "").Replace("{b}", "")
.Replace("{id}", EventReason.HumanizeId(model?.ActionId ?? ""))
.Replace("{", "").Replace("}", "").Trim();
}
if (!string.IsNullOrEmpty(beat))
{
beat = char.ToUpperInvariant(beat[0]) + (beat.Length > 1 ? beat.Substring(1) : "");
}
return beat;
}
private static string StripRichText(string value)
{
if (string.IsNullOrEmpty(value) || value.IndexOf('<') < 0)
{
return value ?? "";
}
var sb = new StringBuilder(value.Length);
bool tag = false;
for (int i = 0; i < value.Length; i++)
{
char ch = value[i];
if (ch == '<')
{
tag = true;
continue;
}
if (tag)
{
if (ch == '>')
{
tag = false;
}
continue;
}
sb.Append(ch);
}
return sb.ToString();
}
}
/// <summary>Bounded counters for selected-caption prose provenance and quality.</summary>
public static class NarrativePresentationCoverage
{
public static int IntakeStructuredCount { get; private set; }
public static int IntakeCompatibilityCount { get; private set; }
public static int StructuredCount { get; private set; }
public static int ExactDevelopmentCount { get; private set; }
public static int CompatibilityCount { get; private set; }
public static int RedundancyCount { get; private set; }
public static string LastSource { get; private set; } = "";
public static string LastEventId { get; private set; } = "";
public static string LastDevelopmentId { get; private set; } = "";
public static string Summary =>
"intake=" + IntakeStructuredCount + "/" + IntakeCompatibilityCount
+ " structured=" + StructuredCount
+ " exact=" + ExactDevelopmentCount
+ " compat=" + CompatibilityCount
+ " redundant=" + RedundancyCount
+ " last=" + LastSource
+ " event=" + LastEventId
+ " development=" + LastDevelopmentId;
public static void Clear()
{
IntakeStructuredCount = 0;
IntakeCompatibilityCount = 0;
StructuredCount = 0;
ExactDevelopmentCount = 0;
CompatibilityCount = 0;
RedundancyCount = 0;
LastSource = "";
LastEventId = "";
LastDevelopmentId = "";
}
public static void NoteIntake(InterestCandidate candidate)
{
if (candidate?.NarrativePresentation != null)
{
IntakeStructuredCount++;
}
else
{
IntakeCompatibilityCount++;
}
}
public static void Note(
string source,
string beat,
NarrativePresentationModel presentation,
bool exactDevelopment)
{
bool structured = presentation != null;
if (structured)
{
StructuredCount++;
if (exactDevelopment)
{
ExactDevelopmentCount++;
}
}
else
{
CompatibilityCount++;
}
if (NarrativeRenderer.HasSemanticRedundancy(beat, presentation))
{
RedundancyCount++;
}
LastSource = source ?? "";
LastEventId = presentation?.EventId ?? "";
LastDevelopmentId = presentation?.DevelopmentId ?? "";
}
}