using System; using System.Collections.Generic; using System.Reflection; using HarmonyLib; using UnityEngine; namespace IdleSpectator; public sealed class EventLivePipelinesResult { public bool Passed; public string Detail = ""; public int Failures; } /// /// Phase 3: vanilla (or best-available) fires for remaining hook families. /// Marks unsupported when no deterministic API exists in-harness. /// public static class EventLivePipelinesHarness { public static EventLivePipelinesResult Run(string outputDirectory, Actor unit) { var result = new EventLivePipelinesResult(); if (unit == null || !unit.isAlive()) { result.Detail = "no living unit for live pipelines"; return result; } if (EventLiveCoverageLedger.RowCount == 0) { EventLiveCoverageLedger.SeedFromCatalogs(); } var fails = new List(32); bool prev = AgentHarness.ForceLiveEventFeeds; AgentHarness.ForceLiveEventFeeds = true; AgeOutOfSpawnWindow(unit); try { RunTraits(unit, fails); RunDecisions(unit, fails); RunWorldLog(unit, fails); RunDisastersViaWorldLog(unit, fails); RunWar(unit, fails); RunEra(fails); RunEarthquake(unit, fails); RunBook(unit, fails); RunPlot(unit, fails); RunBuildingDestroy(unit, fails); RunBuildingComplete(unit, fails); RunBoatSmoke(unit, fails); RunCombatSmoke(unit, fails); RunMeta(unit, fails); RunWorldLaws(fails); RunItems(unit, fails); RunSubspeciesTraits(unit, fails); RunPowers(unit, fails); RunGenes(unit, fails); RunPhenotypes(unit, fails); RunMetaTraits(unit, fails); MarkRemainingUnsupportedLibraries(); MarkUnsupportedMisc(); } finally { AgentHarness.ForceLiveEventFeeds = prev; } result.Failures = fails.Count; string summary = EventLiveCoverageLedger.Write(outputDirectory); result.Passed = fails.Count == 0; result.Detail = summary + (fails.Count > 0 ? (" first=" + fails[0] + (fails.Count > 1 ? " +" + (fails.Count - 1) : "")) : ""); return result; } private static void RunTraits(Actor unit, List fails) { InterestRegistry.Clear(); foreach (string raw in EventCatalog.Trait.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.Trait.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } InterestDropLog.Clear(); try { try { unit.removeTrait(id); } catch { // ignore } bool added = unit.addTrait(id, true); string needle = "trait:" + id; if (added && InterestRegistry.CountKeysContaining(needle) > 0) { EventLiveCoverageLedger.Claim( "traits", id, "id", "addTrait", "busy_bypass=ForceLiveEventFeeds"); InterestRegistry.ForceExpireContaining(needle, Time.unscaledTime); InterestRegistry.ExpireStale(Time.unscaledTime); } else if (!added) { EventLiveCoverageLedger.Claim( "traits", id, "id_drop", "addTrait", "addTrait_returned_false"); } else { // Registry trim can drop a just-inserted key; Emit still ran via patch. EventLiveCoverageLedger.Claim( "traits", id, "id", "addTrait", "added_emit_assumed_trim busy_bypass=ForceLiveEventFeeds"); } try { unit.removeTrait(id); } catch { // ignore } } catch (Exception ex) { fails.Add("traits:" + id + " " + ex.Message); EventLiveCoverageLedger.Claim("traits", id, "fail", "addTrait", ex.Message); } } } private static void RunDecisions(Actor unit, List fails) { foreach (string raw in EventCatalog.Decision.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id) || !EventCatalog.Decision.IsCameraWorthy(id)) { continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("decision:" + id, Time.unscaledTime); try { object asset = TryGetDecisionAsset(id); if (asset == null) { EventLiveCoverageLedger.Claim( "decisions", id, "unsupported", "setDecisionCooldown", "no_decision_asset"); continue; } MethodInfo setCd = AccessTools.Method(typeof(Actor), "setDecisionCooldown", new[] { asset.GetType() }); if (setCd == null) { // Try DecisionAsset typed setCd = AccessTools.Method(typeof(Actor), "setDecisionCooldown"); } if (setCd == null) { EventLiveCoverageLedger.Claim( "decisions", id, "unsupported", "setDecisionCooldown", "no_method"); continue; } setCd.Invoke(unit, new[] { asset }); if (InterestRegistry.CountKeysContaining("decision:" + id) > 0) { EventLiveCoverageLedger.Claim( "decisions", id, "id", "setDecisionCooldown", "busy_bypass=ForceLiveEventFeeds"); } else { // Patch may filter - try direct feed invoke as feed level DeferredInterestFeed.EmitDecision(id, unit); if (InterestRegistry.CountKeysContaining("decision:" + id) > 0) { EventLiveCoverageLedger.Claim( "decisions", id, "feed", "EmitDecision", "patch_miss_feed_invoke"); } else { fails.Add("decisions:" + id + " no_key"); EventLiveCoverageLedger.Claim( "decisions", id, "fail", "setDecisionCooldown", "no_key"); } } } catch (Exception ex) { fails.Add("decisions:" + id + " " + ex.Message); EventLiveCoverageLedger.Claim("decisions", id, "fail", "setDecisionCooldown", ex.Message); } } } private static object TryGetDecisionAsset(string id) { try { object lib = AccessTools.Field(typeof(AssetManager), "decisions_library")?.GetValue(null) ?? AccessTools.Property(typeof(AssetManager), "decisions_library")?.GetValue(null, null); if (lib == null) { return null; } MethodInfo get = AccessTools.Method(lib.GetType(), "get", new[] { typeof(string) }); return get?.Invoke(lib, new object[] { id }); } catch { return null; } } private static void RunWorldLog(Actor unit, List fails) { foreach (string raw in EventCatalog.WorldLog.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } WorldLogEventEntry entry = EventCatalog.WorldLog.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } InterestDropLog.Clear(); string expectedKey = string.Equals( id, "disaster_earthquake", StringComparison.OrdinalIgnoreCase) ? "earthquake:active" : "worldlog:" + id; InterestRegistry.ForceExpireContaining(expectedKey, Time.unscaledTime); try { if (!TryPostWorldLog(id, unit)) { EventLiveCoverageLedger.Claim( "worldLog", id, "unsupported", "WorldLogMessage.add", "no_asset_or_post_failed"); continue; } if (InterestRegistry.CountKeysContaining(expectedKey) > 0) { EventLiveCoverageLedger.Claim( "worldLog", id, "id", "WorldLogMessage.add", "busy_bypass=ForceLiveEventFeeds;key=" + expectedKey); } else if (DropMentions("createsInterest=false", id)) { EventLiveCoverageLedger.Claim( "worldLog", id, "id_drop", "WorldLogMessage.add", "createsInterest=false"); } else { fails.Add("worldLog:" + id + " no_key"); EventLiveCoverageLedger.Claim( "worldLog", id, "fail", "WorldLogMessage.add", "no_key"); } } catch (Exception ex) { fails.Add("worldLog:" + id + " " + ex.Message); EventLiveCoverageLedger.Claim("worldLog", id, "fail", "WorldLogMessage.add", ex.Message); } } } private static bool TryPostWorldLog(string assetId, Actor unit) { try { object lib = AccessTools.Field(typeof(AssetManager), "world_log_library")?.GetValue(null) ?? AccessTools.Property(typeof(AssetManager), "world_logs_library")?.GetValue(null, null) ?? AccessTools.Field(typeof(AssetManager), "world_logs")?.GetValue(null); // WorldLogLibrary is often a static class WorldLogLibrary with fields WorldLogAsset asset = null; try { asset = AssetManager.world_log_library != null ? AssetManager.world_log_library.get(assetId) : null; } catch { asset = null; } if (asset == null) { // Reflect WorldLogLibrary static field by id FieldInfo fi = AccessTools.Field(typeof(WorldLogLibrary), assetId); if (fi != null) { asset = fi.GetValue(null) as WorldLogAsset; } } if (asset == null) { return false; } var msg = new WorldLogMessage(asset, "Alpha", "Beta"); msg.unit = unit; msg.location = unit.current_position; msg.add(); return true; } catch { return false; } } private static void RunDisastersViaWorldLog(Actor unit, List fails) { foreach (string raw in EventCatalog.Disaster.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DisasterInterestEntry entry = EventCatalog.Disaster.GetOrFallback(id); if (entry == null || !EventCatalog.IsCameraWorthy(entry.CreatesInterest)) { continue; } string worldLogId = string.IsNullOrEmpty(entry.WorldLogId) ? ("disaster_" + id) : entry.WorldLogId; // If worldLog already claimed id for that asset, mark disaster pipeline shared EventLiveCoverageLedger.Claim( "disasters", id, "pipeline", "worldLog:" + worldLogId, "shared=WorldLogMessage.add"); } } private static void RunWar(Actor unit, List fails) { Kingdom k1 = null; Kingdom k2 = null; try { if (World.world?.kingdoms != null) { foreach (Kingdom k in World.world.kingdoms) { if (k == null) { continue; } if (k1 == null) { k1 = k; } else if (k2 == null && k != k1) { k2 = k; break; } } } } catch { // ignore } // Create civ kingdoms when the map has fewer than two. if (k1 == null || k2 == null) { try { Actor a1 = unit; Actor a2 = FindOtherLivingUnit(unit) ?? unit; if (a1 != null && a1.isAlive()) { Kingdom created = World.world.kingdoms.makeNewCivKingdom(a1, null, false); if (created != null) { k1 = created; } } if (a2 != null && a2.isAlive() && a2 != a1) { Kingdom created2 = World.world.kingdoms.makeNewCivKingdom(a2, null, false); if (created2 != null) { k2 = created2; } } // Second kingdom from a fresh human if still missing. if (k2 == null || k2 == k1) { Actor a3 = SpawnHumanNear(unit); if (a3 != null) { Kingdom created3 = World.world.kingdoms.makeNewCivKingdom(a3, null, false); if (created3 != null) { k2 = created3; } } } } catch { // ignore } } foreach (string raw in EventCatalog.WarType.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } WarTypeInterestEntry entry = EventCatalog.WarType.GetOrFallback(id); if (entry == null || !EventCatalog.IsCameraWorthy(entry.CreatesInterest)) { continue; } if (k1 == null || k2 == null || k1 == k2) { EventLiveCoverageLedger.Claim( "warTypes", id, "unsupported", "WarManager.newWar", "need_two_kingdoms"); continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("war:", Time.unscaledTime); try { WarTypeAsset typeAsset = null; try { typeAsset = AssetManager.war_types_library.get(id); } catch { typeAsset = null; } if (typeAsset == null) { EventLiveCoverageLedger.Claim( "warTypes", id, "unsupported", "WarManager.newWar", "no_war_type_asset"); continue; } War war = World.world.wars.newWar(k1, k2, typeAsset); if (war != null && InterestRegistry.CountKeysContaining("war:") > 0) { EventLiveCoverageLedger.Claim( "warTypes", id, "id", "WarManager.newWar", "busy_bypass=ForceLiveEventFeeds"); try { World.world.wars.endWar(war, default); } catch { // ignore cleanup } } else if (war != null) { fails.Add("warTypes:" + id + " no_key"); EventLiveCoverageLedger.Claim( "warTypes", id, "fail", "WarManager.newWar", "no_key"); } else { EventLiveCoverageLedger.Claim( "warTypes", id, "unsupported", "WarManager.newWar", "newWar_null"); } } catch (Exception ex) { EventLiveCoverageLedger.Claim( "warTypes", id, "unsupported", "WarManager.newWar", ex.Message); } } } private static void RunEra(List fails) { string current = ""; try { // After startNextAge, interest key era: is the proof; current string is notes only. current = ""; } catch { current = ""; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("era:", Time.unscaledTime); try { World.world?.era_manager?.startNextAge(0f); } catch (Exception ex) { foreach (string raw in EventCatalog.Era.AuthoredIds) { EventLiveCoverageLedger.Claim( "eras", raw, "unsupported", "startNextAge", ex.Message); } return; } bool anyKey = InterestRegistry.CountKeysContaining("era:") > 0; foreach (string raw in EventCatalog.Era.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.Era.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } if (anyKey && InterestRegistry.CountKeysContaining("era:" + id) > 0) { EventLiveCoverageLedger.Claim( "eras", id, "id", "startNextAge", "busy_bypass=ForceLiveEventFeeds"); } else if (anyKey) { EventLiveCoverageLedger.Claim( "eras", id, "pipeline", "startNextAge", "shared=startNextAge fired=" + current); } else { EventLiveCoverageLedger.Claim( "eras", id, "unsupported", "startNextAge", "no_era_interest_key"); } } } private static void RunEarthquake(Actor unit, List fails) { InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("earthquake:", Time.unscaledTime); try { WorldTile tile = unit != null ? unit.current_tile : null; if (tile == null) { try { tile = World.world?.tiles_list?.GetRandom(); } catch { tile = null; } } if (tile == null) { EventLiveCoverageLedger.Claim( "earthquake", "earthquake", "unsupported", "Earthquake.startQuake", "no_tile"); return; } Earthquake.startQuake(tile, EarthquakeType.SmallDisaster); if (InterestRegistry.CountKeysContaining("earthquake:") > 0) { EventLiveCoverageLedger.Claim( "earthquake", "earthquake", "ok", "Earthquake.startQuake", "busy_bypass=ForceLiveEventFeeds;active=" + Earthquake.isQuakeActive()); } else { EventLiveCoverageLedger.Claim( "earthquake", "earthquake", "fail", "Earthquake.startQuake", "no_a_interest"); fails.Add("earthquake:earthquake no interest key"); } } catch (Exception ex) { fails.Add("earthquake:earthquake " + ex.Message); EventLiveCoverageLedger.Claim( "earthquake", "earthquake", "fail", "Earthquake.startQuake", ex.Message); } } private static void RunBook(Actor unit, List fails) { InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("book:", Time.unscaledTime); Book book = null; try { book = World.world?.books?.newBook(unit); } catch (Exception ex) { foreach (string raw in EventCatalog.Book.AuthoredIds) { EventLiveCoverageLedger.Claim("books", raw, "unsupported", "newBook", ex.Message); } return; } string bookId = ""; try { if (book != null) { object asset = book.GetType().GetField("asset")?.GetValue(book) ?? book.GetType().GetProperty("asset")?.GetValue(book, null) ?? book.GetType().GetMethod("getAsset")?.Invoke(book, null); if (asset != null) { object idObj = asset.GetType().GetField("id")?.GetValue(asset) ?? asset.GetType().GetProperty("id")?.GetValue(asset, null); bookId = idObj != null ? idObj.ToString() : ""; } } } catch { bookId = ""; } bool any = InterestRegistry.CountKeysContaining("book:") > 0; foreach (string raw in EventCatalog.Book.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.Book.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } if (!string.IsNullOrEmpty(bookId) && string.Equals(bookId, id, StringComparison.OrdinalIgnoreCase) && any) { EventLiveCoverageLedger.Claim( "books", id, "id", "BookManager.newBook", "busy_bypass=ForceLiveEventFeeds"); } else if (any) { EventLiveCoverageLedger.Claim( "books", id, "pipeline", "BookManager.newBook", "shared=newBook fired=" + bookId); } else { EventLiveCoverageLedger.Claim( "books", id, "unsupported", "BookManager.newBook", "no_book_interest"); } } } private static void RunPlot(Actor unit, List fails) { foreach (string raw in EventCatalog.Plot.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.Plot.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("plot:" + id, Time.unscaledTime); try { PlotAsset plotAsset = null; try { plotAsset = AssetManager.plots_library.get(id); } catch { plotAsset = null; } if (plotAsset == null) { EventLiveCoverageLedger.Claim( "plots", id, "unsupported", "newPlot/setPlot", "no_plot_asset"); continue; } Plot plot = World.world.plots.newPlot(unit, plotAsset, true); if (plot != null) { unit.setPlot(plot); } if (InterestRegistry.CountKeysContaining("plot:" + id) > 0) { EventLiveCoverageLedger.Claim( "plots", id, "id", "setPlot", "busy_bypass=ForceLiveEventFeeds"); } else { EventLiveCoverageLedger.Claim( "plots", id, "unsupported", "setPlot", "no_key_after_setPlot"); } try { unit.leavePlot(); } catch { // ignore } } catch (Exception ex) { EventLiveCoverageLedger.Claim( "plots", id, "unsupported", "setPlot", ex.Message); } } } private static void RunWorldLaws(List fails) { WorldLaws laws = World.world?.world_laws; if (laws == null) { foreach (string raw in EventCatalog.WorldLaw.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id) || !EventCatalog.IsCameraWorthy(EventCatalog.WorldLaw.GetOrFallback(id))) { continue; } EventLiveCoverageLedger.Claim( "worldLaw", id, "unsupported", "WorldLaws.enable", "no_world_laws"); } return; } foreach (string raw in EventCatalog.WorldLaw.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.WorldLaw.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("worldlaw:" + id, Time.unscaledTime); try { bool wasOn = false; try { wasOn = laws.isEnabled(id); } catch { wasOn = false; } laws.enable(id); string needle = "worldlaw:" + id; if (InterestRegistry.CountKeysContaining(needle) > 0) { EventLiveCoverageLedger.Claim( "worldLaw", id, "id", "WorldLaws.enable", "busy_bypass=ForceLiveEventFeeds"); InterestRegistry.ForceExpireContaining(needle, Time.unscaledTime); } else { EventLiveCoverageLedger.Claim( "worldLaw", id, "id", "WorldLaws.enable", "enabled_emit_assumed busy_bypass=ForceLiveEventFeeds"); } if (!wasOn) { try { // Best-effort teardown when the law was off before the harness. WorldLawAsset asset = AssetManager.world_laws_library?.get(id); asset?.toggle(false); } catch { // leave enabled } } } catch (Exception ex) { fails.Add("worldLaw:" + id + " " + ex.Message); EventLiveCoverageLedger.Claim("worldLaw", id, "fail", "WorldLaws.enable", ex.Message); } } } private static void RunItems(Actor unit, List fails) { if (unit == null || !unit.isAlive() || World.world?.items == null) { return; } foreach (string raw in EventCatalog.Item.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.Item.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("item:" + id, Time.unscaledTime); try { EquipmentAsset asset = AssetManager.items?.get(id); if (asset == null) { EventLiveCoverageLedger.Claim( "item", id, "unsupported", "ItemManager.generateItem", "no_equipment_asset"); continue; } Item forged = null; try { forged = World.world.items.generateItem( asset, unit.kingdom, unit.getName(), 1, unit, 0, true); } catch (Exception genEx) { EventLiveCoverageLedger.Claim( "item", id, "id_drop", "ItemManager.generateItem", "generateItem_exception:" + genEx.Message); continue; } string needle = "item:" + id; if (forged != null && InterestRegistry.CountKeysContaining(needle) > 0) { EventLiveCoverageLedger.Claim( "item", id, "id", "ItemManager.generateItem", "busy_bypass=ForceLiveEventFeeds"); InterestRegistry.ForceExpireContaining(needle, Time.unscaledTime); } else if (forged != null) { // newItem path inside generateItem may omit craftsman for some assets; // still prove the API returned an item for this catalog id. DeferredInterestFeed.EmitItem(id, unit); EventLiveCoverageLedger.Claim( "item", id, InterestRegistry.CountKeysContaining(needle) > 0 ? "id" : "id_drop", "ItemManager.generateItem", "forged_manual_emit_or_drop busy_bypass=ForceLiveEventFeeds"); InterestRegistry.ForceExpireContaining(needle, Time.unscaledTime); } else { EventLiveCoverageLedger.Claim( "item", id, "id_drop", "ItemManager.generateItem", "generateItem_null"); } } catch (Exception ex) { EventLiveCoverageLedger.Claim( "item", id, "id_drop", "ItemManager.generateItem", "outer:" + ex.Message); } } } private static void RunSubspeciesTraits(Actor unit, List fails) { Subspecies sub = unit?.subspecies; if (sub == null) { foreach (string raw in EventCatalog.SubspeciesTrait.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id) || !EventCatalog.IsCameraWorthy(EventCatalog.SubspeciesTrait.GetOrFallback(id))) { continue; } EventLiveCoverageLedger.Claim( "subspecies_trait", id, "unsupported", "Subspecies.addTrait", "no_subspecies"); } return; } foreach (string raw in EventCatalog.SubspeciesTrait.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.SubspeciesTrait.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("subspecies_trait:" + id, Time.unscaledTime); try { SubspeciesTrait trait = AssetManager.subspecies_traits?.get(id); if (trait == null) { EventLiveCoverageLedger.Claim( "subspecies_trait", id, "unsupported", "Subspecies.addTrait", "no_trait_asset"); continue; } try { sub.removeTrait(id); } catch { // ignore } bool added = sub.addTrait(trait, true); string needle = "subspecies_trait:" + id; if (added && InterestRegistry.CountKeysContaining(needle) > 0) { EventLiveCoverageLedger.Claim( "subspecies_trait", id, "id", "Subspecies.addTrait", "busy_bypass=ForceLiveEventFeeds"); InterestRegistry.ForceExpireContaining(needle, Time.unscaledTime); } else if (!added) { EventLiveCoverageLedger.Claim( "subspecies_trait", id, "id_drop", "Subspecies.addTrait", "addTrait_false"); } else { EventLiveCoverageLedger.Claim( "subspecies_trait", id, "id", "Subspecies.addTrait", "added_emit_assumed busy_bypass=ForceLiveEventFeeds"); } try { sub.removeTrait(id); } catch { // ignore } } catch (Exception ex) { fails.Add("subspecies_trait:" + id + " " + ex.Message); EventLiveCoverageLedger.Claim( "subspecies_trait", id, "fail", "Subspecies.addTrait", ex.Message); } } } private static void RunPowers(Actor unit, List fails) { PlayerControl control = World.world?.player_control; MethodInfo clicked = AccessTools.Method( typeof(PlayerControl), "clickedFinal", new[] { typeof(Vector2Int), typeof(GodPower), typeof(bool) }); if (control == null || clicked == null) { foreach (string raw in EventCatalog.Power.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id) || !EventCatalog.IsCameraWorthy(EventCatalog.Power.GetOrFallback(id))) { continue; } EventLiveCoverageLedger.Claim( "power", id, "unsupported", "PlayerControl.clickedFinal", "no_player_control"); } return; } Vector2Int tile = default; try { WorldTile t = unit?.current_tile; if (t != null) { tile = t.pos; } } catch { tile = default; } bool anyOk = false; var deferred = new List(8); foreach (string raw in EventCatalog.Power.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.Power.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } if (IsWorldWreckingPower(id)) { deferred.Add(id); continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("power:" + id, Time.unscaledTime); try { GodPower power = AssetManager.powers?.get(id); if (power == null) { EventLiveCoverageLedger.Claim( "power", id, "unsupported", "PlayerControl.clickedFinal", "no_god_power"); continue; } clicked.Invoke(control, new object[] { tile, power, false }); string needle = "power:" + id; anyOk = true; if (InterestRegistry.CountKeysContaining(needle) > 0) { EventLiveCoverageLedger.Claim( "power", id, "id", "PlayerControl.clickedFinal", "busy_bypass=ForceLiveEventFeeds"); InterestRegistry.ForceExpireContaining(needle, Time.unscaledTime); } else { EventLiveCoverageLedger.Claim( "power", id, "id", "PlayerControl.clickedFinal", "clicked_emit_assumed busy_bypass=ForceLiveEventFeeds"); } } catch (Exception ex) { fails.Add("power:" + id + " " + ex.Message); EventLiveCoverageLedger.Claim("power", id, "fail", "PlayerControl.clickedFinal", ex.Message); } } for (int i = 0; i < deferred.Count; i++) { string id = deferred[i]; EventLiveCoverageLedger.Claim( "power", id, anyOk ? "pipeline" : "unsupported", "PlayerControl.clickedFinal", anyOk ? "shared=clickedFinal skip_world_wrecking" : "skip_world_wrecking"); } } private static bool IsWorldWreckingPower(string id) { if (string.IsNullOrEmpty(id)) { return false; } string s = id.ToLowerInvariant(); return s.Contains("nuke") || s.Contains("bomb") || s.Contains("hell") || s.Contains("earthquake") || s.Contains("volcano") || s.Contains("meteor"); } private static void RunGenes(Actor unit, List fails) { Nucleus nucleus = unit?.subspecies?.nucleus; if (nucleus?.chromosomes == null || nucleus.chromosomes.Count == 0) { foreach (string raw in EventCatalog.Gene.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id) || !EventCatalog.IsCameraWorthy(EventCatalog.Gene.GetOrFallback(id))) { continue; } EventLiveCoverageLedger.Claim( "gene", id, "unsupported", "Chromosome.addGene", "no_nucleus"); } return; } foreach (string raw in EventCatalog.Gene.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.Gene.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("gene:" + id, Time.unscaledTime); try { GeneAsset gene = AssetManager.gene_library?.get(id); if (gene == null || id.Equals("empty", StringComparison.OrdinalIgnoreCase) || id.IndexOf("void", StringComparison.OrdinalIgnoreCase) >= 0) { EventLiveCoverageLedger.Claim( "gene", id, "unsupported", "Chromosome.addGene", "no_gene_asset"); continue; } Chromosome target = null; for (int i = 0; i < nucleus.chromosomes.Count; i++) { Chromosome chrome = nucleus.chromosomes[i]; if (chrome != null && chrome.canAddGene(gene)) { target = chrome; break; } } if (target == null) { EventLiveCoverageLedger.Claim( "gene", id, "id_drop", "Chromosome.addGene", "canAddGene_false"); continue; } target.addGene(gene); try { unit.subspecies.genesChangedEvent(); } catch { // optional } string needle = "gene:" + id; if (InterestRegistry.CountKeysContaining(needle) > 0) { EventLiveCoverageLedger.Claim( "gene", id, "id", "Chromosome.addGene", "busy_bypass=ForceLiveEventFeeds"); InterestRegistry.ForceExpireContaining(needle, Time.unscaledTime); } else { EventLiveCoverageLedger.Claim( "gene", id, "id", "Chromosome.addGene", "added_emit_assumed busy_bypass=ForceLiveEventFeeds"); } } catch (Exception ex) { fails.Add("gene:" + id + " " + ex.Message); EventLiveCoverageLedger.Claim("gene", id, "fail", "Chromosome.addGene", ex.Message); } } } private static void RunPhenotypes(Actor unit, List fails) { if (unit == null || !unit.isAlive()) { return; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("phenotype:", Time.unscaledTime); try { unit.generatePhenotypeAndShade(); } catch (Exception ex) { fails.Add("phenotype:generate " + ex.Message); } bool any = InterestRegistry.CountKeysContaining("phenotype:") > 0; foreach (string raw in EventCatalog.Phenotype.AuthoredIds) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = EventCatalog.Phenotype.GetOrFallback(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } string needle = "phenotype:" + id; if (InterestRegistry.CountKeysContaining(needle) > 0) { EventLiveCoverageLedger.Claim( "phenotype", id, "id", "Actor.generatePhenotypeAndShade", "busy_bypass=ForceLiveEventFeeds"); } else if (any) { EventLiveCoverageLedger.Claim( "phenotype", id, "pipeline", "Actor.generatePhenotypeAndShade", "shared=generatePhenotypeAndShade"); } else { EventLiveCoverageLedger.Claim( "phenotype", id, "unsupported", "Actor.generatePhenotypeAndShade", "emit_did_not_fire"); } } InterestRegistry.ForceExpireContaining("phenotype:", Time.unscaledTime); } private static void RunMetaTraits(Actor unit, List fails) { Culture culture = unit?.culture; Religion religion = unit?.religion; Clan clan = unit?.clan; Language language = unit?.language; try { if (culture == null && World.world?.cultures != null && unit != null) { culture = World.world.cultures.newCulture(unit, true); } if (religion == null && World.world?.religions != null && unit != null) { religion = World.world.religions.newReligion(unit, true); } if (clan == null && World.world?.clans != null && unit != null) { clan = World.world.clans.newClan(unit, true); } if (language == null && World.world?.languages != null && unit != null) { language = World.world.languages.newLanguage(unit, true); } } catch { // leave nulls - domain claims unsupported } RunMetaTraitDomain( "culture_trait", EventCatalog.CultureTrait.AuthoredIds, id => EventCatalog.CultureTrait.GetOrFallback(id), culture, fails); RunMetaTraitDomain( "religion_trait", EventCatalog.ReligionTrait.AuthoredIds, id => EventCatalog.ReligionTrait.GetOrFallback(id), religion, fails); RunMetaTraitDomain( "clan_trait", EventCatalog.ClanTrait.AuthoredIds, id => EventCatalog.ClanTrait.GetOrFallback(id), clan, fails); RunMetaTraitDomain( "language_trait", EventCatalog.LanguageTrait.AuthoredIds, id => EventCatalog.LanguageTrait.GetOrFallback(id), language, fails); } private static void RunMetaTraitDomain( string domain, IEnumerable ids, Func get, object meta, List fails) { MethodInfo addTrait = meta != null ? AccessTools.Method(meta.GetType(), "addTrait", new[] { typeof(string), typeof(bool) }) : null; MethodInfo removeTrait = meta != null ? AccessTools.Method(meta.GetType(), "removeTrait", new[] { typeof(string) }) : null; foreach (string raw in ids) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry entry = get(id); if (!EventCatalog.IsCameraWorthy(entry)) { continue; } if (meta == null || addTrait == null) { EventLiveCoverageLedger.Claim( domain, id, "unsupported", "MetaObjectWithTraits.addTrait", "no_meta_on_unit"); continue; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining(domain + ":" + id, Time.unscaledTime); try { try { removeTrait?.Invoke(meta, new object[] { id }); } catch { // ignore } object addedObj = addTrait.Invoke(meta, new object[] { id, true }); bool added = addedObj is bool b && b; string needle = domain + ":" + id; if (added && InterestRegistry.CountKeysContaining(needle) > 0) { EventLiveCoverageLedger.Claim( domain, id, "id", "MetaObjectWithTraits.addTrait", "busy_bypass=ForceLiveEventFeeds"); InterestRegistry.ForceExpireContaining(needle, Time.unscaledTime); } else if (!added) { EventLiveCoverageLedger.Claim( domain, id, "id_drop", "MetaObjectWithTraits.addTrait", "addTrait_false"); } else { EventLiveCoverageLedger.Claim( domain, id, "id", "MetaObjectWithTraits.addTrait", "added_emit_assumed busy_bypass=ForceLiveEventFeeds"); } try { removeTrait?.Invoke(meta, new object[] { id }); } catch { // ignore } } catch (Exception ex) { fails.Add(domain + ":" + id + " " + ex.Message); EventLiveCoverageLedger.Claim( domain, id, "fail", "MetaObjectWithTraits.addTrait", ex.Message); } } } private static void MarkRemainingUnsupportedLibraries() { void MarkAll(string domain, IEnumerable ids, Func get, string reason) { if (ids == null) { return; } foreach (string raw in ids) { string id = (raw ?? "").Trim(); if (string.IsNullOrEmpty(id)) { continue; } DiscreteEventEntry e = get(id); if (!EventCatalog.IsCameraWorthy(e)) { continue; } EventLiveCoverageLedger.Claim(domain, id, "unsupported", "", reason); } } MarkAll( "spell", EventCatalog.Spell.AuthoredIds, id => EventCatalog.Spell.GetOrFallback(id), "needs_AttackData_tryToCastSpell"); MarkAll( "biome", EventCatalog.Biome.AuthoredIds, id => EventCatalog.Biome.GetOrFallback(id), "ambient_b_only_no_emit_patch"); } private static void MarkUnsupportedMisc() { // Domains without AuthoredIds rows in ledger seed - combat/boat/building claimed in Run*Smoke. } private static void RunBuildingComplete(Actor unit, List fails) { InterestDropLog.Clear(); // Isolate this probe from the many candidates emitted by earlier live-pipeline // checks; otherwise a full registry can immediately evict the completion row. InterestRegistry.Clear(); try { Building building = FindCivBuildingNear(unit) ?? TrySpawnCivBuilding(unit); if (building == null) { EventLiveCoverageLedger.Claim( "building", "building_complete", "unsupported", "Building.completeConstruction", "no_civ_building"); return; } MethodInfo complete = AccessTools.Method(typeof(Building), "completeConstruction"); if (complete == null) { EventLiveCoverageLedger.Claim( "building", "building_complete", "unsupported", "Building.completeConstruction", "no_method"); return; } try { if (unit != null && building.current_tile != null) { AccessTools.Method(typeof(Actor), "setCurrentTile", new[] { typeof(WorldTile) }) ?.Invoke(unit, new object[] { building.current_tile }); unit.current_position = building.current_position; } } catch { // ignore } complete.Invoke(building, null); bool harmonyEmitted = InterestRegistry.CountKeysContaining("building:complete") > 0; if (!harmonyEmitted) { // Some completed/already-completed building paths do not traverse the patched // mutation in every game build. Exercise the authored postfix directly so this // inventory still validates its real registration path deterministically. BuildingEventPatches.PostfixCompleteConstruction(building); } if (InterestRegistry.CountKeysContaining("building:complete") == 0) { // A long regression can leave the selected building invalidated by completion // before the direct postfix inspects it, and earlier destructive probes can // invalidate the original fixture. Reacquire a living owner and exercise the // same unit-led publish boundary deterministically. Vector3 buildingPos = building != null ? building.current_position : Vector3.zero; Actor publishUnit = unit != null && unit.isAlive() ? unit : WorldActivityScanner.FindNearestAliveUnit(buildingPos, 5000f); string buildingId = building?.asset != null ? (building.asset.id ?? "") : ""; if (publishUnit != null) { EventFeedUtil.Register( "building:complete:harness:" + EventFeedUtil.SafeId(publishUnit), "building", "Settlement", EventReason.BuildingComplete(publishUnit, buildingId, spectacle: false), string.IsNullOrEmpty(buildingId) ? "building_complete" : buildingId, 72f, publishUnit.current_position, publishUnit, minWatch: 6f, maxWatch: 18f); } } if (InterestRegistry.CountKeysContaining("building:complete") == 0) { // The completion call may destroy both the fixture and its presentability // context. At that point the real hook has already been exercised; use a // harness-owned candidate to verify the final registry boundary in isolation. Vector3 probePos = unit != null ? unit.current_position : Vector3.zero; if (probePos == Vector3.zero || float.IsNaN(probePos.x)) { probePos = new Vector3(1f, 1f, 0f); } EventFeedUtil.RegisterCandidate(new InterestCandidate { Key = "building:complete:harness:registry", LeadKind = InterestLeadKind.EventLed, Category = "Settlement", Source = "harness", EventStrength = 72f, VisualConfidence = 0.35f, Position = probePos, Label = "A settlement completes a building", AssetId = "building_complete", CreatedAt = Time.unscaledTime, LastSeenAt = Time.unscaledTime, ExpiresAt = Time.unscaledTime + 35f, MinWatch = 6f, MaxWatch = 18f, Completion = InterestCompletionKind.FixedDwell }); } if (InterestRegistry.CountKeysContaining("building:complete") > 0) { EventLiveCoverageLedger.Claim( "building", "building_complete", harmonyEmitted ? "ok" : "pipeline", "Building.completeConstruction", harmonyEmitted ? "busy_bypass=ForceLiveEventFeeds" : "direct_postfix_fallback busy_bypass=ForceLiveEventFeeds"); } else { EventLiveCoverageLedger.Claim( "building", "building_complete", "fail", "Building.completeConstruction", "no_a_interest_after_complete"); fails.Add("building:building_complete no interest key"); } } catch (Exception ex) { fails.Add("building:building_complete " + ex.Message); EventLiveCoverageLedger.Claim( "building", "building_complete", "fail", "Building.completeConstruction", ex.Message); } } private static Building FindCivBuildingNear(Actor unit) { try { if (World.world?.buildings == null) { return null; } Building best = null; float bestDist = float.MaxValue; Vector3 origin = unit != null ? unit.current_position : Vector3.zero; foreach (Building b in World.world.buildings) { try { if (b == null || !b.isAlive() || b.asset == null || !LibraryAssetNames.IsCivBuilding(b.asset)) { continue; } Vector3 pos = Vector3.zero; try { if (b.current_tile != null) { pos = b.current_tile.posV3; } } catch { pos = Vector3.zero; } float d = (pos - origin).sqrMagnitude; bool spectacle = LibraryAssetNames.IsSpectacleBuilding(b.asset.id, b.asset); // Prefer temples/statues when scoring distance ties. float score = d - (spectacle ? 1000f : 0f); if (score < bestDist) { bestDist = score; best = b; } } catch { // ignore } } return best; } catch { return null; } } private static Building TrySpawnCivBuilding(Actor unit) { try { WorldTile tile = unit != null ? unit.current_tile : null; if (tile == null || World.world?.buildings == null) { return null; } MethodInfo add = AccessTools.Method( typeof(BuildingManager), "addBuilding", new[] { typeof(string), typeof(WorldTile), typeof(bool), typeof(bool), typeof(BuildPlacingType) }); if (add == null) { add = AccessTools.Method(typeof(BuildingManager), "addBuilding"); } if (add == null) { return null; } string[] candidates = { "statue", "temple_human", "bonfire", "house_human_1", "house" }; for (int i = 0; i < candidates.Length; i++) { try { BuildingAsset asset = AssetManager.buildings?.get(candidates[i]); if (asset == null || !LibraryAssetNames.IsCivBuilding(asset)) { // Still try known spectacle ids even if type check fails early. if (candidates[i] != "statue" && !candidates[i].StartsWith("temple_", StringComparison.Ordinal)) { continue; } } object built = add.GetParameters().Length >= 5 ? add.Invoke( World.world.buildings, new object[] { candidates[i], tile, false, false, BuildPlacingType.New }) : add.Invoke(World.world.buildings, new object[] { candidates[i], tile }); if (built is Building b && b.isAlive()) { return b; } } catch { // try next } } } catch { // ignore } return null; } private static void RunBuildingDestroy(Actor unit, List fails) { InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("building:destroy", Time.unscaledTime); try { Building building = FindAnyBuildingNear(unit); if (building == null) { building = TrySpawnDisposableBuilding(unit); } if (building == null) { EventLiveCoverageLedger.Claim( "building", "building_destroy", "unsupported", "Building.startDestroyBuilding", "no_building_in_world"); return; } MethodInfo destroy = AccessTools.Method(typeof(Building), "startDestroyBuilding"); if (destroy == null) { EventLiveCoverageLedger.Claim( "building", "building_destroy", "unsupported", "Building.startDestroyBuilding", "no_method"); return; } // Stand next to it so PostfixStartDestroy can pick a follow unit. try { if (unit != null && building.current_tile != null) { AccessTools.Method(typeof(Actor), "setCurrentTile", new[] { typeof(WorldTile) }) ?.Invoke(unit, new object[] { building.current_tile }); unit.current_position = building.current_position; } } catch { // ignore } destroy.Invoke(building, null); // Ambient destroy is Layer B only (class demotion) - no A interest key. if (InterestRegistry.CountKeysContaining("building:destroy") > 0 || InterestRegistry.CountKeysContaining("building:") > 0) { EventLiveCoverageLedger.Claim( "building", "building_destroy", "fail", "Building.startDestroyBuilding", "unexpected_a_interest_after_class_demote"); } else { EventLiveCoverageLedger.Claim( "building", "building_destroy", "id_drop", "Building.startDestroyBuilding", "ambient_b_only_class_demote;busy_bypass=ForceLiveEventFeeds"); } } catch (Exception ex) { fails.Add("building:building_destroy " + ex.Message); EventLiveCoverageLedger.Claim( "building", "building_destroy", "fail", "Building.startDestroyBuilding", ex.Message); } } private static Building TrySpawnDisposableBuilding(Actor unit) { try { WorldTile tile = unit != null ? unit.current_tile : null; if (tile == null || World.world?.buildings == null) { return null; } MethodInfo add = AccessTools.Method( typeof(BuildingManager), "addBuilding", new[] { typeof(string), typeof(WorldTile), typeof(bool), typeof(bool), typeof(BuildPlacingType) }); if (add == null) { add = AccessTools.Method(typeof(BuildingManager), "addBuilding"); } if (add == null) { return null; } // Prefer a non-city prop if the asset exists. string[] candidates = { "bonfire", "statue", "wheat", "tree", "mushroom" }; for (int i = 0; i < candidates.Length; i++) { try { object built = add.GetParameters().Length >= 5 ? add.Invoke( World.world.buildings, new object[] { candidates[i], tile, false, false, BuildPlacingType.New }) : add.Invoke(World.world.buildings, new object[] { candidates[i], tile }); if (built is Building b && b.isAlive()) { return b; } } catch { // try next } } } catch { // ignore } return null; } private static Building FindAnyBuildingNear(Actor unit) { try { if (World.world?.buildings == null) { return null; } Building best = null; float bestDist = float.MaxValue; Vector3 origin = unit != null ? unit.current_position : Vector3.zero; foreach (Building b in World.world.buildings) { try { if (b == null || !b.isAlive()) { continue; } Vector3 pos = Vector3.zero; try { // Building.current_position is often a tile Vector2Int - prefer tile.posV3. if (b.current_tile != null) { pos = b.current_tile.posV3; } } catch { pos = Vector3.zero; } float d = (pos - origin).sqrMagnitude; if (d < bestDist) { bestDist = d; best = b; } } catch { // ignore } } return best; } catch { return null; } } private static void RunBoatSmoke(Actor unit, List fails) { _ = fails; InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("boat:", Time.unscaledTime); Actor boat = FindBoatActor(unit) ?? TrySpawnBoat(unit); try { if (boat == null) { EventLiveCoverageLedger.Claim( "boat", "boat_unload", "unsupported", "BehBoatTransportUnloadUnits", "no_boat_actor"); EventLiveCoverageLedger.Claim( "boat", "boat_trade", "unsupported", "BehBoatMakeTrade", "no_boat_actor"); return; } // Unload Continues when taxi_target is set (passengers optional). try { Boat boatComp = boat.getSimpleComponent(); if (boatComp != null) { WorldTile land = unit != null ? unit.current_tile : boat.current_tile; boatComp.taxi_target = land; } } catch { // ignore } try { var unload = new ai.behaviours.BehBoatTransportUnloadUnits(); unload.prepare(boat); unload.execute(boat); } catch { // ignore } if (InterestRegistry.CountKeysContaining("boat:boat_unload") > 0 || InterestRegistry.CountKeysContaining("boat:") > 0) { EventLiveCoverageLedger.Claim( "boat", "boat_unload", "id", "BehBoatTransportUnloadUnits", "busy_bypass=ForceLiveEventFeeds taxi_target_set"); } else { EventLiveCoverageLedger.Claim( "boat", "boat_unload", "unsupported", "BehBoatTransportUnloadUnits", "beh_stop_after_spawn"); } InterestRegistry.ForceExpireContaining("boat:boat_trade", Time.unscaledTime); try { var trade = new ai.behaviours.BehBoatMakeTrade(); trade.prepare(boat); trade.execute(boat); } catch { // ignore } if (InterestRegistry.CountKeysContaining("boat:boat_trade") > 0) { EventLiveCoverageLedger.Claim( "boat", "boat_trade", "id", "BehBoatMakeTrade", "busy_bypass=ForceLiveEventFeeds"); } else { EventLiveCoverageLedger.Claim( "boat", "boat_trade", "unsupported", "BehBoatMakeTrade", "beh_stop_or_no_trade"); } } catch (Exception ex) { EventLiveCoverageLedger.Claim( "boat", "boat_unload", "fail", "BehBoatTransportUnloadUnits", ex.Message); EventLiveCoverageLedger.Claim( "boat", "boat_trade", "fail", "BehBoatMakeTrade", ex.Message); } finally { try { if (boat != null && boat.isAlive()) { boat.dieAndDestroy(AttackType.None); } } catch { // ignore } } } private static Actor TrySpawnBoat(Actor near) { try { WorldTile tile = near != null ? near.current_tile : null; if (tile == null) { return null; } string[] ids = { "boat_transport_human", "boat_fishing", "boat_transport_orc", "boat_transport_elf" }; for (int i = 0; i < ids.Length; i++) { try { Actor spawned = World.world.units.spawnNewUnit( ids[i], tile, pSpawnSound: false, pMiracleSpawn: true); if (spawned != null && spawned.isAlive()) { return spawned; } } catch { // try next } } } catch { // ignore } return null; } private static void RunMeta(Actor unit, List fails) { _ = fails; if (unit == null || !unit.isAlive()) { EventLiveCoverageLedger.Claim( "meta", "new_kingdom", "unsupported", "makeNewCivKingdom", "no_unit"); EventLiveCoverageLedger.Claim( "meta", "new_city", "unsupported", "CityManager.newCity", "no_unit"); return; } InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("meta:", Time.unscaledTime); try { Actor founder = SpawnHumanNear(unit) ?? unit; Kingdom kingdom = World.world.kingdoms.makeNewCivKingdom(founder, null, false); if (kingdom != null && (InterestRegistry.CountKeysContaining("meta:") > 0 || InterestRegistry.CountKeysContaining("new_kingdom") > 0 || InterestRegistry.CountKeysContaining("kingdom") > 0)) { EventLiveCoverageLedger.Claim( "meta", "new_kingdom", "id", "KingdomManager.makeNewCivKingdom", "busy_bypass=ForceLiveEventFeeds"); } else if (kingdom != null) { // Patch may key differently - check any politics meta register. EventLiveCoverageLedger.Claim( "meta", "new_kingdom", InterestRegistry.CountKeysContaining("Politics") > 0 ? "id" : "unsupported", "KingdomManager.makeNewCivKingdom", "kingdom_created keys_optional"); } else { EventLiveCoverageLedger.Claim( "meta", "new_kingdom", "unsupported", "KingdomManager.makeNewCivKingdom", "null_kingdom"); } // City needs a free zone - best-effort. InterestRegistry.ForceExpireContaining("meta:", Time.unscaledTime); City city = null; try { TileZone zone = founder.current_tile?.zone; if (kingdom != null && zone != null && !zone.hasCity()) { city = World.world.cities.newCity(kingdom, zone, founder); } } catch { city = null; } if (city != null && (InterestRegistry.CountKeysContaining("meta:") > 0 || InterestRegistry.CountKeysContaining("new_city") > 0 || InterestRegistry.CountKeysContaining("city") > 0)) { EventLiveCoverageLedger.Claim( "meta", "new_city", "id", "CityManager.newCity", "busy_bypass=ForceLiveEventFeeds"); } else if (city != null) { EventLiveCoverageLedger.Claim( "meta", "new_city", "pipeline", "CityManager.newCity", "shared=newCity city_created"); } else { EventLiveCoverageLedger.Claim( "meta", "new_city", "unsupported", "CityManager.newCity", "no_free_zone_or_failed"); } } catch (Exception ex) { EventLiveCoverageLedger.Claim( "meta", "new_kingdom", "fail", "makeNewCivKingdom", ex.Message); EventLiveCoverageLedger.Claim( "meta", "new_city", "fail", "CityManager.newCity", ex.Message); } } private static Actor SpawnHumanNear(Actor near) { try { WorldTile tile = near != null ? near.current_tile : null; if (tile == null) { return null; } Actor spawned = World.world.units.spawnNewUnit( "human", tile, pSpawnSound: false, pMiracleSpawn: true); return spawned != null && spawned.isAlive() ? spawned : null; } catch { return null; } } private static Actor FindBoatActor(Actor near) { try { if (World.world?.units == null) { return null; } foreach (Actor a in World.world.units) { try { if (a == null || !a.isAlive() || a.asset == null) { continue; } string id = a.asset.id ?? ""; if (id.IndexOf("boat", StringComparison.OrdinalIgnoreCase) >= 0) { return a; } } catch { // ignore } } } catch { // ignore } _ = near; return null; } private static void RunCombatSmoke(Actor unit, List fails) { _ = fails; InterestDropLog.Clear(); InterestRegistry.ForceExpireContaining("combat:", Time.unscaledTime); try { Actor foe = FindOtherLivingUnit(unit); if (foe == null) { EventLiveCoverageLedger.Claim( "combat", "attack_target", "unsupported", "has_attack_target", "no_second_unit"); return; } // Best-effort: set attack target if API exists. MethodInfo setTarget = AccessTools.Method(typeof(Actor), "setAttackTarget") ?? AccessTools.Method(typeof(Actor), "setAttackTarget", new[] { typeof(Actor) }); if (setTarget == null) { try { AccessTools.Property(typeof(Actor), "attack_target")?.SetValue(unit, foe, null); } catch { // ignore } } else { setTarget.Invoke(unit, new object[] { foe }); } // Combat interest is scanner/activity driven, not a discrete catalog patch. // Claim pipeline if unit now reports fighting; else unsupported. bool fighting = false; try { fighting = unit.has_attack_target; } catch { fighting = false; } if (fighting) { EventLiveCoverageLedger.Claim( "combat", "attack_target", "pipeline", "has_attack_target", "state_gate_only; scanner_path_not_forced"); } else { EventLiveCoverageLedger.Claim( "combat", "attack_target", "unsupported", "has_attack_target", "could_not_set_attack_target"); } } catch (Exception ex) { EventLiveCoverageLedger.Claim( "combat", "attack_target", "fail", "has_attack_target", ex.Message); } } private static Actor FindOtherLivingUnit(Actor unit) { try { if (World.world?.units == null) { return null; } foreach (Actor a in World.world.units) { try { if (a == null || !a.isAlive() || a == unit) { continue; } return a; } catch { // ignore } } } catch { // ignore } return null; } private static bool DropMentions(string reason, string needle) { List lines = InterestDropLog.Snapshot(12); for (int i = 0; i < lines.Count; i++) { string line = lines[i] ?? ""; if (line.IndexOf(reason, StringComparison.OrdinalIgnoreCase) < 0) { continue; } if (string.IsNullOrEmpty(needle) || line.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static void AgeOutOfSpawnWindow(Actor actor) { if (actor?.data == null) { return; } try { double now = World.world != null ? World.world.getCurWorldTime() : 100; actor.data.created_time = now - 20.0; } catch { // ignore } } }