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

162 lines
6.4 KiB
C#

using UnityEngine;
namespace IdleSpectator;
/// <summary>Thin adapters from confirmed game observations to semantic narrative developments.</summary>
public static class NarrativeDevelopmentRouter
{
public static bool BondFormed(Actor subject, Actor partner, string source)
{
return Record(NarrativeDevelopmentKind.BondFormed, NarrativeFunction.ThreadOpener,
subject, partner, "bond:" + Pair(subject, partner) + ":formed:" + TimeBucket(), source, magnitude: 72f);
}
public static bool BondBroken(Actor subject, Actor former, string source)
{
return Record(NarrativeDevelopmentKind.BondBroken, NarrativeFunction.TurningPoint,
subject, former, "bond:" + Pair(subject, former) + ":broken:" + TimeBucket(), source, magnitude: 88f,
outcome: "partnership ended");
}
public static bool ChildBorn(Actor parent, Actor child, string source)
{
string family = FamilyKey(parent);
return Record(NarrativeDevelopmentKind.ChildBorn, NarrativeFunction.Consequence,
parent, child, "child:" + EventFeedUtil.SafeId(parent) + ":" + EventFeedUtil.SafeId(child),
source, familyKey: family, magnitude: 76f, outcome: "child born");
}
public static bool FriendFormed(Actor subject, Actor friend, string source)
{
return Record(NarrativeDevelopmentKind.FriendFormed, NarrativeFunction.ThreadOpener,
subject, friend, "friend:" + Pair(subject, friend), source, magnitude: 55f);
}
public static bool Death(Actor victim, Actor killer, string attackType, string source)
{
return Record(NarrativeDevelopmentKind.Death, NarrativeFunction.Resolution,
victim, killer, "death:" + EventFeedUtil.SafeId(victim), source,
newState: "dead", outcome: attackType ?? "", magnitude: 100f);
}
public static bool Kill(Actor killer, Actor victim, string source)
{
return Record(NarrativeDevelopmentKind.Kill, NarrativeFunction.TurningPoint,
killer, victim, "kill:" + EventFeedUtil.SafeId(killer) + ":" + EventFeedUtil.SafeId(victim),
source, outcome: "victim died", magnitude: 82f);
}
public static bool Role(Actor subject, string role, bool gained, string source)
{
return Record(gained ? NarrativeDevelopmentKind.RoleGained : NarrativeDevelopmentKind.RoleLost,
gained ? NarrativeFunction.TurningPoint : NarrativeFunction.Consequence,
subject, null, "role:" + EventFeedUtil.SafeId(subject) + ":" + (role ?? ""), source,
newState: role, magnitude: 78f);
}
public static bool Founding(Actor subject, string kind, string key, string source)
{
return Record(NarrativeDevelopmentKind.HomeFounded, NarrativeFunction.TurningPoint,
subject, null, "founding:" + kind + ":" + key + ":" + EventFeedUtil.SafeId(subject), source,
cityKey: kind == "city" ? key : "", kingdomKey: kind == "kingdom" ? key : "",
newState: kind, magnitude: 82f);
}
public static bool War(Actor subject, Actor other, string warKey, bool ended, string source)
{
return Record(ended ? NarrativeDevelopmentKind.WarEnded : NarrativeDevelopmentKind.WarJoined,
ended ? NarrativeFunction.Resolution : NarrativeFunction.Escalation,
subject, other, "war:" + warKey + ":" + EventFeedUtil.SafeId(subject) + ":" + ended,
source, warKey: warKey, magnitude: ended ? 86f : 68f,
outcome: ended ? "war ended" : "");
}
public static bool Plot(Actor subject, Actor other, string plotKey, bool ended, string source)
{
return Record(ended ? NarrativeDevelopmentKind.PlotEnded : NarrativeDevelopmentKind.PlotJoined,
ended ? NarrativeFunction.Resolution : NarrativeFunction.Escalation,
subject, other, "plot:" + plotKey + ":" + EventFeedUtil.SafeId(subject) + ":" + ended,
source, plotKey: plotKey, magnitude: ended ? 78f : 64f,
outcome: ended ? "plot ended" : "");
}
private static bool Record(
NarrativeDevelopmentKind kind,
NarrativeFunction function,
Actor subject,
Actor other,
string id,
string source,
string familyKey = "",
string cityKey = "",
string kingdomKey = "",
string warKey = "",
string plotKey = "",
string newState = "",
string outcome = "",
float magnitude = 50f)
{
long subjectId = EventFeedUtil.SafeId(subject);
if (subjectId == 0) return false;
long otherId = EventFeedUtil.SafeId(other);
return NarrativeStoryStore.Record(new NarrativeDevelopment
{
Id = id ?? "",
Kind = kind,
Function = function,
SubjectId = subjectId,
Subject = LifeSagaMemory.Snapshot(subject),
OtherId = otherId,
Other = LifeSagaMemory.Snapshot(other),
CorrelationKey = id ?? "",
FamilyKey = familyKey ?? "",
CityKey = cityKey ?? "",
KingdomKey = kingdomKey ?? "",
WarKey = warKey ?? "",
PlotKey = plotKey ?? "",
NewState = newState ?? "",
Outcome = outcome ?? "",
EvidenceSource = source ?? "",
DateLabel = LatestDate(subjectId),
OccurredAt = Time.unscaledTime,
Magnitude = magnitude,
Confidence = 1f,
Presentable = true,
IsNewStateChange = true
});
}
private static string LatestDate(long subjectId)
{
var facts = LifeSagaMemory.FactsFor(subjectId);
if (facts != null && facts.Count > 0 && facts[0] != null) return facts[0].DateLabel ?? "";
return "";
}
private static string Pair(Actor a, Actor b)
{
long ai = EventFeedUtil.SafeId(a);
long bi = EventFeedUtil.SafeId(b);
if (bi == 0) return ai.ToString();
return ai <= bi ? ai + ":" + bi : bi + ":" + ai;
}
private static string FamilyKey(Actor actor)
{
try
{
if (actor != null && actor.hasFamily() && actor.family != null)
{
return actor.family.getID().ToString();
}
}
catch
{
// Subject id remains a stable lineage fallback.
}
return EventFeedUtil.SafeId(actor).ToString();
}
private static int TimeBucket() => Mathf.FloorToInt(Time.unscaledTime * 10f);
}