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