- 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
337 lines
12 KiB
C#
337 lines
12 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 BondFormed(subject, partner, source, out _);
|
|
}
|
|
|
|
public static bool BondFormed(Actor subject, Actor partner, string source, out string developmentId)
|
|
{
|
|
developmentId = BondFormedId(subject, partner);
|
|
return Record(NarrativeDevelopmentKind.BondFormed, NarrativeFunction.ThreadOpener,
|
|
subject, partner, developmentId, source, magnitude: 72f);
|
|
}
|
|
|
|
public static bool BondBroken(Actor subject, Actor former, string source)
|
|
{
|
|
return BondBroken(subject, former, source, out _);
|
|
}
|
|
|
|
public static bool BondBroken(Actor subject, Actor former, string source, out string developmentId)
|
|
{
|
|
developmentId = BondBrokenId(subject, former);
|
|
return Record(NarrativeDevelopmentKind.BondBroken, NarrativeFunction.TurningPoint,
|
|
subject, former, developmentId, source, magnitude: 88f,
|
|
outcome: "partnership ended");
|
|
}
|
|
|
|
public static bool ChildBorn(Actor parent, Actor child, string source)
|
|
{
|
|
return ChildBorn(parent, child, source, out _);
|
|
}
|
|
|
|
public static bool ChildBorn(Actor parent, Actor child, string source, out string developmentId)
|
|
{
|
|
string family = FamilyKey(parent);
|
|
developmentId = ChildBornId(parent, child);
|
|
return Record(NarrativeDevelopmentKind.ChildBorn, NarrativeFunction.Consequence,
|
|
parent, child, developmentId,
|
|
source, familyKey: family, magnitude: 76f, outcome: "child born");
|
|
}
|
|
|
|
public static bool FriendFormed(Actor subject, Actor friend, string source)
|
|
{
|
|
return FriendFormed(subject, friend, source, out _);
|
|
}
|
|
|
|
public static bool FriendFormed(Actor subject, Actor friend, string source, out string developmentId)
|
|
{
|
|
developmentId = FriendFormedId(subject, friend);
|
|
return Record(NarrativeDevelopmentKind.FriendFormed, NarrativeFunction.ThreadOpener,
|
|
subject, friend, developmentId, source, magnitude: 55f);
|
|
}
|
|
|
|
public static string BondFormedId(Actor subject, Actor partner) =>
|
|
"bond:" + Pair(subject, partner) + ":formed:" + TimeBucket();
|
|
|
|
public static string BondBrokenId(Actor subject, Actor former) =>
|
|
"bond:" + Pair(subject, former) + ":broken:" + TimeBucket();
|
|
|
|
public static string ChildBornId(Actor parent, Actor child) =>
|
|
"child:" + EventFeedUtil.SafeId(parent) + ":" + EventFeedUtil.SafeId(child);
|
|
|
|
public static string FriendFormedId(Actor subject, Actor friend) =>
|
|
"friend:" + Pair(subject, friend);
|
|
|
|
internal static bool BondFormedExact(
|
|
Actor subject,
|
|
Actor partner,
|
|
string source,
|
|
string developmentId) =>
|
|
Record(NarrativeDevelopmentKind.BondFormed, NarrativeFunction.ThreadOpener,
|
|
subject, partner, developmentId, source, magnitude: 72f);
|
|
|
|
internal static bool BondBrokenExact(
|
|
Actor subject,
|
|
Actor former,
|
|
string source,
|
|
string developmentId) =>
|
|
Record(NarrativeDevelopmentKind.BondBroken, NarrativeFunction.TurningPoint,
|
|
subject, former, developmentId, source, magnitude: 88f,
|
|
outcome: "partnership ended");
|
|
|
|
internal static bool ChildBornExact(
|
|
Actor parent,
|
|
Actor child,
|
|
string source,
|
|
string developmentId) =>
|
|
Record(NarrativeDevelopmentKind.ChildBorn, NarrativeFunction.Consequence,
|
|
parent, child, developmentId, source, familyKey: FamilyKey(parent),
|
|
magnitude: 76f, outcome: "child born");
|
|
|
|
internal static bool FriendFormedExact(
|
|
Actor subject,
|
|
Actor friend,
|
|
string source,
|
|
string developmentId) =>
|
|
Record(NarrativeDevelopmentKind.FriendFormed, NarrativeFunction.ThreadOpener,
|
|
subject, friend, developmentId, source, magnitude: 55f);
|
|
|
|
public static bool Death(Actor victim, Actor killer, string attackType, string source)
|
|
{
|
|
return DeathExact(victim, killer, attackType, source, DeathId(victim));
|
|
}
|
|
|
|
public static string DeathId(Actor victim) =>
|
|
"death:" + EventFeedUtil.SafeId(victim);
|
|
|
|
internal static bool DeathExact(
|
|
Actor victim,
|
|
Actor killer,
|
|
string attackType,
|
|
string source,
|
|
string developmentId)
|
|
{
|
|
return Record(NarrativeDevelopmentKind.Death, NarrativeFunction.Resolution,
|
|
victim, killer, developmentId, source,
|
|
newState: "dead", outcome: attackType ?? "", magnitude: 100f);
|
|
}
|
|
|
|
public static bool Kill(Actor killer, Actor victim, string source)
|
|
{
|
|
return KillExact(killer, victim, source, KillId(killer, victim));
|
|
}
|
|
|
|
public static string KillId(Actor killer, Actor victim) =>
|
|
"kill:" + EventFeedUtil.SafeId(killer) + ":" + EventFeedUtil.SafeId(victim);
|
|
|
|
internal static bool KillExact(
|
|
Actor killer,
|
|
Actor victim,
|
|
string source,
|
|
string developmentId)
|
|
{
|
|
return Record(NarrativeDevelopmentKind.Kill, NarrativeFunction.TurningPoint,
|
|
killer, victim, developmentId,
|
|
source, outcome: "victim died", magnitude: 82f);
|
|
}
|
|
|
|
public static bool Role(Actor subject, string role, bool gained, string source)
|
|
{
|
|
return RoleExact(subject, role, gained, source, RoleId(subject, role, gained));
|
|
}
|
|
|
|
public static string RoleId(Actor subject, string role, bool gained) =>
|
|
"role:" + EventFeedUtil.SafeId(subject) + ":" + (role ?? "") + ":" + gained;
|
|
|
|
internal static bool RoleExact(
|
|
Actor subject,
|
|
string role,
|
|
bool gained,
|
|
string source,
|
|
string developmentId)
|
|
{
|
|
return Record(gained ? NarrativeDevelopmentKind.RoleGained : NarrativeDevelopmentKind.RoleLost,
|
|
gained ? NarrativeFunction.TurningPoint : NarrativeFunction.Consequence,
|
|
subject, null, developmentId, source,
|
|
newState: role, magnitude: 78f);
|
|
}
|
|
|
|
public static bool Home(Actor subject, bool gained, string source)
|
|
{
|
|
return HomeExact(subject, gained, source, HomeId(subject, gained));
|
|
}
|
|
|
|
public static string HomeId(Actor subject, bool gained) =>
|
|
"home:" + EventFeedUtil.SafeId(subject) + ":" + gained;
|
|
|
|
internal static bool HomeExact(
|
|
Actor subject,
|
|
bool gained,
|
|
string source,
|
|
string developmentId)
|
|
{
|
|
return Record(
|
|
gained ? NarrativeDevelopmentKind.HomeFounded : NarrativeDevelopmentKind.HomeLost,
|
|
gained ? NarrativeFunction.TurningPoint : NarrativeFunction.Consequence,
|
|
subject,
|
|
null,
|
|
developmentId,
|
|
source,
|
|
newState: gained ? "home" : "",
|
|
outcome: gained ? "" : "home lost",
|
|
magnitude: gained ? 64f : 72f);
|
|
}
|
|
|
|
public static bool Founding(Actor subject, string kind, string key, string source)
|
|
{
|
|
return FoundingExact(subject, kind, key, source, FoundingId(subject, kind, key));
|
|
}
|
|
|
|
public static string FoundingId(Actor subject, string kind, string key) =>
|
|
"founding:" + (kind ?? "") + ":" + (key ?? "") + ":" + EventFeedUtil.SafeId(subject);
|
|
|
|
internal static bool FoundingExact(
|
|
Actor subject,
|
|
string kind,
|
|
string key,
|
|
string source,
|
|
string developmentId)
|
|
{
|
|
return Record(NarrativeDevelopmentKind.HomeFounded, NarrativeFunction.TurningPoint,
|
|
subject, null, developmentId, 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 WarExact(subject, other, warKey, ended, source, WarId(subject, warKey, ended));
|
|
}
|
|
|
|
public static string WarId(Actor subject, string warKey, bool ended) =>
|
|
"war:" + (warKey ?? "") + ":" + EventFeedUtil.SafeId(subject) + ":" + ended;
|
|
|
|
internal static bool WarExact(
|
|
Actor subject,
|
|
Actor other,
|
|
string warKey,
|
|
bool ended,
|
|
string source,
|
|
string developmentId)
|
|
{
|
|
return Record(ended ? NarrativeDevelopmentKind.WarEnded : NarrativeDevelopmentKind.WarJoined,
|
|
ended ? NarrativeFunction.Resolution : NarrativeFunction.Escalation,
|
|
subject, other, developmentId,
|
|
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 PlotExact(subject, other, plotKey, ended, source, PlotId(subject, plotKey, ended));
|
|
}
|
|
|
|
public static string PlotId(Actor subject, string plotKey, bool ended) =>
|
|
"plot:" + (plotKey ?? "") + ":" + EventFeedUtil.SafeId(subject) + ":" + ended;
|
|
|
|
internal static bool PlotExact(
|
|
Actor subject,
|
|
Actor other,
|
|
string plotKey,
|
|
bool ended,
|
|
string source,
|
|
string developmentId)
|
|
{
|
|
return Record(ended ? NarrativeDevelopmentKind.PlotEnded : NarrativeDevelopmentKind.PlotJoined,
|
|
ended ? NarrativeFunction.Resolution : NarrativeFunction.Escalation,
|
|
subject, other, developmentId,
|
|
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);
|
|
}
|