using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using UnityEngine; namespace IdleSpectator; public sealed class WorldLogEventAuditResult { public bool Passed; public string Detail = ""; public int Total; public int MissingAuthored; public int OrphanAuthored; public int EmptyLabel; } /// /// Exhaustive live world_log_library audit for authored WorldLog event catalog coverage. /// public static class WorldLogEventHarness { public static WorldLogEventAuditResult RunAudit(string outputDirectory) { List liveIds = ActivityAssetCatalog.EnumerateLiveWorldLogIds(); var authored = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string id in EventCatalog.WorldLog.AuthoredIds) { authored.Add(id); } int missingAuthored = 0; int orphanAuthored = 0; int emptyLabel = 0; var tsv = new StringBuilder(); tsv.AppendLine( "id\tgroup\tlocale\tpath_icon\tdisaster_link\tauthored\tevent_strength\tcategory\tcreates_interest\tlabel_sample\tnotes"); var liveSet = new HashSet(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < liveIds.Count; i++) { string id = liveIds[i]; liveSet.Add(id); WorldLogAsset asset = ActivityAssetCatalog.TryGetWorldLogAsset(id); bool hasAuthored = EventCatalog.WorldLog.HasAuthored(id); WorldLogEventEntry entry = EventCatalog.WorldLog.GetOrFallback(id); if (!hasAuthored || entry.IsFallback) { missingAuthored++; } string group = ReadStringField(asset, "group_id") ?? ReadStringField(asset, "history_group") ?? ReadStringField(asset, "group") ?? ""; string locale = ""; try { locale = asset != null ? (asset.getLocaleID() ?? "") : ""; } catch { locale = ReadStringField(asset, "path_locale") ?? ""; } string pathIcon = ReadStringField(asset, "path_icon") ?? ""; string disasterLink = ReadStringField(asset, "disaster_id") ?? ReadStringField(asset, "linked_disaster") ?? ""; if (string.IsNullOrEmpty(disasterLink) && id.IndexOf("disaster", StringComparison.OrdinalIgnoreCase) >= 0) { disasterLink = "id_hint"; } string labelSample = entry.MakeLabel("Alpha", "Beta"); string emptySlotSample = entry.MakeLabel("", ""); bool hanging = WorldLogEventEntry.IsHangingLabel(labelSample) || WorldLogEventEntry.IsHangingLabel(emptySlotSample) || string.IsNullOrWhiteSpace(emptySlotSample); if (hanging) { emptyLabel++; } string notes = "ok"; if (!hasAuthored || entry.IsFallback) { notes = "missing_authored"; } else if (hanging) { notes = "empty_label"; } tsv.Append(id).Append('\t') .Append(Escape(group)).Append('\t') .Append(Escape(locale)).Append('\t') .Append(Escape(pathIcon)).Append('\t') .Append(Escape(disasterLink)).Append('\t') .Append(hasAuthored ? "1" : "0").Append('\t') .Append(entry.EventStrength.ToString("0.#")).Append('\t') .Append(Escape(entry.Category)).Append('\t') .Append(entry.CreatesInterest ? "1" : "0").Append('\t') .Append(Escape(labelSample)).Append('\t') .Append(notes).AppendLine(); } foreach (string id in authored) { if (!liveSet.Contains(id)) { orphanAuthored++; tsv.Append(id).Append("\t\t\t\t\t1\t0\t\t0\t\torphan_authored").AppendLine(); } } try { if (!string.IsNullOrEmpty(outputDirectory)) { Directory.CreateDirectory(outputDirectory); File.WriteAllText( Path.Combine(outputDirectory, "world-log-event-audit.tsv"), tsv.ToString()); } } catch { // Disk may be unavailable in some harness contexts. } bool passed = missingAuthored == 0 && orphanAuthored == 0 && emptyLabel == 0; return new WorldLogEventAuditResult { Passed = passed, Total = liveIds.Count, MissingAuthored = missingAuthored, OrphanAuthored = orphanAuthored, EmptyLabel = emptyLabel, Detail = $"total={liveIds.Count} missingAuthored={missingAuthored} orphanAuthored={orphanAuthored} " + $"emptyLabel={emptyLabel}" }; } public static string DumpDisasterWarInventory(string outputDirectory) { var tsv = new StringBuilder(); tsv.AppendLine("kind\tid\tnotes"); List disasters = ActivityAssetCatalog.EnumerateLiveDisasterIds(); for (int i = 0; i < disasters.Count; i++) { tsv.Append("disaster\t").Append(disasters[i]).Append("\tok").AppendLine(); } List warTypes = ActivityAssetCatalog.EnumerateLiveWarTypeIds(); for (int i = 0; i < warTypes.Count; i++) { tsv.Append("war_type\t").Append(warTypes[i]).Append("\tok").AppendLine(); } try { if (!string.IsNullOrEmpty(outputDirectory)) { Directory.CreateDirectory(outputDirectory); File.WriteAllText( Path.Combine(outputDirectory, "disaster-war-inventory.tsv"), tsv.ToString()); } } catch { // ignore } return $"disasters={disasters.Count} war_types={warTypes.Count}"; } private static string ReadStringField(object asset, string fieldName) { if (asset == null || string.IsNullOrEmpty(fieldName)) { return null; } try { FieldInfo field = asset.GetType().GetField(fieldName); if (field != null) { return field.GetValue(asset) as string; } PropertyInfo prop = asset.GetType().GetProperty(fieldName); if (prop != null) { return prop.GetValue(asset, null) as string; } } catch { // ignore } return null; } private static string Escape(string value) { if (string.IsNullOrEmpty(value)) { return ""; } return value.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' '); } }