worldbox-observer-mod/IdleSpectator/Narrative/NarrativeTelemetry.cs
DazedAnon ff4b40d00e feat(narrative): enhance narrative probes and presentation coverage.
- Introduce new narrative probes for episode grammar, ensemble balance, and prose redundancy
- Implement narrative presentation coverage tracking for better event visibility
- Update harness scenarios to include new narrative ingestion and persistence tests
- Refactor event emission to include narrative development IDs and presentations
- Expand InterestCandidate to support narrative presentation models and roles
2026-07-23 14:12:23 -05:00

131 lines
5.2 KiB
C#

using System.Collections.Generic;
namespace IdleSpectator;
public sealed class NarrativeTelemetryEntry
{
public float At;
public string ThreadId = "";
public long ProtagonistId;
public string ProposedKey = "";
public string ActualKey = "";
public string Reason = "";
public NarrativeInterruptClass InterruptClass;
public EpisodeShotRole ShotRole;
public float CoverageDebt;
public bool ActualRelated;
}
/// <summary>Bounded in-memory trace for shadow-vs-actual story scheduling.</summary>
public static class NarrativeTelemetry
{
private const int Cap = 256;
private static readonly List<NarrativeTelemetryEntry> Entries = new List<NarrativeTelemetryEntry>(Cap);
public static int Count => Entries.Count;
public static int RelatedActualCount { get; private set; }
public static int ComparedCount { get; private set; }
public static int RelatedProposalCount { get; private set; }
public static int CriticalProposalCount { get; private set; }
public static int AlignedSampleCount { get; private set; }
public static int PermittedInterruptSampleCount { get; private set; }
public static int PermittedContinuitySampleCount { get; private set; }
public static int UnalignedSampleCount { get; private set; }
public static int UnrelatedActualCount => ComparedCount - RelatedActualCount;
public static void Clear()
{
Entries.Clear();
RelatedActualCount = 0;
ComparedCount = 0;
RelatedProposalCount = 0;
CriticalProposalCount = 0;
AlignedSampleCount = 0;
PermittedInterruptSampleCount = 0;
PermittedContinuitySampleCount = 0;
UnalignedSampleCount = 0;
}
public static void NoteProposal(EpisodePlan episode, EpisodeShotProposal proposal, float now)
{
if (episode == null || proposal?.Candidate == null) return;
if (proposal.InterruptClass == NarrativeInterruptClass.RelatedEscalation) RelatedProposalCount++;
if (proposal.InterruptClass == NarrativeInterruptClass.WorldCritical) CriticalProposalCount++;
Entries.Add(new NarrativeTelemetryEntry
{
At = now,
ThreadId = episode.ThreadId,
ProtagonistId = episode.ProtagonistId,
ProposedKey = proposal.Candidate.Key ?? "",
Reason = proposal.Reason ?? "",
InterruptClass = proposal.InterruptClass,
ShotRole = proposal.Role,
CoverageDebt = NarrativeExposureLedger.ThreadDebt(episode.ThreadId)
});
Trim();
}
public static void NoteActual(EpisodePlan episode, NarrativeThread thread, InterestCandidate actual)
{
if (episode == null || actual == null) return;
bool related = InterruptPolicy.IsRelated(actual, episode, thread);
ComparedCount++;
if (related) RelatedActualCount++;
NarrativeTelemetryEntry last = Entries.Count > 0 ? Entries[Entries.Count - 1] : null;
if (last != null && last.ThreadId == episode.ThreadId && string.IsNullOrEmpty(last.ActualKey))
{
last.ActualKey = actual.Key ?? "";
last.ActualRelated = related;
}
}
public static void NoteCurrentAlignment(
EpisodePlan episode,
NarrativeThread thread,
InterestCandidate current)
{
if (episode == null || current == null) return;
if (InterruptPolicy.IsRelated(current, episode, thread))
{
AlignedSampleCount++;
return;
}
NarrativeInterruptClass kind = InterruptPolicy.Classify(current, episode, thread);
if (InterruptPolicy.MayInterrupt(kind))
{
AlignedSampleCount++;
PermittedInterruptSampleCount++;
return;
}
// Live sticky scenes are deliberately allowed to finish their immediate outcome
// before an episode reclaims the camera; report them separately from true drift.
if (current.Completion == InterestCompletionKind.CombatActive
|| current.Completion == InterestCompletionKind.WarFront
|| current.Completion == InterestCompletionKind.PlotActive
|| current.Completion == InterestCompletionKind.StatusOutbreak)
{
AlignedSampleCount++;
PermittedContinuitySampleCount++;
return;
}
UnalignedSampleCount++;
}
public static string Summary => "compared=" + ComparedCount
+ " actualRelated=" + RelatedActualCount
+ " actualUnrelated=" + UnrelatedActualCount
+ " proposals=" + Count
+ " relatedProposals=" + RelatedProposalCount
+ " criticalProposals=" + CriticalProposalCount
+ " alignedSamples=" + AlignedSampleCount
+ " permittedInterruptSamples=" + PermittedInterruptSampleCount
+ " permittedContinuitySamples=" + PermittedContinuitySampleCount
+ " unalignedSamples=" + UnalignedSampleCount;
private static void Trim()
{
while (Entries.Count > Cap) Entries.RemoveAt(0);
}
}