using System; using System.Collections.Generic; using System.IO; using System.Text; namespace IdleSpectator; public sealed class CatalogCoverageDiff { public string Domain = ""; public int LiveTotal; public int AuthoredTotal; public int Missing; public int Orphan; public readonly List MissingIds = new List(); public readonly List OrphanIds = new List(); public bool Passed => Missing == 0 && Orphan == 0; public string Detail => $"{Domain}: live={LiveTotal} authored={AuthoredTotal} missing={Missing} orphan={Orphan}"; } /// Shared live-vs-authored coverage audit used by domain event catalogs. public static class CatalogCoverageAudit { public static CatalogCoverageDiff Diff( string domain, IEnumerable liveIds, IEnumerable authoredIds) { var live = new HashSet(StringComparer.OrdinalIgnoreCase); var authored = new HashSet(StringComparer.OrdinalIgnoreCase); var diff = new CatalogCoverageDiff { Domain = domain ?? "domain" }; if (liveIds != null) { foreach (string id in liveIds) { if (string.IsNullOrEmpty(id)) { continue; } live.Add(id.Trim()); } } if (authoredIds != null) { foreach (string id in authoredIds) { if (string.IsNullOrEmpty(id)) { continue; } authored.Add(id.Trim()); } } diff.LiveTotal = live.Count; diff.AuthoredTotal = authored.Count; foreach (string id in live) { if (!authored.Contains(id)) { diff.Missing++; diff.MissingIds.Add(id); } } foreach (string id in authored) { if (!live.Contains(id)) { diff.Orphan++; diff.OrphanIds.Add(id); } } return diff; } public static void WriteReport(string outputDirectory, string fileName, string body) { if (string.IsNullOrEmpty(outputDirectory) || string.IsNullOrEmpty(fileName)) { return; } try { Directory.CreateDirectory(outputDirectory); File.WriteAllText(Path.Combine(outputDirectory, fileName), body ?? ""); } catch { // ignore IO } } } public sealed class DomainEventAuditResult { public bool Passed; public string Detail = ""; } /// Coverage audits for relationship discrete events + live plot/era/book libraries. public static class DomainEventHarness { public static DomainEventAuditResult RunAudit(string outputDirectory) { var tsv = new StringBuilder(); tsv.AppendLine("domain\tid\tauthored\towns_camera\tevent_strength\tnotes"); CatalogCoverageDiff plots = CatalogCoverageAudit.Diff( "plots", ActivityAssetCatalog.EnumerateLivePlotIds(), PlotInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "plots", ActivityAssetCatalog.EnumerateLivePlotIds(), plots, PlotInterestCatalog.GetOrFallback); CatalogCoverageDiff eras = CatalogCoverageAudit.Diff( "eras", ActivityAssetCatalog.EnumerateLiveEraIds(), EraInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "eras", ActivityAssetCatalog.EnumerateLiveEraIds(), eras, EraInterestCatalog.GetOrFallback); CatalogCoverageDiff books = CatalogCoverageAudit.Diff( "books", ActivityAssetCatalog.EnumerateLiveBookTypeIds(), BookInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "books", ActivityAssetCatalog.EnumerateLiveBookTypeIds(), books, BookInterestCatalog.GetOrFallback); CatalogCoverageDiff traits = CatalogCoverageAudit.Diff( "traits", ActivityAssetCatalog.EnumerateLiveTraitIds(), TraitInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "traits", ActivityAssetCatalog.EnumerateLiveTraitIds(), traits, TraitInterestCatalog.GetOrFallback); int relMissing = 0; int relTotal = 0; foreach (string id in RelationshipEventCatalog.AuthoredIds) { relTotal++; DiscreteEventEntry entry = RelationshipEventCatalog.GetOrFallback(id); bool ok = RelationshipEventCatalog.HasAuthored(id) && !entry.IsFallback; if (!ok) { relMissing++; } tsv.Append("relationship\t").Append(id).Append('\t') .Append(ok ? "1" : "0").Append('\t') .Append(entry.OwnsCamera).Append('\t') .Append(entry.EventStrength.ToString("0.#")).Append('\t') .Append(ok ? "ok" : "missing_authored").AppendLine(); } CatalogCoverageAudit.WriteReport(outputDirectory, "domain_event_audit.tsv", tsv.ToString()); bool passed = plots.Passed && eras.Passed && books.Passed && traits.Passed && relMissing == 0 && relTotal > 0; string detail = $"{plots.Detail}; {eras.Detail}; {books.Detail}; {traits.Detail}; relationship: authored={relTotal} missing={relMissing}"; return new DomainEventAuditResult { Passed = passed, Detail = detail }; } private static void AppendLibraryDomain( StringBuilder tsv, string domain, IEnumerable liveIds, CatalogCoverageDiff diff, Func lookup) { foreach (string id in liveIds) { DiscreteEventEntry entry = lookup(id); 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.EventStrength.ToString("0.#")).Append('\t') .Append(has ? "ok" : "missing_authored").AppendLine(); } foreach (string id in diff.OrphanIds) { tsv.Append(domain).Append('\t').Append(id).Append("\t1\t\t0\torphan_authored").AppendLine(); } } }