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); CatalogCoverageDiff spells = CatalogCoverageAudit.Diff( "spells", ActivityAssetCatalog.EnumerateLiveSpellIds(), SpellInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "spells", ActivityAssetCatalog.EnumerateLiveSpellIds(), spells, SpellInterestCatalog.GetOrFallback); CatalogCoverageDiff powers = CatalogCoverageAudit.Diff( "powers", ActivityAssetCatalog.EnumerateLivePowerIds(), PowerInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "powers", ActivityAssetCatalog.EnumerateLivePowerIds(), powers, PowerInterestCatalog.GetOrFallback); CatalogCoverageDiff decisions = CatalogCoverageAudit.Diff( "decisions", ActivityAssetCatalog.EnumerateLiveDecisionIds(), DecisionInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "decisions", ActivityAssetCatalog.EnumerateLiveDecisionIds(), decisions, DecisionInterestCatalog.GetOrFallback); CatalogCoverageDiff items = CatalogCoverageAudit.Diff( "items", ActivityAssetCatalog.EnumerateLiveItemIds(), ItemInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "items", ActivityAssetCatalog.EnumerateLiveItemIds(), items, ItemInterestCatalog.GetOrFallback); CatalogCoverageDiff subspTraits = CatalogCoverageAudit.Diff( "subspecies_traits", ActivityAssetCatalog.EnumerateLiveSubspeciesTraitIds(), SubspeciesTraitInterestCatalog.AuthoredIds); AppendLibraryDomain( tsv, "subspecies_traits", ActivityAssetCatalog.EnumerateLiveSubspeciesTraitIds(), subspTraits, SubspeciesTraitInterestCatalog.GetOrFallback); CatalogCoverageDiff genes = CatalogCoverageAudit.Diff( "genes", ActivityAssetCatalog.EnumerateLiveGeneIds(), GeneInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "genes", ActivityAssetCatalog.EnumerateLiveGeneIds(), genes, GeneInterestCatalog.GetOrFallback); CatalogCoverageDiff phenotypes = CatalogCoverageAudit.Diff( "phenotypes", ActivityAssetCatalog.EnumerateLivePhenotypeIds(), PhenotypeInterestCatalog.AuthoredIds); AppendLibraryDomain( tsv, "phenotypes", ActivityAssetCatalog.EnumerateLivePhenotypeIds(), phenotypes, PhenotypeInterestCatalog.GetOrFallback); CatalogCoverageDiff worldLaws = CatalogCoverageAudit.Diff( "world_laws", ActivityAssetCatalog.EnumerateLiveWorldLawIds(), WorldLawInterestCatalog.AuthoredIds); AppendLibraryDomain( tsv, "world_laws", ActivityAssetCatalog.EnumerateLiveWorldLawIds(), worldLaws, WorldLawInterestCatalog.GetOrFallback); CatalogCoverageDiff biomes = CatalogCoverageAudit.Diff( "biomes", ActivityAssetCatalog.EnumerateLiveBiomeIds(), BiomeInterestCatalog.AuthoredIds); AppendLibraryDomain(tsv, "biomes", ActivityAssetCatalog.EnumerateLiveBiomeIds(), biomes, BiomeInterestCatalog.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 && spells.Passed && powers.Passed && decisions.Passed && items.Passed && subspTraits.Passed && genes.Passed && phenotypes.Passed && worldLaws.Passed && biomes.Passed && relMissing == 0 && relTotal > 0; string detail = $"{plots.Detail}; {eras.Detail}; {books.Detail}; {traits.Detail}; " + $"{spells.Detail}; {powers.Detail}; {decisions.Detail}; {items.Detail}; " + $"{subspTraits.Detail}; {genes.Detail}; {phenotypes.Detail}; " + $"{worldLaws.Detail}; {biomes.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(); } } }