- 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
110 lines
4.8 KiB
C#
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;
|
|
}
|
|
}
|