worldbox-observer-mod/IdleSpectator/WorldLogEventHarness.cs
2026-07-15 20:09:08 -05:00

216 lines
6.8 KiB
C#

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;
}
/// <summary>
/// Exhaustive live world_log_library audit for authored WorldLog event catalog coverage.
/// </summary>
public static class WorldLogEventHarness
{
public static WorldLogEventAuditResult RunAudit(string outputDirectory)
{
List<string> liveIds = ActivityAssetCatalog.EnumerateLiveWorldLogIds();
var authored = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string id in WorldLogEventCatalog.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<string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < liveIds.Count; i++)
{
string id = liveIds[i];
liveSet.Add(id);
WorldLogAsset asset = ActivityAssetCatalog.TryGetWorldLogAsset(id);
bool hasAuthored = WorldLogEventCatalog.HasAuthored(id);
WorldLogEventEntry entry = WorldLogEventCatalog.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");
if (string.IsNullOrEmpty(labelSample))
{
emptyLabel++;
}
string notes = "ok";
if (!hasAuthored || entry.IsFallback)
{
notes = "missing_authored";
}
else if (string.IsNullOrEmpty(labelSample))
{
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<string> disasters = ActivityAssetCatalog.EnumerateLiveDisasterIds();
for (int i = 0; i < disasters.Count; i++)
{
tsv.Append("disaster\t").Append(disasters[i]).Append("\tok").AppendLine();
}
List<string> 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', ' ');
}
}