diff --git a/IdleSpectator/ActivityHappinessHarness.cs b/IdleSpectator/ActivityHappinessHarness.cs index ec9ca84..3de7814 100644 --- a/IdleSpectator/ActivityHappinessHarness.cs +++ b/IdleSpectator/ActivityHappinessHarness.cs @@ -211,11 +211,11 @@ public static class ActivityHappinessHarness mustContain: new[] { "hatches from an egg" }, mustNotContain: Array.Empty()); - // Camera demotion: ticker crumbs must stay Ambient (Layer B only). + // Camera demotion: only true FX / bookkeeping stay Ambient (Layer B). + // Interesting life/social beats are Signal; director ranks by EventStrength. string[] ambientRequired = { - "just_received_gift", "just_gave_gift", "just_cried", "just_inspired", - "got_robbed", "lost_fight", "just_injured", "just_made_friend" + "got_poked", "got_caught", "paid_tax", "just_ate", "just_pooped" }; for (int i = 0; i < ambientRequired.Length; i++) { @@ -226,6 +226,20 @@ public static class ActivityHappinessHarness } } + string[] signalRequired = + { + "just_made_friend", "just_injured", "just_found_house", "got_robbed", + "fallen_in_love", "become_alpha", "just_had_child" + }; + for (int i = 0; i < signalRequired.Length; i++) + { + HappinessCatalogEntry e = EventCatalog.Happiness.GetOrFallback(signalRequired[i]); + if (e.Presentation != HappinessPresentationTier.Signal) + { + proseRegressions++; + } + } + bool passed = missingAuthored == 0 && missingIcon == 0 && localeLeaks == 0 diff --git a/IdleSpectator/ActivityStatusHarness.cs b/IdleSpectator/ActivityStatusHarness.cs index 1ab3577..13b7936 100644 --- a/IdleSpectator/ActivityStatusHarness.cs +++ b/IdleSpectator/ActivityStatusHarness.cs @@ -185,10 +185,12 @@ public static class ActivityStatusHarness } int cameraDemotionFailures = 0; + // Brief FX / cooldowns must stay B. Story-readable statuses must stay A. string[] bOnlyStatus = { - "surprised", "confused", "motivated", "inspired", "magnetized", - "starving", "ash_fever", "powerup", "voices_in_my_head", "angry" + "afterglow", "cough", "dash", "dodge", "flicked", "just_ate", "on_guard", + "recovery_combat_action", "recovery_plot", "recovery_social", "recovery_spell", + "shield", "slowness", "spell_boost", "spell_silence", "egg" }; for (int i = 0; i < bOnlyStatus.Length; i++) { @@ -202,7 +204,8 @@ public static class ActivityStatusHarness string[] aStatus = { "drowning", "burning", "frozen", "poisoned", "possessed", "cursed", - "pregnant", "crying", "rage", "stunned", "soul_harvested", "tantrum" + "pregnant", "crying", "rage", "stunned", "soul_harvested", "tantrum", + "fell_in_love", "starving", "angry", "sleeping", "inspired", "ash_fever" }; for (int i = 0; i < aStatus.Length; i++) { diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs index f276c62..2a36b95 100644 --- a/IdleSpectator/AgentHarness.cs +++ b/IdleSpectator/AgentHarness.cs @@ -2747,10 +2747,10 @@ public static class AgentHarness source = "boat"; break; case "building": - id = "building_damage"; - key = "building:building_damage:" + EventFeedUtil.SafeId(unit); - label = "Damages a building: " + EventFeedUtil.SafeName(unit); - strength = 70f; + id = "building_destroy"; + key = "building:destroy:harness:" + EventFeedUtil.SafeId(unit); + label = EventReason.BuildingFalls("harness_hall", unit); + strength = 88f; category = "Spectacle"; source = "building"; break; diff --git a/IdleSpectator/Events/Catalogs/EventCatalog.Book.cs b/IdleSpectator/Events/Catalogs/EventCatalog.Book.cs index d077d5a..1b15e43 100644 --- a/IdleSpectator/Events/Catalogs/EventCatalog.Book.cs +++ b/IdleSpectator/Events/Catalogs/EventCatalog.Book.cs @@ -7,63 +7,65 @@ namespace IdleSpectator; public static partial class EventCatalog { public static class Book -{ - private static readonly Dictionary Entries = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - static Book() { - Add("family_story", 52f, "Culture", "{a} writes a family story"); - Add("love_story", 58f, "Culture", "{a} writes a love story"); - Add("friendship_story", 50f, "Culture", "{a} writes a friendship story"); - Add("bad_story_about_king", 62f, "Culture", "{a} writes a scandalous book"); - Add("fable", 48f, "Culture", "{a} writes a fable"); - Add("warfare_manual", 60f, "Culture", "{a} writes a warfare manual"); - Add("economy_manual", 48f, "Culture", "{a} writes an economy manual"); - Add("stewardship_manual", 48f, "Culture", "{a} writes a stewardship manual"); - Add("diplomacy_manual", 55f, "Culture", "{a} writes a diplomacy manual"); - Add("mathbook", 45f, "Culture", "{a} writes a math book"); - Add("biology_book", 45f, "Culture", "{a} writes a biology book"); - Add("history_book", 55f, "Culture", "{a} writes a history book"); - } + private static readonly Dictionary Entries = + new Dictionary(StringComparer.OrdinalIgnoreCase); - private static void Add( - string id, - float strength, - string category, - string label) - { - Entries[id] = new DiscreteEventEntry + static Book() { - Id = id, - EventStrength = strength, - Category = category, - CreatesInterest = true, - LabelTemplate = label - }; - } - - public static IEnumerable AuthoredIds => Entries.Keys; - - public static bool HasAuthored(string id) => - !string.IsNullOrEmpty(id) && Entries.ContainsKey(id.Trim()); - - public static DiscreteEventEntry GetOrFallback(string id) - { - if (!string.IsNullOrEmpty(id) && Entries.TryGetValue(id.Trim(), out DiscreteEventEntry entry)) - { - return entry; + // All authored books are Layer A; EventStrength ranks them. + Add("family_story", 52f, "Culture", "{a} writes a family story"); + Add("love_story", 58f, "Culture", "{a} writes a love story"); + Add("friendship_story", 50f, "Culture", "{a} writes a friendship story"); + Add("bad_story_about_king", 62f, "Culture", "{a} writes a scandalous book"); + Add("fable", 48f, "Culture", "{a} writes a fable"); + Add("warfare_manual", 60f, "Culture", "{a} writes a warfare manual"); + Add("economy_manual", 48f, "Culture", "{a} writes an economy manual"); + Add("stewardship_manual", 48f, "Culture", "{a} writes a stewardship manual"); + Add("diplomacy_manual", 55f, "Culture", "{a} writes a diplomacy manual"); + Add("mathbook", 45f, "Culture", "{a} writes a math book"); + Add("biology_book", 45f, "Culture", "{a} writes a biology book"); + Add("history_book", 55f, "Culture", "{a} writes a history book"); } - string key = string.IsNullOrEmpty(id) ? "unknown" : id.Trim(); - return new DiscreteEventEntry + private static void Add( + string id, + float strength, + string category, + string label) { - Id = key, - EventStrength = 45f, - Category = "Culture", - LabelTemplate = "{a} writes a book", - IsFallback = true - }; + Entries[id] = new DiscreteEventEntry + { + Id = id, + EventStrength = strength, + Category = category, + CreatesInterest = true, + LabelTemplate = label + }; + } + + public static IEnumerable AuthoredIds => Entries.Keys; + + public static bool HasAuthored(string id) => + !string.IsNullOrEmpty(id) && Entries.ContainsKey(id.Trim()); + + public static DiscreteEventEntry GetOrFallback(string id) + { + if (!string.IsNullOrEmpty(id) && Entries.TryGetValue(id.Trim(), out DiscreteEventEntry entry)) + { + return entry; + } + + string key = string.IsNullOrEmpty(id) ? "unknown" : id.Trim(); + return new DiscreteEventEntry + { + Id = key, + EventStrength = 45f, + Category = "Culture", + LabelTemplate = "{a} writes a book", + CreatesInterest = true, + IsFallback = true + }; + } } } -} diff --git a/IdleSpectator/Events/Catalogs/EventCatalog.Happiness.cs b/IdleSpectator/Events/Catalogs/EventCatalog.Happiness.cs index 4c98347..c926608 100644 --- a/IdleSpectator/Events/Catalogs/EventCatalog.Happiness.cs +++ b/IdleSpectator/Events/Catalogs/EventCatalog.Happiness.cs @@ -67,7 +67,7 @@ public static partial class EventCatalog { Id = "got_robbed", EventStrength = 50f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.RelatedOptional, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -93,7 +93,7 @@ public static partial class EventCatalog { Id = "lost_fight", EventStrength = 55f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -147,7 +147,7 @@ public static partial class EventCatalog { Id = "just_received_gift", EventStrength = 50f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.RelatedOptional, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -160,7 +160,7 @@ public static partial class EventCatalog { Id = "just_gave_gift", EventStrength = 50f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.RelatedOptional, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -186,7 +186,7 @@ public static partial class EventCatalog { Id = "just_slept", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -199,7 +199,7 @@ public static partial class EventCatalog { Id = "had_bad_dream", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -213,7 +213,7 @@ public static partial class EventCatalog { Id = "had_good_dream", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -227,7 +227,7 @@ public static partial class EventCatalog { Id = "had_nightmare", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -241,7 +241,7 @@ public static partial class EventCatalog { Id = "slept_outside", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -429,7 +429,7 @@ public static partial class EventCatalog { Id = "just_read_book", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -442,7 +442,7 @@ public static partial class EventCatalog { Id = "just_played", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -455,7 +455,7 @@ public static partial class EventCatalog { Id = "just_talked", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.RelatedOptional, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -468,7 +468,7 @@ public static partial class EventCatalog { Id = "just_laughed", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -482,7 +482,7 @@ public static partial class EventCatalog { Id = "just_sang", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -496,7 +496,7 @@ public static partial class EventCatalog { Id = "just_swore", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -510,7 +510,7 @@ public static partial class EventCatalog { Id = "just_cried", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -524,7 +524,7 @@ public static partial class EventCatalog { Id = "just_talked_gossip", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.RelatedOptional, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -537,7 +537,7 @@ public static partial class EventCatalog { Id = "just_surprised", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -566,7 +566,7 @@ public static partial class EventCatalog // Game id uses British spelling; status asset is magnetized. Id = "just_magnetised", EventStrength = 45f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -580,7 +580,7 @@ public static partial class EventCatalog { Id = "just_forced_power", EventStrength = 45f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -607,7 +607,7 @@ public static partial class EventCatalog { Id = "strange_urge", EventStrength = 45f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -621,7 +621,7 @@ public static partial class EventCatalog { Id = "just_had_tantrum", EventStrength = 45f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -635,7 +635,7 @@ public static partial class EventCatalog { Id = "just_felt_the_divine", EventStrength = 45f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Independent, @@ -648,7 +648,7 @@ public static partial class EventCatalog { Id = "just_enchanted", EventStrength = 45f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -662,7 +662,7 @@ public static partial class EventCatalog { Id = "just_inspired", EventStrength = 45f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, @@ -729,7 +729,7 @@ public static partial class EventCatalog { Id = "just_found_house", EventStrength = 55f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ProjectToLife, Overlap = HappinessOverlapPolicy.Independent, @@ -742,7 +742,7 @@ public static partial class EventCatalog { Id = "just_lost_house", EventStrength = 65f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ProjectToLife, Overlap = HappinessOverlapPolicy.Independent, @@ -755,7 +755,7 @@ public static partial class EventCatalog { Id = "just_made_friend", EventStrength = 50f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.RelatedOptional, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.CanonicalMilestone, @@ -769,7 +769,7 @@ public static partial class EventCatalog { Id = "just_injured", EventStrength = 65f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ProjectToLife, Overlap = HappinessOverlapPolicy.Independent, @@ -796,7 +796,7 @@ public static partial class EventCatalog { Id = "starving", EventStrength = 30f, - Presentation = HappinessPresentationTier.Ambient, + Presentation = HappinessPresentationTier.Signal, Context = HappinessContextRequirement.None, Life = HappinessLifePolicy.ActivityOnly, Overlap = HappinessOverlapPolicy.Continuation, diff --git a/IdleSpectator/Events/Catalogs/EventCatalog.Libraries.cs b/IdleSpectator/Events/Catalogs/EventCatalog.Libraries.cs index c701d75..8242671 100644 --- a/IdleSpectator/Events/Catalogs/EventCatalog.Libraries.cs +++ b/IdleSpectator/Events/Catalogs/EventCatalog.Libraries.cs @@ -26,7 +26,7 @@ public static partial class EventCatalog ambientStrength: 48f, SignalIds, signalStrength: 88f, - ambientCreatesInterest: false); + ambientCreatesInterest: true); public static IEnumerable AuthoredIds { @@ -115,12 +115,13 @@ public static partial class EventCatalog } string s = id.ToLowerInvariant(); - // Routine AI ticks often include "king" in the name - those are not camera events. + // Pure AI ticks - not story beats. if (s.Contains("idle") || s.Contains("walking") || s.Contains("check") || s.Contains("wait") - || s.Contains("sleep")) + || s.Contains("sleep") + || s.Contains("random")) { return false; } @@ -136,7 +137,17 @@ public static partial class EventCatalog || s.Contains("siege") || s.Contains("found") || s.Contains("city_foundation") - || (s.Contains("king") && (s.Contains("war") || s.Contains("city") || s.Contains("rebellion"))); + || s.Contains("army") + || s.Contains("lover") + || s.Contains("plot") + || s.Contains("marry") + || s.Contains("baby") + || s.Contains("child") + || s.Contains("king") + || s.Contains("leader") + || s.Contains("clan") + || s.Contains("religion") + || s.Contains("culture"); } /// True when a decision id should be allowed to own the idle camera. @@ -148,7 +159,7 @@ public static partial class EventCatalog ActivityAssetCatalog.EnumerateLiveDecisionIds, "Politics", "{a} decides {id}", - ambientStrength: 32f, + ambientStrength: 40f, signalIds: null, signalStrength: 86f, signalPredicate: IsKingdomDecision, @@ -206,7 +217,7 @@ public static partial class EventCatalog signalIds: null, signalStrength: 80f, signalPredicate: IsLegendaryItem, - ambientCreatesInterest: false); + ambientCreatesInterest: true); public static IEnumerable AuthoredIds { @@ -263,7 +274,8 @@ public static partial class EventCatalog ambientStrength: 34f, signalIds: null, signalStrength: 78f, - signalPredicate: IsDramatic); + signalPredicate: IsDramatic, + ambientCreatesInterest: true); public static IEnumerable AuthoredIds { @@ -297,7 +309,7 @@ public static partial class EventCatalog signalIds: null, signalStrength: 70f, signalPredicate: id => id != null && (id.Contains("warfare") || id.Contains("magic") || id.Contains("mutation")), - ambientCreatesInterest: false); + ambientCreatesInterest: true); public static IEnumerable AuthoredIds { @@ -330,7 +342,7 @@ public static partial class EventCatalog ambientStrength: 28f, signalIds: null, signalStrength: 60f, - ambientCreatesInterest: false); + ambientCreatesInterest: true); public static IEnumerable AuthoredIds { @@ -364,7 +376,7 @@ public static partial class EventCatalog signalIds: null, signalStrength: 70f, signalPredicate: id => id != null && (id.Contains("war") || id.Contains("death") || id.Contains("plague")), - ambientCreatesInterest: false); + ambientCreatesInterest: true); public static IEnumerable AuthoredIds { diff --git a/IdleSpectator/Events/Catalogs/EventCatalog.Relationship.cs b/IdleSpectator/Events/Catalogs/EventCatalog.Relationship.cs index 8d2dbab..012fac1 100644 --- a/IdleSpectator/Events/Catalogs/EventCatalog.Relationship.cs +++ b/IdleSpectator/Events/Catalogs/EventCatalog.Relationship.cs @@ -18,10 +18,12 @@ public static partial class EventCatalog Add("add_child", 68f, "Relationship", "{a} gains a child, {b}"); Add("new_family", 70f, "Relationship", "{a} starts a new family"); Add("family_removed", 50f, "Relationship", "{a}'s family ends"); + // Seeking / pack churn are still candidates; low strength lets the director bury them. Add("find_lover", 45f, "Relationship", "{a} is seeking a lover"); + Add("become_alpha", 72f, "LifeChapter", "{a} becomes the family alpha"); Add("family_group_new", 60f, "Relationship", "{a} forms a family pack"); - Add("family_group_join", 36f, "Relationship", "{a} joins a family pack", createsInterest: false); - Add("family_group_leave", 36f, "Relationship", "{a} leaves a family pack", createsInterest: false); + Add("family_group_join", 36f, "Relationship", "{a} joins a family pack"); + Add("family_group_leave", 36f, "Relationship", "{a} leaves a family pack"); Add("baby_created", 70f, "Relationship", "{a} is created as a baby"); } diff --git a/IdleSpectator/Events/Catalogs/EventCatalog.Status.cs b/IdleSpectator/Events/Catalogs/EventCatalog.Status.cs index b4f782c..2ce2f86 100644 --- a/IdleSpectator/Events/Catalogs/EventCatalog.Status.cs +++ b/IdleSpectator/Events/Catalogs/EventCatalog.Status.cs @@ -15,6 +15,7 @@ public sealed class StatusInterestEntry /// /// Authored interest policy for every live status asset. Prose stays in ActivityStatusProse. +/// Default: story-readable statuses are Layer A. Only brief FX / cooldowns stay B-only. /// public static partial class EventCatalog { @@ -25,7 +26,7 @@ public static partial class EventCatalog static Status() { - // Spectacle / camera-worthy transformations + // Spectacles / crisis Add("burning", 70f, "StatusTransformation", true); Add("possessed", 72f, "StatusTransformation", true); Add("possessed_follower", 60f, "StatusTransformation", true); @@ -34,47 +35,52 @@ public static partial class EventCatalog Add("drowning", 50f, "StatusTransformation", true); Add("stunned", 50f, "StatusTransformation", true); Add("rage", 62f, "StatusTransformation", true); - Add("angry", 48f, "StatusAmbient", false); + Add("angry", 48f, "StatusAmbient", true); Add("cursed", 60f, "StatusTransformation", true); Add("soul_harvested", 75f, "StatusTransformation", true); - Add("voices_in_my_head", 55f, "StatusAmbient", false); + Add("voices_in_my_head", 55f, "StatusAmbient", true); Add("tantrum", 52f, "StatusTransformation", true); - Add("egg", 40f, "StatusTransformation", false); - Add("magnetized", 55f, "StatusAmbient", false); - Add("invincible", 40f, "StatusTransformation", false); - Add("powerup", 54f, "StatusAmbient", false); + Add("magnetized", 55f, "StatusAmbient", true); + Add("invincible", 50f, "StatusTransformation", true); + Add("powerup", 54f, "StatusAmbient", true); Add("enchanted", 52f, "StatusTransformation", true); - Add("ash_fever", 50f, "StatusAmbient", false); - Add("starving", 48f, "StatusAmbient", false); - Add("surprised", 42f, "StatusAmbient", false); - Add("confused", 40f, "StatusAmbient", false); - Add("strange_urge", 45f, "StatusAmbient", false); - Add("inspired", 42f, "Emotion", false); - Add("motivated", 40f, "Emotion", false); - Add("fell_in_love", 42f, "Relationship", false); + Add("ash_fever", 50f, "StatusAmbient", true); + Add("starving", 52f, "StatusAmbient", true); + Add("surprised", 42f, "StatusAmbient", true); + Add("confused", 40f, "StatusAmbient", true); + Add("strange_urge", 45f, "StatusAmbient", true); + Add("inspired", 42f, "Emotion", true); + Add("motivated", 40f, "Emotion", true); + Add("fell_in_love", 58f, "Relationship", true); Add("pregnant", 58f, "LifeChapter", true); Add("pregnant_parthenogenesis", 58f, "LifeChapter", true); Add("crying", 50f, "Grief", true, extendsGrief: true); - // Authored Ambient - never owns camera (task chip / enrichment only). - Add("festive_spirit", 28f, "StatusAmbient", false); - Add("laughing", 26f, "StatusAmbient", false); - Add("singing", 26f, "StatusAmbient", false); - Add("sleeping", 22f, "StatusAmbient", false); - Add("handsome_migrant", 30f, "StatusAmbient", false); + // Life / social readable beats (lower strength; still camera-eligible) + Add("festive_spirit", 36f, "StatusAmbient", true); + Add("laughing", 34f, "StatusAmbient", true); + Add("singing", 34f, "StatusAmbient", true); + Add("sleeping", 32f, "StatusAmbient", true); + Add("handsome_migrant", 44f, "StatusAmbient", true); + Add("being_suspicious", 40f, "StatusAmbient", true); + Add("budding", 48f, "LifeChapter", true); + Add("caffeinated", 36f, "StatusAmbient", true); + Add("had_bad_dream", 40f, "Emotion", true); + Add("had_good_dream", 38f, "Emotion", true); + Add("had_nightmare", 44f, "Emotion", true); + Add("swearing", 34f, "StatusAmbient", true); + Add("taking_roots", 46f, "LifeChapter", true); + Add("uprooting", 46f, "LifeChapter", true); - // Authored but do not create interest candidates (cooldowns / ambient / brief FX) + // Egg gain is becoming an egg (not a camera beat). Loss → EmitHatch. + Add("egg", 40f, "StatusTransformation", false); + + // Brief FX / combat cooldowns - ticker only (Layer B). AddNoInterest("afterglow"); - AddNoInterest("being_suspicious"); - AddNoInterest("budding"); - AddNoInterest("caffeinated"); AddNoInterest("cough"); AddNoInterest("dash"); AddNoInterest("dodge"); AddNoInterest("flicked"); - AddNoInterest("had_bad_dream"); - AddNoInterest("had_good_dream"); - AddNoInterest("had_nightmare"); AddNoInterest("just_ate"); AddNoInterest("on_guard"); AddNoInterest("recovery_combat_action"); @@ -85,9 +91,6 @@ public static partial class EventCatalog AddNoInterest("slowness"); AddNoInterest("spell_boost"); AddNoInterest("spell_silence"); - AddNoInterest("swearing"); - AddNoInterest("taking_roots"); - AddNoInterest("uprooting"); } private static void Add( diff --git a/IdleSpectator/Events/EventReason.cs b/IdleSpectator/Events/EventReason.cs index f9edb07..2857de3 100644 --- a/IdleSpectator/Events/EventReason.cs +++ b/IdleSpectator/Events/EventReason.cs @@ -50,6 +50,11 @@ public static class EventReason return NameOrSomeone(a) + " is seeking a lover"; } + public static string BecomeAlpha(Actor a) + { + return NameOrSomeone(a) + " becomes the family alpha"; + } + public static string Hatch(Actor a) { return NameOrSomeone(a) + " hatches from an egg"; diff --git a/IdleSpectator/Events/Feeds/InterestFeeds.cs b/IdleSpectator/Events/Feeds/InterestFeeds.cs index ab75c2c..248af5d 100644 --- a/IdleSpectator/Events/Feeds/InterestFeeds.cs +++ b/IdleSpectator/Events/Feeds/InterestFeeds.cs @@ -479,6 +479,67 @@ public static class InterestFeeds private static string HatchKey(long subjectId) => "hatch:" + subjectId; + private static string AlphaKey(long subjectId) => "alpha:" + subjectId; + + /// + /// Family alpha succession. Shared key with happiness become_alpha so emotion and + /// emotion-less paths merge into one camera beat. + /// + public static void EmitAlpha(Actor actor, string source) + { + if (actor == null || !actor.isAlive()) + { + return; + } + + if (AgentHarness.Busy) + { + return; + } + + long id = SafeId(actor); + if (id == 0) + { + return; + } + + DiscreteEventEntry entry = EventCatalog.Relationship.GetOrFallback("become_alpha"); + if (!EventCatalog.IsCameraWorthy(entry)) + { + InterestDropLog.Record("createsInterest=false", "become_alpha"); + return; + } + + float strength = Mathf.Max( + entry.EventStrength, + InterestScoring.EventStrengthForHappiness("become_alpha")); + var candidate = new InterestCandidate + { + Key = AlphaKey(id), + LeadKind = InterestLeadKind.EventLed, + Category = string.IsNullOrEmpty(entry.Category) ? "LifeChapter" : entry.Category, + Source = string.IsNullOrEmpty(source) ? "alpha" : source, + EventStrength = strength, + VisualConfidence = 0.8f, + Position = actor.current_position, + FollowUnit = actor, + SubjectId = id, + Label = EventReason.BecomeAlpha(actor), + AssetId = SafeAsset(actor), + SpeciesId = SafeAsset(actor), + HappinessEffectId = "become_alpha", + CreatedAt = Time.unscaledTime, + LastSeenAt = Time.unscaledTime, + ExpiresAt = Time.unscaledTime + 25f, + MinWatch = 5f, + MaxWatch = 22f, + Completion = InterestCompletionKind.FixedDwell + }; + candidate.ParticipantIds.Add(id); + InterestScoring.ScoreCheap(candidate); + EventFeedUtil.RegisterCandidate(candidate); + } + public static void OnChronicleMilestone(Actor subject, string milestoneKey, Actor related, string label) { if (subject == null || !subject.isAlive() || string.IsNullOrEmpty(milestoneKey)) @@ -614,12 +675,23 @@ public static class InterestFeeds bool grief = occ.EffectId != null && occ.EffectId.StartsWith("death_"); float strength = InterestScoring.EventStrengthForHappiness(occ.EffectId); bool hatchMoment = IsFreshLifeMoment(occ.EffectId); + bool alphaMoment = IsAlphaMoment(occ.EffectId); - // Hatch shares a key with status egg-loss so both paths merge into one camera beat. - string key = hatchMoment - ? HatchKey(occ.SubjectId) - : ("happiness:" + occ.EffectId + ":" + occ.SubjectId + ":" + occ.RelatedId - + ":" + CorrBucket(occ.CorrelationKey)); + // Hatch / alpha share keys with status-loss / Family.setAlpha so paths merge. + string key; + if (hatchMoment) + { + key = HatchKey(occ.SubjectId); + } + else if (alphaMoment) + { + key = AlphaKey(occ.SubjectId); + } + else + { + key = "happiness:" + occ.EffectId + ":" + occ.SubjectId + ":" + occ.RelatedId + + ":" + CorrBucket(occ.CorrelationKey); + } // Hatch: short queue TTL, but on-camera floor comes from minCameraDwell. float ttl = hatchMoment ? 7f : 35f; @@ -630,11 +702,23 @@ public static class InterestFeeds strength = Mathf.Max(strength, 88f); } + if (alphaMoment) + { + strength = Mathf.Max(strength, EventCatalog.Relationship.GetOrFallback("become_alpha").EventStrength); + ttl = 25f; + minWatch = 5f; + maxWatch = 22f; + } + string label; if (hatchMoment) { label = EventReason.Hatch(subject); } + else if (alphaMoment) + { + label = EventReason.BecomeAlpha(subject); + } else if (!string.IsNullOrEmpty(occ.PlainClause)) { label = occ.SubjectName + " " + occ.PlainClause; @@ -649,7 +733,9 @@ public static class InterestFeeds Key = key, LeadKind = InterestLeadKind.EventLed, Category = grief ? "FamilyEmotion" : MapHappinessCategory(cat), - Source = hatchMoment ? "happiness_hatch" : "happiness", + Source = hatchMoment + ? "happiness_hatch" + : (alphaMoment ? "happiness_alpha" : "happiness"), EventStrength = strength, VisualConfidence = 0.75f, Position = occ.Position != Vector3.zero ? occ.Position : subject.current_position, @@ -887,6 +973,12 @@ public static class InterestFeeds || string.Equals(id, "just_hatched", StringComparison.OrdinalIgnoreCase); } + private static bool IsAlphaMoment(string effectId) + { + return !string.IsNullOrEmpty(effectId) + && string.Equals(effectId.Trim(), "become_alpha", StringComparison.OrdinalIgnoreCase); + } + private static string MapHappinessCategory(string cat) { switch ((cat ?? "").ToLowerInvariant()) diff --git a/IdleSpectator/Events/Feeds/RelationshipInterestFeed.cs b/IdleSpectator/Events/Feeds/RelationshipInterestFeed.cs index 70adf77..45e68f4 100644 --- a/IdleSpectator/Events/Feeds/RelationshipInterestFeed.cs +++ b/IdleSpectator/Events/Feeds/RelationshipInterestFeed.cs @@ -107,6 +107,8 @@ public static class RelationshipInterestFeed return EventReason.LoverParted(subject); case "find_lover": return EventReason.SeekingLover(subject); + case "become_alpha": + return EventReason.BecomeAlpha(subject); case "add_child": return EventReason.NewChild(subject, related); case "baby_created": diff --git a/IdleSpectator/Events/Patches/BoatEventPatches.cs b/IdleSpectator/Events/Patches/BoatEventPatches.cs index 1b8e6b6..07a509f 100644 --- a/IdleSpectator/Events/Patches/BoatEventPatches.cs +++ b/IdleSpectator/Events/Patches/BoatEventPatches.cs @@ -35,12 +35,9 @@ public static class BoatEventPatches [HarmonyPostfix] public static void PostfixLoad(Actor pActor, BehResult __result) { - if (pActor == null || !pActor.isAlive() || __result == BehResult.Stop) - { - return; - } - - EmitBoat("boat_load", 55f, pActor, EventReason.Boat(pActor, "load")); + // Loading is routine transport churn - unload/trade stay on Layer A. + _ = pActor; + _ = __result; } private static void EmitBoat(string eventId, float strength, Actor actor, string label) diff --git a/IdleSpectator/Events/Patches/BuildingEventPatches.cs b/IdleSpectator/Events/Patches/BuildingEventPatches.cs index c785bab..78f0c7f 100644 --- a/IdleSpectator/Events/Patches/BuildingEventPatches.cs +++ b/IdleSpectator/Events/Patches/BuildingEventPatches.cs @@ -12,23 +12,9 @@ public static class BuildingEventPatches [HarmonyPostfix] public static void PostfixDamageBuilding(Actor pActor, BehResult __result) { - if (pActor == null || !pActor.isAlive() || __result == BehResult.Stop) - { - return; - } - - Building target = null; - try - { - target = pActor.beh_building_target; - } - catch - { - target = null; - } - - string label = EventReason.BuildingDamage(pActor, target); - Emit("building_damage", 70f, pActor, label); + // Routine siege chips are Layer B noise. Camera waits for consume/destroy. + _ = pActor; + _ = __result; } [HarmonyPatch(typeof(BehConsumeTargetBuilding), nameof(BehConsumeTargetBuilding.execute))] diff --git a/IdleSpectator/Events/Patches/MetaEventPatches.cs b/IdleSpectator/Events/Patches/MetaEventPatches.cs index cda1078..4d06e35 100644 --- a/IdleSpectator/Events/Patches/MetaEventPatches.cs +++ b/IdleSpectator/Events/Patches/MetaEventPatches.cs @@ -62,6 +62,29 @@ public static class MetaEventPatches EmitNew("new_city", "Politics", 74f, __result, "city"); } + [HarmonyPatch(typeof(KingdomManager), nameof(KingdomManager.makeNewCivKingdom))] + [HarmonyPostfix] + public static void PostfixNewCivKingdom(Kingdom __result, Actor pActor) + { + if (__result == null) + { + return; + } + + Actor anchor = pActor != null && pActor.isAlive() ? pActor : TryReadActor(__result); + if (anchor == null) + { + return; + } + + MetaInterestFeed.EmitMeta( + "new_kingdom", + "Politics", + 82f, + anchor, + EventReason.MetaNew(anchor, "kingdom")); + } + private static void EmitNew( string eventId, string category, diff --git a/IdleSpectator/Events/Patches/PlotEventPatches.cs b/IdleSpectator/Events/Patches/PlotEventPatches.cs index 933305f..5e02199 100644 --- a/IdleSpectator/Events/Patches/PlotEventPatches.cs +++ b/IdleSpectator/Events/Patches/PlotEventPatches.cs @@ -74,6 +74,32 @@ public static class PlotEventPatches PlotInterestFeed.Emit(__state, __instance, "leave"); } + [HarmonyPatch(typeof(Plot), nameof(Plot.finishPlot))] + [HarmonyPostfix] + public static void PostfixFinishPlot(Plot __instance, PlotState pState, Actor pActor) + { + if (__instance == null || pState != PlotState.Finished) + { + return; + } + + // Prefer finishing actor; fall back to plot author inside EmitFromPlot. + if (pActor != null && pActor.isAlive()) + { + string assetId = ReadPlotAssetId(__instance); + if (string.IsNullOrEmpty(assetId)) + { + PlotInterestFeed.EmitFromPlot(__instance, "complete"); + return; + } + + PlotInterestFeed.Emit(assetId, pActor, "complete"); + return; + } + + PlotInterestFeed.EmitFromPlot(__instance, "complete"); + } + private static string ReadPlotAssetId(object plot) { try diff --git a/IdleSpectator/Events/Patches/RelationshipEventPatches.cs b/IdleSpectator/Events/Patches/RelationshipEventPatches.cs index 1c091c9..5670a69 100644 --- a/IdleSpectator/Events/Patches/RelationshipEventPatches.cs +++ b/IdleSpectator/Events/Patches/RelationshipEventPatches.cs @@ -183,4 +183,20 @@ public static class RelationshipEventPatches RelationshipInterestFeed.Emit("family_group_leave", pActor, ActorRelation.ResolvePackRelated(pActor)); } + + /// + /// Alpha succession is happiness-gated by emotions in vanilla. Patch the family API so + /// animals without emotions still get a Layer A / Activity-capable beat. + /// + [HarmonyPatch(typeof(Family), "setAlpha")] + [HarmonyPostfix] + public static void PostfixSetAlpha(Actor pActor, bool pNew) + { + if (!pNew || pActor == null || !pActor.isAlive()) + { + return; + } + + InterestFeeds.EmitAlpha(pActor, "family_set_alpha"); + } } diff --git a/IdleSpectator/Events/Patches/TraitEventPatches.cs b/IdleSpectator/Events/Patches/TraitEventPatches.cs index 3d565bd..0d752b4 100644 --- a/IdleSpectator/Events/Patches/TraitEventPatches.cs +++ b/IdleSpectator/Events/Patches/TraitEventPatches.cs @@ -119,12 +119,7 @@ public static class TraitEventPatches return; } - // Quiet-world Ambient traits must not steal the camera. - if (entry.EventStrength < InterestScoringConfig.W.noticeScoreMin) - { - return; - } - + // Quiet traits still register; low EventStrength lets the director bury them. // Spawn/birth endowment: only legendary rarities. if (gained && IsSpawnTraitWindow(actor) && !LegendaryBirthTraits.Contains(entry.Id)) { diff --git a/IdleSpectator/HarnessScenarios.cs b/IdleSpectator/HarnessScenarios.cs index b9ed5bc..b845966 100644 --- a/IdleSpectator/HarnessScenarios.cs +++ b/IdleSpectator/HarnessScenarios.cs @@ -1197,12 +1197,20 @@ internal static class HarnessScenarios Step("et20", "domain_feed", asset: "relationship", value: "set_lover"), Step("et21", "assert", expect: "interest_has_key", value: "rel:set_lover"), + Step("et21a", "domain_feed", asset: "relationship", value: "become_alpha"), + Step("et21b", "assert", expect: "interest_has_key", value: "rel:become_alpha"), + Step("et21c", "interest_force_session", asset: "human", + label: "{a} becomes the family alpha", tier: "Action", expect: "alpha_reason", + value: "lead=event;evt=72"), + Step("et21d", "assert", expect: "dossier_contains", value: "family alpha"), Step("et22", "domain_feed", asset: "plot", value: "summon_meteor_rain"), Step("et23", "assert", expect: "interest_has_key", value: "plot:summon_meteor_rain"), Step("et24", "domain_feed", asset: "era", value: "age_chaos"), Step("et25", "assert", expect: "interest_has_key", value: "era:age_chaos"), Step("et26", "domain_feed", asset: "meta", value: "new_city"), Step("et27", "assert", expect: "interest_has_key", value: "meta:new_city"), + Step("et27b", "domain_feed", asset: "meta", value: "new_kingdom"), + Step("et27c", "assert", expect: "interest_has_key", value: "meta:new_kingdom"), Step("et28", "domain_feed", asset: "book", value: "bad_story_about_king"), Step("et29", "assert", expect: "interest_has_key", value: "book:bad_story_about_king"), Step("et30", "domain_feed", asset: "trait", value: "immortal"), diff --git a/IdleSpectator/InterestScoring.cs b/IdleSpectator/InterestScoring.cs index b119ea7..aaa4737 100644 --- a/IdleSpectator/InterestScoring.cs +++ b/IdleSpectator/InterestScoring.cs @@ -199,11 +199,46 @@ public static class InterestScoring sig += Mathf.Min(w.metaRenownCap, snap.Renown / renownDiv); sig += Mathf.Min(w.metaKillsCap, snap.Kills * w.metaKillsFactor); sig += Mathf.Min(w.metaLevelCap, snap.Level * w.metaLevelFactor); + sig += SpeciesRarityBonus(snap.SpeciesId, w); snap.CharacterSignificance = sig; MetaCache[id] = snap; return snap; } + private static float SpeciesRarityBonus(string speciesId, ScoringWeights w) + { + if (string.IsNullOrEmpty(speciesId) || w == null) + { + return 0f; + } + + try + { + Dictionary counts = WorldActivityScanner.CountSpeciesPopulations(); + if (counts == null || !counts.TryGetValue(speciesId, out int pop) || pop <= 0) + { + return 0f; + } + + if (pop == 1) + { + return w.speciesSingletonBonus; + } + + int rareMax = w.speciesRareMaxPop > 0 ? w.speciesRareMaxPop : 5; + if (pop <= rareMax) + { + return w.speciesRareBonus; + } + } + catch + { + // ignore + } + + return 0f; + } + private static void ApplyMeta(InterestCandidate c, InterestMetadataSnapshot snap) { if (c == null || snap == null) diff --git a/IdleSpectator/InterestScoringConfig.cs b/IdleSpectator/InterestScoringConfig.cs index f252e39..1b642c9 100644 --- a/IdleSpectator/InterestScoringConfig.cs +++ b/IdleSpectator/InterestScoringConfig.cs @@ -79,6 +79,11 @@ public class ScoringWeights public float metaKillsCap = 12f; public float metaLevelFactor = 0.4f; public float metaLevelCap = 10f; + /// Bonus when the follow unit is the only living member of its species. + public float speciesSingletonBonus = 14f; + /// Bonus when species population is between 2 and speciesRareMaxPop inclusive. + public float speciesRareBonus = 8f; + public int speciesRareMaxPop = 5; public int notableKills = 40; public float notableRenown = 200f; diff --git a/IdleSpectator/MutationDiscoveryHarness.cs b/IdleSpectator/MutationDiscoveryHarness.cs index ba80a44..e9c6f43 100644 --- a/IdleSpectator/MutationDiscoveryHarness.cs +++ b/IdleSpectator/MutationDiscoveryHarness.cs @@ -106,6 +106,9 @@ public static class MutationDiscoveryHarness "ArmyManager.newArmy", "AllianceManager.newAlliance", "CityManager.newCity", + "KingdomManager.makeNewCivKingdom", + "Family.setAlpha", + "Plot.finishPlot", "ItemManager.generateItem", "ItemManager.newItem", "SubspeciesManager.newSpecies", diff --git a/IdleSpectator/mod.json b/IdleSpectator/mod.json index 48cfc90..0d2d362 100644 --- a/IdleSpectator/mod.json +++ b/IdleSpectator/mod.json @@ -1,7 +1,7 @@ { "name": "IdleSpectator", "author": "dazed", - "version": "0.22.0", - "description": "AFK Idle Spectator (I) + Lore (L). Layer B ticker + Layer A story camera.", + "version": "0.24.0", + "description": "AFK Idle Spectator (I) + Lore (L). Interesting beats are candidates; director ranks.", "GUID": "com.dazed.idlespectator" } diff --git a/IdleSpectator/scoring-model.json b/IdleSpectator/scoring-model.json index 0aa2c6e..bef2ca4 100644 --- a/IdleSpectator/scoring-model.json +++ b/IdleSpectator/scoring-model.json @@ -59,6 +59,9 @@ "metaKillsCap": 12, "metaLevelFactor": 0.4, "metaLevelCap": 10, + "speciesSingletonBonus": 14, + "speciesRareBonus": 8, + "speciesRareMaxPop": 5, "notableKills": 40, "notableRenown": 200, diff --git a/docs/event-audit.md b/docs/event-audit.md index f48df8b..776e1ad 100644 --- a/docs/event-audit.md +++ b/docs/event-audit.md @@ -39,15 +39,25 @@ Ambient / Character fill is exempt - A events may take the camera immediately. | Happiness | `IngestHappiness` | catalog + EventReason | Signal = A; see [event-prose-audit.md](event-prose-audit.md) | | Status | `OnStatusChange` | EventReason.Status | Gain A if CreatesInterest; egg **loss** → hatch A | | Scanner combat | WorldActivityScanner | EventReason.Fight / Battle | `EventCatalog.Combat` | -| War / Plot / Relationship / Trait / Building / Boat / Book / Era | Feeds + patches | EventReason / catalog | A via CreatesInterest | +| War / Plot / Relationship / Trait / Building / Boat / Book / Era / Kingdom | Feeds + patches | EventReason / catalog | A via CreatesInterest; alpha via Family.setAlpha | | Libraries | Deferred feeds | EventReason.Library | Mostly B; spectacle ids A | | Character fill | InterestDirector.TryCharacterFill | empty Label | Only when no pending EventLed | -## Layer A demotion (this pass) +## Layer A policy -Happiness Signal → Ambient (B ticker only): gifts, cry, inspired, enchanted, divine, magnetised, strange_urge, tantrum, forced_power, lost_fight, got_robbed, house find/lose, made friend, injured. +Interesting beats register as interest candidates (`Signal` / `CreatesInterest`). +Director ranks by `EventStrength` + scoring - do not hard-gate interesting life off Layer A. -Status CreatesInterest → false (Activity remains): surprised, confused, motivated, inspired, magnetized, starving, ash_fever, powerup, voices_in_my_head, angry. +Happiness Ambient (B) only: `got_poked`, `got_caught`, `paid_tax`, `just_ate`, `just_pooped`. +Happiness Aggregates stay civic mass (B unit camera). + +Status B: brief FX/cooldowns + egg gain only. + +Noise still skipped at patch (not dial): building_damage chips, boat_load. + +## Full surface + +See [world-event-inventory.md](world-event-inventory.md) for live library counts, gaps, and refresh commands. ## Interrupt / hold / grace diff --git a/docs/event-prose-audit.md b/docs/event-prose-audit.md index f8fd858..30394ea 100644 --- a/docs/event-prose-audit.md +++ b/docs/event-prose-audit.md @@ -39,11 +39,12 @@ Scope: Happiness (all authored), Relationship (all), Status/WorldLog/Plot/etc. s Grief deaths, `fallen_in_love`, gifts, house find/lose, `just_got_out_of_egg`, `just_became_adult`, WorldLog `king_new` template roles, Status `egg` / `pregnant` / `drowning` prose. -## A/B camera demotion (2026-07-16) +## A/B camera policy (2026-07-16) -Happiness Signal → Ambient (ticker only): gifts, cry, inspired, enchanted, divine, magnetised, strange_urge, tantrum happiness, forced_power, lost_fight, got_robbed, house find/lose, made friend, injured. +Interesting = interest candidate. Director ranks by strength. -Status CreatesInterest → false (Activity remains): surprised, confused, motivated, inspired, magnetized, starving, ash_fever, powerup, voices_in_my_head, angry. +Happiness Ambient only for true FX: poke, caught stub, tax, ate, pooped. +Status CreatesInterest false only for brief FX/cooldowns + egg gain. Gate helper: `EventCatalog.IsCameraWorthy`. Drops: `InterestDropLog`. diff --git a/docs/world-event-inventory.md b/docs/world-event-inventory.md new file mode 100644 index 0000000..98ba16c --- /dev/null +++ b/docs/world-event-inventory.md @@ -0,0 +1,91 @@ +# World event inventory (full surface) + +Source of truth for what IdleSpectator covers across the **whole** game. +Refresh via live libraries + `mutation_discovery` / domain audits under `.harness/`. + +Audit date: 2026-07-16. + +## Policy + +**If it is interesting, it is an interest candidate.** +`CreatesInterest` / Happiness `Signal` registers Layer A. +`EventStrength` + scoring decide who gets the spotlight. +Only true brief FX, cooldowns, civic Aggregates, and god-tool / biome spam stay B-only. + +## How to read this + +| Layer | Meaning | +|-------|---------| +| **B** | Activity / Life / chronicle prose may fire; no interest candidate | +| **A** | Registers interest; director ranks by points | +| **Gap** | Game fires a lifecycle beat; we have no reliable A path | + +## Live libraries (enumerated) + +| Library | ~Count | Owner | +|---------|-------:|-------| +| happiness_library | 67 | EventCatalog.Happiness | +| status | 58 | EventCatalog.Status | +| world_log_library | 41 | EventCatalog.WorldLog | +| disasters | 17 | via WorldLog | +| war_types_library | 5 | EventCatalog.WarType | +| traits | 116 | EventCatalog.Trait (all CI; strength ranks) | +| plots_library | 28 | EventCatalog.Plot | +| era_library | 11 | EventCatalog.Era | +| book_types | 12 | EventCatalog.Book (all A) | +| decisions / powers / spells / items | 127 / 339 / 12 / 111 | EventCatalog.Libraries | +| subspecies_traits / genes / phenotypes | 204 / 47 / 52 | Libraries | +| buildings | 618 | patches (consume/destroy A; damage skipped) | +| actor_library | 322 | SpeciesDiscovery / scoring | +| culture/religion/clan/language traits | 78 / 40 / 29 / 26 | **no event catalog yet** | + +## Layer A sizes (policy) + +| Domain | Authored | Layer A | +|--------|--------:|--------:| +| Happiness | 67 | ~49 Signal (5 Ambient FX; 13 Aggregate civic) | +| Status | 58 | ~42 CreatesInterest (FX/cooldowns + egg gain stay B) | +| WorldLog | 41 | ~40 | +| Relationship | 11 | all interesting discrete (seeker/pack at low strength) | +| Plot / Era / War / Book | 28 / 11 / 5 / 12 | all A | +| Trait | 116 | all CI (no noticeScoreMin hard drop) | +| Spell / Item / Gene / Phenotype / WorldLaw / SubspeciesTrait | live | ambient A at low strength; Signal bumps spectacle | +| Power / Decision / Biome | live | Signal predicate only (tools / idle AI / terrain) | +| Building / Boat | none | destroy/consume/trade/unload; damage/load skipped | + +## Wired hooks (major) + +Happiness, status (+ egg-loss hatch), WorldLog, wars, plots (new/cancel/join/leave/finish), +meta genesis (culture/religion/language/clan/army/alliance/city/kingdom), +books, traits, deferred libraries, boats (trade/unload), buildings (consume/destroy), +relationship (lover/child/family/baby/alpha), Family.setAlpha, SpeciesDiscovery. + +## Known gaps / thin areas + +| Gap | Why it matters | Status | +|-----|----------------|--------| +| Culture/religion/clan/language trait gain | Meta drama libraries exist | **Open** | +| Building complete / wonder finish | Settlement spectacle | **Open** | +| Alliance/kingdom destroy managers | End beats if WorldLog thin | **Open** | +| Era/earthquake poll (ongoing) | Age change is wired; poll not | **Open** | + +## Stay B (true noise) + +- Happiness: `got_poked`, `got_caught`, `paid_tax`, `just_ate`, `just_pooped` + all Aggregates +- Status: dash/dodge/shield/recovery_*/spell_*/cough/flicked/just_ate/on_guard/afterglow/slowness + egg gain +- Libraries: Power ambient (god paint tools), Decision ambient (idle AI; interesting decisions are Signal), Biome ambient +- Patches: `building_damage`, `boat_load` + +## Scoring notes + +`TotalScore = EventStrength + scale + CharacterSignificance * charWeight + visual + novelty`. + +CharacterSignificance includes favorite/king/leader/renown/kills/level and species rarity. + +## Refresh commands + +```bash +./scripts/harness-run.sh --no-launch mutation_discovery +./scripts/harness-run.sh --no-launch domain_event_audit +./scripts/harness-run.sh --no-launch activity_happiness_audit +```