worldbox-observer-mod/IdleSpectator/Narrative/NarrativeFunctionPolicy.cs
DazedAnon 739cc6492c feat(spectator): make narrative threads own camera direction
- Route confirmed life events into evidence-based threads, consequences, and Legacy chapters
- Schedule episode shots with critical interrupts, replay guards, and a combat budget
- Admit emerging main characters through story momentum instead of spectacle
- Remove causal heat, hard-arc memory writes, and synthetic epilogue continuity
- Expand narrative harness coverage, soak auditing, and product documentation
2026-07-23 12:17:18 -05:00

110 lines
4.8 KiB
C#

using System;
namespace IdleSpectator;
/// <summary>
/// Editorial meaning of a camera candidate. This is deliberately separate from numeric
/// spectacle strength: a loud event can still be context, while a quiet state change can
/// advance a life story.
/// </summary>
public static class NarrativeFunctionPolicy
{
public static NarrativeFunction Classify(InterestCandidate candidate)
{
if (candidate == null) return NarrativeFunction.Noise;
if (candidate.HasNarrativeFunction) return candidate.NarrativeFunction;
string asset = (candidate.AssetId ?? "").ToLowerInvariant();
string category = (candidate.Category ?? "").ToLowerInvariant();
string source = (candidate.Source ?? "").ToLowerInvariant();
string verb = (candidate.Verb ?? "").ToLowerInvariant();
string text = asset + " " + category + " " + source + " " + verb;
if (ContainsAny(text, "add_child", "child_born", "new_child", "became_parent"))
return NarrativeFunction.Consequence;
if (ContainsAny(text, "clear_lover", "lover_died", "partner_died", "grief"))
return NarrativeFunction.TurningPoint;
if (ContainsAny(text, "set_lover", "new_family", "married", "bond_formed"))
return NarrativeFunction.ThreadOpener;
if (ContainsAny(text, "war_ended", "plot_ended", "plot_resolved", "recovered"))
return NarrativeFunction.Resolution;
if (ContainsAny(text, "death", "died", "killed", "destroyed", "role_lost", "lost_home"))
return NarrativeFunction.Consequence;
if (ContainsAny(text, "become_king", "new_king", "founded", "new_city", "role_gained"))
return NarrativeFunction.TurningPoint;
if (ContainsAny(text, "war_started", "joined_war", "plot_joined", "betray", "rival"))
return NarrativeFunction.Escalation;
switch (candidate.Completion)
{
case InterestCompletionKind.CombatActive:
case InterestCompletionKind.WarFront:
case InterestCompletionKind.PlotActive:
case InterestCompletionKind.StatusOutbreak:
return NarrativeFunction.Escalation;
case InterestCompletionKind.HappinessGrief:
return NarrativeFunction.TurningPoint;
case InterestCompletionKind.EarthquakeActive:
return NarrativeFunction.WorldContext;
case InterestCompletionKind.FamilyPack:
return NarrativeFunction.Development;
case InterestCompletionKind.ActivityActive:
return candidate.LeadKind == InterestLeadKind.EventLed
? NarrativeFunction.Development
: NarrativeFunction.CharacterTexture;
case InterestCompletionKind.CharacterVignette:
case InterestCompletionKind.StatusPhase:
return NarrativeFunction.CharacterTexture;
}
if (StoryReason.IsStoryAsset(candidate.AssetId)
|| source.IndexOf("story", StringComparison.Ordinal) >= 0)
{
return NarrativeFunction.Development;
}
if (candidate.LeadKind == InterestLeadKind.CharacterLed)
return NarrativeFunction.CharacterTexture;
if (candidate.EventStrength >= 95f) return NarrativeFunction.WorldContext;
if (candidate.EventStrength >= 70f) return NarrativeFunction.Development;
return candidate.EventStrength < 35f
? NarrativeFunction.Noise
: NarrativeFunction.CharacterTexture;
}
public static bool ChangesStoryState(NarrativeFunction function)
{
return function == NarrativeFunction.ThreadOpener
|| function == NarrativeFunction.Development
|| function == NarrativeFunction.Escalation
|| function == NarrativeFunction.TurningPoint
|| function == NarrativeFunction.Resolution
|| function == NarrativeFunction.Consequence;
}
public static float EditorialBonus(NarrativeFunction function)
{
switch (function)
{
case NarrativeFunction.ThreadOpener: return 8f;
case NarrativeFunction.Development: return 12f;
case NarrativeFunction.Escalation: return 18f;
case NarrativeFunction.TurningPoint: return 28f;
case NarrativeFunction.Resolution: return 32f;
case NarrativeFunction.Consequence: return 24f;
case NarrativeFunction.WorldContext: return 10f;
case NarrativeFunction.CharacterTexture: return -15f;
default: return -35f;
}
}
private static bool ContainsAny(string text, params string[] needles)
{
for (int i = 0; i < needles.Length; i++)
{
if (text.IndexOf(needles[i], StringComparison.Ordinal) >= 0) return true;
}
return false;
}
}