using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using UnityEngine; namespace IdleSpectator; public sealed class MutationDiscoveryResult { public bool Passed; public string Detail = ""; public int Managers; public int ManagerMethods; public int ActorMethods; public int BehTypes; public int Libraries; public int LibraryAssets; public int Gaps; } /// /// Discovers event-relevant mutation paths from live game assemblies and AssetManager. /// Writes TSVs under .harness/ for the idle-camera coverage plan. /// public static class MutationDiscoveryHarness { private static readonly string[] ManagerNameNeedles = { "Manager", "Keeper" }; private static readonly string[] MethodPrefixNeedles = { "new", "add", "remove", "set", "become", "start", "finish", "cancel", "create", "destroy", "die", "kill", "birth", "clear", "leave", "join", "make", "end", "load", "spawn", "generate", "burn", "write" }; private static readonly string[] ActorMethodNeedles = { "lover", "friend", "family", "child", "parent", "baby", "born", "birth", "mate", "breed", "social", "clan", "king", "leader", "trait", "status", "happiness", "plot", "army", "war", "alliance", "culture", "religion", "language", "book", "item", "boat", "home", "house", "city", "kingdom", "die", "kill", "task", "job" }; private static readonly string[] BehNeedles = { "Lover", "Friend", "Family", "Social", "Child", "Baby", "Birth", "Plot", "Clan", "Army", "War", "Book", "Build", "Boat", "Trade", "Era", "Age", "Trait", "Religion", "Culture", "Language", "Alliance", "King", "Marry" }; private static readonly string[] LibraryFields = { "actor_library", "status", "happiness_library", "world_log_library", "disasters", "war_types_library", "traits", "plots_library", "plot_category_library", "era_library", "buildings", "architecture_library", "city_build_orders", "culture_traits", "religion_traits", "clan_traits", "language_traits", "subspecies_traits", "kingdoms_traits", "book_types", "items", "items_modifiers", "spells", "powers", "combat_action_library", "decisions_library", "loyalty_library", "opinion_library", "knowledge_library", "gene_library", "phenotype_library", "communication_library", "story_library", "world_laws_library", "biome_library", "citizen_job_library", "professions", "personalities", "tasks_actor" }; private static readonly HashSet AlreadyWired = new HashSet(StringComparer.OrdinalIgnoreCase) { "Actor.changeHappiness", "Actor.checkHappinessChangeFromDeathEvent", "Actor.birthEvent", "Actor.becomeLoversWith", "Actor.setBestFriend", "Actor.newKillAction", "Actor.die", "Actor.setTask", "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", "Earthquake.startQuake", "Earthquake.spawnQuake", "CultureManager.newCulture", "ReligionManager.newReligion", "LanguageManager.newLanguage", "ClanManager.newClan", "ArmyManager.newArmy", "AllianceManager.newAlliance", "CityManager.newCity", "KingdomManager.makeNewCivKingdom", "Family.setAlpha", "Plot.finishPlot", "ItemManager.generateItem", "ItemManager.newItem", "SubspeciesManager.newSpecies", "SubspeciesManager.addRandomTraitFromBiomeToSubspecies", "SubspeciesManager.addTraitsFromBiomeToSubspecies", "Subspecies.mutateFrom", "BehDealDamageToTargetBuilding.execute", "BehConsumeTargetBuilding.execute", "Building.startDestroyBuilding", "Building.completeConstruction", "BehBoatTransportUnloadUnits.execute", "BehBoatMakeTrade.execute", "BehBoatTransportDoLoading.execute", "BehFindLover.execute", "BehFamilyGroupNew.execute", "BehFamilyGroupJoin.execute", "BehFamilyGroupLeave.execute", "BehPollinate.execute", "BehUnloadResources.execute", "BehAttackActorHuntingTarget.execute", "BehCityActorRemoveFire.execute", "BehTryToSocialize.execute", "BehPlantCrops.execute", "BehCityActorFertilizeCrop.execute", "BehThrowResources.execute", "BehBuildTarget.execute", "BehPoopOutside.execute", "BehPoopInside.execute", "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", // 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) { Assembly gameAsm = typeof(Actor).Assembly; var managersTsv = new StringBuilder(); managersTsv.AppendLine("type\tmethod\tarity\tstatic\tkind_guess\talready_wired\tnotes"); var actorTsv = new StringBuilder(); actorTsv.AppendLine("type\tmethod\tarity\tstatic\tneedle\talready_wired\tnotes"); var behTsv = new StringBuilder(); behTsv.AppendLine("type\tmethod\tarity\tneedle\talready_wired\tnotes"); var libTsv = new StringBuilder(); libTsv.AppendLine("field\tcount\tsample_ids\tnotes"); var gapsTsv = new StringBuilder(); gapsTsv.AppendLine( "kind\ttype\tmethod_or_field\tevent_worthy\tsuggested_creates_interest\talready_wired\treason"); int managerCount = 0; int managerMethods = 0; int actorMethods = 0; int behTypes = 0; int libraries = 0; int libraryAssets = 0; int gaps = 0; Type[] types; try { types = gameAsm.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types ?? Array.Empty(); } var managerTypes = new List(); var behTypesList = new List(); Type actorType = typeof(Actor); Type baseSimType = typeof(BaseSimObject); for (int i = 0; i < types.Length; i++) { Type t = types[i]; if (t == null || string.IsNullOrEmpty(t.Name)) { continue; } string name = t.Name; if (IsManagerish(name)) { managerTypes.Add(t); } if (name.StartsWith("Beh", StringComparison.Ordinal) && MatchesAny(name, BehNeedles)) { behTypesList.Add(t); } } managerTypes.Sort((a, b) => string.CompareOrdinal(a.FullName, b.FullName)); for (int i = 0; i < managerTypes.Count; i++) { Type t = managerTypes[i]; managerCount++; MethodInfo[] methods = SafeMethods(t); for (int m = 0; m < methods.Length; m++) { MethodInfo method = methods[m]; if (method == null || method.IsSpecialName) { continue; } if (!MethodLooksMutating(method.Name)) { continue; } managerMethods++; string key = t.Name + "." + method.Name; bool wired = AlreadyWired.Contains(key); string kind = GuessManagerKind(t.Name, method.Name); managersTsv.Append(t.Name).Append('\t') .Append(method.Name).Append('\t') .Append(method.GetParameters().Length).Append('\t') .Append(method.IsStatic ? "1" : "0").Append('\t') .Append(kind).Append('\t') .Append(wired ? "1" : "0").Append('\t') .Append(Escape(method.ToString())).AppendLine(); if (!wired && IsEventWorthyManager(t.Name, method.Name)) { gaps++; gapsTsv.Append("ManagerGenesis").Append('\t') .Append(t.Name).Append('\t') .Append(method.Name).Append('\t') .Append("Yes").Append('\t') .Append(SuggestCreatesInterest(t.Name, method.Name)).Append('\t') .Append("0").Append('\t') .Append("unwired_manager_mutation").AppendLine(); } } } DumpTypeMethods(actorType, ActorMethodNeedles, actorTsv, gapsTsv, ref actorMethods, ref gaps); DumpTypeMethods(baseSimType, ActorMethodNeedles, actorTsv, gapsTsv, ref actorMethods, ref gaps); // Also scan Actor for lover/friend clear-style methods even if needle-filtered narrowly. DumpExtraActorRelationMethods(actorType, actorTsv, gapsTsv, ref actorMethods, ref gaps); behTypesList.Sort((a, b) => string.CompareOrdinal(a.Name, b.Name)); for (int i = 0; i < behTypesList.Count; i++) { Type t = behTypesList[i]; behTypes++; string needle = FirstMatch(t.Name, BehNeedles); MethodInfo exec = t.GetMethod("execute", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); string methodName = exec != null ? "execute" : ""; string key = t.Name + (string.IsNullOrEmpty(methodName) ? "" : "." + methodName); bool wired = AlreadyWired.Contains(key); int arity = exec != null ? exec.GetParameters().Length : 0; behTsv.Append(t.Name).Append('\t') .Append(methodName).Append('\t') .Append(arity).Append('\t') .Append(needle).Append('\t') .Append(wired ? "1" : "0").Append('\t') .Append("BehPulse").AppendLine(); if (!wired && IsEventWorthyBeh(t.Name)) { gaps++; gapsTsv.Append("BehPulse").Append('\t') .Append(t.Name).Append('\t') .Append(methodName).Append('\t') .Append(BehSoft(t.Name) ? "Soft" : "Yes").Append('\t') .Append(BehSoft(t.Name) ? "false" : "true").Append('\t') .Append("0").Append('\t') .Append("unwired_beh").AppendLine(); } } for (int i = 0; i < LibraryFields.Length; i++) { string field = LibraryFields[i]; List ids = EnumerateLibraryIds(field); libraries++; libraryAssets += ids.Count; string sample = ""; int sampleN = Math.Min(5, ids.Count); for (int s = 0; s < sampleN; s++) { if (s > 0) { sample += ","; } sample += ids[s]; } // 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(LibraryCoverageNote(field)).AppendLine(); // Full id lists for priority event catalogs. if (!string.IsNullOrEmpty(outputDirectory) && (field == "plots_library" || field == "plot_category_library" || field == "era_library" || field == "book_types" || field == "traits" || field == "buildings")) { try { var full = new StringBuilder(); full.AppendLine("id"); for (int s = 0; s < ids.Count; s++) { full.AppendLine(ids[s]); } File.WriteAllText( Path.Combine(outputDirectory, "library-" + field + ".tsv"), full.ToString()); } catch { // ignore } } } // World / MapBox live manager fields AppendWorldFields(gapsTsv, managersTsv, ref gaps, ref managerMethods); try { if (!string.IsNullOrEmpty(outputDirectory)) { Directory.CreateDirectory(outputDirectory); File.WriteAllText(Path.Combine(outputDirectory, "mutation_managers.tsv"), managersTsv.ToString()); File.WriteAllText(Path.Combine(outputDirectory, "mutation_actor.tsv"), actorTsv.ToString()); File.WriteAllText(Path.Combine(outputDirectory, "mutation_beh.tsv"), behTsv.ToString()); File.WriteAllText(Path.Combine(outputDirectory, "mutation_libraries.tsv"), libTsv.ToString()); File.WriteAllText(Path.Combine(outputDirectory, "mutation_gaps.tsv"), gapsTsv.ToString()); } } catch (Exception ex) { Debug.LogWarning("[IdleSpectator] mutation discovery write failed: " + ex.Message); } return new MutationDiscoveryResult { Passed = managerCount > 0 && libraries > 0, Managers = managerCount, ManagerMethods = managerMethods, ActorMethods = actorMethods, BehTypes = behTypes, Libraries = libraries, LibraryAssets = libraryAssets, Gaps = gaps, Detail = $"managers={managerCount} managerMethods={managerMethods} actorMethods={actorMethods} " + $"beh={behTypes} libraries={libraries} libraryAssets={libraryAssets} gaps={gaps}" }; } private static void AppendWorldFields( StringBuilder gapsTsv, StringBuilder managersTsv, ref int gaps, ref int managerMethods) { object world = null; try { world = World.world; } catch { // ignore } if (world == null) { return; } string[] fields = { "plots", "wars", "alliances", "clans", "cultures", "religions", "languages", "families", "armies", "books", "buildings", "items", "subspecies", "cities", "kingdoms", "diplomacy", "era_manager", "earthquake_manager", "units" }; Type worldType = world.GetType(); for (int i = 0; i < fields.Length; i++) { string name = fields[i]; object value = null; try { FieldInfo f = worldType.GetField(name); PropertyInfo p = worldType.GetProperty(name); value = f != null ? f.GetValue(world) : p?.GetValue(world, null); } catch { // ignore } string typeName = value != null ? value.GetType().Name : "null"; bool wired = name == "wars" || name == "units" || name == "subspecies" || name == "plots" // Meta genesis Harmony covers new*; era/quake have dedicated wires. || 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" || name == "era_manager" || name == "earthquake_manager"; managersTsv.Append("World").Append('\t') .Append(name).Append('\t') .Append("0").Append('\t') .Append("0").Append('\t') .Append("PollState").Append('\t') .Append(wired ? "1" : "0").Append('\t') .Append(typeName).AppendLine(); managerMethods++; if (!wired && value != null) { gaps++; gapsTsv.Append("PollState").Append('\t') .Append("World").Append('\t') .Append(name).Append('\t') .Append("Yes").Append('\t') .Append("true").Append('\t') .Append("0").Append('\t') .Append("world_field=" + typeName).AppendLine(); } } } private static void DumpTypeMethods( Type type, string[] needles, StringBuilder actorTsv, StringBuilder gapsTsv, ref int actorMethods, ref int gaps) { MethodInfo[] methods = SafeMethods(type); for (int i = 0; i < methods.Length; i++) { MethodInfo method = methods[i]; if (method == null || method.IsSpecialName) { continue; } string needle = FirstMatch(method.Name, needles); if (string.IsNullOrEmpty(needle) && !MethodLooksMutating(method.Name)) { continue; } if (string.IsNullOrEmpty(needle)) { needle = "mutate"; } actorMethods++; string key = type.Name + "." + method.Name; bool wired = AlreadyWired.Contains(key); actorTsv.Append(type.Name).Append('\t') .Append(method.Name).Append('\t') .Append(method.GetParameters().Length).Append('\t') .Append(method.IsStatic ? "1" : "0").Append('\t') .Append(needle).Append('\t') .Append(wired ? "1" : "0").Append('\t') .Append(Escape(method.ToString())).AppendLine(); if (!wired && IsEventWorthyActor(method.Name)) { gaps++; gapsTsv.Append("ActorMutation").Append('\t') .Append(type.Name).Append('\t') .Append(method.Name).Append('\t') .Append("Yes").Append('\t') .Append(SuggestCreatesInterest("Actor", method.Name)).Append('\t') .Append("0").Append('\t') .Append("unwired_actor_mutation").AppendLine(); } } } private static void DumpExtraActorRelationMethods( Type actorType, StringBuilder actorTsv, StringBuilder gapsTsv, ref int actorMethods, ref int gaps) { string[] extra = { "removeLover", "clearLover", "setLover", "getLover", "hasLover", "removeBestFriend", "clearBestFriend", "getBestFriend", "addChild", "removeChild", "makeBaby", "haveChild", "addTrait", "removeTrait", "setPlot", "leavePlot", "finishPlot" }; var seen = new HashSet(StringComparer.OrdinalIgnoreCase); MethodInfo[] found = actorType.GetMethods( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int m = 0; m < found.Length; m++) { MethodInfo method = found[m]; if (method == null || method.IsSpecialName) { continue; } bool match = false; for (int i = 0; i < extra.Length; i++) { if (method.Name.Equals(extra[i], StringComparison.OrdinalIgnoreCase) || method.Name.StartsWith(extra[i], StringComparison.OrdinalIgnoreCase)) { match = true; break; } } if (!match || !seen.Add(method.Name)) { continue; } actorMethods++; string key = actorType.Name + "." + method.Name; bool wired = AlreadyWired.Contains(key); actorTsv.Append(actorType.Name).Append('\t') .Append(method.Name).Append('\t') .Append(method.GetParameters().Length).Append('\t') .Append(method.IsStatic ? "1" : "0").Append('\t') .Append("relation").Append('\t') .Append(wired ? "1" : "0").Append('\t') .Append("extra_relation_scan").AppendLine(); if (!wired && !method.Name.StartsWith("get", StringComparison.OrdinalIgnoreCase) && !method.Name.StartsWith("has", StringComparison.OrdinalIgnoreCase)) { gaps++; gapsTsv.Append("ActorMutation").Append('\t') .Append(actorType.Name).Append('\t') .Append(method.Name).Append('\t') .Append("Yes").Append('\t') .Append("true").Append('\t') .Append("0").Append('\t') .Append("relation_lifecycle").AppendLine(); } } } private static List EnumerateLibraryIds(string fieldName) { var ids = new List(); try { object lib = typeof(AssetManager).GetField(fieldName)?.GetValue(null); if (lib == null) { return ids; } object listObj = lib.GetType().GetField("list")?.GetValue(lib) ?? lib.GetType().GetProperty("list")?.GetValue(lib, null); System.Collections.IList list = listObj as System.Collections.IList; if (list == null) { return ids; } for (int i = 0; i < list.Count; i++) { object asset = list[i]; object idObj = asset?.GetType().GetField("id")?.GetValue(asset) ?? asset?.GetType().GetProperty("id")?.GetValue(asset, null); string id = idObj as string; if (!string.IsNullOrEmpty(id)) { ids.Add(id); } } } catch { // Library missing in this build. } return ids; } private static MethodInfo[] SafeMethods(Type type) { try { return type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); } catch { return Array.Empty(); } } private static bool IsManagerish(string name) { for (int i = 0; i < ManagerNameNeedles.Length; i++) { if (name.IndexOf(ManagerNameNeedles[i], StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static bool MethodLooksMutating(string name) { string n = name ?? ""; for (int i = 0; i < MethodPrefixNeedles.Length; i++) { if (n.StartsWith(MethodPrefixNeedles[i], StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool MatchesAny(string value, string[] needles) { return !string.IsNullOrEmpty(FirstMatch(value, needles)); } private static string FirstMatch(string value, string[] needles) { if (string.IsNullOrEmpty(value) || needles == null) { return ""; } for (int i = 0; i < needles.Length; i++) { if (value.IndexOf(needles[i], StringComparison.OrdinalIgnoreCase) >= 0) { return needles[i]; } } return ""; } private static string GuessManagerKind(string typeName, string methodName) { if (methodName.StartsWith("new", StringComparison.OrdinalIgnoreCase) || methodName.StartsWith("create", StringComparison.OrdinalIgnoreCase) || methodName.StartsWith("generate", StringComparison.OrdinalIgnoreCase)) { return "ManagerGenesis"; } if (methodName.StartsWith("remove", StringComparison.OrdinalIgnoreCase) || methodName.StartsWith("destroy", StringComparison.OrdinalIgnoreCase) || methodName.StartsWith("finish", StringComparison.OrdinalIgnoreCase) || methodName.StartsWith("cancel", StringComparison.OrdinalIgnoreCase) || methodName.StartsWith("end", StringComparison.OrdinalIgnoreCase)) { return "ManagerEnd"; } 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 ?? ""; // 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")) { return false; } 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 || m.IndexOf("birth", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("baby", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("trait", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("plot", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("die", StringComparison.OrdinalIgnoreCase) >= 0 || m.IndexOf("kill", StringComparison.OrdinalIgnoreCase) >= 0; } private static bool IsEventWorthyBeh(string typeName) { // 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) { string t = typeName ?? ""; return t.IndexOf("Social", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("Talk", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("Check", StringComparison.OrdinalIgnoreCase) >= 0 || t.IndexOf("Follow", StringComparison.OrdinalIgnoreCase) >= 0; } private static string SuggestCreatesInterest(string typeName, string methodName) { string blob = (typeName ?? "") + "." + (methodName ?? ""); if (blob.IndexOf("War", StringComparison.OrdinalIgnoreCase) >= 0 || blob.IndexOf("Plot", StringComparison.OrdinalIgnoreCase) >= 0 || blob.IndexOf("Age", StringComparison.OrdinalIgnoreCase) >= 0 || blob.IndexOf("die", StringComparison.OrdinalIgnoreCase) >= 0 || blob.IndexOf("kill", StringComparison.OrdinalIgnoreCase) >= 0 || blob.IndexOf("lover", StringComparison.OrdinalIgnoreCase) >= 0 || blob.IndexOf("destroy", StringComparison.OrdinalIgnoreCase) >= 0 || blob.IndexOf("new", StringComparison.OrdinalIgnoreCase) >= 0) { return "true"; } return "false"; } private static string Escape(string value) { if (string.IsNullOrEmpty(value)) { return ""; } return value.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' '); } }