worldbox-observer-mod/IdleSpectator/CameraAInventoryHarness.cs
DazedAnon f10424c5df Add camera inventory audit and expand event catalog actions.
- Introduce "camera_a_inventory_ok" check in AgentHarness for inventory audits
- Add new actions to event catalog for civilization founding and reproduction
- Update decision catalog to include new action phrases and signal predicates
- Increment version to 0.28.27 in mod.json
2026-07-17 18:37:10 -05:00

278 lines
9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IdleSpectator;
public sealed class CameraAInventoryResult
{
public bool Passed;
public string Detail = "";
public int Total;
public int CameraA;
public int CameraB;
}
/// <summary>
/// Live dump of Layer A vs B fate for decisions + library dial domains.
/// Source of truth for Signal / decision promote work (never promote from memory).
/// </summary>
public static class CameraAInventoryHarness
{
public static CameraAInventoryResult RunAudit(string outputDirectory)
{
var lines = new List<string>
{
"domain\tid\tdisplay\tlayer\tstrength\treason"
};
int total = 0;
int cameraA = 0;
int cameraB = 0;
var summary = new StringBuilder();
AppendDomain(
lines,
ref total,
ref cameraA,
ref cameraB,
summary,
"decisions",
"decision",
ActivityAssetCatalog.EnumerateLiveDecisionIds(),
id => EventCatalog.Decision.GetOrFallback(id),
id => EventCatalog.Decision.IsCameraWorthy(id)
? (EventCatalog.Decision.TryGetAuthoredActionPhrase(id, out _)
? "signal_predicate+phrase"
: "signal_predicate")
: ReasonDecisionB(id));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"spells", "spell",
ActivityAssetCatalog.EnumerateLiveSpellIds(),
EventCatalog.Spell.GetOrFallback,
id => ReasonLibrary(EventCatalog.Spell.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"powers", "power",
ActivityAssetCatalog.EnumerateLivePowerIds(),
EventCatalog.Power.GetOrFallback,
id => ReasonLibrary(EventCatalog.Power.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"items", "item",
ActivityAssetCatalog.EnumerateLiveItemIds(),
EventCatalog.Item.GetOrFallback,
id => ReasonLibrary(EventCatalog.Item.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"genes", "gene",
ActivityAssetCatalog.EnumerateLiveGeneIds(),
EventCatalog.Gene.GetOrFallback,
id => ReasonLibrary(EventCatalog.Gene.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"phenotypes", "phenotype",
ActivityAssetCatalog.EnumerateLivePhenotypeIds(),
EventCatalog.Phenotype.GetOrFallback,
id => ReasonLibrary(EventCatalog.Phenotype.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"world_laws", "worldlaw",
ActivityAssetCatalog.EnumerateLiveWorldLawIds(),
EventCatalog.WorldLaw.GetOrFallback,
id => ReasonLibrary(EventCatalog.WorldLaw.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"subspecies_traits", "subspecies_trait",
ActivityAssetCatalog.EnumerateLiveSubspeciesTraitIds(),
EventCatalog.SubspeciesTrait.GetOrFallback,
id => ReasonLibrary(EventCatalog.SubspeciesTrait.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"biomes", "biome",
ActivityAssetCatalog.EnumerateLiveBiomeIds(),
EventCatalog.Biome.GetOrFallback,
id => ReasonLibrary(EventCatalog.Biome.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"culture_traits", "culture_trait",
ActivityAssetCatalog.EnumerateLiveCultureTraitIds(),
EventCatalog.CultureTrait.GetOrFallback,
id => ReasonLibrary(EventCatalog.CultureTrait.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"religion_traits", "religion_trait",
ActivityAssetCatalog.EnumerateLiveReligionTraitIds(),
EventCatalog.ReligionTrait.GetOrFallback,
id => ReasonLibrary(EventCatalog.ReligionTrait.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"clan_traits", "clan_trait",
ActivityAssetCatalog.EnumerateLiveClanTraitIds(),
EventCatalog.ClanTrait.GetOrFallback,
id => ReasonLibrary(EventCatalog.ClanTrait.GetOrFallback(id)));
AppendDomain(
lines, ref total, ref cameraA, ref cameraB, summary,
"language_traits", "language_trait",
ActivityAssetCatalog.EnumerateLiveLanguageTraitIds(),
EventCatalog.LanguageTrait.GetOrFallback,
id => ReasonLibrary(EventCatalog.LanguageTrait.GetOrFallback(id)));
try
{
if (!string.IsNullOrEmpty(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
File.WriteAllLines(Path.Combine(outputDirectory, "camera-a-inventory.tsv"), lines);
File.WriteAllText(
Path.Combine(outputDirectory, "camera-a-inventory-summary.txt"),
summary.ToString());
}
}
catch
{
// diagnostic dump only
}
bool passed = total > 0 && cameraA > 0;
return new CameraAInventoryResult
{
Passed = passed,
Total = total,
CameraA = cameraA,
CameraB = cameraB,
Detail = $"total={total} A={cameraA} B={cameraB} domains=13"
};
}
private static void AppendDomain(
List<string> lines,
ref int total,
ref int cameraA,
ref int cameraB,
StringBuilder summary,
string domain,
string displayKind,
List<string> ids,
Func<string, DiscreteEventEntry> getEntry,
Func<string, string> reasonFor)
{
if (ids == null)
{
ids = new List<string>();
}
int a = 0;
int b = 0;
for (int i = 0; i < ids.Count; i++)
{
string id = ids[i];
if (string.IsNullOrEmpty(id))
{
continue;
}
DiscreteEventEntry entry = getEntry(id);
bool isA = entry != null
&& EventCatalog.IsCameraWorthy(entry);
string layer = isA ? "A" : "B";
if (isA)
{
a++;
cameraA++;
}
else
{
b++;
cameraB++;
}
total++;
string display = domain == "decisions"
? EventReason.HumanizeId(id)
: LibraryAssetNames.DisplayName(displayKind, id);
display = SanitizeTsv(display);
float strength = entry != null ? entry.EventStrength : 0f;
string reason = SanitizeTsv(reasonFor(id) ?? "");
lines.Add(
domain + "\t" + id + "\t" + display + "\t" + layer + "\t"
+ strength.ToString("0.#") + "\t" + reason);
}
summary.AppendLine(domain + "\tlive=" + ids.Count + "\tA=" + a + "\tB=" + b);
}
private static string ReasonLibrary(DiscreteEventEntry entry)
{
if (entry == null)
{
return "missing";
}
if (entry.IsFallback)
{
return "fallback";
}
return entry.CreatesInterest ? "signal" : "ambient";
}
private static string ReasonDecisionB(string id)
{
if (string.IsNullOrEmpty(id))
{
return "empty";
}
string s = id.ToLowerInvariant();
if (s == "try_new_plot" || s.StartsWith("try_new_plot_", StringComparison.Ordinal))
{
return "deny_plot_intent";
}
if (s.Contains("idle")
|| s.Contains("walking")
|| s.Contains("check")
|| s.Contains("wait")
|| s.Contains("sleep")
|| s.Contains("random")
|| s.Contains("play")
|| s.Contains("jump")
|| s.Contains("flip")
|| s.Contains("diet_")
|| s.Contains("move")
|| s.Contains("swim")
|| s.Contains("follow_parent")
|| s.Contains("follow_desire")
|| s.Contains("reflection")
|| s.Contains("poop"))
{
return "deny_fluff";
}
return "no_signal_token";
}
private static string SanitizeTsv(string value)
{
if (string.IsNullOrEmpty(value))
{
return "";
}
return value.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' ');
}
}