Build Inventories
This commit is contained in:
parent
8427079188
commit
199adee644
20 changed files with 1598 additions and 192 deletions
|
|
@ -204,11 +204,82 @@ public static class ActivityAssetCatalog
|
|||
}
|
||||
|
||||
public static List<string> EnumerateLiveTaskIds()
|
||||
{
|
||||
return EnumerateLibraryIds("tasks_actor");
|
||||
}
|
||||
|
||||
public static List<string> EnumerateLiveWorldLogIds()
|
||||
{
|
||||
return EnumerateLibraryIds("world_log_library");
|
||||
}
|
||||
|
||||
public static WorldLogAsset TryGetWorldLogAsset(string assetId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return AssetManager.world_log_library?.get(assetId.Trim());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> EnumerateLiveDisasterIds()
|
||||
{
|
||||
return EnumerateLibraryIds("disasters");
|
||||
}
|
||||
|
||||
public static DisasterAsset TryGetDisasterAsset(string disasterId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(disasterId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return AssetManager.disasters?.get(disasterId.Trim());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> EnumerateLiveWarTypeIds()
|
||||
{
|
||||
return EnumerateLibraryIds("war_types_library");
|
||||
}
|
||||
|
||||
public static WarTypeAsset TryGetWarTypeAsset(string warTypeId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(warTypeId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return AssetManager.war_types_library?.get(warTypeId.Trim());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> EnumerateLibraryIds(string assetManagerField)
|
||||
{
|
||||
var ids = new List<string>();
|
||||
try
|
||||
{
|
||||
object lib = typeof(AssetManager).GetField("tasks_actor")?.GetValue(null);
|
||||
object lib = typeof(AssetManager).GetField(assetManagerField)?.GetValue(null);
|
||||
object listObj = lib?.GetType().GetField("list")?.GetValue(lib)
|
||||
?? lib?.GetType().GetProperty("list")?.GetValue(lib, null);
|
||||
System.Collections.IList list = listObj as System.Collections.IList;
|
||||
|
|
@ -219,9 +290,9 @@ public static class ActivityAssetCatalog
|
|||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
object task = list[i];
|
||||
object idObj = task?.GetType().GetField("id")?.GetValue(task)
|
||||
?? task?.GetType().GetProperty("id")?.GetValue(task, null);
|
||||
object asset = list[i];
|
||||
object idObj = asset?.GetType().GetField("id")?.GetValue(asset)
|
||||
?? asset?.GetType().GetProperty("id")?.GetValue(asset, null);
|
||||
string id = idObj as string;
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public sealed class ActivityHappinessAuditResult
|
|||
public int OrphanAuthored;
|
||||
public int MissingPolicy;
|
||||
public int RequiredContextFailures;
|
||||
public int MissingEventStrength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -41,9 +42,10 @@ public static class ActivityHappinessHarness
|
|||
int orphanAuthored = 0;
|
||||
int missingPolicy = 0;
|
||||
int requiredContextFailures = 0;
|
||||
int missingEventStrength = 0;
|
||||
var tsv = new StringBuilder();
|
||||
tsv.AppendLine(
|
||||
"id\tvalue\tpsychopath\tpresentation\tlife\tcontext\toverlap\tclause\tauthored\ticon\tlocale_leak\tnotes");
|
||||
"id\tvalue\tpsychopath\tpresentation\tlife\tcontext\toverlap\tevent_strength\tclause\tauthored\ticon\tlocale_leak\tnotes");
|
||||
|
||||
var liveSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 0; i < liveIds.Count; i++)
|
||||
|
|
@ -58,6 +60,11 @@ public static class ActivityHappinessHarness
|
|||
missingAuthored++;
|
||||
}
|
||||
|
||||
if (hasAuthored && entry.EventStrength <= 0f)
|
||||
{
|
||||
missingEventStrength++;
|
||||
}
|
||||
|
||||
string clauseWith = ActivityHappinessProse.Clause(entry, "Ava", out bool usedRelated, out _);
|
||||
string clauseWithout = ActivityHappinessProse.Clause(entry, "", out _, out _);
|
||||
if (string.IsNullOrEmpty(clauseWith) || string.IsNullOrEmpty(clauseWithout))
|
||||
|
|
@ -126,6 +133,10 @@ public static class ActivityHappinessHarness
|
|||
{
|
||||
notes = "missing_authored";
|
||||
}
|
||||
else if (entry.EventStrength <= 0f)
|
||||
{
|
||||
notes = "missing_event_strength";
|
||||
}
|
||||
else if (!iconOk)
|
||||
{
|
||||
notes = "missing_icon";
|
||||
|
|
@ -151,6 +162,7 @@ public static class ActivityHappinessHarness
|
|||
.Append(entry.Life).Append('\t')
|
||||
.Append(entry.Context).Append('\t')
|
||||
.Append(entry.Overlap).Append('\t')
|
||||
.Append(entry.EventStrength.ToString("0.#")).Append('\t')
|
||||
.Append(Escape(clauseWith)).Append('\t')
|
||||
.Append(hasAuthored ? "1" : "0").Append('\t')
|
||||
.Append(iconOk ? "1" : "0").Append('\t')
|
||||
|
|
@ -164,7 +176,7 @@ public static class ActivityHappinessHarness
|
|||
{
|
||||
orphanAuthored++;
|
||||
tsv.Append(id).Append('\t')
|
||||
.Append("\t\t\t\t\t\t\t1\t0\t0\torphan_authored").AppendLine();
|
||||
.Append("\t\t\t\t\t\t\t0\t\t1\t0\t0\torphan_authored").AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +201,8 @@ public static class ActivityHappinessHarness
|
|||
&& emptyPhrase == 0
|
||||
&& orphanAuthored == 0
|
||||
&& missingPolicy == 0
|
||||
&& requiredContextFailures == 0;
|
||||
&& requiredContextFailures == 0
|
||||
&& missingEventStrength == 0;
|
||||
|
||||
return new ActivityHappinessAuditResult
|
||||
{
|
||||
|
|
@ -202,10 +215,12 @@ public static class ActivityHappinessHarness
|
|||
OrphanAuthored = orphanAuthored,
|
||||
MissingPolicy = missingPolicy,
|
||||
RequiredContextFailures = requiredContextFailures,
|
||||
MissingEventStrength = missingEventStrength,
|
||||
Detail =
|
||||
$"total={liveIds.Count} missingAuthored={missingAuthored} missingIcon={missingIcon} "
|
||||
+ $"localeLeaks={localeLeaks} emptyPhrase={emptyPhrase} orphanAuthored={orphanAuthored} "
|
||||
+ $"missingPolicy={missingPolicy} requiredContextFailures={requiredContextFailures}"
|
||||
+ $"missingPolicy={missingPolicy} requiredContextFailures={requiredContextFailures} "
|
||||
+ $"missingEventStrength={missingEventStrength}"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@ public sealed class ActivityStatusAuditResult
|
|||
public int LocaleLeaks;
|
||||
public int EmptyPhrase;
|
||||
public int OrphanAuthored;
|
||||
public int MissingInterest;
|
||||
public int OrphanInterest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exhaustive live-library audit for status Activity prose and icons.
|
||||
/// Exhaustive live-library audit for status Activity prose, icons, and interest policy.
|
||||
/// </summary>
|
||||
public static class ActivityStatusHarness
|
||||
{
|
||||
|
|
@ -37,11 +39,19 @@ public static class ActivityStatusHarness
|
|||
int localeLeaks = 0;
|
||||
int emptyPhrase = 0;
|
||||
int orphanAuthored = 0;
|
||||
int missingInterest = 0;
|
||||
int orphanInterest = 0;
|
||||
var tsv = new StringBuilder();
|
||||
tsv.AppendLine(
|
||||
"id\tgain\tloss\tauthored\ticon\tlocale_leak\tduration\tallow_timer_reset\tnotes");
|
||||
"id\tgain\tloss\tauthored\tinterest\tevent_strength\tcreates_interest\ticon\tlocale_leak\tduration\tallow_timer_reset\tnotes");
|
||||
|
||||
var liveSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var interestAuthored = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (string id in StatusInterestCatalog.AuthoredIds)
|
||||
{
|
||||
interestAuthored.Add(id);
|
||||
}
|
||||
|
||||
for (int i = 0; i < liveIds.Count; i++)
|
||||
{
|
||||
string id = liveIds[i];
|
||||
|
|
@ -55,6 +65,13 @@ public static class ActivityStatusHarness
|
|||
missingAuthored++;
|
||||
}
|
||||
|
||||
StatusInterestEntry interest = StatusInterestCatalog.GetOrFallback(id);
|
||||
bool hasInterest = StatusInterestCatalog.HasAuthored(id) && !interest.IsFallback;
|
||||
if (!hasInterest)
|
||||
{
|
||||
missingInterest++;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(gain) || string.IsNullOrEmpty(loss))
|
||||
{
|
||||
emptyPhrase++;
|
||||
|
|
@ -90,11 +107,15 @@ public static class ActivityStatusHarness
|
|||
// ignore
|
||||
}
|
||||
|
||||
string notes = "";
|
||||
string notes = "ok";
|
||||
if (!hasAuthored)
|
||||
{
|
||||
notes = "missing_authored";
|
||||
}
|
||||
else if (!hasInterest)
|
||||
{
|
||||
notes = "missing_interest";
|
||||
}
|
||||
else if (!iconOk)
|
||||
{
|
||||
notes = "missing_icon";
|
||||
|
|
@ -107,22 +128,20 @@ public static class ActivityStatusHarness
|
|||
{
|
||||
notes = "empty_phrase";
|
||||
}
|
||||
else
|
||||
{
|
||||
notes = "ok";
|
||||
}
|
||||
|
||||
tsv.Append(id).Append('\t')
|
||||
.Append(Escape(gain)).Append('\t')
|
||||
.Append(Escape(loss)).Append('\t')
|
||||
.Append(hasAuthored ? "1" : "0").Append('\t')
|
||||
.Append(hasInterest ? "1" : "0").Append('\t')
|
||||
.Append(interest.EventStrength.ToString("0.#")).Append('\t')
|
||||
.Append(interest.CreatesInterest ? "1" : "0").Append('\t')
|
||||
.Append(iconOk ? "1" : "0").Append('\t')
|
||||
.Append(leak ? "1" : "0").Append('\t')
|
||||
.Append(duration).Append('\t')
|
||||
.Append(allowReset).Append('\t')
|
||||
.Append(notes).AppendLine();
|
||||
|
||||
// Smoke the milestone formatting + keys.
|
||||
string gainKey = ActivityStatusProse.TaskKey(id, true);
|
||||
string lossKey = ActivityStatusProse.TaskKey(id, false);
|
||||
if (string.IsNullOrEmpty(gainKey) || string.IsNullOrEmpty(lossKey))
|
||||
|
|
@ -136,9 +155,16 @@ public static class ActivityStatusHarness
|
|||
if (!liveSet.Contains(id))
|
||||
{
|
||||
orphanAuthored++;
|
||||
tsv.Append(id).Append('\t')
|
||||
.Append('\t').Append('\t')
|
||||
.Append("1\t0\t0\t\t\torphan_authored").AppendLine();
|
||||
tsv.Append(id).Append("\t\t\t1\t0\t0\t0\t0\t0\t\t\torphan_authored").AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string id in interestAuthored)
|
||||
{
|
||||
if (!liveSet.Contains(id))
|
||||
{
|
||||
orphanInterest++;
|
||||
tsv.Append(id).Append("\t\t\t0\t1\t0\t0\t0\t0\t\t\torphan_interest").AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +189,9 @@ public static class ActivityStatusHarness
|
|||
&& missingIcon == 0
|
||||
&& localeLeaks == 0
|
||||
&& emptyPhrase == 0
|
||||
&& orphanAuthored == 0;
|
||||
&& orphanAuthored == 0
|
||||
&& missingInterest == 0
|
||||
&& orphanInterest == 0;
|
||||
|
||||
return new ActivityStatusAuditResult
|
||||
{
|
||||
|
|
@ -174,10 +202,12 @@ public static class ActivityStatusHarness
|
|||
LocaleLeaks = localeLeaks,
|
||||
EmptyPhrase = emptyPhrase,
|
||||
OrphanAuthored = orphanAuthored,
|
||||
MissingInterest = missingInterest,
|
||||
OrphanInterest = orphanInterest,
|
||||
Detail =
|
||||
$"total={liveIds.Count} missingAuthored={missingAuthored} missingIcon={missingIcon} "
|
||||
+ $"localeLeaks={localeLeaks} emptyPhrase={emptyPhrase} orphanAuthored={orphanAuthored} "
|
||||
+ $"passed={passed}"
|
||||
+ $"missingInterest={missingInterest} orphanInterest={orphanInterest} passed={passed}"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1797,6 +1797,10 @@ public static class AgentHarness
|
|||
DoTriggerInterest(cmd);
|
||||
break;
|
||||
|
||||
case "world_log_feed":
|
||||
DoWorldLogFeed(cmd);
|
||||
break;
|
||||
|
||||
case "interest_inject":
|
||||
DoInterestInject(cmd);
|
||||
break;
|
||||
|
|
@ -2447,6 +2451,77 @@ public static class AgentHarness
|
|||
detail: $"triggered label={tipLabel} evt={tierEvt:0.#} forced={forceCamera}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Feed a live WorldLog asset id through InterestFeeds as if WorldLog fired (coverage of authored catalog).
|
||||
/// </summary>
|
||||
private static void DoWorldLogFeed(HarnessCommand cmd)
|
||||
{
|
||||
if (!WorldReady())
|
||||
{
|
||||
_cmdFail++;
|
||||
Emit(cmd, ok: false, detail: "world_not_ready");
|
||||
return;
|
||||
}
|
||||
|
||||
string assetId = string.IsNullOrEmpty(cmd.asset) ? (cmd.value ?? "") : cmd.asset;
|
||||
if (string.IsNullOrEmpty(assetId) || assetId == "auto")
|
||||
{
|
||||
assetId = "disaster_tornado";
|
||||
}
|
||||
|
||||
if (!WorldLogEventCatalog.HasAuthored(assetId))
|
||||
{
|
||||
_cmdFail++;
|
||||
Emit(cmd, ok: false, detail: "not_authored:" + assetId);
|
||||
return;
|
||||
}
|
||||
|
||||
WorldLogEventEntry entry = WorldLogEventCatalog.GetOrFallback(assetId);
|
||||
Actor follow = ResolveUnit(null) ?? WorldActivityScanner.FindNearestAliveUnit(CameraPos(), 200f);
|
||||
if (follow == null && entry.CreatesInterest)
|
||||
{
|
||||
_cmdFail++;
|
||||
Emit(cmd, ok: false, detail: "no unit for world_log_feed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry.CreatesInterest)
|
||||
{
|
||||
_cmdOk++;
|
||||
Emit(cmd, ok: true, detail: $"skipped_no_interest id={assetId}");
|
||||
return;
|
||||
}
|
||||
|
||||
string kingdom = follow.kingdom != null ? (follow.kingdom.name ?? "Alpha") : "Alpha";
|
||||
string key = "worldlog:" + assetId + ":" + kingdom;
|
||||
var candidate = new InterestCandidate
|
||||
{
|
||||
Key = key,
|
||||
LeadKind = InterestLeadKind.EventLed,
|
||||
Category = entry.Category,
|
||||
Source = "worldlog",
|
||||
EventStrength = entry.EventStrength,
|
||||
VisualConfidence = 0.7f,
|
||||
Position = follow.current_position,
|
||||
FollowUnit = follow,
|
||||
SubjectId = follow.getID(),
|
||||
Label = entry.MakeLabel(kingdom, "Beta"),
|
||||
AssetId = assetId,
|
||||
KingdomKey = kingdom,
|
||||
SpeciesId = follow.asset != null ? follow.asset.id : "",
|
||||
CreatedAt = Time.unscaledTime,
|
||||
LastSeenAt = Time.unscaledTime,
|
||||
ExpiresAt = Time.unscaledTime + 40f,
|
||||
MinWatch = entry.MinWatch,
|
||||
MaxWatch = entry.MaxWatch,
|
||||
Completion = InterestCompletionKind.FixedDwell
|
||||
};
|
||||
InterestScoring.ScoreCheap(candidate);
|
||||
InterestRegistry.Upsert(candidate);
|
||||
_cmdOk++;
|
||||
Emit(cmd, ok: true, detail: $"fed id={assetId} key={key} evt={entry.EventStrength:0.#}");
|
||||
}
|
||||
|
||||
private static void DoInterestInject(HarnessCommand cmd)
|
||||
{
|
||||
if (!WorldReady())
|
||||
|
|
@ -3970,6 +4045,23 @@ public static class AgentHarness
|
|||
detail = audit.Detail;
|
||||
break;
|
||||
}
|
||||
case "world_log_audit":
|
||||
{
|
||||
WorldLogEventAuditResult audit = WorldLogEventHarness.RunAudit(HarnessDir());
|
||||
pass = audit.Passed;
|
||||
detail = audit.Detail;
|
||||
break;
|
||||
}
|
||||
case "disaster_war_inventory":
|
||||
case "disaster_war_audit":
|
||||
{
|
||||
DisasterWarAuditResult audit = DisasterWarHarness.RunAudit(HarnessDir());
|
||||
// Keep inventory dump for debugging alongside audit.
|
||||
WorldLogEventHarness.DumpDisasterWarInventory(HarnessDir());
|
||||
pass = audit.Passed;
|
||||
detail = audit.Detail;
|
||||
break;
|
||||
}
|
||||
case "activity_happiness_count":
|
||||
{
|
||||
int want = ParseCountExpect(cmd, defaultValue: 1);
|
||||
|
|
|
|||
91
IdleSpectator/DisasterInterestCatalog.cs
Normal file
91
IdleSpectator/DisasterInterestCatalog.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
public sealed class DisasterInterestEntry
|
||||
{
|
||||
public string Id = "";
|
||||
public float EventStrength = 90f;
|
||||
public string Category = "Disaster";
|
||||
public string WorldLogId = "";
|
||||
public bool CreatesInterest = true;
|
||||
public bool IsFallback;
|
||||
}
|
||||
|
||||
/// <summary>Authored interest overlay for every live disasters library asset.</summary>
|
||||
public static class DisasterInterestCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, DisasterInterestEntry> Entries =
|
||||
new Dictionary<string, DisasterInterestEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
static DisasterInterestCatalog()
|
||||
{
|
||||
Add("tornado", 95f, "disaster_tornado");
|
||||
Add("heatwave", 88f, "disaster_heatwave");
|
||||
Add("small_meteorite", 95f, "disaster_meteorite");
|
||||
Add("small_earthquake", 95f, "disaster_earthquake");
|
||||
Add("hellspawn", 96f, "disaster_hellspawn");
|
||||
Add("ice_ones_awoken", 95f, "disaster_ice_ones");
|
||||
Add("sudden_snowman", 90f, "disaster_sudden_snowman");
|
||||
Add("garden_surprise", 90f, "disaster_garden_surprise");
|
||||
Add("dragon_from_farlands", 98f, "disaster_dragon_from_farlands");
|
||||
Add("ash_bandits", 92f, "disaster_bandits");
|
||||
Add("alien_invasion", 97f, "disaster_alien_invasion");
|
||||
Add("biomass", 96f, "disaster_biomass");
|
||||
Add("tumor", 96f, "disaster_tumor");
|
||||
Add("wild_mage", 94f, "disaster_evil_mage");
|
||||
Add("underground_necromancer", 95f, "disaster_underground_necromancer");
|
||||
Add("mad_thoughts", 90f, "disaster_mad_thoughts");
|
||||
Add("greg_abominations", 96f, "disaster_greg_abominations");
|
||||
}
|
||||
|
||||
private static void Add(string id, float strength, string worldLogId)
|
||||
{
|
||||
Entries[id] = new DisasterInterestEntry
|
||||
{
|
||||
Id = id,
|
||||
EventStrength = strength,
|
||||
WorldLogId = worldLogId,
|
||||
CreatesInterest = true,
|
||||
Category = "Disaster"
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AuthoredIds => Entries.Keys;
|
||||
|
||||
public static bool HasAuthored(string disasterId)
|
||||
{
|
||||
return !string.IsNullOrEmpty(disasterId) && Entries.ContainsKey(disasterId.Trim());
|
||||
}
|
||||
|
||||
public static bool TryGet(string disasterId, out DisasterInterestEntry entry)
|
||||
{
|
||||
entry = null;
|
||||
if (string.IsNullOrEmpty(disasterId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Entries.TryGetValue(disasterId.Trim(), out entry);
|
||||
}
|
||||
|
||||
public static DisasterInterestEntry GetOrFallback(string disasterId)
|
||||
{
|
||||
if (TryGet(disasterId, out DisasterInterestEntry entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
string id = string.IsNullOrEmpty(disasterId) ? "unknown" : disasterId.Trim();
|
||||
return new DisasterInterestEntry
|
||||
{
|
||||
Id = id,
|
||||
EventStrength = 90f,
|
||||
Category = "Disaster",
|
||||
WorldLogId = "",
|
||||
CreatesInterest = true,
|
||||
IsFallback = true
|
||||
};
|
||||
}
|
||||
}
|
||||
139
IdleSpectator/DisasterWarHarness.cs
Normal file
139
IdleSpectator/DisasterWarHarness.cs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
public sealed class DisasterWarAuditResult
|
||||
{
|
||||
public bool Passed;
|
||||
public string Detail = "";
|
||||
public int DisasterTotal;
|
||||
public int WarTypeTotal;
|
||||
public int MissingDisaster;
|
||||
public int OrphanDisaster;
|
||||
public int MissingWarType;
|
||||
public int OrphanWarType;
|
||||
}
|
||||
|
||||
/// <summary>Live disasters + war_types library coverage audit.</summary>
|
||||
public static class DisasterWarHarness
|
||||
{
|
||||
public static DisasterWarAuditResult RunAudit(string outputDirectory)
|
||||
{
|
||||
List<string> disasters = ActivityAssetCatalog.EnumerateLiveDisasterIds();
|
||||
List<string> warTypes = ActivityAssetCatalog.EnumerateLiveWarTypeIds();
|
||||
|
||||
var authoredDisasters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (string id in DisasterInterestCatalog.AuthoredIds)
|
||||
{
|
||||
authoredDisasters.Add(id);
|
||||
}
|
||||
|
||||
var authoredWars = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (string id in WarTypeInterestCatalog.AuthoredIds)
|
||||
{
|
||||
authoredWars.Add(id);
|
||||
}
|
||||
|
||||
int missingDisaster = 0;
|
||||
int orphanDisaster = 0;
|
||||
int missingWarType = 0;
|
||||
int orphanWarType = 0;
|
||||
var tsv = new StringBuilder();
|
||||
tsv.AppendLine("kind\tid\tauthored\tevent_strength\tworld_log_id\tnotes");
|
||||
|
||||
var liveDisasters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 0; i < disasters.Count; i++)
|
||||
{
|
||||
string id = disasters[i];
|
||||
liveDisasters.Add(id);
|
||||
DisasterInterestEntry entry = DisasterInterestCatalog.GetOrFallback(id);
|
||||
bool has = DisasterInterestCatalog.HasAuthored(id) && !entry.IsFallback;
|
||||
if (!has)
|
||||
{
|
||||
missingDisaster++;
|
||||
}
|
||||
|
||||
tsv.Append("disaster\t").Append(id).Append('\t')
|
||||
.Append(has ? "1" : "0").Append('\t')
|
||||
.Append(entry.EventStrength.ToString("0.#")).Append('\t')
|
||||
.Append(entry.WorldLogId ?? "").Append('\t')
|
||||
.Append(has ? "ok" : "missing_authored").AppendLine();
|
||||
}
|
||||
|
||||
foreach (string id in authoredDisasters)
|
||||
{
|
||||
if (!liveDisasters.Contains(id))
|
||||
{
|
||||
orphanDisaster++;
|
||||
tsv.Append("disaster\t").Append(id).Append("\t1\t0\t\torphan_authored").AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
var liveWars = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 0; i < warTypes.Count; i++)
|
||||
{
|
||||
string id = warTypes[i];
|
||||
liveWars.Add(id);
|
||||
WarTypeInterestEntry entry = WarTypeInterestCatalog.GetOrFallback(id);
|
||||
bool has = WarTypeInterestCatalog.HasAuthored(id) && !entry.IsFallback;
|
||||
if (!has)
|
||||
{
|
||||
missingWarType++;
|
||||
}
|
||||
|
||||
tsv.Append("war_type\t").Append(id).Append('\t')
|
||||
.Append(has ? "1" : "0").Append('\t')
|
||||
.Append(entry.EventStrength.ToString("0.#")).Append('\t')
|
||||
.Append('\t')
|
||||
.Append(has ? "ok" : "missing_authored").AppendLine();
|
||||
}
|
||||
|
||||
foreach (string id in authoredWars)
|
||||
{
|
||||
if (!liveWars.Contains(id))
|
||||
{
|
||||
orphanWarType++;
|
||||
tsv.Append("war_type\t").Append(id).Append("\t1\t0\t\torphan_authored").AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(outputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
File.WriteAllText(
|
||||
Path.Combine(outputDirectory, "disaster-war-audit.tsv"),
|
||||
tsv.ToString());
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
bool passed = disasters.Count > 0
|
||||
&& warTypes.Count > 0
|
||||
&& missingDisaster == 0
|
||||
&& orphanDisaster == 0
|
||||
&& missingWarType == 0
|
||||
&& orphanWarType == 0;
|
||||
|
||||
return new DisasterWarAuditResult
|
||||
{
|
||||
Passed = passed,
|
||||
DisasterTotal = disasters.Count,
|
||||
WarTypeTotal = warTypes.Count,
|
||||
MissingDisaster = missingDisaster,
|
||||
OrphanDisaster = orphanDisaster,
|
||||
MissingWarType = missingWarType,
|
||||
OrphanWarType = orphanWarType,
|
||||
Detail =
|
||||
$"disasters={disasters.Count} missingDisaster={missingDisaster} orphanDisaster={orphanDisaster} "
|
||||
+ $"war_types={warTypes.Count} missingWarType={missingWarType} orphanWarType={orphanWarType}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ public static class HappinessEventCatalog
|
|||
["death_family_member"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "death_family_member",
|
||||
EventStrength = 76f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RequiresRelatedActor,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -24,6 +25,7 @@ public static class HappinessEventCatalog
|
|||
["death_lover"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "death_lover",
|
||||
EventStrength = 88f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RequiresRelatedActor,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -36,6 +38,7 @@ public static class HappinessEventCatalog
|
|||
["death_child"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "death_child",
|
||||
EventStrength = 95f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RequiresRelatedActor,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -48,6 +51,7 @@ public static class HappinessEventCatalog
|
|||
["death_best_friend"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "death_best_friend",
|
||||
EventStrength = 78f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RequiresRelatedActor,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -60,6 +64,7 @@ public static class HappinessEventCatalog
|
|||
["got_robbed"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "got_robbed",
|
||||
EventStrength = 50f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -72,6 +77,7 @@ public static class HappinessEventCatalog
|
|||
["got_poked"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "got_poked",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -84,6 +90,7 @@ public static class HappinessEventCatalog
|
|||
["lost_fight"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "lost_fight",
|
||||
EventStrength = 55f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -96,6 +103,7 @@ public static class HappinessEventCatalog
|
|||
["got_caught"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "got_caught",
|
||||
EventStrength = 48f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -108,6 +116,7 @@ public static class HappinessEventCatalog
|
|||
["paid_tax"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "paid_tax",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -120,6 +129,7 @@ public static class HappinessEventCatalog
|
|||
["just_ate"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_ate",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -133,6 +143,7 @@ public static class HappinessEventCatalog
|
|||
["just_received_gift"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_received_gift",
|
||||
EventStrength = 50f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -145,6 +156,7 @@ public static class HappinessEventCatalog
|
|||
["just_gave_gift"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_gave_gift",
|
||||
EventStrength = 50f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -157,6 +169,7 @@ public static class HappinessEventCatalog
|
|||
["just_pooped"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_pooped",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -169,6 +182,7 @@ public static class HappinessEventCatalog
|
|||
["just_slept"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_slept",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -181,6 +195,7 @@ public static class HappinessEventCatalog
|
|||
["had_bad_dream"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "had_bad_dream",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -194,6 +209,7 @@ public static class HappinessEventCatalog
|
|||
["had_good_dream"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "had_good_dream",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -207,6 +223,7 @@ public static class HappinessEventCatalog
|
|||
["had_nightmare"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "had_nightmare",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -220,6 +237,7 @@ public static class HappinessEventCatalog
|
|||
["slept_outside"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "slept_outside",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -232,6 +250,7 @@ public static class HappinessEventCatalog
|
|||
["just_kissed"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_kissed",
|
||||
EventStrength = 50f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -244,6 +263,7 @@ public static class HappinessEventCatalog
|
|||
["just_killed"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_killed",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -257,6 +277,7 @@ public static class HappinessEventCatalog
|
|||
["become_king"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "become_king",
|
||||
EventStrength = 72f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -269,6 +290,7 @@ public static class HappinessEventCatalog
|
|||
["become_leader"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "become_leader",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -281,6 +303,7 @@ public static class HappinessEventCatalog
|
|||
["just_won_war"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_won_war",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -293,6 +316,7 @@ public static class HappinessEventCatalog
|
|||
["just_made_peace"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_made_peace",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -305,6 +329,7 @@ public static class HappinessEventCatalog
|
|||
["just_lost_war"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_lost_war",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -317,6 +342,7 @@ public static class HappinessEventCatalog
|
|||
["was_conquered"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "was_conquered",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -329,6 +355,7 @@ public static class HappinessEventCatalog
|
|||
["kingdom_fell_apart"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "kingdom_fell_apart",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -341,6 +368,7 @@ public static class HappinessEventCatalog
|
|||
["just_started_war"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_started_war",
|
||||
EventStrength = 80f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -353,6 +381,7 @@ public static class HappinessEventCatalog
|
|||
["just_rebelled"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_rebelled",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -365,6 +394,7 @@ public static class HappinessEventCatalog
|
|||
["fallen_in_love"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "fallen_in_love",
|
||||
EventStrength = 50f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -379,6 +409,7 @@ public static class HappinessEventCatalog
|
|||
["just_had_child"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_had_child",
|
||||
EventStrength = 70f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -391,6 +422,7 @@ public static class HappinessEventCatalog
|
|||
["just_read_book"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_read_book",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -403,6 +435,7 @@ public static class HappinessEventCatalog
|
|||
["just_played"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_played",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -415,6 +448,7 @@ public static class HappinessEventCatalog
|
|||
["just_talked"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_talked",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -427,6 +461,7 @@ public static class HappinessEventCatalog
|
|||
["just_laughed"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_laughed",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -440,6 +475,7 @@ public static class HappinessEventCatalog
|
|||
["just_sang"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_sang",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -453,6 +489,7 @@ public static class HappinessEventCatalog
|
|||
["just_swore"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_swore",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -466,6 +503,7 @@ public static class HappinessEventCatalog
|
|||
["just_cried"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_cried",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -479,6 +517,7 @@ public static class HappinessEventCatalog
|
|||
["just_talked_gossip"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_talked_gossip",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -491,6 +530,7 @@ public static class HappinessEventCatalog
|
|||
["just_surprised"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_surprised",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -504,6 +544,7 @@ public static class HappinessEventCatalog
|
|||
["just_born"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_born",
|
||||
EventStrength = 70f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -516,6 +557,7 @@ public static class HappinessEventCatalog
|
|||
["just_magnetised"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_magnetised",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -529,6 +571,7 @@ public static class HappinessEventCatalog
|
|||
["just_forced_power"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_forced_power",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -541,6 +584,7 @@ public static class HappinessEventCatalog
|
|||
["just_possessed"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_possessed",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -554,6 +598,7 @@ public static class HappinessEventCatalog
|
|||
["strange_urge"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "strange_urge",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -567,6 +612,7 @@ public static class HappinessEventCatalog
|
|||
["just_had_tantrum"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_had_tantrum",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -580,6 +626,7 @@ public static class HappinessEventCatalog
|
|||
["just_felt_the_divine"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_felt_the_divine",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -592,6 +639,7 @@ public static class HappinessEventCatalog
|
|||
["just_enchanted"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_enchanted",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -605,6 +653,7 @@ public static class HappinessEventCatalog
|
|||
["just_inspired"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_inspired",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -618,6 +667,7 @@ public static class HappinessEventCatalog
|
|||
["wrote_book"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "wrote_book",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -630,6 +680,7 @@ public static class HappinessEventCatalog
|
|||
["just_became_adult"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_became_adult",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -642,6 +693,7 @@ public static class HappinessEventCatalog
|
|||
["just_got_out_of_egg"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_got_out_of_egg",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -654,6 +706,7 @@ public static class HappinessEventCatalog
|
|||
["just_finished_plot"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_finished_plot",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -666,6 +719,7 @@ public static class HappinessEventCatalog
|
|||
["just_found_house"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_found_house",
|
||||
EventStrength = 55f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -678,6 +732,7 @@ public static class HappinessEventCatalog
|
|||
["just_lost_house"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_lost_house",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -690,6 +745,7 @@ public static class HappinessEventCatalog
|
|||
["just_made_friend"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_made_friend",
|
||||
EventStrength = 50f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.RelatedOptional,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -703,6 +759,7 @@ public static class HappinessEventCatalog
|
|||
["just_injured"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_injured",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -715,6 +772,7 @@ public static class HappinessEventCatalog
|
|||
["just_cursed"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "just_cursed",
|
||||
EventStrength = 45f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -728,6 +786,7 @@ public static class HappinessEventCatalog
|
|||
["starving"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "starving",
|
||||
EventStrength = 30f,
|
||||
Presentation = HappinessPresentationTier.Ambient,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -741,6 +800,7 @@ public static class HappinessEventCatalog
|
|||
["conquered_city"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "conquered_city",
|
||||
EventStrength = 75f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -753,6 +813,7 @@ public static class HappinessEventCatalog
|
|||
["destroyed_city"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "destroyed_city",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -765,6 +826,7 @@ public static class HappinessEventCatalog
|
|||
["lost_crown"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "lost_crown",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -777,6 +839,7 @@ public static class HappinessEventCatalog
|
|||
["razed_capital"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "razed_capital",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -789,6 +852,7 @@ public static class HappinessEventCatalog
|
|||
["lost_capital"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "lost_capital",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -801,6 +865,7 @@ public static class HappinessEventCatalog
|
|||
["razed_city"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "razed_city",
|
||||
EventStrength = 35f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -813,6 +878,7 @@ public static class HappinessEventCatalog
|
|||
["lost_city"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "lost_city",
|
||||
EventStrength = 72f,
|
||||
Presentation = HappinessPresentationTier.Aggregate,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ActivityOnly,
|
||||
|
|
@ -825,6 +891,7 @@ public static class HappinessEventCatalog
|
|||
["become_alpha"] = new HappinessCatalogEntry
|
||||
{
|
||||
Id = "become_alpha",
|
||||
EventStrength = 65f,
|
||||
Presentation = HappinessPresentationTier.Signal,
|
||||
Context = HappinessContextRequirement.None,
|
||||
Life = HappinessLifePolicy.ProjectToLife,
|
||||
|
|
@ -872,6 +939,7 @@ public static class HappinessEventCatalog
|
|||
InterestCategory = "Unknown",
|
||||
VariantsWithRelated = new[] { "feels a change in happiness" },
|
||||
VariantsWithoutRelated = new[] { "feels a change in happiness" },
|
||||
EventStrength = 40f,
|
||||
IsFallback = true
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ public sealed class HappinessCatalogEntry
|
|||
public HappinessOverlapPolicy Overlap = HappinessOverlapPolicy.Independent;
|
||||
public HappinessRelationRole RelationRole = HappinessRelationRole.None;
|
||||
public string InterestCategory = "";
|
||||
public float EventStrength = 40f;
|
||||
public string CanonicalMilestoneKey = "";
|
||||
public string StatusOverlapId = "";
|
||||
public string[] VariantsWithRelated = Array.Empty<string>();
|
||||
|
|
|
|||
|
|
@ -48,6 +48,13 @@ internal static class HarnessScenarios
|
|||
return Discovery();
|
||||
case "world_log":
|
||||
return WorldLog();
|
||||
case "world_log_audit":
|
||||
return WorldLogAudit();
|
||||
case "disaster_war_audit":
|
||||
case "disaster_war_inventory":
|
||||
return DisasterWarAudit();
|
||||
case "event_coverage":
|
||||
return EventCoverage();
|
||||
case "chronicle":
|
||||
case "chronicle_smoke":
|
||||
return ChronicleSmoke();
|
||||
|
|
@ -146,6 +153,7 @@ internal static class HarnessScenarios
|
|||
Nested("reg_action_priority", "director_action_priority"),
|
||||
Nested("reg_discovery", "discovery"),
|
||||
Nested("reg_worldlog", "world_log"),
|
||||
Nested("reg_event_coverage", "event_coverage"),
|
||||
Nested("reg_chronicle", "chronicle_smoke"),
|
||||
Nested("reg_activity", "activity_idle_smoke"),
|
||||
Nested("reg_activity_status", "activity_status_smoke"),
|
||||
|
|
@ -1069,6 +1077,56 @@ internal static class HarnessScenarios
|
|||
};
|
||||
}
|
||||
|
||||
private static List<HarnessCommand> WorldLogAudit()
|
||||
{
|
||||
return new List<HarnessCommand>
|
||||
{
|
||||
Step("wla0", "wait_world"),
|
||||
Step("wla1", "assert", expect: "world_log_audit"),
|
||||
Step("wla2", "snapshot")
|
||||
};
|
||||
}
|
||||
|
||||
private static List<HarnessCommand> DisasterWarAudit()
|
||||
{
|
||||
return new List<HarnessCommand>
|
||||
{
|
||||
Step("dwa0", "wait_world"),
|
||||
Step("dwa1", "assert", expect: "disaster_war_audit"),
|
||||
Step("dwa2", "snapshot")
|
||||
};
|
||||
}
|
||||
|
||||
private static List<HarnessCommand> EventCoverage()
|
||||
{
|
||||
return new List<HarnessCommand>
|
||||
{
|
||||
Step("ec0", "dismiss_windows"),
|
||||
Step("ec1", "wait_world"),
|
||||
Step("ec2", "set_setting", expect: "enabled", value: "true"),
|
||||
Step("ec3", "pick_unit", asset: "auto"),
|
||||
Step("ec4", "spectator", value: "off"),
|
||||
Step("ec5", "spectator", value: "on"),
|
||||
Step("ec6", "focus", asset: "auto"),
|
||||
|
||||
// Full authored coverage gates
|
||||
Step("ec10", "assert", expect: "world_log_audit"),
|
||||
Step("ec11", "assert", expect: "disaster_war_audit"),
|
||||
Step("ec12", "assert", expect: "activity_status_audit"),
|
||||
Step("ec13", "assert", expect: "activity_happiness_audit"),
|
||||
|
||||
// Previously silent-dropped WorldLog disaster id must enter the registry
|
||||
Step("ec20", "world_log_feed", asset: "disaster_tornado"),
|
||||
Step("ec21", "assert", expect: "interest_has_key", value: "worldlog:disaster_tornado"),
|
||||
Step("ec22", "world_log_feed", asset: "disaster_alien_invasion"),
|
||||
Step("ec23", "assert", expect: "interest_has_key", value: "worldlog:disaster_alien_invasion"),
|
||||
|
||||
Step("ec90", "assert", expect: "health"),
|
||||
Step("ec91", "assert", expect: "no_bad"),
|
||||
Step("ec99", "snapshot")
|
||||
};
|
||||
}
|
||||
|
||||
private static List<HarnessCommand> ActivityRateSample()
|
||||
{
|
||||
return new List<HarnessCommand>
|
||||
|
|
|
|||
|
|
@ -92,11 +92,13 @@ public static class InterestFeeds
|
|||
return;
|
||||
}
|
||||
|
||||
if (!WorldLogInterestTable.TryGetEventStrength(message.asset_id, out float evtSeed))
|
||||
WorldLogEventEntry entry = WorldLogEventCatalog.GetOrFallback(message.asset_id);
|
||||
if (!entry.CreatesInterest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float evtSeed = entry.EventStrength;
|
||||
Vector3 position = message.getLocation();
|
||||
Actor unit = null;
|
||||
if (message.hasFollowLocation())
|
||||
|
|
@ -109,32 +111,32 @@ public static class InterestFeeds
|
|||
return;
|
||||
}
|
||||
|
||||
string assetId = message.asset_id ?? "worldlog";
|
||||
string assetId = message.asset_id ?? entry.Id ?? "worldlog";
|
||||
string kingdom = message.special1 ?? "";
|
||||
string key = "worldlog:" + assetId + ":" + kingdom;
|
||||
float boost = PeekCivicBoost(kingdom, assetId);
|
||||
bool politics = evtSeed >= 90f;
|
||||
string category = string.IsNullOrEmpty(entry.Category) ? "WorldLog" : entry.Category;
|
||||
|
||||
var candidate = new InterestCandidate
|
||||
{
|
||||
Key = key,
|
||||
LeadKind = InterestLeadKind.EventLed,
|
||||
Category = politics ? "Politics" : "Settlement",
|
||||
Category = category,
|
||||
Source = "worldlog",
|
||||
EventStrength = evtSeed + boost,
|
||||
VisualConfidence = unit != null ? 0.7f : 0.4f,
|
||||
Position = unit != null ? unit.current_position : position,
|
||||
FollowUnit = unit,
|
||||
SubjectId = SafeId(unit),
|
||||
Label = WorldLogInterestTable.MakeLabel(message),
|
||||
Label = entry.MakeLabel(message),
|
||||
AssetId = assetId,
|
||||
KingdomKey = kingdom,
|
||||
SpeciesId = unit?.asset != null ? unit.asset.id : "",
|
||||
CreatedAt = Time.unscaledTime,
|
||||
LastSeenAt = Time.unscaledTime,
|
||||
ExpiresAt = Time.unscaledTime + 40f,
|
||||
MinWatch = politics ? 8f : 6f,
|
||||
MaxWatch = politics ? 35f : 28f,
|
||||
MinWatch = entry.MinWatch,
|
||||
MaxWatch = entry.MaxWatch,
|
||||
Completion = InterestCompletionKind.FixedDwell
|
||||
};
|
||||
InterestScoring.ScoreCheap(candidate);
|
||||
|
|
@ -342,19 +344,14 @@ public static class InterestFeeds
|
|||
return;
|
||||
}
|
||||
|
||||
// Visible spectacle statuses amplify / extend scenes; crying extends grief.
|
||||
bool spectacle = statusId == "burning"
|
||||
|| statusId == "possessed"
|
||||
|| statusId == "crying"
|
||||
|| statusId == "frozen"
|
||||
|| statusId == "shocked";
|
||||
if (!spectacle)
|
||||
StatusInterestEntry entry = StatusInterestCatalog.GetOrFallback(statusId);
|
||||
if (!entry.CreatesInterest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long id = SafeId(actor);
|
||||
if (statusId == "crying")
|
||||
if (entry.ExtendsGrief || statusId == "crying")
|
||||
{
|
||||
// Extend existing grief candidate rather than spawning a second scene.
|
||||
string griefPrefix = "happiness:death_";
|
||||
|
|
@ -362,20 +359,21 @@ public static class InterestFeeds
|
|||
return;
|
||||
}
|
||||
|
||||
string phrase = ActivityStatusProse.Phrase(statusId, gained: true, out _);
|
||||
string key = "status:" + statusId + ":" + id;
|
||||
var candidate = new InterestCandidate
|
||||
{
|
||||
Key = key,
|
||||
LeadKind = InterestLeadKind.EventLed,
|
||||
Category = "StatusTransformation",
|
||||
Category = string.IsNullOrEmpty(entry.Category) ? "StatusTransformation" : entry.Category,
|
||||
Source = "status",
|
||||
EventStrength = 55f,
|
||||
EventStrength = entry.EventStrength,
|
||||
VisualConfidence = 0.85f,
|
||||
Position = actor.current_position,
|
||||
FollowUnit = actor,
|
||||
SubjectId = id,
|
||||
StatusId = statusId,
|
||||
Label = SafeName(actor) + " · " + statusId,
|
||||
Label = SafeName(actor) + " · " + phrase,
|
||||
AssetId = actor.asset != null ? actor.asset.id : "",
|
||||
SpeciesId = actor.asset != null ? actor.asset.id : "",
|
||||
CreatedAt = Time.unscaledTime,
|
||||
|
|
@ -659,6 +657,9 @@ public static class InterestFeeds
|
|||
RegisterDirect(battle);
|
||||
}
|
||||
|
||||
// Ongoing wars from WarManager (complements WorldLog war start/end).
|
||||
WarInterestFeed.Tick();
|
||||
|
||||
// Character-led vignette seeds (not sole one-best monopoly: register best + a few notables).
|
||||
WorldActivityScanner.RegisterTopCharacterCandidates(max: 4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,37 +39,12 @@ public static class InterestScoring
|
|||
|
||||
public static float EventStrengthForHappiness(string effectId)
|
||||
{
|
||||
string id = effectId ?? "";
|
||||
switch (id)
|
||||
{
|
||||
case "death_child":
|
||||
return 95f;
|
||||
case "death_lover":
|
||||
return 88f;
|
||||
case "death_best_friend":
|
||||
return 78f;
|
||||
case "death_family_member":
|
||||
return 76f;
|
||||
case "just_born":
|
||||
case "just_hatched":
|
||||
case "had_child":
|
||||
case "just_had_child":
|
||||
return 70f;
|
||||
case "become_king":
|
||||
case "become_city_leader":
|
||||
case "become_clan_chief":
|
||||
return 72f;
|
||||
case "just_found_house":
|
||||
case "new_home":
|
||||
return 55f;
|
||||
default:
|
||||
return 40f;
|
||||
}
|
||||
return HappinessEventCatalog.GetOrFallback(effectId).EventStrength;
|
||||
}
|
||||
|
||||
public static float EventStrengthForWorldLog(string assetId)
|
||||
{
|
||||
return WorldLogInterestTable.TryGetEventStrength(assetId, out float seed) ? seed : 0f;
|
||||
return WorldLogEventCatalog.GetOrFallback(assetId).EventStrength;
|
||||
}
|
||||
|
||||
public static bool IsFillScore(float totalScore)
|
||||
|
|
|
|||
146
IdleSpectator/StatusInterestCatalog.cs
Normal file
146
IdleSpectator/StatusInterestCatalog.cs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
public sealed class StatusInterestEntry
|
||||
{
|
||||
public string Id = "";
|
||||
public float EventStrength = 40f;
|
||||
public string Category = "Status";
|
||||
public bool CreatesInterest;
|
||||
public bool ExtendsGrief;
|
||||
public bool IsFallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authored interest policy for every live status asset. Prose stays in ActivityStatusProse.
|
||||
/// </summary>
|
||||
public static class StatusInterestCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, StatusInterestEntry> Entries =
|
||||
new Dictionary<string, StatusInterestEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
static StatusInterestCatalog()
|
||||
{
|
||||
// Spectacle / camera-worthy transformations
|
||||
Add("burning", 70f, "StatusTransformation", true);
|
||||
Add("possessed", 72f, "StatusTransformation", true);
|
||||
Add("possessed_follower", 60f, "StatusTransformation", true);
|
||||
Add("frozen", 65f, "StatusTransformation", true);
|
||||
Add("poisoned", 58f, "StatusTransformation", true);
|
||||
Add("drowning", 68f, "StatusTransformation", true);
|
||||
Add("stunned", 50f, "StatusTransformation", true);
|
||||
Add("rage", 62f, "StatusTransformation", true);
|
||||
Add("angry", 48f, "StatusTransformation", true);
|
||||
Add("cursed", 60f, "StatusTransformation", true);
|
||||
Add("soul_harvested", 75f, "StatusTransformation", true);
|
||||
Add("voices_in_my_head", 55f, "StatusTransformation", true);
|
||||
Add("tantrum", 52f, "StatusTransformation", true);
|
||||
Add("egg", 58f, "StatusTransformation", true);
|
||||
Add("magnetized", 55f, "StatusTransformation", true);
|
||||
Add("invincible", 56f, "StatusTransformation", true);
|
||||
Add("powerup", 54f, "StatusTransformation", true);
|
||||
Add("enchanted", 52f, "StatusTransformation", true);
|
||||
Add("ash_fever", 50f, "StatusTransformation", true);
|
||||
Add("starving", 48f, "StatusTransformation", true);
|
||||
Add("surprised", 42f, "StatusTransformation", true);
|
||||
Add("confused", 40f, "StatusTransformation", true);
|
||||
Add("strange_urge", 45f, "StatusTransformation", true);
|
||||
Add("inspired", 42f, "Emotion", true);
|
||||
Add("motivated", 40f, "Emotion", true);
|
||||
Add("fell_in_love", 55f, "Relationship", true);
|
||||
Add("pregnant", 58f, "LifeChapter", true);
|
||||
Add("pregnant_parthenogenesis", 58f, "LifeChapter", true);
|
||||
Add("crying", 50f, "Grief", true, extendsGrief: true);
|
||||
|
||||
// Authored but do not create interest candidates (cooldowns / ambient / brief FX)
|
||||
AddNoInterest("afterglow");
|
||||
AddNoInterest("being_suspicious");
|
||||
AddNoInterest("budding");
|
||||
AddNoInterest("caffeinated");
|
||||
AddNoInterest("cough");
|
||||
AddNoInterest("dash");
|
||||
AddNoInterest("dodge");
|
||||
AddNoInterest("festive_spirit");
|
||||
AddNoInterest("flicked");
|
||||
AddNoInterest("had_bad_dream");
|
||||
AddNoInterest("had_good_dream");
|
||||
AddNoInterest("had_nightmare");
|
||||
AddNoInterest("handsome_migrant");
|
||||
AddNoInterest("just_ate");
|
||||
AddNoInterest("laughing");
|
||||
AddNoInterest("on_guard");
|
||||
AddNoInterest("recovery_combat_action");
|
||||
AddNoInterest("recovery_plot");
|
||||
AddNoInterest("recovery_social");
|
||||
AddNoInterest("recovery_spell");
|
||||
AddNoInterest("shield");
|
||||
AddNoInterest("singing");
|
||||
AddNoInterest("sleeping");
|
||||
AddNoInterest("slowness");
|
||||
AddNoInterest("spell_boost");
|
||||
AddNoInterest("spell_silence");
|
||||
AddNoInterest("swearing");
|
||||
AddNoInterest("taking_roots");
|
||||
AddNoInterest("uprooting");
|
||||
}
|
||||
|
||||
private static void Add(
|
||||
string id,
|
||||
float strength,
|
||||
string category,
|
||||
bool createsInterest,
|
||||
bool extendsGrief = false)
|
||||
{
|
||||
Entries[id] = new StatusInterestEntry
|
||||
{
|
||||
Id = id,
|
||||
EventStrength = strength,
|
||||
Category = category,
|
||||
CreatesInterest = createsInterest,
|
||||
ExtendsGrief = extendsGrief
|
||||
};
|
||||
}
|
||||
|
||||
private static void AddNoInterest(string id)
|
||||
{
|
||||
Add(id, 20f, "StatusAmbient", false);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AuthoredIds => Entries.Keys;
|
||||
|
||||
public static bool HasAuthored(string statusId)
|
||||
{
|
||||
return !string.IsNullOrEmpty(statusId) && Entries.ContainsKey(statusId.Trim());
|
||||
}
|
||||
|
||||
public static bool TryGet(string statusId, out StatusInterestEntry entry)
|
||||
{
|
||||
entry = null;
|
||||
if (string.IsNullOrEmpty(statusId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Entries.TryGetValue(statusId.Trim(), out entry);
|
||||
}
|
||||
|
||||
public static StatusInterestEntry GetOrFallback(string statusId)
|
||||
{
|
||||
if (TryGet(statusId, out StatusInterestEntry entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
string id = string.IsNullOrEmpty(statusId) ? "unknown" : statusId.Trim();
|
||||
return new StatusInterestEntry
|
||||
{
|
||||
Id = id,
|
||||
EventStrength = 40f,
|
||||
Category = "Status",
|
||||
CreatesInterest = false,
|
||||
IsFallback = true
|
||||
};
|
||||
}
|
||||
}
|
||||
336
IdleSpectator/WarInterestFeed.cs
Normal file
336
IdleSpectator/WarInterestFeed.cs
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
/// <summary>
|
||||
/// Polls live WarManager state and registers ongoing war interest candidates.
|
||||
/// </summary>
|
||||
public static class WarInterestFeed
|
||||
{
|
||||
public static void Tick()
|
||||
{
|
||||
if (AgentHarness.Busy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
object warsManager = ResolveWarsManager();
|
||||
if (warsManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable active = InvokeEnumerable(warsManager, "getActiveWars")
|
||||
?? InvokeEnumerable(warsManager, "getWars");
|
||||
if (active == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int registered = 0;
|
||||
foreach (object war in active)
|
||||
{
|
||||
if (war == null || registered >= 6)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (TryRegisterWar(war))
|
||||
{
|
||||
registered++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryRegisterWar(object war)
|
||||
{
|
||||
string warTypeId = ReadWarTypeId(war);
|
||||
WarTypeInterestEntry entry = WarTypeInterestCatalog.GetOrFallback(warTypeId);
|
||||
if (!entry.CreatesInterest)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
object attacker = ReadPropOrField(war, "main_attacker") ?? Invoke(war, "get_main_attacker");
|
||||
object defender = ReadPropOrField(war, "main_defender") ?? Invoke(war, "get_main_defender");
|
||||
string attackerName = ReadMetaName(attacker);
|
||||
string defenderName = ReadMetaName(defender);
|
||||
string labelPair = string.IsNullOrEmpty(defenderName)
|
||||
? attackerName
|
||||
: attackerName + " vs " + defenderName;
|
||||
if (string.IsNullOrEmpty(labelPair))
|
||||
{
|
||||
labelPair = ReadString(war, "name") ?? warTypeId ?? "war";
|
||||
}
|
||||
|
||||
Vector3 position = Vector3.zero;
|
||||
Actor follow = null;
|
||||
if (!TryLocateWar(attacker, defender, out position, out follow))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string key = "war:" + (ReadString(war, "id") ?? labelPair) + ":" + (warTypeId ?? "normal");
|
||||
string label = WarTypeInterestCatalog.MakeLabel(entry, labelPair);
|
||||
var candidate = new InterestCandidate
|
||||
{
|
||||
Key = key,
|
||||
LeadKind = InterestLeadKind.EventLed,
|
||||
Category = entry.Category,
|
||||
Source = "war",
|
||||
EventStrength = entry.EventStrength,
|
||||
VisualConfidence = follow != null ? 0.65f : 0.35f,
|
||||
Position = position,
|
||||
FollowUnit = follow,
|
||||
SubjectId = follow != null ? SafeActorId(follow) : 0,
|
||||
Label = label,
|
||||
AssetId = warTypeId ?? "war",
|
||||
KingdomKey = attackerName,
|
||||
CreatedAt = Time.unscaledTime,
|
||||
LastSeenAt = Time.unscaledTime,
|
||||
ExpiresAt = Time.unscaledTime + 25f,
|
||||
MinWatch = 6f,
|
||||
MaxWatch = 30f,
|
||||
Completion = InterestCompletionKind.FixedDwell
|
||||
};
|
||||
InterestScoring.ScoreCheap(candidate);
|
||||
InterestRegistry.Upsert(candidate);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryLocateWar(object attacker, object defender, out Vector3 position, out Actor follow)
|
||||
{
|
||||
position = Vector3.zero;
|
||||
follow = null;
|
||||
if (TryKingdomAnchor(attacker, out position, out follow))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryKingdomAnchor(defender, out position, out follow))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fall back to any living unit.
|
||||
try
|
||||
{
|
||||
if (World.world?.units != null)
|
||||
{
|
||||
foreach (Actor actor in World.world.units)
|
||||
{
|
||||
if (actor == null || !actor.isAlive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
follow = actor;
|
||||
position = actor.current_position;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryKingdomAnchor(object kingdom, out Vector3 position, out Actor follow)
|
||||
{
|
||||
position = Vector3.zero;
|
||||
follow = null;
|
||||
if (kingdom == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object capital = ReadPropOrField(kingdom, "capital") ?? Invoke(kingdom, "getCapital");
|
||||
if (capital != null)
|
||||
{
|
||||
object posObj = ReadPropOrField(capital, "current_position")
|
||||
?? ReadPropOrField(capital, "city_center");
|
||||
if (posObj is Vector3 v && v != Vector3.zero)
|
||||
{
|
||||
position = v;
|
||||
}
|
||||
else if (posObj is Vector2 v2)
|
||||
{
|
||||
position = new Vector3(v2.x, v2.y, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
object king = ReadPropOrField(kingdom, "king");
|
||||
if (king is Actor actorKing && actorKing.isAlive())
|
||||
{
|
||||
follow = actorKing;
|
||||
position = actorKing.current_position;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (position != Vector3.zero)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static object ResolveWarsManager()
|
||||
{
|
||||
try
|
||||
{
|
||||
object world = World.world;
|
||||
if (world == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
object wars = ReadPropOrField(world, "wars");
|
||||
if (wars != null)
|
||||
{
|
||||
return wars;
|
||||
}
|
||||
|
||||
return typeof(AssetManager).Assembly.GetType("WarManager");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadWarTypeId(object war)
|
||||
{
|
||||
try
|
||||
{
|
||||
object warType = ReadPropOrField(war, "war_type") ?? Invoke(war, "get_war_type");
|
||||
if (warType == null)
|
||||
{
|
||||
return "normal";
|
||||
}
|
||||
|
||||
if (warType is string s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
string id = ReadString(warType, "id");
|
||||
return string.IsNullOrEmpty(id) ? "normal" : id;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "normal";
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadMetaName(object meta)
|
||||
{
|
||||
if (meta == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object name = Invoke(meta, "getName") ?? ReadPropOrField(meta, "name") ?? ReadString(meta, "name");
|
||||
return name as string ?? "";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable InvokeEnumerable(object target, string methodName)
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodInfo method = target.GetType().GetMethod(methodName, Type.EmptyTypes);
|
||||
object result = method?.Invoke(target, null);
|
||||
return result as IEnumerable;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static object Invoke(object target, string methodName)
|
||||
{
|
||||
if (target == null || string.IsNullOrEmpty(methodName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
MethodInfo method = target.GetType().GetMethod(methodName, Type.EmptyTypes);
|
||||
return method?.Invoke(target, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static object ReadPropOrField(object target, string name)
|
||||
{
|
||||
if (target == null || string.IsNullOrEmpty(name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Type type = target.GetType();
|
||||
PropertyInfo prop = type.GetProperty(name);
|
||||
if (prop != null)
|
||||
{
|
||||
return prop.GetValue(target, null);
|
||||
}
|
||||
|
||||
FieldInfo field = type.GetField(name);
|
||||
return field?.GetValue(target);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadString(object target, string name)
|
||||
{
|
||||
return ReadPropOrField(target, name) as string;
|
||||
}
|
||||
|
||||
private static long SafeActorId(Actor actor)
|
||||
{
|
||||
if (actor == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return actor.getID();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
92
IdleSpectator/WarTypeInterestCatalog.cs
Normal file
92
IdleSpectator/WarTypeInterestCatalog.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
public sealed class WarTypeInterestEntry
|
||||
{
|
||||
public string Id = "";
|
||||
public float EventStrength = 85f;
|
||||
public string Category = "Politics";
|
||||
public string LabelTemplate = "War ({id})";
|
||||
public bool CreatesInterest = true;
|
||||
public bool IsFallback;
|
||||
}
|
||||
|
||||
/// <summary>Authored interest overlay for every live war_types_library asset.</summary>
|
||||
public static class WarTypeInterestCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, WarTypeInterestEntry> Entries =
|
||||
new Dictionary<string, WarTypeInterestEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
static WarTypeInterestCatalog()
|
||||
{
|
||||
Add("normal", 90f, "War: {a}");
|
||||
Add("spite", 88f, "Spite war: {a}");
|
||||
Add("inspire", 86f, "Inspired war: {a}");
|
||||
Add("rebellion", 92f, "Rebellion: {a}");
|
||||
Add("whisper_of_war", 84f, "Whisper of war: {a}");
|
||||
}
|
||||
|
||||
private static void Add(string id, float strength, string labelTemplate)
|
||||
{
|
||||
Entries[id] = new WarTypeInterestEntry
|
||||
{
|
||||
Id = id,
|
||||
EventStrength = strength,
|
||||
Category = "Politics",
|
||||
LabelTemplate = labelTemplate,
|
||||
CreatesInterest = true
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AuthoredIds => Entries.Keys;
|
||||
|
||||
public static bool HasAuthored(string warTypeId)
|
||||
{
|
||||
return !string.IsNullOrEmpty(warTypeId) && Entries.ContainsKey(warTypeId.Trim());
|
||||
}
|
||||
|
||||
public static bool TryGet(string warTypeId, out WarTypeInterestEntry entry)
|
||||
{
|
||||
entry = null;
|
||||
if (string.IsNullOrEmpty(warTypeId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Entries.TryGetValue(warTypeId.Trim(), out entry);
|
||||
}
|
||||
|
||||
public static WarTypeInterestEntry GetOrFallback(string warTypeId)
|
||||
{
|
||||
if (TryGet(warTypeId, out WarTypeInterestEntry entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
string id = string.IsNullOrEmpty(warTypeId) ? "unknown" : warTypeId.Trim();
|
||||
return new WarTypeInterestEntry
|
||||
{
|
||||
Id = id,
|
||||
EventStrength = 85f,
|
||||
Category = "Politics",
|
||||
LabelTemplate = "War ({id}): {a}",
|
||||
CreatesInterest = true,
|
||||
IsFallback = true
|
||||
};
|
||||
}
|
||||
|
||||
public static string MakeLabel(WarTypeInterestEntry entry, string special1)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
return "War";
|
||||
}
|
||||
|
||||
string template = string.IsNullOrEmpty(entry.LabelTemplate) ? "War ({id})" : entry.LabelTemplate;
|
||||
return template
|
||||
.Replace("{id}", entry.Id ?? "")
|
||||
.Replace("{a}", special1 ?? "");
|
||||
}
|
||||
}
|
||||
190
IdleSpectator/WorldLogEventCatalog.cs
Normal file
190
IdleSpectator/WorldLogEventCatalog.cs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
public sealed class WorldLogEventEntry
|
||||
{
|
||||
public string Id = "";
|
||||
public float EventStrength = 40f;
|
||||
public string Category = "WorldLog";
|
||||
public bool CreatesInterest = true;
|
||||
public bool ChronicleEligible;
|
||||
public float MinWatch = 6f;
|
||||
public float MaxWatch = 28f;
|
||||
public string LabelTemplate = "{id}";
|
||||
public bool IsFallback;
|
||||
|
||||
public string MakeLabel(string special1, string special2)
|
||||
{
|
||||
string a = special1 ?? "";
|
||||
string b = special2 ?? "";
|
||||
string template = string.IsNullOrEmpty(LabelTemplate) ? "{id}" : LabelTemplate;
|
||||
return template
|
||||
.Replace("{id}", Id ?? "")
|
||||
.Replace("{a}", a)
|
||||
.Replace("{b}", b)
|
||||
.Replace("{special1}", a)
|
||||
.Replace("{special2}", b);
|
||||
}
|
||||
|
||||
public string MakeLabel(WorldLogMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
return MakeLabel("", "");
|
||||
}
|
||||
|
||||
return MakeLabel(message.special1, message.special2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authored policy for every live WorldLog asset. Game library is inventory; this is presentation/scoring only.
|
||||
/// </summary>
|
||||
public static class WorldLogEventCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, WorldLogEventEntry> Entries =
|
||||
new Dictionary<string, WorldLogEventEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
static WorldLogEventCatalog()
|
||||
{
|
||||
// Kings
|
||||
Add("king_new", 78f, "Politics", true, "New king: {b} ({a})", 6f, 28f);
|
||||
Add("king_left", 65f, "Politics", true, "King left: {b} ({a})", 6f, 28f);
|
||||
Add("king_fled_capital", 70f, "Politics", true, "King fled capital: {b}", 6f, 28f);
|
||||
Add("king_fled_city", 68f, "Politics", true, "King fled city: {b}", 6f, 28f);
|
||||
Add("king_dead", 80f, "Politics", true, "King died: {b} ({a})", 6f, 28f);
|
||||
Add("king_killed", 85f, "Politics", true, "King killed: {b} ({a})", 6f, 28f);
|
||||
|
||||
// Favorites
|
||||
Add("favorite_dead", 82f, "Politics", true, "Favorite fell: {a}", 6f, 28f);
|
||||
Add("favorite_killed", 85f, "Politics", true, "Favorite fell: {a}", 6f, 28f);
|
||||
|
||||
// Cities / kingdoms / clans / wars / alliances
|
||||
Add("city_new", 72f, "Settlement", true, "New city: {a}", 6f, 28f);
|
||||
Add("log_city_revolted", 90f, "Politics", true, "Revolt: {a}", 8f, 35f);
|
||||
Add("city_destroyed", 92f, "Settlement", true, "City destroyed: {a}", 8f, 35f);
|
||||
Add("diplomacy_war_ended", 72f, "Politics", true, "War ended: {a}", 6f, 28f);
|
||||
Add("diplomacy_war_started", 100f, "Politics", true, "War: {a} vs {b}", 8f, 35f);
|
||||
Add("total_war_started", 100f, "Politics", true, "Total war: {a}", 8f, 35f);
|
||||
Add("alliance_new", 70f, "Politics", true, "Alliance: {a}", 6f, 28f);
|
||||
Add("alliance_dissolved", 68f, "Politics", true, "Alliance dissolved: {a}", 6f, 28f);
|
||||
Add("kingdom_new", 75f, "Settlement", true, "New kingdom: {a}", 6f, 28f);
|
||||
Add("kingdom_destroyed", 100f, "Politics", true, "Kingdom fell: {a}", 8f, 35f);
|
||||
Add("kingdom_shattered", 98f, "Politics", true, "Kingdom shattered: {a}", 8f, 35f);
|
||||
Add("kingdom_fractured", 95f, "Politics", true, "Kingdom fractured: {a}", 8f, 35f);
|
||||
Add("kingdom_royal_clan_new", 74f, "Politics", true, "Royal clan: {a}", 6f, 28f);
|
||||
Add("kingdom_royal_clan_changed", 72f, "Politics", true, "Royal clan: {a}", 6f, 28f);
|
||||
Add("kingdom_royal_clan_dead", 76f, "Politics", true, "Royal clan ended: {a}", 6f, 28f);
|
||||
|
||||
// Disasters (WorldLog ids; linked to disasters library by naming)
|
||||
Add("disaster_tornado", 95f, "Disaster", true, "Tornado: {a}", 8f, 35f);
|
||||
Add("disaster_meteorite", 95f, "Disaster", true, "Meteorite: {a}", 8f, 35f);
|
||||
Add("disaster_hellspawn", 96f, "Disaster", true, "Hellspawn: {a}", 8f, 35f);
|
||||
Add("disaster_earthquake", 95f, "Disaster", true, "Earthquake: {a}", 8f, 35f);
|
||||
Add("disaster_greg_abominations", 96f, "Disaster", true, "Greg abominations: {a}", 8f, 35f);
|
||||
Add("disaster_ice_ones", 95f, "Disaster", true, "Ice ones awaken: {a}", 8f, 35f);
|
||||
Add("disaster_sudden_snowman", 90f, "Disaster", true, "Sudden snowman: {a}", 8f, 35f);
|
||||
Add("disaster_garden_surprise", 90f, "Disaster", true, "Garden surprise: {a}", 8f, 35f);
|
||||
Add("disaster_dragon_from_farlands", 98f, "Disaster", true, "Dragon from the farlands: {a}", 8f, 35f);
|
||||
Add("disaster_bandits", 92f, "Disaster", true, "Bandit raid: {a}", 8f, 35f);
|
||||
Add("disaster_alien_invasion", 97f, "Disaster", true, "Alien invasion: {a}", 8f, 35f);
|
||||
Add("disaster_biomass", 96f, "Disaster", true, "Biomass outbreak: {a}", 8f, 35f);
|
||||
Add("disaster_tumor", 96f, "Disaster", true, "Tumor outbreak: {a}", 8f, 35f);
|
||||
Add("disaster_heatwave", 88f, "Disaster", true, "Heatwave: {a}", 8f, 35f);
|
||||
Add("disaster_evil_mage", 94f, "Disaster", true, "Evil mage: {a}", 8f, 35f);
|
||||
Add("disaster_underground_necromancer", 95f, "Disaster", true, "Underground necromancer: {a}", 8f, 35f);
|
||||
Add("disaster_mad_thoughts", 90f, "Disaster", true, "Mad thoughts: {a}", 8f, 35f);
|
||||
|
||||
// Harness / internal - authored but never owns camera interest
|
||||
Add("auto_tester", 10f, "Harness", false, "Auto tester: {a}", 2f, 6f);
|
||||
}
|
||||
|
||||
private static void Add(
|
||||
string id,
|
||||
float strength,
|
||||
string category,
|
||||
bool createsInterest,
|
||||
string labelTemplate,
|
||||
float minWatch,
|
||||
float maxWatch)
|
||||
{
|
||||
Entries[id] = new WorldLogEventEntry
|
||||
{
|
||||
Id = id,
|
||||
EventStrength = strength,
|
||||
Category = category,
|
||||
CreatesInterest = createsInterest,
|
||||
ChronicleEligible = createsInterest && strength >= 65f,
|
||||
LabelTemplate = labelTemplate,
|
||||
MinWatch = minWatch,
|
||||
MaxWatch = maxWatch
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AuthoredIds => Entries.Keys;
|
||||
|
||||
public static bool HasAuthored(string assetId)
|
||||
{
|
||||
return !string.IsNullOrEmpty(assetId) && Entries.ContainsKey(assetId.Trim());
|
||||
}
|
||||
|
||||
public static bool TryGet(string assetId, out WorldLogEventEntry entry)
|
||||
{
|
||||
entry = null;
|
||||
if (string.IsNullOrEmpty(assetId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Entries.TryGetValue(assetId.Trim(), out entry);
|
||||
}
|
||||
|
||||
public static WorldLogEventEntry GetOrFallback(string assetId)
|
||||
{
|
||||
if (TryGet(assetId, out WorldLogEventEntry entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
string id = string.IsNullOrEmpty(assetId) ? "unknown" : assetId.Trim();
|
||||
float strength = 40f;
|
||||
string category = "WorldLog";
|
||||
if (id.IndexOf("disaster", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
strength = 95f;
|
||||
category = "Disaster";
|
||||
}
|
||||
|
||||
return new WorldLogEventEntry
|
||||
{
|
||||
Id = id,
|
||||
EventStrength = strength,
|
||||
Category = category,
|
||||
CreatesInterest = true,
|
||||
ChronicleEligible = strength >= 65f,
|
||||
LabelTemplate = "{id}: {a}",
|
||||
MinWatch = strength >= 90f ? 8f : 6f,
|
||||
MaxWatch = strength >= 90f ? 35f : 28f,
|
||||
IsFallback = true
|
||||
};
|
||||
}
|
||||
|
||||
public static bool TryGetEventStrength(string assetId, out float eventStrength)
|
||||
{
|
||||
WorldLogEventEntry entry = GetOrFallback(assetId);
|
||||
eventStrength = entry.EventStrength;
|
||||
return !string.IsNullOrEmpty(assetId);
|
||||
}
|
||||
|
||||
public static string MakeLabel(WorldLogMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
return "event";
|
||||
}
|
||||
|
||||
return GetOrFallback(message.asset_id).MakeLabel(message);
|
||||
}
|
||||
}
|
||||
216
IdleSpectator/WorldLogEventHarness.cs
Normal file
216
IdleSpectator/WorldLogEventHarness.cs
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
public sealed class WorldLogEventAuditResult
|
||||
{
|
||||
public bool Passed;
|
||||
public string Detail = "";
|
||||
public int Total;
|
||||
public int MissingAuthored;
|
||||
public int OrphanAuthored;
|
||||
public int EmptyLabel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exhaustive live world_log_library audit for authored WorldLog event catalog coverage.
|
||||
/// </summary>
|
||||
public static class WorldLogEventHarness
|
||||
{
|
||||
public static WorldLogEventAuditResult RunAudit(string outputDirectory)
|
||||
{
|
||||
List<string> liveIds = ActivityAssetCatalog.EnumerateLiveWorldLogIds();
|
||||
var authored = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (string id in WorldLogEventCatalog.AuthoredIds)
|
||||
{
|
||||
authored.Add(id);
|
||||
}
|
||||
|
||||
int missingAuthored = 0;
|
||||
int orphanAuthored = 0;
|
||||
int emptyLabel = 0;
|
||||
var tsv = new StringBuilder();
|
||||
tsv.AppendLine(
|
||||
"id\tgroup\tlocale\tpath_icon\tdisaster_link\tauthored\tevent_strength\tcategory\tcreates_interest\tlabel_sample\tnotes");
|
||||
|
||||
var liveSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 0; i < liveIds.Count; i++)
|
||||
{
|
||||
string id = liveIds[i];
|
||||
liveSet.Add(id);
|
||||
WorldLogAsset asset = ActivityAssetCatalog.TryGetWorldLogAsset(id);
|
||||
bool hasAuthored = WorldLogEventCatalog.HasAuthored(id);
|
||||
WorldLogEventEntry entry = WorldLogEventCatalog.GetOrFallback(id);
|
||||
if (!hasAuthored || entry.IsFallback)
|
||||
{
|
||||
missingAuthored++;
|
||||
}
|
||||
|
||||
string group = ReadStringField(asset, "group_id")
|
||||
?? ReadStringField(asset, "history_group")
|
||||
?? ReadStringField(asset, "group")
|
||||
?? "";
|
||||
string locale = "";
|
||||
try
|
||||
{
|
||||
locale = asset != null ? (asset.getLocaleID() ?? "") : "";
|
||||
}
|
||||
catch
|
||||
{
|
||||
locale = ReadStringField(asset, "path_locale") ?? "";
|
||||
}
|
||||
|
||||
string pathIcon = ReadStringField(asset, "path_icon") ?? "";
|
||||
string disasterLink = ReadStringField(asset, "disaster_id")
|
||||
?? ReadStringField(asset, "linked_disaster")
|
||||
?? "";
|
||||
if (string.IsNullOrEmpty(disasterLink) && id.IndexOf("disaster", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
disasterLink = "id_hint";
|
||||
}
|
||||
|
||||
string labelSample = entry.MakeLabel("Alpha", "Beta");
|
||||
if (string.IsNullOrEmpty(labelSample))
|
||||
{
|
||||
emptyLabel++;
|
||||
}
|
||||
|
||||
string notes = "ok";
|
||||
if (!hasAuthored || entry.IsFallback)
|
||||
{
|
||||
notes = "missing_authored";
|
||||
}
|
||||
else if (string.IsNullOrEmpty(labelSample))
|
||||
{
|
||||
notes = "empty_label";
|
||||
}
|
||||
|
||||
tsv.Append(id).Append('\t')
|
||||
.Append(Escape(group)).Append('\t')
|
||||
.Append(Escape(locale)).Append('\t')
|
||||
.Append(Escape(pathIcon)).Append('\t')
|
||||
.Append(Escape(disasterLink)).Append('\t')
|
||||
.Append(hasAuthored ? "1" : "0").Append('\t')
|
||||
.Append(entry.EventStrength.ToString("0.#")).Append('\t')
|
||||
.Append(Escape(entry.Category)).Append('\t')
|
||||
.Append(entry.CreatesInterest ? "1" : "0").Append('\t')
|
||||
.Append(Escape(labelSample)).Append('\t')
|
||||
.Append(notes).AppendLine();
|
||||
}
|
||||
|
||||
foreach (string id in authored)
|
||||
{
|
||||
if (!liveSet.Contains(id))
|
||||
{
|
||||
orphanAuthored++;
|
||||
tsv.Append(id).Append("\t\t\t\t\t1\t0\t\t0\t\torphan_authored").AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(outputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
File.WriteAllText(
|
||||
Path.Combine(outputDirectory, "world-log-event-audit.tsv"),
|
||||
tsv.ToString());
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Disk may be unavailable in some harness contexts.
|
||||
}
|
||||
|
||||
bool passed = missingAuthored == 0 && orphanAuthored == 0 && emptyLabel == 0;
|
||||
return new WorldLogEventAuditResult
|
||||
{
|
||||
Passed = passed,
|
||||
Total = liveIds.Count,
|
||||
MissingAuthored = missingAuthored,
|
||||
OrphanAuthored = orphanAuthored,
|
||||
EmptyLabel = emptyLabel,
|
||||
Detail =
|
||||
$"total={liveIds.Count} missingAuthored={missingAuthored} orphanAuthored={orphanAuthored} "
|
||||
+ $"emptyLabel={emptyLabel}"
|
||||
};
|
||||
}
|
||||
|
||||
public static string DumpDisasterWarInventory(string outputDirectory)
|
||||
{
|
||||
var tsv = new StringBuilder();
|
||||
tsv.AppendLine("kind\tid\tnotes");
|
||||
List<string> disasters = ActivityAssetCatalog.EnumerateLiveDisasterIds();
|
||||
for (int i = 0; i < disasters.Count; i++)
|
||||
{
|
||||
tsv.Append("disaster\t").Append(disasters[i]).Append("\tok").AppendLine();
|
||||
}
|
||||
|
||||
List<string> warTypes = ActivityAssetCatalog.EnumerateLiveWarTypeIds();
|
||||
for (int i = 0; i < warTypes.Count; i++)
|
||||
{
|
||||
tsv.Append("war_type\t").Append(warTypes[i]).Append("\tok").AppendLine();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(outputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
File.WriteAllText(
|
||||
Path.Combine(outputDirectory, "disaster-war-inventory.tsv"),
|
||||
tsv.ToString());
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return $"disasters={disasters.Count} war_types={warTypes.Count}";
|
||||
}
|
||||
|
||||
private static string ReadStringField(object asset, string fieldName)
|
||||
{
|
||||
if (asset == null || string.IsNullOrEmpty(fieldName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FieldInfo field = asset.GetType().GetField(fieldName);
|
||||
if (field != null)
|
||||
{
|
||||
return field.GetValue(asset) as string;
|
||||
}
|
||||
|
||||
PropertyInfo prop = asset.GetType().GetProperty(fieldName);
|
||||
if (prop != null)
|
||||
{
|
||||
return prop.GetValue(asset, null) as string;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string Escape(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return value.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' ');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
/// <summary>
|
||||
/// WorldLog asset ids → EventStrength seeds (score-native; no InterestTier).
|
||||
/// Compatibility facade over <see cref="WorldLogEventCatalog"/>. Prefer the catalog directly.
|
||||
/// </summary>
|
||||
public static class WorldLogInterestTable
|
||||
{
|
||||
private static readonly Dictionary<string, float> EventSeeds = new Dictionary<string, float>
|
||||
{
|
||||
// World-shaping
|
||||
["diplomacy_war_started"] = 100f,
|
||||
["total_war_started"] = 100f,
|
||||
["kingdom_destroyed"] = 100f,
|
||||
["kingdom_shattered"] = 98f,
|
||||
["kingdom_fractured"] = 95f,
|
||||
["city_destroyed"] = 92f,
|
||||
["log_city_revolted"] = 90f,
|
||||
|
||||
// Leadership / politics / favorites / founding
|
||||
["kingdom_new"] = 75f,
|
||||
["city_new"] = 72f,
|
||||
["king_new"] = 78f,
|
||||
["king_dead"] = 80f,
|
||||
["king_killed"] = 85f,
|
||||
["king_fled_capital"] = 70f,
|
||||
["king_fled_city"] = 68f,
|
||||
["king_left"] = 65f,
|
||||
["alliance_new"] = 70f,
|
||||
["alliance_dissolved"] = 68f,
|
||||
["diplomacy_war_ended"] = 72f,
|
||||
["favorite_dead"] = 82f,
|
||||
["favorite_killed"] = 85f,
|
||||
["kingdom_royal_clan_new"] = 74f,
|
||||
["kingdom_royal_clan_changed"] = 72f,
|
||||
["kingdom_royal_clan_dead"] = 76f,
|
||||
|
||||
// Biosphere footnotes
|
||||
["race_dead"] = 40f
|
||||
};
|
||||
|
||||
public static bool TryGetEventStrength(string assetId, out float eventStrength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetId))
|
||||
{
|
||||
eventStrength = 0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EventSeeds.TryGetValue(assetId, out eventStrength))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (assetId.Contains("disaster") || assetId.StartsWith("worldlog_disaster"))
|
||||
{
|
||||
eventStrength = 95f;
|
||||
return true;
|
||||
}
|
||||
|
||||
eventStrength = 0f;
|
||||
return false;
|
||||
return WorldLogEventCatalog.TryGetEventStrength(assetId, out eventStrength);
|
||||
}
|
||||
|
||||
public static string MakeLabel(WorldLogMessage message)
|
||||
{
|
||||
string id = message.asset_id ?? "event";
|
||||
string a = message.special1;
|
||||
string b = message.special2;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case "diplomacy_war_started":
|
||||
return $"War: {a} vs {b}";
|
||||
case "total_war_started":
|
||||
return $"Total war: {a}";
|
||||
case "kingdom_destroyed":
|
||||
return $"Kingdom fell: {a}";
|
||||
case "kingdom_shattered":
|
||||
return $"Kingdom shattered: {a}";
|
||||
case "kingdom_fractured":
|
||||
return $"Kingdom fractured: {a}";
|
||||
case "city_destroyed":
|
||||
return $"City destroyed: {a}";
|
||||
case "log_city_revolted":
|
||||
return $"Revolt: {a}";
|
||||
case "kingdom_new":
|
||||
return $"New kingdom: {a}";
|
||||
case "city_new":
|
||||
return $"New city: {a}";
|
||||
case "king_new":
|
||||
return $"New king: {b} ({a})";
|
||||
case "king_dead":
|
||||
return $"King died: {b} ({a})";
|
||||
case "king_killed":
|
||||
return $"King killed: {b} ({a})";
|
||||
case "king_fled_capital":
|
||||
return $"King fled capital: {b}";
|
||||
case "king_fled_city":
|
||||
return $"King fled city: {b}";
|
||||
case "alliance_new":
|
||||
return $"Alliance: {a}";
|
||||
case "alliance_dissolved":
|
||||
return $"Alliance dissolved: {a}";
|
||||
case "diplomacy_war_ended":
|
||||
return $"War ended: {a}";
|
||||
case "kingdom_royal_clan_new":
|
||||
case "kingdom_royal_clan_changed":
|
||||
return $"Royal clan: {a}";
|
||||
case "kingdom_royal_clan_dead":
|
||||
return $"Royal clan ended: {a}";
|
||||
case "race_dead":
|
||||
return $"Species extinct: {a}";
|
||||
case "favorite_dead":
|
||||
case "favorite_killed":
|
||||
return $"Favorite fell: {a}";
|
||||
default:
|
||||
if (!string.IsNullOrEmpty(a) && !string.IsNullOrEmpty(b))
|
||||
{
|
||||
return $"{id}: {a} / {b}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(a))
|
||||
{
|
||||
return $"{id}: {a}";
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
return WorldLogEventCatalog.MakeLabel(message);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AuthoredIds => WorldLogEventCatalog.AuthoredIds;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,17 +14,13 @@ public static class WorldLogMessageAddPatch
|
|||
return;
|
||||
}
|
||||
|
||||
if (!WorldLogInterestTable.TryGetEventStrength(pMessage.asset_id, out float evtSeed))
|
||||
WorldLogEventEntry entry = WorldLogEventCatalog.GetOrFallback(pMessage.asset_id);
|
||||
// Chronicle notable world events only (authored ChronicleEligible / story-strength+).
|
||||
if (!entry.ChronicleEligible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Chronicle notable world events (story-strength and above); skip footnotes.
|
||||
if (evtSeed < 65f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Chronicle.NoteWorldLog(pMessage, WorldLogInterestTable.MakeLabel(pMessage));
|
||||
Chronicle.NoteWorldLog(pMessage, entry.MakeLabel(pMessage));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "IdleSpectator",
|
||||
"author": "dazed",
|
||||
"version": "0.16.8",
|
||||
"version": "0.16.10",
|
||||
"description": "AFK Idle Spectator (I) + Lore (L). Event-centered scene director + happiness Activity/Life logging.",
|
||||
"GUID": "com.dazed.idlespectator"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ REGRESSION_SCENARIOS=(
|
|||
director_action_priority
|
||||
discovery
|
||||
world_log
|
||||
event_coverage
|
||||
chronicle_smoke
|
||||
activity_idle_smoke
|
||||
activity_status_smoke
|
||||
|
|
|
|||
Loading…
Reference in a new issue