Build Inventories

This commit is contained in:
DazedAnon 2026-07-15 20:09:08 -05:00
parent 8427079188
commit 199adee644
20 changed files with 1598 additions and 192 deletions

View file

@ -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))
{

View file

@ -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}"
};
}

View file

@ -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}"
};
}

View file

@ -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);

View 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
};
}
}

View 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}"
};
}
}

View file

@ -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
};
}

View file

@ -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>();

View file

@ -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>

View file

@ -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);
}

View file

@ -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)

View 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
};
}
}

View 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;
}
}
}

View 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 ?? "");
}
}

View 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);
}
}

View 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', ' ');
}
}

View file

@ -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;
}

View file

@ -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));
}
}

View file

@ -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"
}

View file

@ -37,6 +37,7 @@ REGRESSION_SCENARIOS=(
director_action_priority
discovery
world_log
event_coverage
chronicle_smoke
activity_idle_smoke
activity_status_smoke