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(), EventCatalog.Plot.AuthoredIds); AppendLibraryDomain(tsv, "plots", ActivityAssetCatalog.EnumerateLivePlotIds(), plots, EventCatalog.Plot.GetOrFallback); CatalogCoverageDiff eras = CatalogCoverageAudit.Diff( "eras", ActivityAssetCatalog.EnumerateLiveEraIds(), EventCatalog.Era.AuthoredIds); AppendLibraryDomain(tsv, "eras", ActivityAssetCatalog.EnumerateLiveEraIds(), eras, EventCatalog.Era.GetOrFallback); CatalogCoverageDiff books = CatalogCoverageAudit.Diff( "books", ActivityAssetCatalog.EnumerateLiveBookTypeIds(), EventCatalog.Book.AuthoredIds); AppendLibraryDomain(tsv, "books", ActivityAssetCatalog.EnumerateLiveBookTypeIds(), books, EventCatalog.Book.GetOrFallback); CatalogCoverageDiff traits = CatalogCoverageAudit.Diff( "traits", ActivityAssetCatalog.EnumerateLiveTraitIds(), EventCatalog.Trait.AuthoredIds); AppendLibraryDomain(tsv, "traits", ActivityAssetCatalog.EnumerateLiveTraitIds(), traits, EventCatalog.Trait.GetOrFallback); CatalogCoverageDiff spells = CatalogCoverageAudit.Diff( "spells", ActivityAssetCatalog.EnumerateLiveSpellIds(), EventCatalog.Spell.AuthoredIds); AppendLibraryDomain(tsv, "spells", ActivityAssetCatalog.EnumerateLiveSpellIds(), spells, EventCatalog.Spell.GetOrFallback); CatalogCoverageDiff powers = CatalogCoverageAudit.Diff( "powers", ActivityAssetCatalog.EnumerateLivePowerIds(), EventCatalog.Power.AuthoredIds); AppendLibraryDomain(tsv, "powers", ActivityAssetCatalog.EnumerateLivePowerIds(), powers, EventCatalog.Power.GetOrFallback); CatalogCoverageDiff decisions = CatalogCoverageAudit.Diff( "decisions", ActivityAssetCatalog.EnumerateLiveDecisionIds(), EventCatalog.Decision.AuthoredIds); AppendLibraryDomain(tsv, "decisions", ActivityAssetCatalog.EnumerateLiveDecisionIds(), decisions, EventCatalog.Decision.GetOrFallback); CatalogCoverageDiff items = CatalogCoverageAudit.Diff( "items", ActivityAssetCatalog.EnumerateLiveItemIds(), EventCatalog.Item.AuthoredIds); AppendLibraryDomain(tsv, "items", ActivityAssetCatalog.EnumerateLiveItemIds(), items, EventCatalog.Item.GetOrFallback); CatalogCoverageDiff subspTraits = CatalogCoverageAudit.Diff( "subspecies_traits", ActivityAssetCatalog.EnumerateLiveSubspeciesTraitIds(), EventCatalog.SubspeciesTrait.AuthoredIds); AppendLibraryDomain( tsv, "subspecies_traits", ActivityAssetCatalog.EnumerateLiveSubspeciesTraitIds(), subspTraits, EventCatalog.SubspeciesTrait.GetOrFallback); CatalogCoverageDiff genes = CatalogCoverageAudit.Diff( "genes", ActivityAssetCatalog.EnumerateLiveGeneIds(), EventCatalog.Gene.AuthoredIds); AppendLibraryDomain(tsv, "genes", ActivityAssetCatalog.EnumerateLiveGeneIds(), genes, EventCatalog.Gene.GetOrFallback); CatalogCoverageDiff phenotypes = CatalogCoverageAudit.Diff( "phenotypes", ActivityAssetCatalog.EnumerateLivePhenotypeIds(), EventCatalog.Phenotype.AuthoredIds); AppendLibraryDomain( tsv, "phenotypes", ActivityAssetCatalog.EnumerateLivePhenotypeIds(), phenotypes, EventCatalog.Phenotype.GetOrFallback); CatalogCoverageDiff worldLaws = CatalogCoverageAudit.Diff( "world_laws", ActivityAssetCatalog.EnumerateLiveWorldLawIds(), EventCatalog.WorldLaw.AuthoredIds); AppendLibraryDomain( tsv, "world_laws", ActivityAssetCatalog.EnumerateLiveWorldLawIds(), worldLaws, EventCatalog.WorldLaw.GetOrFallback); CatalogCoverageDiff biomes = CatalogCoverageAudit.Diff( "biomes", ActivityAssetCatalog.EnumerateLiveBiomeIds(), EventCatalog.Biome.AuthoredIds); AppendLibraryDomain(tsv, "biomes", ActivityAssetCatalog.EnumerateLiveBiomeIds(), biomes, EventCatalog.Biome.GetOrFallback); CatalogCoverageDiff cultureTraits = CatalogCoverageAudit.Diff( "culture_traits", ActivityAssetCatalog.EnumerateLiveCultureTraitIds(), EventCatalog.CultureTrait.AuthoredIds); AppendLibraryDomain( tsv, "culture_traits", ActivityAssetCatalog.EnumerateLiveCultureTraitIds(), cultureTraits, EventCatalog.CultureTrait.GetOrFallback); CatalogCoverageDiff religionTraits = CatalogCoverageAudit.Diff( "religion_traits", ActivityAssetCatalog.EnumerateLiveReligionTraitIds(), EventCatalog.ReligionTrait.AuthoredIds); AppendLibraryDomain( tsv, "religion_traits", ActivityAssetCatalog.EnumerateLiveReligionTraitIds(), religionTraits, EventCatalog.ReligionTrait.GetOrFallback); CatalogCoverageDiff clanTraits = CatalogCoverageAudit.Diff( "clan_traits", ActivityAssetCatalog.EnumerateLiveClanTraitIds(), EventCatalog.ClanTrait.AuthoredIds); AppendLibraryDomain( tsv, "clan_traits", ActivityAssetCatalog.EnumerateLiveClanTraitIds(), clanTraits, EventCatalog.ClanTrait.GetOrFallback); CatalogCoverageDiff languageTraits = CatalogCoverageAudit.Diff( "language_traits", ActivityAssetCatalog.EnumerateLiveLanguageTraitIds(), EventCatalog.LanguageTrait.AuthoredIds); AppendLibraryDomain( tsv, "language_traits", ActivityAssetCatalog.EnumerateLiveLanguageTraitIds(), languageTraits, EventCatalog.LanguageTrait.GetOrFallback); int relMissing = 0; int relTotal = 0; foreach (string id in EventCatalog.Relationship.AuthoredIds) { relTotal++; DiscreteEventEntry entry = EventCatalog.Relationship.GetOrFallback(id); bool ok = EventCatalog.Relationship.HasAuthored(id) && !entry.IsFallback; if (!ok) { relMissing++; } tsv.Append("relationship\t").Append(id).Append('\t') .Append(ok ? "1" : "0").Append('\t') .Append(entry.CreatesInterest).Append('\t') .Append(entry.EventStrength.ToString("0.#")).Append('\t') .Append(ok ? "ok" : "missing_authored").AppendLine(); } CatalogCoverageAudit.WriteReport(outputDirectory, "domain_event_audit.tsv", tsv.ToString()); // Camera decisions must have authored ActionPhrase + EventStrength (event-catalog.json). int decisionProseMissing = 0; int decisionStrengthMissing = 0; int decisionProseTotal = 0; List liveDecisions = ActivityAssetCatalog.EnumerateLiveDecisionIds(); for (int i = 0; i < liveDecisions.Count; i++) { string id = liveDecisions[i]; if (!EventCatalog.Decision.IsCameraWorthy(id)) { continue; } decisionProseTotal++; DiscreteEventEntry entry = EventCatalog.Decision.GetOrFallback(id); if (!EventCatalog.Decision.TryGetAuthoredActionPhrase(id, out _)) { decisionProseMissing++; tsv.Append("decision_prose\t").Append(id).Append("\t0\t1\t\tmissing_action_phrase").AppendLine(); } else if (entry.EventStrength <= 0f) { decisionStrengthMissing++; tsv.Append("decision_prose\t").Append(id).Append("\t0\t1\t") .Append(entry.EventStrength.ToString("0.#")) .Append("\tmissing_strength").AppendLine(); } else { string phrase = EventCatalog.Decision.ActionPhraseFor(id); tsv.Append("decision_prose\t").Append(id).Append("\t1\t1\t") .Append(entry.EventStrength.ToString("0.#")).Append('\t') .Append(phrase).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 && decisionProseMissing == 0 && decisionStrengthMissing == 0 && decisionProseTotal > 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}; " + $"decision_prose: camera={decisionProseTotal} missing={decisionProseMissing} " + $"strengthMissing={decisionStrengthMissing} catalog={EventCatalogConfig.LoadedFromFile}"; 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.CreatesInterest).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(); } } }