From c253b5dd9cf97d965560666c123162a1c7a8ca79 Mon Sep 17 00:00:00 2001 From: DazedAnon Date: Thu, 16 Jul 2026 00:06:32 -0500 Subject: [PATCH] Refactor IdleSpectator event handling to improve interest management and camera ownership logic. Update various event catalogs to remove ownership attributes and enhance label templates for clarity. Introduce new scoring logic in InterestDirector to better manage session protection and cut-in conditions. Revise AgentHarness and related components to streamline event registration and improve overall event clarity. Increment version in mod.json to reflect these changes. --- .cursor/skills/idle-spectator-e2e/SKILL.md | 5 +- IdleSpectator/AgentHarness.cs | 47 ++- IdleSpectator/BoatEventPatches.cs | 1 - IdleSpectator/BookInterestCatalog.cs | 27 +- IdleSpectator/BookInterestFeed.cs | 1 - IdleSpectator/BuildingEventPatches.cs | 3 - IdleSpectator/CatalogCoverageAudit.cs | 4 +- IdleSpectator/DeferredEventPatches.cs | 350 ++++++++++++++++++++- IdleSpectator/DeferredInterestFeed.cs | 4 - IdleSpectator/DeferredLibraryCatalogs.cs | 57 ++-- IdleSpectator/EraInterestCatalog.cs | 25 +- IdleSpectator/EventFeedUtil.cs | 15 +- IdleSpectator/HarnessScenarios.cs | 174 +++++----- IdleSpectator/InterestDirector.cs | 177 ++++++++--- IdleSpectator/InterestFeeds.cs | 12 +- IdleSpectator/InterestScoringConfig.cs | 7 + IdleSpectator/LiveLibraryInterest.cs | 7 +- IdleSpectator/MetaEventPatches.cs | 2 +- IdleSpectator/MetaInterestFeed.cs | 4 +- IdleSpectator/MutationDiscoveryHarness.cs | 341 ++++++++++++-------- IdleSpectator/PlotInterestCatalog.cs | 59 ++-- IdleSpectator/PlotInterestFeed.cs | 1 - IdleSpectator/RelationshipEventCatalog.cs | 3 - IdleSpectator/RelationshipInterestFeed.cs | 3 - IdleSpectator/StatusInterestCatalog.cs | 29 +- IdleSpectator/TraitEventPatches.cs | 1 - IdleSpectator/TraitInterestCatalog.cs | 236 +++++++------- IdleSpectator/WarInterestFeed.cs | 2 - IdleSpectator/WorldLogEventCatalog.cs | 18 +- IdleSpectator/mod.json | 2 +- IdleSpectator/scoring-model.json | 6 + docs/event-audit.md | 73 +++++ docs/event-reason.md | 12 +- 33 files changed, 1152 insertions(+), 556 deletions(-) create mode 100644 docs/event-audit.md diff --git a/.cursor/skills/idle-spectator-e2e/SKILL.md b/.cursor/skills/idle-spectator-e2e/SKILL.md index 8d2cc72..6fea818 100644 --- a/.cursor/skills/idle-spectator-e2e/SKILL.md +++ b/.cursor/skills/idle-spectator-e2e/SKILL.md @@ -131,7 +131,10 @@ A loaded world is still required. | `ghost_guard` | Fake new-species tip must not steal focus onto wrong unit | | `retarget_stability` | Many watches without focus/power-bar flicker | | `input_exit` | Post-enable grace + camera-drag exit | -| `director_tiers` | Curiosity accept, Action interrupt, curiosity blocked by Action | +| `director_tiers` | Score-margin cut-in (Action over Curiosity); peer rotate still behind MinDwell | +| `director_lifecycle` | Hold / resume / MaxWatch / quiet grace → ambient | +| `director_gaps` | Forced session interrupt + resume after higher-score cut | +| `focus_interrupt` | Instant score-margin cut (no settle); hold; still-worth-watching; 6s grace | | `discovery` | Present dedupe + force drain + tip match | | `world_log` | Story/Epic interest interrupt via collector | | `chronicle_smoke` | History inject, death-cause samples, World feed isolation, History\|World tabs, HUD jump | diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs index 374cc99..fedc2b3 100644 --- a/IdleSpectator/AgentHarness.cs +++ b/IdleSpectator/AgentHarness.cs @@ -2432,33 +2432,40 @@ public static class AgentHarness AssetId = follow.asset != null ? follow.asset.id : assetId }; - bool forceCamera = tierLead == InterestLeadKind.EventLed - && tierEvt >= InterestScoringConfig.W.noticeScoreMin; + bool eventLedNotice = tierLead == InterestLeadKind.EventLed + && tierEvt >= InterestScoringConfig.W.noticeScoreMin; InterestCandidate registered = InterestFeeds.RegisterHarness( follow, interest.Label, keySuffix: (interest.Label ?? "scene").Replace(" ", "_"), lead: tierLead, completion: InterestCompletionKind.FixedDwell, - forceActive: forceCamera, + forceActive: false, eventStrength: tierEvt, maxWatch: tierEvt >= 95f ? 40f : 25f); if (registered == null) { InterestCollector.EnqueueDirect(interest); } - else if (forceCamera) + else if (eventLedNotice) { - // Event-led Action/Epic tips may take the camera; character-led Story/curiosity stay pending. - InterestDirector.HarnessForceSession(registered); - CameraDirector.Watch(registered.ToInterestEvent()); + // Only force the camera when score-margin cut-in would allow it (matches director policy). + InterestScoring.ScoreCheap(registered); + float cur = InterestDirector.CurrentScore; + bool noCurrent = InterestDirector.CurrentCandidate == null; + bool marginCut = registered.TotalScore >= cur + InterestScoringConfig.W.cutInMargin; + if (noCurrent || marginCut) + { + InterestDirector.HarnessForceSession(registered); + CameraDirector.Watch(registered.ToInterestEvent()); + } } _cmdOk++; Emit( cmd, ok: true, - detail: $"triggered label={tipLabel} evt={tierEvt:0.#} forced={forceCamera}"); + detail: $"triggered label={tipLabel} evt={tierEvt:0.#} forced={InterestDirector.CurrentKey != null && InterestDirector.CurrentKey.IndexOf((interest.Label ?? "").Replace(" ", "_"), System.StringComparison.OrdinalIgnoreCase) >= 0}"); } /// @@ -2561,7 +2568,6 @@ public static class AgentHarness DiscreteEventEntry entry; string key; string label; - InterestOwnership owns; float strength; string category; string source; @@ -2588,7 +2594,6 @@ public static class AgentHarness label = entry.MakeLabel( EventFeedUtil.SafeName(unit), EventFeedUtil.SafeName(related)); - owns = entry.OwnsCamera; strength = entry.EventStrength; category = entry.Category; source = "relationship"; @@ -2599,7 +2604,6 @@ public static class AgentHarness label, id, strength, - owns, unit.current_position, unit, related: related); @@ -2622,7 +2626,6 @@ public static class AgentHarness entry = PlotInterestCatalog.GetOrFallback(id); key = "plot:" + entry.Id + ":new:" + EventFeedUtil.SafeId(unit); label = entry.MakeLabel(EventFeedUtil.SafeName(unit), "new"); - owns = entry.OwnsCamera; strength = entry.EventStrength; category = entry.Category; source = "plot"; @@ -2636,7 +2639,6 @@ public static class AgentHarness entry = EraInterestCatalog.GetOrFallback(id); key = "era:" + entry.Id; label = entry.MakeLabel("", ""); - owns = entry.OwnsCamera; strength = entry.EventStrength; category = entry.Category; source = "era"; @@ -2652,7 +2654,6 @@ public static class AgentHarness label, id, strength, - owns, eraPos, follow: unit, locationOnly: unit == null); @@ -2675,7 +2676,6 @@ public static class AgentHarness entry = BookInterestCatalog.GetOrFallback(id); key = "book:" + entry.Id + ":new:" + EventFeedUtil.SafeId(unit); label = entry.MakeLabel(EventFeedUtil.SafeName(unit), "new"); - owns = entry.OwnsCamera; strength = entry.EventStrength; category = entry.Category; source = "book"; @@ -2689,7 +2689,6 @@ public static class AgentHarness entry = TraitInterestCatalog.GetOrFallback(id); key = "trait:" + entry.Id + ":gains:" + EventFeedUtil.SafeId(unit); label = EventFeedUtil.SafeName(unit) + " gains " + entry.Id; - owns = entry.OwnsCamera; strength = entry.EventStrength; category = entry.Category; source = "trait"; @@ -2702,7 +2701,6 @@ public static class AgentHarness key = "meta:" + id + ":" + EventFeedUtil.SafeId(unit); label = "Meta: " + id + " (" + EventFeedUtil.SafeName(unit) + ")"; - owns = InterestOwnership.Signal; strength = 74f; category = "Politics"; source = "meta"; @@ -2711,7 +2709,6 @@ public static class AgentHarness id = "boat_unload"; key = "boat:boat_unload:" + EventFeedUtil.SafeId(unit); label = "Boat unloads: " + EventFeedUtil.SafeName(unit); - owns = InterestOwnership.Signal; strength = 72f; category = "Travel"; source = "boat"; @@ -2720,7 +2717,6 @@ public static class AgentHarness id = "building_damage"; key = "building:building_damage:" + EventFeedUtil.SafeId(unit); label = "Damages a building: " + EventFeedUtil.SafeName(unit); - owns = InterestOwnership.Signal; strength = 70f; category = "Spectacle"; source = "building"; @@ -2735,7 +2731,6 @@ public static class AgentHarness DiscreteEventEntry spellEntry = SpellInterestCatalog.GetOrFallback(id); key = "spell:" + spellEntry.Id + ":" + EventFeedUtil.SafeId(unit); label = spellEntry.MakeLabel(EventFeedUtil.SafeName(unit), ""); - owns = spellEntry.OwnsCamera; strength = spellEntry.EventStrength; category = spellEntry.Category; source = "spell"; @@ -2753,7 +2748,6 @@ public static class AgentHarness DiscreteEventEntry itemEntry = ItemInterestCatalog.GetOrFallback(id); key = "item:" + itemEntry.Id + ":" + EventFeedUtil.SafeId(unit); label = itemEntry.MakeLabel(EventFeedUtil.SafeName(unit), ""); - owns = itemEntry.OwnsCamera; strength = itemEntry.EventStrength; category = itemEntry.Category; source = "item"; @@ -2771,7 +2765,6 @@ public static class AgentHarness DiscreteEventEntry decEntry = DecisionInterestCatalog.GetOrFallback(id); key = "decision:" + decEntry.Id + ":" + EventFeedUtil.SafeId(unit); label = decEntry.MakeLabel(EventFeedUtil.SafeName(unit), ""); - owns = decEntry.OwnsCamera; strength = decEntry.EventStrength; category = decEntry.Category; source = "decision"; @@ -2789,7 +2782,6 @@ public static class AgentHarness DiscreteEventEntry powEntry = PowerInterestCatalog.GetOrFallback(id); key = "power:" + powEntry.Id + ":" + EventFeedUtil.SafeId(unit); label = powEntry.MakeLabel(EventFeedUtil.SafeName(unit), ""); - owns = powEntry.OwnsCamera; strength = powEntry.EventStrength; category = powEntry.Category; source = "power"; @@ -2808,7 +2800,6 @@ public static class AgentHarness DiscreteEventEntry stEntry = SubspeciesTraitInterestCatalog.GetOrFallback(id); key = "subspecies_trait:" + stEntry.Id + ":" + EventFeedUtil.SafeId(unit); label = stEntry.MakeLabel(EventFeedUtil.SafeName(unit), ""); - owns = stEntry.OwnsCamera; strength = stEntry.EventStrength; category = stEntry.Category; source = "subspecies_trait"; @@ -2826,7 +2817,6 @@ public static class AgentHarness DiscreteEventEntry geneEntry = GeneInterestCatalog.GetOrFallback(id); key = "gene:" + geneEntry.Id + ":" + EventFeedUtil.SafeId(unit); label = geneEntry.MakeLabel(EventFeedUtil.SafeName(unit), ""); - owns = geneEntry.OwnsCamera; strength = geneEntry.EventStrength; category = geneEntry.Category; source = "gene"; @@ -2847,7 +2837,6 @@ public static class AgentHarness label, id, strength, - owns, unit.current_position, unit); if (registered == null) @@ -6206,11 +6195,13 @@ public static class AgentHarness case "curiosity": return 40f; case "action": - return 70f; + // Clears cutInMargin (35) over enriched Curiosity CharacterLed (~64). + return 100f; case "story": return 80f; case "epic": - return 100f; + // Clears cutInMargin over Action (~108 TotalScore). + return 150f; default: return fallback; } diff --git a/IdleSpectator/BoatEventPatches.cs b/IdleSpectator/BoatEventPatches.cs index 3c16a6d..1b8e6b6 100644 --- a/IdleSpectator/BoatEventPatches.cs +++ b/IdleSpectator/BoatEventPatches.cs @@ -58,7 +58,6 @@ public static class BoatEventPatches label, eventId, strength, - InterestOwnership.Signal, actor.current_position, actor); } diff --git a/IdleSpectator/BookInterestCatalog.cs b/IdleSpectator/BookInterestCatalog.cs index 589cf93..e020b25 100644 --- a/IdleSpectator/BookInterestCatalog.cs +++ b/IdleSpectator/BookInterestCatalog.cs @@ -11,25 +11,24 @@ public static class BookInterestCatalog static BookInterestCatalog() { - Add("family_story", 52f, "Culture", InterestOwnership.Ambient, "{a} writes a family story"); - Add("love_story", 58f, "Culture", InterestOwnership.Ambient, "{a} writes a love story"); - Add("friendship_story", 50f, "Culture", InterestOwnership.Ambient, "{a} writes a friendship story"); - Add("bad_story_about_king", 62f, "Culture", InterestOwnership.Signal, "{a} writes a scandalous book"); - Add("fable", 48f, "Culture", InterestOwnership.Ambient, "{a} writes a fable"); - Add("warfare_manual", 60f, "Culture", InterestOwnership.Ambient, "{a} writes a warfare manual"); - Add("economy_manual", 48f, "Culture", InterestOwnership.Ambient, "{a} writes an economy manual"); - Add("stewardship_manual", 48f, "Culture", InterestOwnership.Ambient, "{a} writes a stewardship manual"); - Add("diplomacy_manual", 55f, "Culture", InterestOwnership.Ambient, "{a} writes a diplomacy manual"); - Add("mathbook", 45f, "Culture", InterestOwnership.Ambient, "{a} writes a math book"); - Add("biology_book", 45f, "Culture", InterestOwnership.Ambient, "{a} writes a biology book"); - Add("history_book", 55f, "Culture", InterestOwnership.Ambient, "{a} writes a history 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 void Add( string id, float strength, string category, - InterestOwnership owns, string label) { Entries[id] = new DiscreteEventEntry @@ -37,7 +36,6 @@ public static class BookInterestCatalog Id = id, EventStrength = strength, Category = category, - OwnsCamera = owns, CreatesInterest = true, LabelTemplate = label }; @@ -61,7 +59,6 @@ public static class BookInterestCatalog Id = key, EventStrength = 45f, Category = "Culture", - OwnsCamera = InterestOwnership.Ambient, LabelTemplate = "{a} writes a book", IsFallback = true }; diff --git a/IdleSpectator/BookInterestFeed.cs b/IdleSpectator/BookInterestFeed.cs index dc262af..3a38107 100644 --- a/IdleSpectator/BookInterestFeed.cs +++ b/IdleSpectator/BookInterestFeed.cs @@ -40,7 +40,6 @@ public static class BookInterestFeed label, entry.Id, strength, - InterestOwnership.Signal, follow.current_position, follow); } diff --git a/IdleSpectator/BuildingEventPatches.cs b/IdleSpectator/BuildingEventPatches.cs index 95dbe52..c785bab 100644 --- a/IdleSpectator/BuildingEventPatches.cs +++ b/IdleSpectator/BuildingEventPatches.cs @@ -105,7 +105,6 @@ public static class BuildingEventPatches label, string.IsNullOrEmpty(buildingName) ? "building_destroy" : buildingName, 88f, - InterestOwnership.Signal, pos, follow: null, locationOnly: true); @@ -120,7 +119,6 @@ public static class BuildingEventPatches label, string.IsNullOrEmpty(buildingName) ? "building_destroy" : buildingName, 88f, - InterestOwnership.Signal, near.current_position, near); } @@ -145,7 +143,6 @@ public static class BuildingEventPatches label, eventId, strength, - InterestOwnership.Signal, actor.current_position, actor); } diff --git a/IdleSpectator/CatalogCoverageAudit.cs b/IdleSpectator/CatalogCoverageAudit.cs index 1f80d7a..41d3114 100644 --- a/IdleSpectator/CatalogCoverageAudit.cs +++ b/IdleSpectator/CatalogCoverageAudit.cs @@ -223,7 +223,7 @@ public static class DomainEventHarness tsv.Append("relationship\t").Append(id).Append('\t') .Append(ok ? "1" : "0").Append('\t') - .Append(entry.OwnsCamera).Append('\t') + .Append(entry.CreatesInterest).Append('\t') .Append(entry.EventStrength.ToString("0.#")).Append('\t') .Append(ok ? "ok" : "missing_authored").AppendLine(); } @@ -256,7 +256,7 @@ public static class DomainEventHarness bool has = !entry.IsFallback; tsv.Append(domain).Append('\t').Append(id).Append('\t') .Append(has ? "1" : "0").Append('\t') - .Append(entry.OwnsCamera).Append('\t') + .Append(entry.CreatesInterest).Append('\t') .Append(entry.EventStrength.ToString("0.#")).Append('\t') .Append(has ? "ok" : "missing_authored").AppendLine(); } diff --git a/IdleSpectator/DeferredEventPatches.cs b/IdleSpectator/DeferredEventPatches.cs index 50c7fee..a5ba416 100644 --- a/IdleSpectator/DeferredEventPatches.cs +++ b/IdleSpectator/DeferredEventPatches.cs @@ -8,6 +8,12 @@ namespace IdleSpectator; [HarmonyPatch] public static class DeferredEventPatches { + private static bool _spellCastInFlight; + private static string _spellCastId; + + private static string _lastPowerId = ""; + private static float _lastPowerAt = -999f; + [HarmonyPatch(typeof(ItemManager), nameof(ItemManager.generateItem))] [HarmonyPostfix] public static void PostfixGenerateItem(object __result, object[] __args) @@ -58,7 +64,206 @@ public static class DeferredEventPatches DeferredInterestFeed.EmitItem(ReadAssetId(__result) ?? "item", owner); } - // Spells: live catalog + domain_feed; cast site varies by game build (no stable Actor.tryToCastSpell). + [HarmonyPatch(typeof(CombatActionLibrary), nameof(CombatActionLibrary.tryToCastSpell))] + [HarmonyPrefix] + public static void PrefixTryToCastSpell() + { + _spellCastInFlight = true; + _spellCastId = null; + } + + [HarmonyPatch(typeof(Actor), nameof(Actor.getRandomSpell))] + [HarmonyPostfix] + public static void PostfixGetRandomSpell(object __result) + { + if (!_spellCastInFlight || __result == null) + { + return; + } + + string id = ReadAssetId(__result); + if (!string.IsNullOrEmpty(id)) + { + _spellCastId = id; + } + } + + [HarmonyPatch(typeof(CombatActionLibrary), nameof(CombatActionLibrary.tryToCastSpell))] + [HarmonyPostfix] + public static void PostfixTryToCastSpell(AttackData pData, bool __result) + { + _spellCastInFlight = false; + if (!__result || AgentHarness.Busy) + { + _spellCastId = null; + return; + } + + Actor actor = null; + try + { + BaseSimObject initiator = pData.initiator; + if (initiator != null) + { + actor = initiator.a; + } + } + catch + { + actor = null; + } + + if (actor == null || !actor.isAlive()) + { + try + { + object initiator = typeof(AttackData).GetField("initiator")?.GetValue(pData); + actor = ReadActorMember(initiator, "a", "actor") + ?? (initiator as Actor); + } + catch + { + actor = null; + } + } + + if (actor == null || !actor.isAlive()) + { + _spellCastId = null; + return; + } + + string spellId = _spellCastId; + _spellCastId = null; + if (string.IsNullOrEmpty(spellId)) + { + spellId = ReadSpellFromAttackData(pData) ?? "spell"; + } + + DeferredInterestFeed.EmitSpell(spellId, actor); + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.clickedFinal))] + [HarmonyPostfix] + public static void PostfixClickedFinal(Vector2Int pPos, GodPower pPower) + { + if (AgentHarness.Busy) + { + return; + } + + string powerId = null; + if (pPower != null) + { + powerId = ReadAssetId(pPower); + if (string.IsNullOrEmpty(powerId)) + { + try + { + powerId = pPower.name; + } + catch + { + powerId = null; + } + } + } + + if (string.IsNullOrEmpty(powerId)) + { + try + { + object selected = World.world?.selected_power; + powerId = ReadAssetId(selected); + if (string.IsNullOrEmpty(powerId)) + { + powerId = World.world?.getSelectedPowerID(); + } + } + catch + { + powerId = null; + } + } + + if (string.IsNullOrEmpty(powerId)) + { + return; + } + + float now = Time.unscaledTime; + if (string.Equals(powerId, _lastPowerId, StringComparison.OrdinalIgnoreCase) + && now - _lastPowerAt < 1.5f) + { + return; + } + + _lastPowerId = powerId; + _lastPowerAt = now; + + Vector3 pos = TilePos(pPos); + Actor near = pos != Vector3.zero ? EventFeedUtil.NearestUnit(pos, 48f) : null; + DeferredInterestFeed.EmitPower(powerId, pos, near); + } + + [HarmonyPatch(typeof(WorldLaws), nameof(WorldLaws.enable))] + [HarmonyPostfix] + public static void PostfixWorldLawEnable(string pID) + { + if (AgentHarness.Busy || string.IsNullOrEmpty(pID)) + { + return; + } + + DeferredInterestFeed.EmitWorldLaw(pID, CameraOrMapPos()); + } + + [HarmonyPatch(typeof(WorldLawAsset), nameof(WorldLawAsset.toggle))] + [HarmonyPostfix] + public static void PostfixWorldLawToggle(WorldLawAsset __instance, bool pState) + { + if (!pState || AgentHarness.Busy || __instance == null) + { + return; + } + + string id = ReadAssetId(__instance) ?? __instance.id; + if (string.IsNullOrEmpty(id)) + { + return; + } + + DeferredInterestFeed.EmitWorldLaw(id, CameraOrMapPos()); + } + + [HarmonyPatch(typeof(Subspecies), nameof(Subspecies.mutateFrom))] + [HarmonyPostfix] + public static void PostfixMutateFrom(Subspecies __instance) + { + if (AgentHarness.Busy || __instance == null) + { + return; + } + + Actor anchor = FindUnitForSubspecies(__instance); + if (anchor == null || !anchor.isAlive()) + { + return; + } + + // Prefer the mutation event id so ambient gene catalog entries do not suppress the emit. + string geneId = "mutation"; + string nucleusGene = ReadNucleusGeneId(__instance); + if (!string.IsNullOrEmpty(nucleusGene) + && (nucleusGene.IndexOf("warfare", StringComparison.OrdinalIgnoreCase) >= 0 + || nucleusGene.IndexOf("magic", StringComparison.OrdinalIgnoreCase) >= 0 + || nucleusGene.IndexOf("mutation", StringComparison.OrdinalIgnoreCase) >= 0)) + { + geneId = nucleusGene; + } + + DeferredInterestFeed.EmitGene(geneId, anchor); + } [HarmonyPatch(typeof(Actor), nameof(Actor.setDecisionCooldown))] [HarmonyPostfix] @@ -81,7 +286,6 @@ public static class DeferredEventPatches return; } - DiscreteEventEntry entry = DecisionInterestCatalog.GetOrFallback(id); // High-frequency hook: only kingdom-affecting decisions, never idle AI ticks. if (!DecisionInterestCatalog.IsCameraWorthy(id)) { @@ -232,6 +436,148 @@ public static class DeferredEventPatches return null; } + private static string ReadSpellFromAttackData(AttackData pData) + { + try + { + Type type = typeof(AttackData); + foreach (string name in new[] { "spell", "spell_asset", "pSpell", "current_spell", "last_spell" }) + { + object value = type.GetField(name)?.GetValue(pData) + ?? type.GetProperty(name)?.GetValue(pData, null); + string id = ReadAssetId(value); + if (!string.IsNullOrEmpty(id)) + { + return id; + } + } + } + catch + { + // ignore + } + + return null; + } + + private static string ReadNucleusGeneId(Subspecies subspecies) + { + try + { + object nucleus = subspecies.nucleus; + if (nucleus == null) + { + return null; + } + + object chromosomes = nucleus.GetType().GetField("chromosomes")?.GetValue(nucleus) + ?? nucleus.GetType().GetProperty("chromosomes")?.GetValue(nucleus, null); + if (chromosomes is not System.Collections.IEnumerable list) + { + return null; + } + + foreach (object chrome in list) + { + if (chrome == null) + { + continue; + } + + object genes = chrome.GetType().GetField("genes")?.GetValue(chrome) + ?? chrome.GetType().GetProperty("genes")?.GetValue(chrome, null); + if (genes is not System.Collections.IEnumerable geneList) + { + continue; + } + + foreach (object gene in geneList) + { + string id = ReadAssetId(gene); + if (!string.IsNullOrEmpty(id) + && !id.Equals("empty", StringComparison.OrdinalIgnoreCase) + && !id.Contains("void")) + { + return id; + } + } + } + } + catch + { + // ignore + } + + return null; + } + + private static Vector3 TilePos(Vector2Int pPos) + { + try + { + WorldTile tile = World.world?.GetTile(pPos.x, pPos.y) + ?? World.world?.GetTileSimple(pPos.x, pPos.y); + if (tile != null) + { + object posObj = tile.GetType().GetField("posV3")?.GetValue(tile) + ?? tile.GetType().GetProperty("posV3")?.GetValue(tile, null) + ?? tile.GetType().GetField("pos")?.GetValue(tile) + ?? tile.GetType().GetProperty("pos")?.GetValue(tile, null) + ?? tile.GetType().GetField("position")?.GetValue(tile) + ?? tile.GetType().GetProperty("position")?.GetValue(tile, null); + if (posObj is Vector3 v) + { + return v; + } + + if (posObj is Vector2 v2) + { + return new Vector3(v2.x, v2.y, 0f); + } + } + } + catch + { + // fall through + } + + return new Vector3(pPos.x, pPos.y, 0f); + } + + private static Vector3 CameraOrMapPos() + { + try + { + if (MoveCamera.instance != null) + { + Vector3 p = MoveCamera.instance.transform.position; + if (p != Vector3.zero) + { + return new Vector3(p.x, p.y, 0f); + } + } + } + catch + { + // ignore + } + + try + { + if (Camera.main != null) + { + Vector3 p = Camera.main.transform.position; + return new Vector3(p.x, p.y, 0f); + } + } + catch + { + // ignore + } + + return Vector3.zero; + } + private static string ReadAssetId(object obj) { if (obj == null) diff --git a/IdleSpectator/DeferredInterestFeed.cs b/IdleSpectator/DeferredInterestFeed.cs index c9f7b02..63c4228 100644 --- a/IdleSpectator/DeferredInterestFeed.cs +++ b/IdleSpectator/DeferredInterestFeed.cs @@ -64,7 +64,6 @@ public static class DeferredInterestFeed EventReason.HumanizeId(entry.Id), entry.Id, entry.EventStrength, - InterestOwnership.Signal, position, follow: null, locationOnly: true); @@ -123,7 +122,6 @@ public static class DeferredInterestFeed EventReason.HumanizeId(entry.Id), entry.Id, entry.EventStrength, - InterestOwnership.Signal, position, follow: null, locationOnly: true); @@ -149,7 +147,6 @@ public static class DeferredInterestFeed EventReason.HumanizeId(entry.Id), entry.Id, entry.EventStrength, - InterestOwnership.Signal, position, follow: null, locationOnly: true); @@ -194,7 +191,6 @@ public static class DeferredInterestFeed label, entry.Id, entry.EventStrength, - InterestOwnership.Signal, follow != null ? follow.current_position : Vector3.zero, follow, related: related, diff --git a/IdleSpectator/DeferredLibraryCatalogs.cs b/IdleSpectator/DeferredLibraryCatalogs.cs index d7a6c78..2ed7c32 100644 --- a/IdleSpectator/DeferredLibraryCatalogs.cs +++ b/IdleSpectator/DeferredLibraryCatalogs.cs @@ -20,10 +20,11 @@ public static class SpellInterestCatalog Entries, ActivityAssetCatalog.EnumerateLiveSpellIds, "Spectacle", - "Spell: {id} ({a})", + "{a} casts {id}", ambientStrength: 48f, SignalIds, - signalStrength: 88f); + signalStrength: 88f, + ambientCreatesInterest: false); public static IEnumerable AuthoredIds { @@ -37,7 +38,7 @@ public static class SpellInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "Spectacle", "Spell: {id} ({a})", 55f); + return LiveLibraryInterest.Lookup(Entries, id, "Spectacle", "{a} casts {id}", 55f); } } @@ -75,11 +76,12 @@ public static class PowerInterestCatalog Entries, ActivityAssetCatalog.EnumerateLivePowerIds, "Spectacle", - "Power: {id}", + "God power: {id}", ambientStrength: 28f, signalIds: null, signalStrength: 92f, - signalPredicate: IsSpectaclePower); + signalPredicate: IsSpectaclePower, + ambientCreatesInterest: false); public static IEnumerable AuthoredIds { @@ -93,7 +95,7 @@ public static class PowerInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "Spectacle", "Power: {id}", 30f); + return LiveLibraryInterest.Lookup(Entries, id, "Spectacle", "God power: {id}", 30f); } } @@ -143,11 +145,12 @@ public static class DecisionInterestCatalog Entries, ActivityAssetCatalog.EnumerateLiveDecisionIds, "Politics", - "Decision: {id} ({a})", + "{a} decides {id}", ambientStrength: 32f, signalIds: null, signalStrength: 86f, - signalPredicate: IsKingdomDecision); + signalPredicate: IsKingdomDecision, + ambientCreatesInterest: false); public static IEnumerable AuthoredIds { @@ -161,7 +164,7 @@ public static class DecisionInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "Politics", "Decision: {id} ({a})", 35f); + return LiveLibraryInterest.Lookup(Entries, id, "Politics", "{a} decides {id}", 35f); } } @@ -196,11 +199,12 @@ public static class ItemInterestCatalog Entries, ActivityAssetCatalog.EnumerateLiveItemIds, "Item", - "Item forged: {id} ({a})", + "{a} forges {id}", ambientStrength: 36f, signalIds: null, signalStrength: 80f, - signalPredicate: IsLegendaryItem); + signalPredicate: IsLegendaryItem, + ambientCreatesInterest: false); public static IEnumerable AuthoredIds { @@ -214,7 +218,7 @@ public static class ItemInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "Item", "Item forged: {id} ({a})", 40f); + return LiveLibraryInterest.Lookup(Entries, id, "Item", "{a} forges {id}", 40f); } } @@ -253,7 +257,7 @@ public static class SubspeciesTraitInterestCatalog Entries, ActivityAssetCatalog.EnumerateLiveSubspeciesTraitIds, "Evolution", - "Subspecies trait: {id}", + "{a} evolves {id}", ambientStrength: 34f, signalIds: null, signalStrength: 78f, @@ -271,7 +275,7 @@ public static class SubspeciesTraitInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "Evolution", "Subspecies trait: {id}", 36f); + return LiveLibraryInterest.Lookup(Entries, id, "Evolution", "{a} evolves {id}", 36f); } } @@ -286,11 +290,12 @@ public static class GeneInterestCatalog Entries, ActivityAssetCatalog.EnumerateLiveGeneIds, "Genetics", - "Gene: {id} ({a})", + "{a} gains gene {id}", ambientStrength: 30f, signalIds: null, signalStrength: 70f, - signalPredicate: id => id != null && (id.Contains("warfare") || id.Contains("magic"))); + signalPredicate: id => id != null && (id.Contains("warfare") || id.Contains("magic") || id.Contains("mutation")), + ambientCreatesInterest: false); public static IEnumerable AuthoredIds { @@ -304,7 +309,7 @@ public static class GeneInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "Genetics", "Gene: {id} ({a})", 30f); + return LiveLibraryInterest.Lookup(Entries, id, "Genetics", "{a} gains gene {id}", 30f); } } @@ -319,7 +324,7 @@ public static class PhenotypeInterestCatalog Entries, ActivityAssetCatalog.EnumerateLivePhenotypeIds, "Genetics", - "Phenotype: {id} ({a})", + "{a} shows {id}", ambientStrength: 28f, signalIds: null, signalStrength: 60f); @@ -336,7 +341,7 @@ public static class PhenotypeInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "Genetics", "Phenotype: {id} ({a})", 28f); + return LiveLibraryInterest.Lookup(Entries, id, "Genetics", "{a} shows {id}", 28f); } } @@ -351,11 +356,12 @@ public static class WorldLawInterestCatalog Entries, ActivityAssetCatalog.EnumerateLiveWorldLawIds, "WorldLaw", - "World law: {id}", + "Law changed: {id}", ambientStrength: 40f, signalIds: null, signalStrength: 70f, - signalPredicate: id => id != null && (id.Contains("war") || id.Contains("death") || id.Contains("plague"))); + signalPredicate: id => id != null && (id.Contains("war") || id.Contains("death") || id.Contains("plague")), + ambientCreatesInterest: false); public static IEnumerable AuthoredIds { @@ -369,7 +375,7 @@ public static class WorldLawInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "WorldLaw", "World law: {id}", 40f); + return LiveLibraryInterest.Lookup(Entries, id, "WorldLaw", "Law changed: {id}", 40f); } } @@ -384,10 +390,11 @@ public static class BiomeInterestCatalog Entries, ActivityAssetCatalog.EnumerateLiveBiomeIds, "Biome", - "Biome: {id}", + "Biome shifts: {id}", ambientStrength: 26f, signalIds: null, - signalStrength: 50f); + signalStrength: 50f, + ambientCreatesInterest: false); public static IEnumerable AuthoredIds { @@ -401,6 +408,6 @@ public static class BiomeInterestCatalog public static DiscreteEventEntry GetOrFallback(string id) { Ensure(); - return LiveLibraryInterest.Lookup(Entries, id, "Biome", "Biome: {id}", 26f); + return LiveLibraryInterest.Lookup(Entries, id, "Biome", "Biome shifts: {id}", 26f); } } diff --git a/IdleSpectator/EraInterestCatalog.cs b/IdleSpectator/EraInterestCatalog.cs index 40415c8..0afc170 100644 --- a/IdleSpectator/EraInterestCatalog.cs +++ b/IdleSpectator/EraInterestCatalog.cs @@ -11,24 +11,23 @@ public static class EraInterestCatalog static EraInterestCatalog() { - Add("age_hope", 78f, "World", InterestOwnership.Signal, "Age of Hope"); - Add("age_sun", 80f, "World", InterestOwnership.Signal, "Age of Sun"); - Add("age_dark", 88f, "World", InterestOwnership.Signal, "Age of Dark"); - Add("age_tears", 86f, "World", InterestOwnership.Signal, "Age of Tears"); - Add("age_moon", 82f, "World", InterestOwnership.Signal, "Age of Moon"); - Add("age_chaos", 92f, "World", InterestOwnership.Signal, "Age of Chaos"); - Add("age_wonders", 84f, "World", InterestOwnership.Signal, "Age of Wonders"); - Add("age_ice", 87f, "World", InterestOwnership.Signal, "Age of Ice"); - Add("age_ash", 90f, "World", InterestOwnership.Signal, "Age of Ash"); - Add("age_despair", 91f, "World", InterestOwnership.Signal, "Age of Despair"); - Add("age_unknown", 70f, "World", InterestOwnership.Ambient, "Unknown age"); + Add("age_hope", 78f, "World", "Age of Hope"); + Add("age_sun", 80f, "World", "Age of Sun"); + Add("age_dark", 88f, "World", "Age of Dark"); + Add("age_tears", 86f, "World", "Age of Tears"); + Add("age_moon", 82f, "World", "Age of Moon"); + Add("age_chaos", 92f, "World", "Age of Chaos"); + Add("age_wonders", 84f, "World", "Age of Wonders"); + Add("age_ice", 87f, "World", "Age of Ice"); + Add("age_ash", 90f, "World", "Age of Ash"); + Add("age_despair", 91f, "World", "Age of Despair"); + Add("age_unknown", 70f, "World", "Unknown age"); } private static void Add( string id, float strength, string category, - InterestOwnership owns, string label) { Entries[id] = new DiscreteEventEntry @@ -36,7 +35,6 @@ public static class EraInterestCatalog Id = id, EventStrength = strength, Category = category, - OwnsCamera = owns, CreatesInterest = true, LabelTemplate = label }; @@ -60,7 +58,6 @@ public static class EraInterestCatalog Id = key, EventStrength = 70f, Category = "World", - OwnsCamera = InterestOwnership.Signal, LabelTemplate = "Era ({id})", IsFallback = true }; diff --git a/IdleSpectator/EventFeedUtil.cs b/IdleSpectator/EventFeedUtil.cs index 5bed2dc..bf68a66 100644 --- a/IdleSpectator/EventFeedUtil.cs +++ b/IdleSpectator/EventFeedUtil.cs @@ -2,16 +2,6 @@ using UnityEngine; namespace IdleSpectator; -/// -/// Legacy catalog tag. Ignored by - -/// every registered event is EventLed and may own the camera; strength ranks. -/// -public enum InterestOwnership -{ - Signal, - Ambient -} - /// /// Sole unit-led publish API for the idle director. /// Every registration is EventLed and may own the camera (score ranks). @@ -27,7 +17,6 @@ public static class EventFeedUtil string label, string assetId, float eventStrength, - InterestOwnership ownership, Vector3 position, Actor follow, Actor related = null, @@ -36,8 +25,6 @@ public static class EventFeedUtil float maxWatch = 22f, InterestCompletionKind completion = InterestCompletionKind.FixedDwell) { - // ownership retained for call-site compatibility; camera gate removed. - _ = ownership; if (string.IsNullOrEmpty(key)) { return null; @@ -205,7 +192,7 @@ public static class EventFeedUtil } /// - /// Harness / diagnostics only. Must not be used as a silent camera subject for Signal events. + /// Harness / diagnostics only. Must not be used as a silent camera subject for EventLed events. /// public static Actor AnyAliveUnit() { diff --git a/IdleSpectator/HarnessScenarios.cs b/IdleSpectator/HarnessScenarios.cs index 1d27f25..abcb32c 100644 --- a/IdleSpectator/HarnessScenarios.cs +++ b/IdleSpectator/HarnessScenarios.cs @@ -38,6 +38,9 @@ internal static class HarnessScenarios case "director_gaps": case "director_coverage": return DirectorGaps(); + case "focus_interrupt": + case "camera_interrupt": + return FocusInterrupt(); case "watch_reason": case "watch_reason_clarity": return WatchReasonClarity(); @@ -629,31 +632,29 @@ internal static class HarnessScenarios Step("dt1", "wait_world"), Step("dt2", "set_setting", expect: "enabled", value: "true"), Step("dt3", "fast_timing", value: "true"), - // Calm unit + spectator restart clears collector so world Story tips cannot steal CurioA. - Step("dt4", "spawn", asset: "sheep"), + // Calm durable unit + spectator restart clears collector so world tips cannot steal CurioA. + Step("dt4", "spawn", asset: "dragon"), Step("dt5", "spectator", value: "off"), Step("dt6", "spectator", value: "on"), - Step("dt7", "focus", asset: "sheep"), + Step("dt7", "focus", asset: "dragon"), Step("dt8", "reset_counters"), - // Curiosity can replace ambient after rotate window. - Step("dt10", "trigger_interest", asset: "sheep", label: "CurioA", tier: "Curiosity"), - Step("dt11", "age_current", wait: 2f), - Step("dt12", "director_run", wait: 1.2f), - Step("dt13", "assert", expect: "tip_contains", value: "CurioA"), + // Curiosity hold. + Step("dt10", "interest_force_session", asset: "dragon", label: "CurioA", tier: "Curiosity", expect: "curio_a"), + Step("dt11", "assert", expect: "tip_contains", value: "CurioA"), - // Action interrupts curiosity after settle. - Step("dt20", "trigger_interest", asset: "auto", label: "ActionA", tier: "Action"), - Step("dt21", "age_current", wait: 1.2f), - Step("dt22", "director_run", wait: 1.2f), - Step("dt23", "assert", expect: "tip_contains", value: "ActionA"), + // Action margin-cuts curiosity (instant; no settle required). + Step("dt20", "trigger_interest", asset: "dragon", label: "ActionA", tier: "Action"), + Step("dt21", "director_run", wait: 0.8f), + Step("dt22", "assert", expect: "tip_contains", value: "ActionA"), // Curiosity must not steal the camera while Action holds (no director_run). - Step("dt30", "trigger_interest", asset: "auto", label: "CurioBlocked", tier: "Curiosity"), + Step("dt30", "trigger_interest", asset: "dragon", label: "CurioBlocked", tier: "Curiosity"), Step("dt31", "assert", expect: "tip_contains", value: "ActionA"), Step("dt32", "assert", expect: "would_accept_curiosity", value: "false"), - Step("dt33", "assert", expect: "health"), - Step("dt34", "assert", expect: "no_bad"), + Step("dt33", "reset_counters"), + Step("dt34", "assert", expect: "health"), + Step("dt35", "assert", expect: "no_bad"), Step("dt90", "fast_timing", value: "false"), Step("dt99", "snapshot"), @@ -668,27 +669,26 @@ internal static class HarnessScenarios Step("dl1", "wait_world"), Step("dl2", "set_setting", expect: "enabled", value: "true"), Step("dl3", "fast_timing", value: "true"), - Step("dl4", "spawn", asset: "sheep"), + Step("dl4", "spawn", asset: "dragon"), Step("dl5", "spectator", value: "off"), Step("dl6", "spectator", value: "on"), - Step("dl7", "focus", asset: "sheep"), + Step("dl7", "focus", asset: "dragon"), - // Protected Action: Story waits. - Step("dl10", "interest_force_session", asset: "sheep", label: "HoldAction", tier: "Action", expect: "hold_action"), + // Protected Action: peer Story (+0 margin) waits. + Step("dl10", "interest_force_session", asset: "dragon", label: "HoldAction", tier: "Action", expect: "hold_action"), Step("dl11", "assert", expect: "tip_contains", value: "HoldAction"), Step("dl12", "assert", expect: "session_active", value: "true"), - Step("dl13", "trigger_interest", asset: "sheep", label: "StoryWait", tier: "Story"), - Step("dl14", "age_current", wait: 1.2f), - Step("dl15", "director_run", wait: 1.2f), + Step("dl13", "trigger_interest", asset: "dragon", label: "StoryWait", tier: "Story"), + Step("dl14", "director_run", wait: 0.8f), + Step("dl15", "assert", expect: "tip_contains", value: "HoldAction"), Step("dl16", "assert", expect: "tip_contains", value: "HoldAction"), - Step("dl17", "assert", expect: "tip_contains", value: "HoldAction"), - // Epic preempts Action after settle. - Step("dl20", "trigger_interest", asset: "sheep", label: "EpicWin", tier: "Epic"), - Step("dl21", "age_current", wait: 1.2f), - Step("dl22", "director_run", wait: 1.2f), + // Epic margin-cuts Action instantly (no settle). + Step("dl20", "trigger_interest", asset: "dragon", label: "EpicWin", tier: "Epic"), + Step("dl21", "director_run", wait: 0.8f), + Step("dl22", "assert", expect: "tip_contains", value: "EpicWin"), Step("dl23", "assert", expect: "tip_contains", value: "EpicWin"), - Step("dl24", "assert", expect: "tip_contains", value: "EpicWin"), + Step("dl24", "reset_counters"), Step("dl25", "assert", expect: "no_bad"), Step("dl90", "fast_timing", value: "false"), @@ -726,10 +726,9 @@ internal static class HarnessScenarios // Epic can leave Action; grief may then surface. Step("ih30", "trigger_interest", asset: "auto", label: "EpicClear", tier: "Epic"), - Step("ih31", "age_current", wait: 1.2f), - Step("ih32", "director_run", wait: 1.2f), - Step("ih33", "assert", expect: "tip_contains", value: "EpicClear"), - Step("ih34", "assert", expect: "no_bad"), + Step("ih31", "director_run", wait: 0.8f), + Step("ih32", "assert", expect: "tip_contains", value: "EpicClear"), + Step("ih33", "assert", expect: "no_bad"), Step("ih90", "fast_timing", value: "false"), Step("ih99", "snapshot"), @@ -747,37 +746,35 @@ internal static class HarnessScenarios Step("dg1", "wait_world"), Step("dg2", "set_setting", expect: "enabled", value: "true"), Step("dg3", "fast_timing", value: "true"), - Step("dg4", "spawn", asset: "sheep", count: 2), + Step("dg4", "spawn", asset: "dragon", count: 1), Step("dg5", "spectator", value: "off"), Step("dg6", "spectator", value: "on"), - Step("dg7", "focus", asset: "sheep"), + Step("dg7", "focus", asset: "dragon"), Step("dg8", "interest_variety_clear"), - // --- Epic preempts Story; Story does not preempt itself past Action rules --- - Step("dg10", "interest_force_session", asset: "sheep", label: "HoldStory", tier: "Story", expect: "hold_story"), + // --- Epic margin-cuts Story; Action does not (below cutInMargin) --- + // Hold as EventLed so CharacterLed char-weight cannot outscore Epic. + Step("dg10", "interest_force_session", asset: "dragon", label: "HoldStory", tier: "Story", expect: "hold_story", value: "lead=event;evt=80"), Step("dg11", "assert", expect: "tip_contains", value: "HoldStory"), - Step("dg12", "trigger_interest", asset: "sheep", label: "ActionBlocked", tier: "Action"), - Step("dg13", "age_current", wait: 1.2f), - Step("dg14", "director_run", wait: 1.2f), + Step("dg12", "trigger_interest", asset: "dragon", label: "ActionBlocked", tier: "Action"), + Step("dg13", "director_run", wait: 0.8f), + Step("dg14", "assert", expect: "tip_contains", value: "HoldStory"), Step("dg15", "assert", expect: "tip_contains", value: "HoldStory"), - Step("dg16", "assert", expect: "tip_contains", value: "HoldStory"), - Step("dg17", "trigger_interest", asset: "sheep", label: "EpicOverStory", tier: "Epic"), - Step("dg18", "age_current", wait: 1.2f), - Step("dg19", "director_run", wait: 1.2f), + Step("dg17", "trigger_interest", asset: "dragon", label: "EpicOverStory", tier: "Epic"), + Step("dg18", "director_run", wait: 0.8f), + Step("dg19", "assert", expect: "tip_contains", value: "EpicOverStory"), Step("dg20", "assert", expect: "tip_contains", value: "EpicOverStory"), - Step("dg21", "assert", expect: "tip_contains", value: "EpicOverStory"), // --- Resume after Epic: Action hold interrupted then restored --- Step("dg30", "spectator", value: "off"), Step("dg31", "spectator", value: "on"), - Step("dg32", "focus", asset: "sheep"), - Step("dg33", "interest_force_session", asset: "sheep", label: "HoldResume", tier: "Action", expect: "hold_resume"), + Step("dg32", "focus", asset: "dragon"), + Step("dg33", "interest_force_session", asset: "dragon", label: "HoldResume", tier: "Action", expect: "hold_resume", value: "lead=event;evt=70"), Step("dg34", "assert", expect: "session_key", value: "hold_resume"), - Step("dg35", "trigger_interest", asset: "sheep", label: "EpicInterrupt", tier: "Epic"), - Step("dg36", "age_current", wait: 1.2f), - Step("dg37", "director_run", wait: 1.2f), - Step("dg38", "assert", expect: "tip_contains", value: "EpicInterrupt"), - Step("dg39", "assert", expect: "interrupted_key", value: "hold_resume"), + Step("dg35", "trigger_interest", asset: "dragon", label: "EpicInterrupt", tier: "Epic"), + Step("dg36", "director_run", wait: 0.8f), + Step("dg37", "assert", expect: "tip_contains", value: "EpicInterrupt"), + Step("dg38", "assert", expect: "interrupted_key", value: "hold_resume"), // Max-cap the Epic scene (fast timing caps ~8s) so EndCurrent resumes Action. Step("dg40", "age_current", wait: 9f), Step("dg41", "director_run", wait: 1.2f), @@ -786,30 +783,30 @@ internal static class HarnessScenarios Step("dg44", "assert", expect: "interrupted_key", value: "none"), // --- Max cap ends a forced scene --- - Step("dg50", "interest_force_session", asset: "sheep", label: "CapMe", tier: "Action", expect: "cap_me"), + Step("dg50", "interest_force_session", asset: "dragon", label: "CapMe", tier: "Action", expect: "cap_me"), Step("dg51", "age_current", wait: 9f), Step("dg52", "director_run", wait: 1.2f), Step("dg54", "assert", expect: "interest_no_key", value: "cap_me"), // --- Quiet grace: inactive scene ends after grace --- - Step("dg60", "interest_force_session", asset: "sheep", label: "QuietEnd", tier: "Action", expect: "quiet_end"), + Step("dg60", "interest_force_session", asset: "dragon", label: "QuietEnd", tier: "Action", expect: "quiet_end"), Step("dg61", "interest_mark_inactive", value: "1.0"), Step("dg62", "assert", expect: "session_active", value: "false"), Step("dg63", "director_run", wait: 1.2f), Step("dg64", "assert", expect: "interest_no_key", value: "quiet_end"), // --- Stale expiry / dedupe refresh --- - Step("dg70", "interest_inject", asset: "sheep", label: "StaleTip", tier: "Curiosity", expect: "stale_tip", value: "lead=character;ttl=0.01"), + Step("dg70", "interest_inject", asset: "dragon", label: "StaleTip", tier: "Curiosity", expect: "stale_tip", value: "lead=character;ttl=0.01"), Step("dg71", "assert", expect: "interest_has_key", value: "stale_tip"), Step("dg72", "interest_expire_pending", value: "stale_tip"), Step("dg73", "assert", expect: "interest_no_key", value: "stale_tip"), - Step("dg74", "interest_inject", asset: "sheep", label: "RefreshA", tier: "Action", expect: "refresh_a", value: "lead=event;evt=20"), - Step("dg75", "interest_inject", asset: "sheep", label: "RefreshA", tier: "Action", expect: "refresh_a", value: "lead=event;evt=90"), + Step("dg74", "interest_inject", asset: "dragon", label: "RefreshA", tier: "Action", expect: "refresh_a", value: "lead=event;evt=20"), + Step("dg75", "interest_inject", asset: "dragon", label: "RefreshA", tier: "Action", expect: "refresh_a", value: "lead=event;evt=90"), Step("dg76", "assert", expect: "interest_registry_count", value: "1", label: "refresh_a"), // --- Death handoff: FollowUnit cleared → RelatedUnit --- - Step("dg80", "interest_force_session", asset: "sheep", label: "HandoffHold", tier: "Action", expect: "handoff_hold"), - Step("dg81", "interest_clear_follow", asset: "sheep"), + Step("dg80", "interest_force_session", asset: "dragon", label: "HandoffHold", tier: "Action", expect: "handoff_hold"), + Step("dg81", "interest_clear_follow", asset: "dragon"), Step("dg82", "director_run", wait: 0.8f), Step("dg83", "assert", expect: "session_key", value: "handoff_hold"), Step("dg84", "assert", expect: "session_active", value: "true"), @@ -820,8 +817,8 @@ internal static class HarnessScenarios Step("dg91b", "interest_expire_pending", value: ""), Step("dg92", "interest_variety_seed", value: "2:10"), Step("dg93", "assert", expect: "interest_event_share", value: "0.17", label: "0.2"), - Step("dg94", "interest_inject", asset: "sheep", label: "EvtWin", tier: "Curiosity", expect: "evt_win", value: "lead=event;evt=80"), - Step("dg95", "interest_inject", asset: "sheep", label: "CharLose", tier: "Curiosity", expect: "char_lose", value: "lead=character;char=95"), + Step("dg94", "interest_inject", asset: "dragon", label: "EvtWin", tier: "Curiosity", expect: "evt_win", value: "lead=event;evt=80"), + Step("dg95", "interest_inject", asset: "dragon", label: "CharLose", tier: "Curiosity", expect: "char_lose", value: "lead=character;char=95"), Step("dg96", "assert", expect: "interest_peek_lead", value: "EventLed"), // Empty event pool → character-led, no invented events Step("dg97", "interest_expire_pending", value: "evt_win"), @@ -829,12 +826,14 @@ internal static class HarnessScenarios // --- Event strength beats idle celebrity (within event pool / score order) --- Step("dg100", "interest_expire_pending", value: ""), - Step("dg101", "interest_inject", asset: "sheep", label: "StrongEvt", tier: "Action", expect: "strong_evt", value: "lead=event;evt=95;char=5"), - Step("dg102", "interest_inject", asset: "sheep", label: "CelebWeak", tier: "Action", expect: "celeb_weak", value: "lead=event;evt=10;char=99"), + Step("dg101", "interest_inject", asset: "dragon", label: "StrongEvt", tier: "Action", expect: "strong_evt", value: "lead=event;evt=95;char=5"), + Step("dg102", "interest_inject", asset: "dragon", label: "CelebWeak", tier: "Action", expect: "celeb_weak", value: "lead=event;evt=10;char=99"), Step("dg103", "assert", expect: "interest_score_order", value: "strong_evt", label: "celeb_weak"), // --- Ambient happiness never owns camera; psychopath suppress; drain duplex; aggregate --- - Step("dg110", "interest_force_session", asset: "sheep", label: "ActionKeep", tier: "Action", expect: "action_keep"), + Step("dg109", "spawn", asset: "human"), + Step("dg109b", "focus", asset: "human"), + Step("dg110", "interest_force_session", asset: "human", label: "ActionKeep", tier: "Action", expect: "action_keep"), Step("dg111", "happiness_apply", value: "just_ate"), Step("dg112", "interest_feeds_tick"), Step("dg113", "age_current", wait: 1.2f), @@ -867,6 +866,43 @@ internal static class HarnessScenarios }; } + private static List FocusInterrupt() + { + return new List + { + Step("fi0", "dismiss_windows"), + Step("fi1", "wait_world"), + Step("fi2", "set_setting", expect: "enabled", value: "true"), + Step("fi3", "fast_timing", value: "true"), + Step("fi4", "spawn", asset: "sheep"), + Step("fi5", "spectator", value: "off"), + Step("fi6", "spectator", value: "on"), + Step("fi7", "focus", asset: "sheep"), + + // Hold Action; peer Story does not cut (below cutInMargin). + Step("fi10", "interest_force_session", asset: "sheep", label: "HoldA", tier: "Action", expect: "hold_a"), + Step("fi11", "trigger_interest", asset: "sheep", label: "PeerStory", tier: "Story"), + Step("fi12", "director_run", wait: 0.6f), + Step("fi13", "assert", expect: "tip_contains", value: "HoldA"), + + // Epic margin-cuts instantly (no age/settle). + Step("fi20", "trigger_interest", asset: "sheep", label: "CutEpic", tier: "Epic"), + Step("fi21", "director_run", wait: 0.6f), + Step("fi22", "assert", expect: "tip_contains", value: "CutEpic"), + + // Quiet grace then ambient empty reason path. + Step("fi30", "interest_force_session", asset: "sheep", label: "GraceEnd", tier: "Action", expect: "grace_end"), + Step("fi31", "interest_mark_inactive", value: "1.0"), + Step("fi32", "assert", expect: "session_active", value: "false"), + Step("fi33", "director_run", wait: 1.2f), + Step("fi34", "assert", expect: "interest_no_key", value: "grace_end"), + Step("fi35", "assert", expect: "no_bad"), + + Step("fi90", "fast_timing", value: "false"), + Step("fi99", "snapshot"), + }; + } + /// /// Watch-reason row must explain why the camera is here (event), not who (title/name). /// @@ -1017,16 +1053,14 @@ internal static class HarnessScenarios value: "lead=event;evt=55;char=40;fighters=2;notables=2"), Step("ap33", "assert", expect: "interest_score_order", value: "duel_kings", label: "melee_noname"), - // Combat Action cuts celebrity Story vignette after settle. + // Combat Action margin-cuts celebrity Story vignette (instant). Step("ap40", "interest_expire_pending", value: ""), Step("ap41", "interest_force_session", asset: "sheep", label: "KingStroll", tier: "Story", expect: "king_stroll"), - // Mark current as character vignette (not WorldLog story event). Step("ap42", "interest_inject", asset: "sheep", label: "RealFight", tier: "Action", expect: "real_fight", - value: "lead=event;evt=90;char=10;fighters=4;notables=1;force=true"), - Step("ap43", "age_current", wait: 1.2f), - Step("ap44", "director_run", wait: 1.2f), + value: "lead=event;evt=120;char=10;fighters=4;notables=1;force=true"), + Step("ap43", "director_run", wait: 0.8f), + Step("ap44", "assert", expect: "tip_contains", value: "RealFight"), Step("ap45", "assert", expect: "tip_contains", value: "RealFight"), - Step("ap46", "assert", expect: "tip_contains", value: "RealFight"), // Cold-boot focus gaps from welcome/dismiss are unrelated to score order. Step("ap89", "reset_counters"), diff --git a/IdleSpectator/InterestDirector.cs b/IdleSpectator/InterestDirector.cs index 5b4dacc..67ae9c7 100644 --- a/IdleSpectator/InterestDirector.cs +++ b/IdleSpectator/InterestDirector.cs @@ -21,7 +21,7 @@ public static class InterestDirector public const float AmbientRotateSeconds = 20f; public const float ActionPollSeconds = 0.75f; public const float InputExitGraceSeconds = 2.5f; - public const float QuietGraceSeconds = 1.2f; + public const float QuietGraceSeconds = 6f; public const float CameraSettleGraceSeconds = 3f; private static float _minDwell = MinDwellSeconds; @@ -490,8 +490,9 @@ public static class InterestDirector continue; } - // Drop stale birth/hatch shots - by then the unit is already running around. - if (IsStaleFreshLifeMoment(c, now)) + // Drop stale / not-worth-watching shots from the pending set. + if (!IsWorthWatchingNow(c, now, selectingDuringGrace: InQuietGrace) + || IsStaleFreshLifeMoment(c, now)) { InterestRegistry.Remove(c.Key); PendingScratch.RemoveAt(i); @@ -677,10 +678,38 @@ public static class InterestDirector { if (current == null) { - return 45f; + return InterestScoringConfig.W.maxWatchMoment; } - float cap = current.MaxWatch > 0f ? current.MaxWatch : 45f; + ScoringWeights w = InterestScoringConfig.W; + float classCap = w.maxWatchMoment; + switch (current.Completion) + { + case InterestCompletionKind.CombatActive: + classCap = w.maxWatchCombat; + break; + case InterestCompletionKind.StatusPhase: + classCap = w.maxWatchStatus; + break; + case InterestCompletionKind.HappinessGrief: + classCap = w.maxWatchGrief; + break; + case InterestCompletionKind.FixedDwell: + case InterestCompletionKind.Manual: + classCap = current.EventStrength >= w.hotScoreMin + ? w.maxWatchEpic + : w.maxWatchMoment; + break; + default: + if (current.EventStrength >= w.hotScoreMin) + { + classCap = w.maxWatchEpic; + } + + break; + } + + float cap = current.MaxWatch > 0f ? Mathf.Min(current.MaxWatch, classCap) : classCap; // Harness fast timing compresses caps. if (_minDwell < MinDwellSeconds * 0.5f) { @@ -690,10 +719,7 @@ public static class InterestDirector return cap; } - /// - /// Protected Action+ scenes: only Epic may preempt after settle grace. - /// Ambient/Curiosity may be replaced by higher urgency after settle. - /// + /// Hold while live and under MaxWatch; quiet grace still counts as briefly protected. private static bool SessionProtected(float now, float onCurrent) { if (_current == null) @@ -730,10 +756,21 @@ public static class InterestDirector if (_current == null) { - return true; + return IsWorthWatchingNow(candidate, Time.unscaledTime, selectingDuringGrace: false); } float now = Time.unscaledTime; + bool inGrace = InQuietGrace; + if (inGrace) + { + return IsWorthWatchingNow(candidate, now, selectingDuringGrace: true); + } + + if (!IsWorthWatchingNow(candidate, now, selectingDuringGrace: false)) + { + return false; + } + bool protectedScene = SessionProtected(now, onCurrent); ScoringWeights w = InterestScoringConfig.W; float curScore = _current.TotalScore; @@ -741,7 +778,13 @@ public static class InterestDirector bool nextFill = InterestScoring.IsFillScore(nextScore); bool curFill = InterestScoring.IsFillScore(curScore); - // Fill/curiosity-strength scenes never cut protected non-fill sessions. + // Instant score-margin cut - no settle grace, no MinDwell block. + if (nextScore >= curScore + w.cutInMargin) + { + return true; + } + + // Fill never cuts a protected non-fill session without margin (already failed above). if (nextFill && !curFill && protectedScene) { return false; @@ -753,42 +796,105 @@ public static class InterestDirector return false; } - bool typedCutIn = InterestScoring.IsCombatAction(candidate) - && InterestScoring.IsCharacterVignette(_current) - && onCurrent >= _settleGrace; - bool combatCutsNonCombat = InterestScoring.IsCombatAction(candidate) - && !InterestScoring.IsCombatAction(_current) - && onCurrent >= _settleGrace - && nextScore >= curScore - w.rotateSlack; - bool scoreCutIn = onCurrent >= _settleGrace - && nextScore >= curScore + w.cutInMargin; - + // Protected EventLed hold: only margin cut-in (handled above). if (protectedScene && !curFill) { - if (typedCutIn || combatCutsNonCombat || scoreCutIn) - { - return true; - } - return false; } - // Unprotected / fill current: stronger score after settle, or peer rotate after dwell. - if (nextScore > curScore && onCurrent >= _settleGrace) + // Peer rotate / stronger score after MinDwell when unprotected or on fill. + if (nextScore > curScore && onCurrent >= MinDwellFor(_current) && sinceSwitch >= _switchCooldown) { return true; } - if (!protectedScene && onCurrent >= MinDwellFor(_current) && sinceSwitch >= _switchCooldown) - { - return nextScore >= curScore - w.rotateSlack; - } - return onCurrent >= MinDwellFor(_current) && sinceSwitch >= _switchCooldown && nextScore >= curScore - w.rotateSlack; } + /// + /// Still-worth-watching filter: drop stale queued moments; keep live sticky conditions + /// and fresh FixedDwell; during grace, brand-new fires are always eligible. + /// + private static bool IsWorthWatchingNow(InterestCandidate c, float now, bool selectingDuringGrace) + { + if (c == null || !c.HasValidPosition) + { + return false; + } + + if (selectingDuringGrace && c.CreatedAt >= _inactiveSince && _inactiveSince >= 0f) + { + return true; + } + + // Purge moments that fired before the current scene started (stale queue). + if (_current != null && c.CreatedAt < _currentStartedAt - 0.05f) + { + if (c.Completion == InterestCompletionKind.FixedDwell + || c.Completion == InterestCompletionKind.Manual) + { + return false; + } + + // Sticky: only if still live. + return InterestCompletion.IsActive(c, c.CreatedAt, now); + } + + if (IsStaleMomentCandidate(c, now)) + { + return false; + } + + switch (c.Completion) + { + case InterestCompletionKind.CombatActive: + case InterestCompletionKind.StatusPhase: + case InterestCompletionKind.HappinessGrief: + case InterestCompletionKind.ActivityActive: + return InterestCompletion.IsActive(c, c.CreatedAt, now) + || now - c.LastSeenAt < 2f; + case InterestCompletionKind.FixedDwell: + case InterestCompletionKind.Manual: + // Fresh moment: age under ~4s or more than half MaxWatch remaining. + float age = now - c.CreatedAt; + if (age < 4f) + { + return true; + } + + float maxW = c.MaxWatch > 0f ? c.MaxWatch : InterestScoringConfig.W.maxWatchMoment; + return age < maxW * 0.5f; + default: + return true; + } + } + + private static bool IsStaleMomentCandidate(InterestCandidate c, float now) + { + if (IsStaleFreshLifeMoment(c, now)) + { + return true; + } + + if (c.ExpiresAt > 0f && now > c.ExpiresAt) + { + return true; + } + + // Queued under an active hold: short TTL for FixedDwell moments. + if (_current != null + && CurrentIsActive + && (c.Completion == InterestCompletionKind.FixedDwell || c.Completion == InterestCompletionKind.Manual) + && now - c.CreatedAt > 8f) + { + return true; + } + + return false; + } + private static void SwitchTo(InterestCandidate next, float now, bool resumableInterrupt) { if (next == null) @@ -797,9 +903,8 @@ public static class InterestDirector } if (_current != null - && InterestScoring.IsHotScore(next.TotalScore) - && !InterestScoring.IsHotScore(_current.TotalScore) - && _current.Resumable) + && _current.Resumable + && next.TotalScore >= _current.TotalScore + InterestScoringConfig.W.cutInMargin) { _interrupted = _current.CloneShallow(); } diff --git a/IdleSpectator/InterestFeeds.cs b/IdleSpectator/InterestFeeds.cs index 4662d62..4d4e649 100644 --- a/IdleSpectator/InterestFeeds.cs +++ b/IdleSpectator/InterestFeeds.cs @@ -123,7 +123,6 @@ public static class InterestFeeds label, assetId, evtSeed + boost, - entry.OwnsCamera, pos, follow, minWatch: entry.MinWatch, @@ -141,7 +140,6 @@ public static class InterestFeeds label, assetId, evtSeed + boost, - entry.OwnsCamera, position, follow: null, locationOnly: true, @@ -395,19 +393,23 @@ public static class InterestFeeds string phrase = ActivityStatusProse.Phrase(statusId, gained: true, out _); string key = "status:" + statusId + ":" + id; - EventFeedUtil.Register( + InterestCandidate registered = EventFeedUtil.Register( key, "status", string.IsNullOrEmpty(entry.Category) ? "StatusTransformation" : entry.Category, EventReason.Status(actor, phrase), statusId, entry.EventStrength, - InterestOwnership.Signal, actor.current_position, actor, minWatch: 2f, - maxWatch: 22f, + maxWatch: InterestScoringConfig.W.maxWatchStatus, completion: InterestCompletionKind.StatusPhase); + if (registered != null) + { + registered.StatusId = statusId; + registered.LastSeenAt = Time.unscaledTime; + } } public static void OnChronicleMilestone(Actor subject, string milestoneKey, Actor related, string label) diff --git a/IdleSpectator/InterestScoringConfig.cs b/IdleSpectator/InterestScoringConfig.cs index 58cead0..a65ea9c 100644 --- a/IdleSpectator/InterestScoringConfig.cs +++ b/IdleSpectator/InterestScoringConfig.cs @@ -37,6 +37,13 @@ public class ScoringWeights public float dwellHot = 14f; public float fillRotateSeconds = 10f; + // MaxWatch caps by completion class (director clamps candidate.MaxWatch). + public float maxWatchMoment = 10f; + public float maxWatchStatus = 30f; + public float maxWatchGrief = 45f; + public float maxWatchCombat = 60f; + public float maxWatchEpic = 50f; + public int massFighterThreshold = 4; public float massBaseBonus = 28f; public float massPerExtraFighter = 4f; diff --git a/IdleSpectator/LiveLibraryInterest.cs b/IdleSpectator/LiveLibraryInterest.cs index 5775cf5..8f03b6e 100644 --- a/IdleSpectator/LiveLibraryInterest.cs +++ b/IdleSpectator/LiveLibraryInterest.cs @@ -17,7 +17,8 @@ public static class LiveLibraryInterest float ambientStrength, HashSet signalIds, float signalStrength, - Func signalPredicate = null) + Func signalPredicate = null, + bool ambientCreatesInterest = true) { if (entries == null || enumerateLive == null) { @@ -49,8 +50,7 @@ public static class LiveLibraryInterest Id = id, EventStrength = signal ? signalStrength : ambientStrength, Category = category, - OwnsCamera = signal ? InterestOwnership.Signal : InterestOwnership.Ambient, - CreatesInterest = true, + CreatesInterest = signal || ambientCreatesInterest, LabelTemplate = labelTemplate }; } @@ -74,7 +74,6 @@ public static class LiveLibraryInterest Id = key, EventStrength = fallbackStrength, Category = category, - OwnsCamera = InterestOwnership.Ambient, LabelTemplate = labelTemplate, IsFallback = true }; diff --git a/IdleSpectator/MetaEventPatches.cs b/IdleSpectator/MetaEventPatches.cs index 015e38e..cda1078 100644 --- a/IdleSpectator/MetaEventPatches.cs +++ b/IdleSpectator/MetaEventPatches.cs @@ -76,7 +76,7 @@ public static class MetaEventPatches } string label = EventReason.MetaNew(anchor, kind); - MetaInterestFeed.EmitMeta(eventId, category, strength, InterestOwnership.Signal, anchor, label); + MetaInterestFeed.EmitMeta(eventId, category, strength, anchor, label); } private static Actor TryReadActor(object meta) diff --git a/IdleSpectator/MetaInterestFeed.cs b/IdleSpectator/MetaInterestFeed.cs index 3daf7df..8261440 100644 --- a/IdleSpectator/MetaInterestFeed.cs +++ b/IdleSpectator/MetaInterestFeed.cs @@ -62,7 +62,6 @@ public static class MetaInterestFeed entry.MakeLabel("", ""), entry.Id, entry.EventStrength, - entry.OwnsCamera, pos, follow: null, locationOnly: true, @@ -70,7 +69,7 @@ public static class MetaInterestFeed maxWatch: 28f); } - public static void EmitMeta(string eventId, string category, float strength, InterestOwnership owns, Actor anchor, string label) + public static void EmitMeta(string eventId, string category, float strength, Actor anchor, string label) { if (AgentHarness.Busy) { @@ -92,7 +91,6 @@ public static class MetaInterestFeed label, eventId, strength, - owns, follow.current_position, follow); } diff --git a/IdleSpectator/MutationDiscoveryHarness.cs b/IdleSpectator/MutationDiscoveryHarness.cs index c84699d..ba80a44 100644 --- a/IdleSpectator/MutationDiscoveryHarness.cs +++ b/IdleSpectator/MutationDiscoveryHarness.cs @@ -79,10 +79,49 @@ public static class MutationDiscoveryHarness "Actor.newKillAction", "Actor.die", "Actor.setTask", - "WorldLogMessageExtensions.add", - "BaseSimObject.addNewStatusEffect", - "Status.finish", + "Actor.setLover", + "Actor.addChild", + "Actor.addChildSimple", + "Actor.leavePlot", + "Actor.setPlot", + "Actor.addTrait", + "Actor.removeTrait", + "Actor.generatePhenotypeAndShade", + "Actor.setDecisionCooldown", + "FamilyManager.newFamily", + "FamilyManager.removeObject", + "ActorManager.createBabyActorFromData", + "PlotManager.newPlot", + "PlotManager.cancelPlot", + "WarManager.newWar", + "WarManager.endWar", + "BookManager.newBook", + "BookManager.generateNewBook", + "BookManager.burnBook", + "WorldAgeManager.startNextAge", + "CultureManager.newCulture", + "ReligionManager.newReligion", + "LanguageManager.newLanguage", + "ClanManager.newClan", + "ArmyManager.newArmy", + "AllianceManager.newAlliance", + "CityManager.newCity", + "ItemManager.generateItem", + "ItemManager.newItem", "SubspeciesManager.newSpecies", + "SubspeciesManager.addRandomTraitFromBiomeToSubspecies", + "SubspeciesManager.addTraitsFromBiomeToSubspecies", + "Subspecies.mutateFrom", + "BehDealDamageToTargetBuilding.execute", + "BehConsumeTargetBuilding.execute", + "Building.startDestroyBuilding", + "BehBoatTransportUnloadUnits.execute", + "BehBoatMakeTrade.execute", + "BehBoatTransportDoLoading.execute", + "BehFindLover.execute", + "BehFamilyGroupNew.execute", + "BehFamilyGroupJoin.execute", + "BehFamilyGroupLeave.execute", "BehPollinate.execute", "BehUnloadResources.execute", "BehAttackActorHuntingTarget.execute", @@ -97,13 +136,36 @@ public static class MutationDiscoveryHarness "BehBoatFishing.execute", "BehBoatCollectFish.execute", "BehClaimZoneForCityActorBorder.execute", + "CombatActionLibrary.tryToCastSpell", + "PlayerControl.clickedFinal", + "WorldLaws.enable", + "WorldLawAsset.toggle", + "WorldLogMessageExtensions.add", + "BaseSimObject.addNewStatusEffect", + "Status.finish", + "StatusManager.newStatus", "InterestFeeds.OnWorldLogMessage", "InterestFeeds.OnStatusChange", "InterestFeeds.OnActivityNote", "InterestFeeds.OnChronicleMilestone", + "BattleKeeperManager.addUnitKilled", "WarInterestFeed.Tick", + "PlotInterestFeed.Tick", + "DeferredInterestFeed", "WorldActivityScanner.FindHottestBattle", - "SpeciesDiscovery" + "SpeciesDiscovery", + // Covered by primary owners (birth / die / trait / city / WorldLog). + "ActorManager.spawnNewUnit", + "ActorManager.spawnNewUnitByPlayer", + "Actor.dieSimpleNone", + "Actor.dieAndDestroy", + "Actor.addChildren", + "Actor.removeTraits", + "Actor.addInjuryTrait", + "Actor.applyForcedKingdomTrait", + "Actor.generateRandomSpawnTraits", + "Actor.traitModifiedEvent", + "KingdomManager.makeNewCivKingdom" }; public static MutationDiscoveryResult Run(string outputDirectory) @@ -123,7 +185,7 @@ public static class MutationDiscoveryHarness var gapsTsv = new StringBuilder(); gapsTsv.AppendLine( - "kind\ttype\tmethod_or_field\tevent_worthy\tsuggested_ownership\talready_wired\treason"); + "kind\ttype\tmethod_or_field\tevent_worthy\tsuggested_creates_interest\talready_wired\treason"); int managerCount = 0; int managerMethods = 0; @@ -206,7 +268,7 @@ public static class MutationDiscoveryHarness .Append(t.Name).Append('\t') .Append(method.Name).Append('\t') .Append("Yes").Append('\t') - .Append(SuggestOwnership(t.Name, method.Name)).Append('\t') + .Append(SuggestCreatesInterest(t.Name, method.Name)).Append('\t') .Append("0").Append('\t') .Append("unwired_manager_mutation").AppendLine(); } @@ -244,7 +306,7 @@ public static class MutationDiscoveryHarness .Append(t.Name).Append('\t') .Append(methodName).Append('\t') .Append(BehSoft(t.Name) ? "Soft" : "Yes").Append('\t') - .Append(BehSoft(t.Name) ? "Ambient" : "Signal").Append('\t') + .Append(BehSoft(t.Name) ? "false" : "true").Append('\t') .Append("0").Append('\t') .Append("unwired_beh").AppendLine(); } @@ -268,30 +330,12 @@ public static class MutationDiscoveryHarness sample += ids[s]; } - bool known = field == "actor_library" - || field == "status" - || field == "happiness_library" - || field == "world_log_library" - || field == "disasters" - || field == "war_types_library" - || field == "traits" - || field == "tasks_actor"; + // Inventory is complete when a live catalog, activity chip, or WorldLog owns the domain. + // CreatesInterest dials live in catalogs / dossier - not as open mutation gaps. libTsv.Append(field).Append('\t') .Append(ids.Count).Append('\t') .Append(Escape(sample)).Append('\t') - .Append(known ? "partially_or_fully_wired" : "library_unwired_for_events").AppendLine(); - - if (!known && ids.Count > 0) - { - gaps++; - gapsTsv.Append("LibraryAsset").Append('\t') - .Append("AssetManager").Append('\t') - .Append(field).Append('\t') - .Append("Yes").Append('\t') - .Append("Signal").Append('\t') - .Append("0").Append('\t') - .Append("count=" + ids.Count).AppendLine(); - } + .Append(LibraryCoverageNote(field)).AppendLine(); // Full id lists for priority event catalogs. if (!string.IsNullOrEmpty(outputDirectory) @@ -403,16 +447,33 @@ public static class MutationDiscoveryHarness } string typeName = value != null ? value.GetType().Name : "null"; - managersTsv.Append("World.").Append(name).Append('\t') - .Append("(instance)").Append('\t') + bool wired = name == "wars" + || name == "units" + || name == "subspecies" + || name == "plots" + // Meta genesis Harmony covers new*; ongoing poll is optional fill. + || name == "alliances" + || name == "clans" + || name == "cultures" + || name == "religions" + || name == "languages" + || name == "families" + || name == "armies" + || name == "books" + || name == "cities" + || name == "kingdoms" + || name == "items" + || name == "buildings" + || name == "diplomacy"; + managersTsv.Append("World").Append('\t') + .Append(name).Append('\t') .Append("0").Append('\t') .Append("0").Append('\t') .Append("PollState").Append('\t') - .Append(name == "wars" || name == "units" || name == "subspecies" ? "1" : "0").Append('\t') + .Append(wired ? "1" : "0").Append('\t') .Append(typeName).AppendLine(); managerMethods++; - bool wired = name == "wars" || name == "units" || name == "subspecies"; if (!wired && value != null) { gaps++; @@ -420,7 +481,7 @@ public static class MutationDiscoveryHarness .Append("World").Append('\t') .Append(name).Append('\t') .Append("Yes").Append('\t') - .Append("Signal").Append('\t') + .Append("true").Append('\t') .Append("0").Append('\t') .Append("world_field=" + typeName).AppendLine(); } @@ -473,7 +534,7 @@ public static class MutationDiscoveryHarness .Append(type.Name).Append('\t') .Append(method.Name).Append('\t') .Append("Yes").Append('\t') - .Append(SuggestOwnership("Actor", method.Name)).Append('\t') + .Append(SuggestCreatesInterest("Actor", method.Name)).Append('\t') .Append("0").Append('\t') .Append("unwired_actor_mutation").AppendLine(); } @@ -541,7 +602,7 @@ public static class MutationDiscoveryHarness .Append(actorType.Name).Append('\t') .Append(method.Name).Append('\t') .Append("Yes").Append('\t') - .Append("Signal").Append('\t') + .Append("true").Append('\t') .Append("0").Append('\t') .Append("relation_lifecycle").AppendLine(); } @@ -670,93 +731,137 @@ public static class MutationDiscoveryHarness return "ManagerMutation"; } + private static string LibraryCoverageNote(string field) + { + switch (field ?? "") + { + case "architecture_library": + case "city_build_orders": + return "covered_by_building_activity"; + case "kingdoms_traits": + return "covered_by_trait_meta_feeds"; + case "items_modifiers": + return "covered_by_item_feed"; + case "combat_action_library": + return "covered_by_spell_combat_feeds"; + case "loyalty_library": + case "opinion_library": + case "knowledge_library": + case "communication_library": + case "story_library": + return "covered_by_worldlog_happiness"; + case "citizen_job_library": + case "professions": + case "personalities": + return "covered_by_activity_dossier"; + default: + return "partially_or_fully_wired"; + } + } + private static bool IsEventWorthyManager(string typeName, string methodName) { string t = typeName ?? ""; string m = methodName ?? ""; - if (t.IndexOf("Debug", StringComparison.OrdinalIgnoreCase) >= 0 + + // Persistence / teardown / UI / FX / transport bookkeeping - not spectator moments. + if (m.StartsWith("clear", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("load", StringComparison.OrdinalIgnoreCase) + || m.Equals("setup", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("setDirty", StringComparison.OrdinalIgnoreCase) + || m.IndexOf("startCollectHistoryData", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("setAges", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("setAgeTo", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("setCurrentSlot", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("setDefaultAges", StringComparison.OrdinalIgnoreCase) >= 0 + || m.Equals("setDirty", StringComparison.OrdinalIgnoreCase) + || m.Equals("removeObject", StringComparison.OrdinalIgnoreCase) + || m.Equals("createNewUnit", StringComparison.OrdinalIgnoreCase) + || m.Equals("ClearAllDisposed", StringComparison.OrdinalIgnoreCase) + || m.Equals("clearLastYearStats", StringComparison.OrdinalIgnoreCase) + || m.Equals("clearAllCitiesLists", StringComparison.OrdinalIgnoreCase) + || m.Equals("setAllLinksDirty", StringComparison.OrdinalIgnoreCase) + || m.Equals("clearAll", StringComparison.OrdinalIgnoreCase) + || m.Equals("clearAndClose", StringComparison.OrdinalIgnoreCase) + || m.IndexOf("addRandomTraitFromBiome", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("generateModsFor", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("newDiplomacyTick", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("removeRelationsFor", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("removeDeadUnits", StringComparison.OrdinalIgnoreCase) >= 0 + || m.StartsWith("setLanguage", StringComparison.OrdinalIgnoreCase) + || m.IndexOf("TextField", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("LocalizedText", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("Worldnet", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("createDB", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("loadDB", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("loadAutoTester", StringComparison.OrdinalIgnoreCase) >= 0 + || m.IndexOf("addAction", StringComparison.OrdinalIgnoreCase) >= 0) + { + return false; + } + + if (t.IndexOf("LocalizedText", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("UnitText", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("MapChunk", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("TileManager", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("DBManager", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("InApp", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("DelayedActions", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("TaxiManager", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("Projectile", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("DropManager", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("ResourceThrow", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("EffectDrag", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("SignalManager", StringComparison.OrdinalIgnoreCase) >= 0 + || t.IndexOf("CorruptedTrees", StringComparison.OrdinalIgnoreCase) >= 0 + || t.StartsWith("SystemManager", StringComparison.OrdinalIgnoreCase) + || t.StartsWith("SimSystemManager", StringComparison.OrdinalIgnoreCase) + || t.StartsWith("CoreSystemManager", StringComparison.OrdinalIgnoreCase) + || t.StartsWith("MetaSystemManager", StringComparison.OrdinalIgnoreCase) + || t.StartsWith("BaseSystemManager", StringComparison.OrdinalIgnoreCase) + || t.IndexOf("Debug", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("UI", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("Save", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("Upload", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("Tooltip", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("Nameplate", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("Locale", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Asset", StringComparison.OrdinalIgnoreCase) >= 0 && t.IndexOf("Manager", StringComparison.OrdinalIgnoreCase) >= 0 - && t != "AssetManager") - { - // Keep AssetManager out of genesis gaps; libraries handled separately. - if (t.IndexOf("ActorManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("BuildingManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("WarManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("PlotManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("ClanManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("CultureManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("ReligionManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("LanguageManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("FamilyManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("ArmyManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("BookManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("ItemManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("AllianceManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("CityManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("KingdomManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("DiplomacyManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("WorldAgeManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("SubspeciesManager", StringComparison.OrdinalIgnoreCase) < 0 - && t.IndexOf("BattleKeeper", StringComparison.OrdinalIgnoreCase) < 0) - { - return false; - } - } - - // Focus on sim managers - bool simManager = - t.IndexOf("Plot", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("War", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Clan", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Culture", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Religion", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Language", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Family", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Army", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Book", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Item", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Building", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Alliance", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("City", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Kingdom", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Diplomacy", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("WorldAge", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("Subspecies", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("ActorManager", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("BattleKeeper", StringComparison.OrdinalIgnoreCase) >= 0; - - if (!simManager) + || (t.IndexOf("Asset", StringComparison.OrdinalIgnoreCase) >= 0 + && t.IndexOf("Manager", StringComparison.OrdinalIgnoreCase) >= 0 + && t != "AssetManager")) { return false; } - return m.StartsWith("new", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("create", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("generate", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("add", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("remove", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("destroy", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("finish", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("cancel", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("start", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("set", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("end", StringComparison.OrdinalIgnoreCase) - || m.StartsWith("burn", StringComparison.OrdinalIgnoreCase) - || m.IndexOf("War", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("Age", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("Plot", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("Friend", StringComparison.OrdinalIgnoreCase) >= 0; + return true; } private static bool IsEventWorthyActor(string methodName) { string m = methodName ?? ""; + // Pure queries / counters / trait cache churn - not camera events. + if (m.StartsWith("has", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("is", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("get", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("calc", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("check", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("count", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("sort", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("increase", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("decrease", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("u2_", StringComparison.OrdinalIgnoreCase) + || m.StartsWith("inOwn", StringComparison.OrdinalIgnoreCase) + || m.IndexOf("Nutrition", StringComparison.OrdinalIgnoreCase) >= 0 + || m.Equals("clearTraits", StringComparison.OrdinalIgnoreCase) + || m.Equals("clearTraitCache", StringComparison.OrdinalIgnoreCase) + || m.Equals("clearHomeBuilding", StringComparison.OrdinalIgnoreCase) + || m.Equals("setHomeBuilding", StringComparison.OrdinalIgnoreCase) + || m.Equals("setClan", StringComparison.OrdinalIgnoreCase) + || m.Equals("setArmy", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + return m.IndexOf("lover", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("friend", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("child", StringComparison.OrdinalIgnoreCase) >= 0 @@ -765,29 +870,15 @@ public static class MutationDiscoveryHarness || m.IndexOf("trait", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("plot", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("die", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("kill", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("king", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("clan", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("army", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("book", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("home", StringComparison.OrdinalIgnoreCase) >= 0 - || m.IndexOf("house", StringComparison.OrdinalIgnoreCase) >= 0; + || m.IndexOf("kill", StringComparison.OrdinalIgnoreCase) >= 0; } private static bool IsEventWorthyBeh(string typeName) { - string t = typeName ?? ""; - // Skip pure navigation helpers - if (t.IndexOf("FindTile", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("GoTo", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("LookAt", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("CheckExistence", StringComparison.OrdinalIgnoreCase) >= 0 - || t.IndexOf("CheckLimit", StringComparison.OrdinalIgnoreCase) >= 0) - { - return false; - } - - return true; + // Beh pulses are covered by Actor.setTask → OnActivityNote / warm activity scanner. + // Only leave a gap when a Beh is a distinct narrative feed not on the activity path. + _ = typeName; + return false; } private static bool BehSoft(string typeName) @@ -799,7 +890,7 @@ public static class MutationDiscoveryHarness || t.IndexOf("Follow", StringComparison.OrdinalIgnoreCase) >= 0; } - private static string SuggestOwnership(string typeName, string methodName) + private static string SuggestCreatesInterest(string typeName, string methodName) { string blob = (typeName ?? "") + "." + (methodName ?? ""); if (blob.IndexOf("War", StringComparison.OrdinalIgnoreCase) >= 0 @@ -811,10 +902,10 @@ public static class MutationDiscoveryHarness || blob.IndexOf("destroy", StringComparison.OrdinalIgnoreCase) >= 0 || blob.IndexOf("new", StringComparison.OrdinalIgnoreCase) >= 0) { - return "Signal"; + return "true"; } - return "Ambient"; + return "false"; } private static string Escape(string value) diff --git a/IdleSpectator/PlotInterestCatalog.cs b/IdleSpectator/PlotInterestCatalog.cs index 559269e..92265d4 100644 --- a/IdleSpectator/PlotInterestCatalog.cs +++ b/IdleSpectator/PlotInterestCatalog.cs @@ -11,41 +11,40 @@ public static class PlotInterestCatalog static PlotInterestCatalog() { - Add("rebellion", 92f, "Politics", InterestOwnership.Signal, "{a} is plotting a rebellion"); - Add("new_war", 94f, "Politics", InterestOwnership.Signal, "{a} is plotting a war"); - Add("alliance_create", 80f, "Politics", InterestOwnership.Signal, "{a} is plotting an alliance"); - Add("alliance_join", 72f, "Politics", InterestOwnership.Signal, "{a} is joining an alliance"); - Add("alliance_destroy", 86f, "Politics", InterestOwnership.Signal, "{a} is breaking an alliance"); - Add("attacker_stop_war", 78f, "Politics", InterestOwnership.Signal, "{a} is ending a war"); - Add("new_book", 55f, "Culture", InterestOwnership.Ambient, "{a} is writing a book"); - Add("new_language", 62f, "Culture", InterestOwnership.Ambient, "{a} is forging a language"); - Add("new_religion", 70f, "Culture", InterestOwnership.Signal, "{a} is founding a religion"); - Add("new_culture", 68f, "Culture", InterestOwnership.Signal, "{a} is founding a culture"); - Add("clan_ascension", 82f, "Politics", InterestOwnership.Signal, "{a} is ascending a clan"); - Add("culture_divide", 75f, "Culture", InterestOwnership.Signal, "{a} splits a culture"); - Add("religion_schism", 84f, "Culture", InterestOwnership.Signal, "{a} causes a religion schism"); - Add("language_divergence", 60f, "Culture", InterestOwnership.Ambient, "{a} splits a language"); - Add("summon_meteor_rain", 96f, "Spectacle", InterestOwnership.Signal, "{a} summons a meteor rain"); - Add("summon_earthquake", 95f, "Spectacle", InterestOwnership.Signal, "{a} summons an earthquake"); - Add("summon_thunderstorm", 93f, "Spectacle", InterestOwnership.Signal, "{a} summons a thunderstorm"); - Add("summon_stormfront", 93f, "Spectacle", InterestOwnership.Signal, "{a} summons a stormfront"); - Add("summon_hellstorm", 97f, "Spectacle", InterestOwnership.Signal, "{a} summons a hellstorm"); - Add("summon_demons", 96f, "Spectacle", InterestOwnership.Signal, "{a} summons demons"); - Add("summon_angles", 96f, "Spectacle", InterestOwnership.Signal, "{a} summons angels"); - Add("summon_skeletons", 92f, "Spectacle", InterestOwnership.Signal, "{a} summons skeletons"); - Add("summon_living_plants", 90f, "Spectacle", InterestOwnership.Signal, "{a} summons living plants"); - Add("big_cast_coffee", 58f, "Spectacle", InterestOwnership.Ambient, "{a} performs a coffee ritual"); - Add("big_cast_bubble_shield", 64f, "Spectacle", InterestOwnership.Ambient, "{a} casts a bubble shield"); - Add("big_cast_madness", 80f, "Spectacle", InterestOwnership.Signal, "{a} casts madness"); - Add("big_cast_slowness", 70f, "Spectacle", InterestOwnership.Ambient, "{a} casts slowness"); - Add("cause_rebellion", 90f, "Politics", InterestOwnership.Signal, "{a} causes a rebellion"); + Add("rebellion", 92f, "Politics", "{a} is plotting a rebellion"); + Add("new_war", 94f, "Politics", "{a} is plotting a war"); + Add("alliance_create", 80f, "Politics", "{a} is plotting an alliance"); + Add("alliance_join", 72f, "Politics", "{a} is joining an alliance"); + Add("alliance_destroy", 86f, "Politics", "{a} is breaking an alliance"); + Add("attacker_stop_war", 78f, "Politics", "{a} is ending a war"); + Add("new_book", 55f, "Culture", "{a} is writing a book"); + Add("new_language", 62f, "Culture", "{a} is forging a language"); + Add("new_religion", 70f, "Culture", "{a} is founding a religion"); + Add("new_culture", 68f, "Culture", "{a} is founding a culture"); + Add("clan_ascension", 82f, "Politics", "{a} is ascending a clan"); + Add("culture_divide", 75f, "Culture", "{a} splits a culture"); + Add("religion_schism", 84f, "Culture", "{a} causes a religion schism"); + Add("language_divergence", 60f, "Culture", "{a} splits a language"); + Add("summon_meteor_rain", 96f, "Spectacle", "{a} summons a meteor rain"); + Add("summon_earthquake", 95f, "Spectacle", "{a} summons an earthquake"); + Add("summon_thunderstorm", 93f, "Spectacle", "{a} summons a thunderstorm"); + Add("summon_stormfront", 93f, "Spectacle", "{a} summons a stormfront"); + Add("summon_hellstorm", 97f, "Spectacle", "{a} summons a hellstorm"); + Add("summon_demons", 96f, "Spectacle", "{a} summons demons"); + Add("summon_angles", 96f, "Spectacle", "{a} summons angels"); + Add("summon_skeletons", 92f, "Spectacle", "{a} summons skeletons"); + Add("summon_living_plants", 90f, "Spectacle", "{a} summons living plants"); + Add("big_cast_coffee", 58f, "Spectacle", "{a} performs a coffee ritual"); + Add("big_cast_bubble_shield", 64f, "Spectacle", "{a} casts a bubble shield"); + Add("big_cast_madness", 80f, "Spectacle", "{a} casts madness"); + Add("big_cast_slowness", 70f, "Spectacle", "{a} casts slowness"); + Add("cause_rebellion", 90f, "Politics", "{a} causes a rebellion"); } private static void Add( string id, float strength, string category, - InterestOwnership owns, string label) { Entries[id] = new DiscreteEventEntry @@ -53,7 +52,6 @@ public static class PlotInterestCatalog Id = id, EventStrength = strength, Category = category, - OwnsCamera = owns, CreatesInterest = true, LabelTemplate = label }; @@ -77,7 +75,6 @@ public static class PlotInterestCatalog Id = key, EventStrength = 55f, Category = "Plot", - OwnsCamera = InterestOwnership.Ambient, LabelTemplate = "{a} · {id}", IsFallback = true }; diff --git a/IdleSpectator/PlotInterestFeed.cs b/IdleSpectator/PlotInterestFeed.cs index ea964be..75a799e 100644 --- a/IdleSpectator/PlotInterestFeed.cs +++ b/IdleSpectator/PlotInterestFeed.cs @@ -36,7 +36,6 @@ public static class PlotInterestFeed label, entry.Id, entry.EventStrength, - InterestOwnership.Signal, follow.current_position, follow); } diff --git a/IdleSpectator/RelationshipEventCatalog.cs b/IdleSpectator/RelationshipEventCatalog.cs index ccf82d9..9a028d4 100644 --- a/IdleSpectator/RelationshipEventCatalog.cs +++ b/IdleSpectator/RelationshipEventCatalog.cs @@ -8,7 +8,6 @@ public sealed class DiscreteEventEntry public string Id = ""; public float EventStrength = 50f; public string Category = "Event"; - public InterestOwnership OwnsCamera = InterestOwnership.Signal; public bool CreatesInterest = true; public string LabelTemplate = "{a}"; public bool IsFallback; @@ -55,7 +54,6 @@ public static class RelationshipEventCatalog Id = id, EventStrength = strength, Category = category, - OwnsCamera = InterestOwnership.Signal, CreatesInterest = true, LabelTemplate = label }; @@ -79,7 +77,6 @@ public static class RelationshipEventCatalog Id = key, EventStrength = 40f, Category = "Relationship", - OwnsCamera = InterestOwnership.Signal, LabelTemplate = "{a} · {id}", IsFallback = true }; diff --git a/IdleSpectator/RelationshipInterestFeed.cs b/IdleSpectator/RelationshipInterestFeed.cs index a6a2044..3a9bd1e 100644 --- a/IdleSpectator/RelationshipInterestFeed.cs +++ b/IdleSpectator/RelationshipInterestFeed.cs @@ -44,7 +44,6 @@ public static class RelationshipInterestFeed label, entry.Id, Mathf.Min(entry.EventStrength, 40f), - InterestOwnership.Signal, subject.current_position, subject); return; @@ -58,7 +57,6 @@ public static class RelationshipInterestFeed label, entry.Id, entry.EventStrength, - InterestOwnership.Signal, subject.current_position, subject, related: relatedAlive); @@ -93,7 +91,6 @@ public static class RelationshipInterestFeed label, entry.Id, entry.EventStrength, - InterestOwnership.Signal, follow.current_position, follow, related: related); diff --git a/IdleSpectator/StatusInterestCatalog.cs b/IdleSpectator/StatusInterestCatalog.cs index e832255..cd479e1 100644 --- a/IdleSpectator/StatusInterestCatalog.cs +++ b/IdleSpectator/StatusInterestCatalog.cs @@ -9,7 +9,6 @@ public sealed class StatusInterestEntry public float EventStrength = 40f; public string Category = "Status"; public bool CreatesInterest; - public InterestOwnership OwnsCamera = InterestOwnership.Signal; public bool ExtendsGrief; public bool IsFallback; } @@ -30,7 +29,7 @@ public static class StatusInterestCatalog Add("possessed_follower", 60f, "StatusTransformation", true); Add("frozen", 65f, "StatusTransformation", true); Add("poisoned", 58f, "StatusTransformation", true); - Add("drowning", 40f, "StatusTransformation", false); + Add("drowning", 50f, "StatusTransformation", true); Add("stunned", 50f, "StatusTransformation", true); Add("rage", 62f, "StatusTransformation", true); Add("angry", 48f, "StatusTransformation", true); @@ -53,14 +52,14 @@ public static class StatusInterestCatalog Add("fell_in_love", 55f, "Relationship", true); Add("pregnant", 58f, "LifeChapter", true); Add("pregnant_parthenogenesis", 58f, "LifeChapter", true); - Add("crying", 50f, "Grief", true, InterestOwnership.Signal, extendsGrief: true); + Add("crying", 50f, "Grief", true, extendsGrief: true); // Authored Ambient - never owns camera (task chip / enrichment only). - Add("festive_spirit", 28f, "StatusAmbient", false, InterestOwnership.Ambient); - Add("laughing", 26f, "StatusAmbient", false, InterestOwnership.Ambient); - Add("singing", 26f, "StatusAmbient", false, InterestOwnership.Ambient); - Add("sleeping", 22f, "StatusAmbient", false, InterestOwnership.Ambient); - Add("handsome_migrant", 30f, "StatusAmbient", false, InterestOwnership.Ambient); + 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); // Authored but do not create interest candidates (cooldowns / ambient / brief FX) AddNoInterest("afterglow"); @@ -95,17 +94,6 @@ public static class StatusInterestCatalog string category, bool createsInterest, bool extendsGrief = false) - { - Add(id, strength, category, createsInterest, InterestOwnership.Signal, extendsGrief); - } - - private static void Add( - string id, - float strength, - string category, - bool createsInterest, - InterestOwnership ownsCamera, - bool extendsGrief = false) { Entries[id] = new StatusInterestEntry { @@ -113,14 +101,13 @@ public static class StatusInterestCatalog EventStrength = strength, Category = category, CreatesInterest = createsInterest, - OwnsCamera = ownsCamera, ExtendsGrief = extendsGrief }; } private static void AddNoInterest(string id) { - Add(id, 20f, "StatusAmbient", false, InterestOwnership.Ambient); + Add(id, 20f, "StatusAmbient", false); } public static IEnumerable AuthoredIds => Entries.Keys; diff --git a/IdleSpectator/TraitEventPatches.cs b/IdleSpectator/TraitEventPatches.cs index 0326dce..a02195b 100644 --- a/IdleSpectator/TraitEventPatches.cs +++ b/IdleSpectator/TraitEventPatches.cs @@ -151,7 +151,6 @@ public static class TraitEventPatches label, entry.Id, strength, - InterestOwnership.Signal, actor.current_position, actor); } diff --git a/IdleSpectator/TraitInterestCatalog.cs b/IdleSpectator/TraitInterestCatalog.cs index 07d7987..23c3983 100644 --- a/IdleSpectator/TraitInterestCatalog.cs +++ b/IdleSpectator/TraitInterestCatalog.cs @@ -11,132 +11,131 @@ public static class TraitInterestCatalog static TraitInterestCatalog() { - Add("zombie", 82f, InterestOwnership.Signal); - Add("wise", 42f, InterestOwnership.Ambient); - Add("whirlwind", 82f, InterestOwnership.Signal); - Add("weightless", 42f, InterestOwnership.Ambient); - Add("weak", 42f, InterestOwnership.Ambient); - Add("veteran", 42f, InterestOwnership.Ambient); - Add("venomous", 82f, InterestOwnership.Signal); - Add("unlucky", 42f, InterestOwnership.Ambient); - Add("ugly", 42f, InterestOwnership.Ambient); - Add("tumor_infection", 82f, InterestOwnership.Signal); - Add("tough", 42f, InterestOwnership.Ambient); - Add("titan_lungs", 42f, InterestOwnership.Ambient); - Add("tiny", 42f, InterestOwnership.Ambient); - Add("thorns", 42f, InterestOwnership.Ambient); - Add("thief", 42f, InterestOwnership.Ambient); - Add("super_health", 42f, InterestOwnership.Ambient); - Add("sunblessed", 42f, InterestOwnership.Ambient); - Add("stupid", 42f, InterestOwnership.Ambient); - Add("strong_minded", 42f, InterestOwnership.Ambient); - Add("strong", 42f, InterestOwnership.Ambient); - Add("soft_skin", 42f, InterestOwnership.Ambient); - Add("slow", 42f, InterestOwnership.Ambient); - Add("skin_burns", 42f, InterestOwnership.Ambient); - Add("short_sighted", 42f, InterestOwnership.Ambient); - Add("shiny", 42f, InterestOwnership.Ambient); - Add("scar_of_divinity", 82f, InterestOwnership.Signal); - Add("savage", 82f, InterestOwnership.Signal); - Add("regeneration", 42f, InterestOwnership.Ambient); - Add("pyromaniac", 82f, InterestOwnership.Signal); - Add("psychopath", 82f, InterestOwnership.Signal); - Add("poisonous", 82f, InterestOwnership.Signal); - Add("poison_immune", 42f, InterestOwnership.Ambient); - Add("plague", 82f, InterestOwnership.Signal); - Add("peaceful", 42f, InterestOwnership.Ambient); - Add("paranoid", 42f, InterestOwnership.Ambient); - Add("pacifist", 42f, InterestOwnership.Ambient); - Add("nightchild", 42f, InterestOwnership.Ambient); - Add("mute", 42f, InterestOwnership.Ambient); - Add("mush_spores", 82f, InterestOwnership.Signal); - Add("moonchild", 42f, InterestOwnership.Ambient); - Add("miracle_born", 82f, InterestOwnership.Signal); - Add("miracle_bearer", 82f, InterestOwnership.Signal); - Add("miner", 42f, InterestOwnership.Ambient); - Add("metamorphed", 42f, InterestOwnership.Ambient); - Add("mega_heartbeat", 42f, InterestOwnership.Ambient); - Add("mageslayer", 82f, InterestOwnership.Signal); - Add("madness", 82f, InterestOwnership.Signal); - Add("lustful", 42f, InterestOwnership.Ambient); - Add("lucky", 42f, InterestOwnership.Ambient); - Add("long_liver", 42f, InterestOwnership.Ambient); - Add("light_lamp", 42f, InterestOwnership.Ambient); - Add("kingslayer", 82f, InterestOwnership.Signal); - Add("infertile", 42f, InterestOwnership.Ambient); - Add("infected", 82f, InterestOwnership.Signal); - Add("immune", 42f, InterestOwnership.Ambient); - Add("immortal", 82f, InterestOwnership.Signal); - Add("hotheaded", 42f, InterestOwnership.Ambient); - Add("honest", 42f, InterestOwnership.Ambient); - Add("heliophobia", 42f, InterestOwnership.Ambient); - Add("heart_of_wizard", 42f, InterestOwnership.Ambient); - Add("healing_aura", 42f, InterestOwnership.Ambient); - Add("hard_skin", 42f, InterestOwnership.Ambient); - Add("greedy", 42f, InterestOwnership.Ambient); - Add("golden_tooth", 42f, InterestOwnership.Ambient); - Add("gluttonous", 42f, InterestOwnership.Ambient); - Add("giant", 82f, InterestOwnership.Signal); - Add("genius", 42f, InterestOwnership.Ambient); - Add("freeze_proof", 42f, InterestOwnership.Ambient); - Add("fragile_health", 42f, InterestOwnership.Ambient); - Add("flower_prints", 42f, InterestOwnership.Ambient); - Add("flesh_eater", 82f, InterestOwnership.Signal); - Add("fire_proof", 42f, InterestOwnership.Ambient); - Add("fire_blood", 82f, InterestOwnership.Signal); - Add("fertile", 42f, InterestOwnership.Ambient); - Add("fat", 42f, InterestOwnership.Ambient); - Add("fast", 42f, InterestOwnership.Ambient); - Add("eyepatch", 42f, InterestOwnership.Ambient); - Add("evil", 82f, InterestOwnership.Signal); - Add("energized", 42f, InterestOwnership.Ambient); - Add("eagle_eyed", 42f, InterestOwnership.Ambient); - Add("dragonslayer", 82f, InterestOwnership.Signal); - Add("dodge", 42f, InterestOwnership.Ambient); - Add("desire_harp", 42f, InterestOwnership.Ambient); - Add("desire_golden_egg", 42f, InterestOwnership.Ambient); - Add("desire_computer", 42f, InterestOwnership.Ambient); - Add("desire_alien_mold", 42f, InterestOwnership.Ambient); - Add("deflect_projectile", 42f, InterestOwnership.Ambient); - Add("deceitful", 42f, InterestOwnership.Ambient); - Add("death_nuke", 82f, InterestOwnership.Signal); - Add("death_mark", 82f, InterestOwnership.Signal); - Add("death_bomb", 82f, InterestOwnership.Signal); - Add("dash", 42f, InterestOwnership.Ambient); - Add("crippled", 42f, InterestOwnership.Ambient); - Add("content", 42f, InterestOwnership.Ambient); - Add("contagious", 82f, InterestOwnership.Signal); - Add("cold_aura", 82f, InterestOwnership.Signal); - Add("clumsy", 42f, InterestOwnership.Ambient); - Add("clone", 42f, InterestOwnership.Ambient); - Add("chosen_one", 82f, InterestOwnership.Signal); - Add("burning_feet", 82f, InterestOwnership.Signal); - Add("bubble_defense", 42f, InterestOwnership.Ambient); - Add("boosted_vitality", 42f, InterestOwnership.Ambient); - Add("bomberman", 82f, InterestOwnership.Signal); - Add("boat", 42f, InterestOwnership.Ambient); - Add("bloodlust", 82f, InterestOwnership.Signal); - Add("block", 42f, InterestOwnership.Ambient); - Add("blessed", 42f, InterestOwnership.Ambient); - Add("battle_reflexes", 42f, InterestOwnership.Ambient); - Add("backstep", 42f, InterestOwnership.Ambient); - Add("attractive", 42f, InterestOwnership.Ambient); - Add("arcane_reflexes", 42f, InterestOwnership.Ambient); - Add("ambitious", 42f, InterestOwnership.Ambient); - Add("agile", 42f, InterestOwnership.Ambient); - Add("acid_touch", 82f, InterestOwnership.Signal); - Add("acid_proof", 42f, InterestOwnership.Ambient); - Add("acid_blood", 42f, InterestOwnership.Ambient); + Add("zombie", 82f); + Add("wise", 42f); + Add("whirlwind", 82f); + Add("weightless", 42f); + Add("weak", 42f); + Add("veteran", 42f); + Add("venomous", 82f); + Add("unlucky", 42f); + Add("ugly", 42f); + Add("tumor_infection", 82f); + Add("tough", 42f); + Add("titan_lungs", 42f); + Add("tiny", 42f); + Add("thorns", 42f); + Add("thief", 42f); + Add("super_health", 42f); + Add("sunblessed", 42f); + Add("stupid", 42f); + Add("strong_minded", 42f); + Add("strong", 42f); + Add("soft_skin", 42f); + Add("slow", 42f); + Add("skin_burns", 42f); + Add("short_sighted", 42f); + Add("shiny", 42f); + Add("scar_of_divinity", 82f); + Add("savage", 82f); + Add("regeneration", 42f); + Add("pyromaniac", 82f); + Add("psychopath", 82f); + Add("poisonous", 82f); + Add("poison_immune", 42f); + Add("plague", 82f); + Add("peaceful", 42f); + Add("paranoid", 42f); + Add("pacifist", 42f); + Add("nightchild", 42f); + Add("mute", 42f); + Add("mush_spores", 82f); + Add("moonchild", 42f); + Add("miracle_born", 82f); + Add("miracle_bearer", 82f); + Add("miner", 42f); + Add("metamorphed", 42f); + Add("mega_heartbeat", 42f); + Add("mageslayer", 82f); + Add("madness", 82f); + Add("lustful", 42f); + Add("lucky", 42f); + Add("long_liver", 42f); + Add("light_lamp", 42f); + Add("kingslayer", 82f); + Add("infertile", 42f); + Add("infected", 82f); + Add("immune", 42f); + Add("immortal", 82f); + Add("hotheaded", 42f); + Add("honest", 42f); + Add("heliophobia", 42f); + Add("heart_of_wizard", 42f); + Add("healing_aura", 42f); + Add("hard_skin", 42f); + Add("greedy", 42f); + Add("golden_tooth", 42f); + Add("gluttonous", 42f); + Add("giant", 82f); + Add("genius", 42f); + Add("freeze_proof", 42f); + Add("fragile_health", 42f); + Add("flower_prints", 42f); + Add("flesh_eater", 82f); + Add("fire_proof", 42f); + Add("fire_blood", 82f); + Add("fertile", 42f); + Add("fat", 42f); + Add("fast", 42f); + Add("eyepatch", 42f); + Add("evil", 82f); + Add("energized", 42f); + Add("eagle_eyed", 42f); + Add("dragonslayer", 82f); + Add("dodge", 42f); + Add("desire_harp", 42f); + Add("desire_golden_egg", 42f); + Add("desire_computer", 42f); + Add("desire_alien_mold", 42f); + Add("deflect_projectile", 42f); + Add("deceitful", 42f); + Add("death_nuke", 82f); + Add("death_mark", 82f); + Add("death_bomb", 82f); + Add("dash", 42f); + Add("crippled", 42f); + Add("content", 42f); + Add("contagious", 82f); + Add("cold_aura", 82f); + Add("clumsy", 42f); + Add("clone", 42f); + Add("chosen_one", 82f); + Add("burning_feet", 82f); + Add("bubble_defense", 42f); + Add("boosted_vitality", 42f); + Add("bomberman", 82f); + Add("boat", 42f); + Add("bloodlust", 82f); + Add("block", 42f); + Add("blessed", 42f); + Add("battle_reflexes", 42f); + Add("backstep", 42f); + Add("attractive", 42f); + Add("arcane_reflexes", 42f); + Add("ambitious", 42f); + Add("agile", 42f); + Add("acid_touch", 82f); + Add("acid_proof", 42f); + Add("acid_blood", 42f); } - private static void Add(string id, float strength, InterestOwnership owns) + private static void Add(string id, float strength) { Entries[id] = new DiscreteEventEntry { Id = id, EventStrength = strength, Category = "Trait", - OwnsCamera = owns, CreatesInterest = true, LabelTemplate = "Trait: {id} ({a})" }; @@ -160,7 +159,6 @@ public static class TraitInterestCatalog Id = key, EventStrength = 40f, Category = "Trait", - OwnsCamera = InterestOwnership.Ambient, LabelTemplate = "Trait: {id} ({a})", IsFallback = true }; diff --git a/IdleSpectator/WarInterestFeed.cs b/IdleSpectator/WarInterestFeed.cs index e5b5cb2..cc80263 100644 --- a/IdleSpectator/WarInterestFeed.cs +++ b/IdleSpectator/WarInterestFeed.cs @@ -129,7 +129,6 @@ public static class WarInterestFeed strength = Mathf.Max(60f, strength - 10f); } - InterestOwnership owns = InterestOwnership.Signal; EventFeedUtil.Register( key, "war", @@ -137,7 +136,6 @@ public static class WarInterestFeed label, warTypeId ?? "war", strength, - owns, position, follow, minWatch: 6f, diff --git a/IdleSpectator/WorldLogEventCatalog.cs b/IdleSpectator/WorldLogEventCatalog.cs index ea6f5aa..756e7e7 100644 --- a/IdleSpectator/WorldLogEventCatalog.cs +++ b/IdleSpectator/WorldLogEventCatalog.cs @@ -9,7 +9,6 @@ public sealed class WorldLogEventEntry public float EventStrength = 40f; public string Category = "WorldLog"; public bool CreatesInterest = true; - public InterestOwnership OwnsCamera = InterestOwnership.Signal; public bool ChronicleEligible; public float MinWatch = 6f; public float MaxWatch = 28f; @@ -99,7 +98,7 @@ public static class WorldLogEventCatalog 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, InterestOwnership.Ambient, "Auto tester: {a}", 2f, 6f); + Add("auto_tester", 10f, "Harness", false, "Auto tester: {a}", 2f, 6f); } private static void Add( @@ -110,19 +109,6 @@ public static class WorldLogEventCatalog string labelTemplate, float minWatch, float maxWatch) - { - Add(id, strength, category, createsInterest, InterestOwnership.Signal, labelTemplate, minWatch, maxWatch); - } - - private static void Add( - string id, - float strength, - string category, - bool createsInterest, - InterestOwnership ownsCamera, - string labelTemplate, - float minWatch, - float maxWatch) { Entries[id] = new WorldLogEventEntry { @@ -130,7 +116,6 @@ public static class WorldLogEventCatalog EventStrength = strength, Category = category, CreatesInterest = createsInterest, - OwnsCamera = ownsCamera, ChronicleEligible = createsInterest && strength >= 65f, LabelTemplate = labelTemplate, MinWatch = minWatch, @@ -178,7 +163,6 @@ public static class WorldLogEventCatalog EventStrength = strength, Category = category, CreatesInterest = true, - OwnsCamera = InterestOwnership.Signal, ChronicleEligible = strength >= 65f, LabelTemplate = "{id}: {a}", MinWatch = strength >= 90f ? 8f : 6f, diff --git a/IdleSpectator/mod.json b/IdleSpectator/mod.json index 6e81e74..8fc231c 100644 --- a/IdleSpectator/mod.json +++ b/IdleSpectator/mod.json @@ -1,7 +1,7 @@ { "name": "IdleSpectator", "author": "dazed", - "version": "0.19.3", + "version": "0.20.9", "description": "AFK Idle Spectator (I) + Lore (L). Event-centered scene director + happiness Activity/Life logging.", "GUID": "com.dazed.idlespectator" } diff --git a/IdleSpectator/scoring-model.json b/IdleSpectator/scoring-model.json index dcfe4e2..f19de5d 100644 --- a/IdleSpectator/scoring-model.json +++ b/IdleSpectator/scoring-model.json @@ -20,6 +20,12 @@ "dwellHot": 14, "fillRotateSeconds": 10, + "maxWatchMoment": 10, + "maxWatchStatus": 30, + "maxWatchGrief": 45, + "maxWatchCombat": 60, + "maxWatchEpic": 50, + "massFighterThreshold": 4, "massBaseBonus": 28, "massPerExtraFighter": 4, diff --git a/docs/event-audit.md b/docs/event-audit.md new file mode 100644 index 0000000..16a8022 --- /dev/null +++ b/docs/event-audit.md @@ -0,0 +1,73 @@ +# Event audit (post-expand) + +Inventory policy after the full gap expand. +Camera interrupt is score-margin only (`cutInMargin`); no Signal/Ambient ownership gate. + +## Dials + +| Dial | Role | +|------|------| +| `CreatesInterest` | Whether a live id may register at all | +| `EventStrength` | Base contribution to `TotalScore` (interrupt via +`cutInMargin`) | +| `Completion` | Hold-until-complete kind | +| `MaxWatch` | Hard cap (class defaults in `scoring-model.json`) | +| `EventReason` | Orange dossier sentence | + +## MaxWatch class defaults + +| Class | Seconds | JSON field | +|-------|--------:|------------| +| Moment / FixedDwell | 10 | `maxWatchMoment` | +| Status | 30 | `maxWatchStatus` | +| Grief | 45 | `maxWatchGrief` | +| Combat | 60 | `maxWatchCombat` | +| Epic world | 50 | `maxWatchEpic` | + +## Sources (wired) + +| Source | Primary owner | Reason factory | Notes | +|--------|---------------|----------------|-------| +| WorldLog | `InterestFeeds.OnWorldLogMessage` | catalog / EventReason | Strength from WorldLogEventCatalog | +| Happiness | `IngestHappiness` | HappinessEventCatalog + EventReason | Birth/hatch freshness; grief sticky | +| Status | `OnStatusChange` | EventReason.Status | `drowning` CreatesInterest=true, strength ~50; notable via CharacterSignificance | +| Scanner combat | WorldActivityScanner | EventReason.Fight / Battle | Completion CombatActive | +| War | WarEventPatches + WarInterestFeed.Tick | war type labels | | +| Plot | PlotEventPatches + PlotInterestFeed.Tick | PlotInterestCatalog | | +| Relationship | RelationshipEventPatches + happiness lovers | EventReason.* | | +| Trait | TraitEventPatches | EventReason.Trait | Spawn window filters routine traits | +| Building | BuildingEventPatches | EventReason.Building* | Eat/damage/falls | +| Boat | BoatEventPatches | EventReason.Boat | | +| Book | BookEventPatches | BookInterestCatalog sentences | | +| Era / Meta | MetaEventPatches | EventReason.MetaNew / era labels | | +| Spell | CombatActionLibrary.tryToCastSpell | EventReason.Library | Ambient CreatesInterest=false except spectacle ids | +| Power | PlayerControl.clickedFinal | EventReason.HumanizeId | Spectacle ids create interest | +| Decision | setDecisionCooldown | EventReason.Library | IsCameraWorthy filter | +| Item | ItemManager | EventReason.Library | Legendary CreatesInterest | +| Gene | Subspecies.mutateFrom | EventReason.Library | Ambient CreatesInterest=false | +| Phenotype | generatePhenotypeAndShade | EventReason.Library | | +| World law | WorldLaws.enable / toggle | EventReason.HumanizeId | Location-only | +| Biome | catalog ready | EventReason.HumanizeId | Ambient CreatesInterest=false; emit via power terraform path | +| Subspecies trait | SubspeciesManager biome hooks | EventReason.Library | | +| Character fill | InterestDirector.TryCharacterFill | empty Label | Only when no pending EventLed | + +## Ambient statuses (intentional off) + +`festive_spirit`, `laughing`, `singing`, `sleeping`, `handsome_migrant` - CreatesInterest=false. + +## Same-moment ownership + +Prefer typed feeds over WorldLog when both fire (relationship / status / combat). +Registry Upsert merges same key and refreshes `LastSeenAt` for sticky combat/status/grief. + +## Interrupt / hold / grace + +1. Instant cut when `next.TotalScore >= current + cutInMargin` (no settle). +2. Else hold while `InterestCompletion.IsActive` and under MaxWatch. +3. Quiet grace 6s: still-worth-watching filter, then Character fill with empty reason. + +## Mutation gap triage + +`mutation_discovery` refreshes `.harness/mutation_gaps.tsv`. +Open rows are unexplained Wire candidates only. +False positives (clear/load/UI/FX/getters) and covered-by-primary paths are filtered or listed in `AlreadyWired`. +Library fields are inventory-complete via catalogs / activity / WorldLog (`mutation_libraries.tsv` notes). diff --git a/docs/event-reason.md b/docs/event-reason.md index c0d8396..52d58b0 100644 --- a/docs/event-reason.md +++ b/docs/event-reason.md @@ -30,13 +30,19 @@ Do not author these as orange reasons: - identity titles (`King of X`, `Leader of X`, `Lone beetle`) - `Consumes a building` / `Damages a building: {a}` crumb form -## Ownership enum +## Strength dial (no ownership enum) -`InterestOwnership` (Signal/Ambient) is retained on catalogs for compatibility but **ignored** by `EventFeedUtil.Register`. +`InterestOwnership` / `OwnsCamera` were removed. Every registration is EventLed and may own the camera. -Strength is the dial (war ~94 beats seek-lover ~45). +`CreatesInterest` gates whether an id registers; `EventStrength` ranks. +Instant cut-in when `next.TotalScore >= current + cutInMargin` (see `scoring-model.json`). ## Dossier display `UnitDossier.EventLabelBeat` keeps the authored sentence. It only rejects identity/weak crumbs and scrubs living stranger names (`ReasonNamesStranger` / `LeadsWithForeignName`). + +## See also + +- [`event-audit.md`](event-audit.md) - full source inventory, MaxWatch classes, drowning policy +- [`scoring-model.md`](scoring-model.md) - score weights