diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs
index aeac97c..4b08c58 100644
--- a/IdleSpectator/AgentHarness.cs
+++ b/IdleSpectator/AgentHarness.cs
@@ -7326,6 +7326,13 @@ public static class AgentHarness
+ $"kinds={kinds.Count}";
break;
}
+ case "camera_a_inventory_ok":
+ {
+ CameraAInventoryResult inv = CameraAInventoryHarness.RunAudit(HarnessDir());
+ pass = inv.Passed;
+ detail = inv.Detail;
+ break;
+ }
case "library_ambient_policy_ok":
{
// Signal-only libraries: ambient dials must be B; phenotypes all B;
diff --git a/IdleSpectator/CameraAInventoryHarness.cs b/IdleSpectator/CameraAInventoryHarness.cs
new file mode 100644
index 0000000..d929bba
--- /dev/null
+++ b/IdleSpectator/CameraAInventoryHarness.cs
@@ -0,0 +1,278 @@
+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;
+}
+
+///
+/// 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).
+///
+public static class CameraAInventoryHarness
+{
+ public static CameraAInventoryResult RunAudit(string outputDirectory)
+ {
+ var lines = new List
+ {
+ "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 lines,
+ ref int total,
+ ref int cameraA,
+ ref int cameraB,
+ StringBuilder summary,
+ string domain,
+ string displayKind,
+ List ids,
+ Func getEntry,
+ Func reasonFor)
+ {
+ if (ids == null)
+ {
+ ids = new List();
+ }
+
+ 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', ' ');
+ }
+}
diff --git a/IdleSpectator/Events/Catalogs/EventCatalog.Decision.cs b/IdleSpectator/Events/Catalogs/EventCatalog.Decision.cs
index 86ee072..ab5f7d3 100644
--- a/IdleSpectator/Events/Catalogs/EventCatalog.Decision.cs
+++ b/IdleSpectator/Events/Catalogs/EventCatalog.Decision.cs
@@ -51,6 +51,37 @@ public static partial class EventCatalog
["warrior_army_follow_leader"] = "follow their leader",
["warrior_train_with_dummy"] = "train with a dummy",
["warrior_try_join_army_group"] = "try to join an army",
+ ["warrior_army_leader_move_to_attack_target"] = "lead the army to attack",
+
+ // Founding / territory
+ ["try_to_start_new_civilization"] = "start a new civilization",
+ ["build_civ_city_here"] = "found a city here",
+ ["claim_land"] = "claim this land",
+ ["king_check_new_city_foundation"] = "found a new city",
+ ["bee_create_hive"] = "create a hive",
+
+ // Life / pack
+ ["sexual_reproduction_try"] = "try to reproduce",
+ ["asexual_reproduction_budding"] = "reproduce by budding",
+ ["asexual_reproduction_divine"] = "reproduce by divine means",
+ ["asexual_reproduction_fission"] = "reproduce by fission",
+ ["asexual_reproduction_parthenogenesis"] = "reproduce by parthenogenesis",
+ ["asexual_reproduction_spores"] = "reproduce by spores",
+ ["asexual_reproduction_vegetative"] = "reproduce vegetatively",
+ ["family_group_follow"] = "follow their family group",
+ ["family_group_join_or_new_herd"] = "join or form a herd",
+ ["family_group_leave"] = "leave their family group",
+
+ // Spectacle / dark / civic
+ ["attack_golden_brain"] = "attack the golden brain",
+ ["make_skeleton"] = "raise a skeleton",
+ ["status_soul_harvested"] = "have their soul harvested",
+ ["possessed_following"] = "follow under possession",
+ ["try_to_take_city_item"] = "try to take a city item",
+ ["put_out_fire"] = "put out a fire",
+ ["burn_tumors"] = "burn tumors",
+ ["try_to_launch_fireworks"] = "launch fireworks",
+ ["try_affect_dreams"] = "affect dreams",
};
/// Drop cached rows so the next Ensure re-applies JSON overlays.
@@ -68,26 +99,6 @@ public static partial class EventCatalog
}
string s = id.ToLowerInvariant();
- // Pure AI ticks / daily fluff - not story beats.
- 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 false;
- }
// Intent-only: setDecisionCooldown fires before a Plot exists (and often never does).
// Real plot tips come from PlotInterestFeed / live World.plots.
@@ -96,7 +107,15 @@ public static partial class EventCatalog
return false;
}
+ // Daily AI / check ticks. Foundation checks stay eligible for Signal tokens.
+ if (IsFluffDecision(s))
+ {
+ return false;
+ }
+
return HasToken(s, "war")
+ || HasToken(s, "king")
+ || HasToken(s, "leader")
|| s.Contains("rebellion")
|| s.Contains("revolt")
|| s.Contains("alliance")
@@ -114,14 +133,79 @@ public static partial class EventCatalog
|| s.Contains("banish")
|| s.Contains("steal")
|| s.Contains("kill_unruly")
- || HasToken(s, "king")
- || HasToken(s, "leader")
|| s.Contains("clan")
|| s.Contains("religion")
|| s.Contains("culture")
|| s.Contains("baby_make")
|| s.Contains("have_child")
- || s.Contains("birth");
+ || s.Contains("birth")
+ || s.Contains("reproduction")
+ || s.Contains("civilization")
+ || s.Contains("civ_city")
+ || s.Contains("claim_land")
+ || s.Contains("golden_brain")
+ || s.Contains("make_skeleton")
+ || s.Contains("soul_harvest")
+ || s.Contains("possessed")
+ || s.Contains("create_hive")
+ || s.Contains("fireworks")
+ || s.Contains("affect_dreams")
+ || s.Contains("family_group")
+ || s.Contains("take_city_item")
+ || s.Contains("put_out_fire")
+ || s.Contains("burn_tumors");
+ }
+
+ ///
+ /// Pure AI ticks / daily fluff. Not story beats.
+ /// city_foundation checks are excluded so kings can Signal founding intent.
+ ///
+ private static bool IsFluffDecision(string s)
+ {
+ if (string.IsNullOrEmpty(s))
+ {
+ return true;
+ }
+
+ if (s.Contains("city_foundation"))
+ {
+ return false;
+ }
+
+ if (s.StartsWith("check_", StringComparison.Ordinal)
+ || s.Contains("_check_")
+ || s.EndsWith("_check", StringComparison.Ordinal))
+ {
+ return true;
+ }
+
+ return s.Contains("idle")
+ || s.Contains("walking")
+ || s.Contains("sleep")
+ || s.Contains("play")
+ || s.Contains("jump")
+ || s.Contains("flip")
+ || s.Contains("diet_")
+ || s.Contains("reflection")
+ || s.Contains("poop")
+ || s.Contains("follow_parent")
+ || s.Contains("follow_desire")
+ || s.Contains("move_random")
+ || s.Contains("random_move")
+ || s.Contains("random_fun")
+ || s.Contains("random_swim")
+ || s.Contains("random_teleport")
+ || s.Contains("random_emotion")
+ || s.Contains("warrior_random")
+ || s.Contains("captain_waiting")
+ || s.Contains("captain_idle")
+ || s.Contains("_waiting")
+ || s == "wait5"
+ || s.Contains("swim_to")
+ || s.Contains("move_to_water")
+ || s.Contains("bored_sleep")
+ || s.Contains("monophasic")
+ || s.Contains("polyphasic");
}
/// Underscore-token match so war does not hit warrior.
diff --git a/IdleSpectator/Events/Catalogs/EventCatalog.Libraries.cs b/IdleSpectator/Events/Catalogs/EventCatalog.Libraries.cs
index 1ed41de..18ca629 100644
--- a/IdleSpectator/Events/Catalogs/EventCatalog.Libraries.cs
+++ b/IdleSpectator/Events/Catalogs/EventCatalog.Libraries.cs
@@ -143,6 +143,17 @@ public static partial class EventCatalog
}
string s = id.ToLowerInvariant();
+ // God-tool editors / drip brushes stay B.
+ if (s.Contains("_edit")
+ || s.Contains("traits_")
+ || s.Contains("equipment_rain")
+ || s.EndsWith("_brush", StringComparison.Ordinal)
+ || s.Contains("draw_")
+ || s.Contains("paint"))
+ {
+ return false;
+ }
+
return s.Contains("meteor")
|| s.Contains("earthquake")
|| s.Contains("tornado")
@@ -150,12 +161,28 @@ public static partial class EventCatalog
|| s.Contains("nuke")
|| s.Contains("bomb")
|| s.Contains("rain_blood")
+ || s.Contains("blood_rain")
|| s.Contains("hell")
|| s.Contains("lightning")
|| s.Contains("storm")
|| s.Contains("acid")
|| s.Contains("fire")
- || s.Contains("disaster");
+ || s.Contains("disaster")
+ || s.Contains("dragon")
+ || s.Contains("demon")
+ || s.Contains("plague")
+ || s.Contains("zombie")
+ || s.Contains("crabzilla")
+ || s == "ufo"
+ || s.Contains("god_finger")
+ || s.Contains("cold_one")
+ || s.Contains("heatray")
+ || s.Contains("curse")
+ || s.Contains("lava")
+ || s.Contains("ash")
+ || s.Contains("golden_brain")
+ || s.Contains("corrupted_brain")
+ || s.Contains("infection");
}
private static void Ensure()
@@ -218,7 +245,8 @@ public static partial class EventCatalog
}
string s = id.ToLowerInvariant();
- // Top-tier materials from live items library (mythril/adamantine) plus named relics.
+ // High / mid-spectacle materials from live items library plus named relics.
+ // Leather/wood/copper/iron stay ambient B.
return s.Contains("legendary")
|| s.Contains("mythic")
|| s.Contains("mythril")
@@ -230,7 +258,12 @@ public static partial class EventCatalog
|| s.Contains("dragon")
|| s.Contains("nuke")
|| s.Contains("excalibur")
- || s.Contains("wonder");
+ || s.Contains("wonder")
+ || s.Contains("silver")
+ || s.Contains("steel")
+ || s.Contains("_bone")
+ || s.StartsWith("bone_", StringComparison.Ordinal)
+ || s.EndsWith("_bone", StringComparison.Ordinal);
}
private static void Ensure()
@@ -306,7 +339,12 @@ public static partial class EventCatalog
|| s.Contains("undead")
|| s.Contains("dragon")
|| s.Contains("evolve")
- || s.Contains("mutate");
+ || s.Contains("mutate")
+ || s.Contains("mutation")
+ || s.Contains("death")
+ || s.Contains("blood")
+ || s.Contains("horror")
+ || s.Contains("tentacle");
}
private static void Ensure()
@@ -379,10 +417,25 @@ public static partial class EventCatalog
d.AmbientStrength,
signalIds: null,
d.SignalStrength,
- signalPredicate: id => id != null && (id.Contains("warfare") || id.Contains("magic") || id.Contains("mutation")),
+ signalPredicate: IsSignalGene,
ambientCreatesInterest: d.AmbientCreatesInterest);
}
+ private static bool IsSignalGene(string id)
+ {
+ if (string.IsNullOrEmpty(id))
+ {
+ return false;
+ }
+
+ string s = id.ToLowerInvariant();
+ return s.Contains("warfare")
+ || s.Contains("magic")
+ || s.Contains("mutation")
+ || s.Contains("mutagen")
+ || s.Contains("damage");
+ }
+
public static IEnumerable AuthoredIds
{
get
@@ -501,7 +554,17 @@ public static partial class EventCatalog
|| s.Contains("hunger")
|| s.Contains("rebell")
|| s.Contains("disaster")
- || s.Contains("angry");
+ || s.Contains("angry")
+ || s.Contains("cursed")
+ || s.Contains("forever_")
+ || s.Contains("tumor")
+ || s.Contains("mutant")
+ || s.Contains("evolution")
+ || s.Contains("old_age")
+ || s.Contains("kingdom_expansion")
+ || s.Contains("border_steal")
+ || s.Contains("civ_army")
+ || s.Contains("exploding");
}
public static IEnumerable AuthoredIds
@@ -596,7 +659,18 @@ public static partial class EventCatalog
|| s.Contains("zealot")
|| s.Contains("fanatic")
|| s.Contains("schism")
- || s.Contains("royal");
+ || s.Contains("royal")
+ || s.Contains("conscript")
+ || s.Contains("join_or_die")
+ || s.Contains("necro")
+ || s.Contains("evil")
+ || s.Contains("shattered")
+ || s.Contains("titan")
+ || s.Contains("ethno")
+ || s.Contains("flame")
+ || s.Contains("ice_weapon")
+ || s.Contains("shotgun")
+ || s.Contains("blaster");
}
public static class CultureTrait
diff --git a/IdleSpectator/HarnessScenarios.cs b/IdleSpectator/HarnessScenarios.cs
index 61b29c2..3a5d695 100644
--- a/IdleSpectator/HarnessScenarios.cs
+++ b/IdleSpectator/HarnessScenarios.cs
@@ -1994,6 +1994,7 @@ internal static class HarnessScenarios
Step("dea1", "assert", expect: "domain_event_audit"),
Step("dea1b", "assert", expect: "library_asset_labels_ok"),
Step("dea1c", "assert", expect: "library_ambient_policy_ok"),
+ Step("dea1d", "assert", expect: "camera_a_inventory_ok"),
Step("dea2", "snapshot")
};
}
diff --git a/IdleSpectator/event-catalog.json b/IdleSpectator/event-catalog.json
index 4ac1359..3af85bf 100644
--- a/IdleSpectator/event-catalog.json
+++ b/IdleSpectator/event-catalog.json
@@ -73,6 +73,131 @@
"id": "warrior_try_join_army_group",
"action": "try to join an army",
"strength": 64
+ },
+ {
+ "id": "warrior_army_leader_move_to_attack_target",
+ "action": "lead the army to attack",
+ "strength": 72
+ },
+ {
+ "id": "try_to_start_new_civilization",
+ "action": "start a new civilization",
+ "strength": 90
+ },
+ {
+ "id": "build_civ_city_here",
+ "action": "found a city here",
+ "strength": 86
+ },
+ {
+ "id": "claim_land",
+ "action": "claim this land",
+ "strength": 70
+ },
+ {
+ "id": "king_check_new_city_foundation",
+ "action": "found a new city",
+ "strength": 88
+ },
+ {
+ "id": "bee_create_hive",
+ "action": "create a hive",
+ "strength": 68
+ },
+ {
+ "id": "sexual_reproduction_try",
+ "action": "try to reproduce",
+ "strength": 66
+ },
+ {
+ "id": "asexual_reproduction_budding",
+ "action": "reproduce by budding",
+ "strength": 64
+ },
+ {
+ "id": "asexual_reproduction_divine",
+ "action": "reproduce by divine means",
+ "strength": 78
+ },
+ {
+ "id": "asexual_reproduction_fission",
+ "action": "reproduce by fission",
+ "strength": 64
+ },
+ {
+ "id": "asexual_reproduction_parthenogenesis",
+ "action": "reproduce by parthenogenesis",
+ "strength": 66
+ },
+ {
+ "id": "asexual_reproduction_spores",
+ "action": "reproduce by spores",
+ "strength": 64
+ },
+ {
+ "id": "asexual_reproduction_vegetative",
+ "action": "reproduce vegetatively",
+ "strength": 62
+ },
+ {
+ "id": "family_group_follow",
+ "action": "follow their family group",
+ "strength": 52
+ },
+ {
+ "id": "family_group_join_or_new_herd",
+ "action": "join or form a herd",
+ "strength": 58
+ },
+ {
+ "id": "family_group_leave",
+ "action": "leave their family group",
+ "strength": 56
+ },
+ {
+ "id": "attack_golden_brain",
+ "action": "attack the golden brain",
+ "strength": 92
+ },
+ {
+ "id": "make_skeleton",
+ "action": "raise a skeleton",
+ "strength": 80
+ },
+ {
+ "id": "status_soul_harvested",
+ "action": "have their soul harvested",
+ "strength": 86
+ },
+ {
+ "id": "possessed_following",
+ "action": "follow under possession",
+ "strength": 74
+ },
+ {
+ "id": "try_to_take_city_item",
+ "action": "try to take a city item",
+ "strength": 66
+ },
+ {
+ "id": "put_out_fire",
+ "action": "put out a fire",
+ "strength": 60
+ },
+ {
+ "id": "burn_tumors",
+ "action": "burn tumors",
+ "strength": 62
+ },
+ {
+ "id": "try_to_launch_fireworks",
+ "action": "launch fireworks",
+ "strength": 58
+ },
+ {
+ "id": "try_affect_dreams",
+ "action": "affect dreams",
+ "strength": 56
}
],
"species": [
diff --git a/IdleSpectator/mod.json b/IdleSpectator/mod.json
index 4d7ce7d..034e654 100644
--- a/IdleSpectator/mod.json
+++ b/IdleSpectator/mod.json
@@ -1,7 +1,7 @@
{
"name": "IdleSpectator",
"author": "dazed",
- "version": "0.28.25",
+ "version": "0.28.27",
"description": "AFK Idle Spectator (I) + Lore (L). Killer button retargets dossier to the killer.",
"GUID": "com.dazed.idlespectator"
}
diff --git a/docs/event-catalog-discovery.md b/docs/event-catalog-discovery.md
index 09270c2..4906b60 100644
--- a/docs/event-catalog-discovery.md
+++ b/docs/event-catalog-discovery.md
@@ -15,12 +15,14 @@ Authored 1:1 libraries match live counts with zero missing/orphan ids.
Live+dial libraries are fully seeded.
`mutation_gaps.tsv` is empty (`gaps=0`).
-The feeling of “lots of events/tips we are not tracking” is mostly **tip quality and Layer A noise**, not missing catalog rows:
+Id inventory and interesting Layer A policy are in good shape as of **0.28.27** (`camera_a_inventory`: **219 A / 924 B** on library+decision domains; decisions **37** camera with phrases).
-1. Library Labels still interpolate raw `{id}` (`culture gains xenophobic`, `forges armor_leather`).
-2. Large ambient-A surfaces (items, genes, phenotypes, subspecies traits, meta traits, world laws) own the camera with generic templates.
-3. Building **complete / wonder finish** still has no Layer A wire (destroy/consume only).
-4. Meta-trait domains are live-seeded and TSV-audited but **not fail-gated** by `domain_event_audit`.
+Remaining feel of thin tips is mostly **soak share / strength vs filler**, not missing catalog rows:
+
+1. Library Labels use display names (Wave 1); keep watching for weak verbs.
+2. Ambient CI stays false; Signal predicates own library A (expanded 0.28.27).
+3. Building civ complete / wonder A is wired.
+4. Meta-trait domains are fail-gated by `domain_event_audit` + `camera_a_inventory_ok`.
Tips here means watch tip / orange reason Labels from interest feeds, not dossier chip hover tooltips.
@@ -60,27 +62,32 @@ Seeded via `LiveLibraryInterest.EnsureSeeded` + JSON `libraries.*` dials.
| Domain | Live | Camera A | Dial label template | Notes | Priority |
|--------|-----:|---------:|---------------------|-------|----------|
-| decisions | 127 | 12 | `{a} decides to {action}` | 12 camera ids have action phrases; 2 JSON overlays are non-camera (`try_new_plot`, `warrior_train_with_dummy`) | P1 only if new interesting decisions appear |
-| spells | 12 | 12 | `{a} casts {id}` | 7 signalIds @86; 5 ambient A @44 | P1 display names; P2 signal set |
-| powers | 339 | 23 | `God power: {id}` | ambient CI=false; spectacle predicate | P1 display names |
-| items | 111 | 111 | `{a} forges {id}` | ambient A; “forges” weak for armor/non-craft | P1 tip quality |
-| genes | 47 | 47 | `{a} gains gene {id}` | ambient A | P1 names; P2 demote ambient spam |
-| phenotypes | 52 | 52 | `{a} shows {id}` | ambient A | P1 names; P2 demote ambient spam |
-| world_laws | 49 | 49 | `Law changed: {id}` | ambient A | P1 names |
-| subspecies_traits | 204 | 204 | `{a} evolves {id}` | ambient A | P1 names; P2 Signal heuristics |
+| decisions | 127 | **37** | `{a} decides to {action}` | Signal tokens + authored phrases; fluff/`try_new_plot` B; dump `.harness/camera-a-inventory.tsv` | P3 expanded 0.28.27 |
+| spells | 12 | 6 | `{a} casts {id}` | Signal spectacle only; FX spells B | P3 |
+| powers | 339 | **41** | `God power: {id}` | ambient CI=false; widened spectacle + creature powers; UI `_edit` B | P3 expanded 0.28.27 |
+| items | 111 | **42** | `{a} gains {id}` | Signal mythril/adamantine + silver/steel/bone; leather/iron B | P3 expanded 0.28.27 |
+| genes | 47 | **7** | `{a} gains gene {id}` | warfare/damage/mutagen Signal | P3 expanded 0.28.27 |
+| phenotypes | 52 | 0 | `{a} shows {id}` | all B | P3 |
+| world_laws | 49 | **17** | `Law changed: {id}` | dramatic + forever_/cursed/army/expansion… | P3 expanded 0.28.27 |
+| subspecies_traits | 204 | **21** | `{a} evolves {id}` | dramatic + mutation/death/blood/horror | P3 expanded 0.28.27 |
| biomes | 29 | 0 | `Biome shifts: {id}` | ambient CI=false | P3 stay B |
-| culture_traits | 78 | 78 | `{a} culture gains {id}` | ~14 dramatic @74; rest ambient @34 | P1 names; hygiene gate |
-| religion_traits | 40 | 40 | `{a} faith gains {id}` | dramatic heuristic | P1 names; hygiene gate |
-| clan_traits | 29 | 29 | `{a} clan gains {id}` | dramatic heuristic | P1 names; hygiene gate |
-| language_traits | 26 | 26 | `{a} tongue gains {id}` | dramatic heuristic | P1 names; hygiene gate |
+| culture_traits | 78 | **27** | `{a} culture gains {id}` | dramatic + conscript/necro/craft weapons… | P3 expanded 0.28.27 |
+| religion_traits | 40 | 6 | `{a} faith gains {id}` | dramatic heuristic | P3 |
+| clan_traits | 29 | 13 | `{a} clan gains {id}` | dramatic heuristic | P3 |
+| language_traits | 26 | 2 | `{a} tongue gains {id}` | dramatic heuristic | P3 |
-Decision camera set (all have authored action phrases):
-`try_to_steal_money`, `kill_unruly_clan_members`, `banish_unruly_clan_members`, `find_lover`,
-`king_change_kingdom_{language,culture,religion}`, `leader_change_city_{language,culture,religion}`,
-`warrior_try_join_army_group`, `warrior_army_follow_leader`.
+**Library+decision Layer A inventory (0.28.27):** live dump total **219 A / 924 B** across 13 domains (`camera_a_inventory_ok`).
-Spell signalIds (strength 86): `summon_lightning`, `summon_tornado`, `cast_curse`, `cast_fire`, `cast_blood_rain`, `summon_meteor`, `teleport`.
-Ambient-A spells @44: `cast_silence`, `cast_grass_seeds`, `spawn_vegetation`, `spawn_skeleton`, `cast_shield`, `cast_cure`.
+### Decision camera classes (0.28.27)
+
+Allow-token Signal after fluff filter (not 127 ambient).
+Authored action phrases required for every A id (`domain_event_audit` decision_prose).
+
+Classes: founding/territory (`civilization`, `civ_city`, `claim_land`, `city_foundation`), reproduction/pack (`reproduction`, `family_group`), army/war/politics (existing), spectacle/dark (`golden_brain`, `make_skeleton`, `soul_harvest`, `possessed`), civic soft (`put_out_fire`, `burn_tumors`, `fireworks`, `affect_dreams`), hive (`create_hive`).
+
+Stay B: `try_new_plot*`, diet/sleep/random/check ticks, `warrior_train_with_dummy`, boat trade/fish, house/job/tax chores.
+
+Spell Signal: spectacle summons/curses/fire/teleport/meteor; FX silence/shield/cure/grass/vegetation/skeleton stay B.
## Patch-only / no catalog rows
@@ -156,6 +163,14 @@ Status (2026-07-17): **implemented** in mod `0.28.14`.
3. `Building.completeConstruction` wires Layer A for `Building_Civ` (temple/statue spectacle strength bump).
4. `domain_event_audit` fail-gates culture/religion/clan/language trait diffs; `library_asset_labels_ok` dumps `.harness/library-asset-labels.tsv`.
+### Interesting A expansion (Phases 0–2)
+
+Status (2026-07-17): **implemented** in mod `0.28.27`.
+
+1. `camera_a_inventory_ok` dumps `.harness/camera-a-inventory.tsv` (nested in `domain_event_audit`).
+2. Decisions: fluff filter refined; Signal classes for founding/reproduction/pack/spectacle; **37** camera A with authored phrases.
+3. Library Signal widened (power/item/gene/worldLaw/subspecies/meta); ambient CI stays false; phenotypes/biomes stay 0 A.
+
### Wave 2 - Signal / ambient A tuning
Status (2026-07-17): **implemented** in mod `0.28.16`.
diff --git a/docs/event-catalog-discovery.tsv b/docs/event-catalog-discovery.tsv
index cc9e652..f15f9ee 100644
--- a/docs/event-catalog-discovery.tsv
+++ b/docs/event-catalog-discovery.tsv
@@ -9,44 +9,21 @@ traits 116 authored 0 116 wired authored_labels P3
disasters 17 authored 0 via_worldlog wired authored_labels P3 disaster_war_audit PASS
war_types_library 5 authored 0 dials wired authored_labels P3
relationship 11 authored_discrete 0 11 wired authored_labels P3 no AssetManager lib
-decisions_library 127 live+dial 0 12 wired 12_action_phrases P1 try_new_plot and warrior_train_with_dummy authored non-camera
-spells 12 live+dial 0 12 wired raw_id_in_label P1 7 signalIds@86; 5 ambient@44
-powers 339 live+dial 0 23 wired raw_id_in_label P1 ambient CI=false
-items 111 live+dial 0 111 wired raw_id_forges_verb P1 ambient A; forge verb weak
-genes 47 live+dial 0 47 wired raw_id_in_label P1/P2 ambient A spam risk
-phenotypes 52 live+dial 0 52 wired raw_id_in_label P1/P2 ambient A spam risk
-world_laws_library 49 live+dial 0 49 wired raw_id_in_label P1/P2 ambient A
-subspecies_traits 204 live+dial 0 204 wired raw_id_in_label P1/P2 ambient A
-biome_library 29 live+dial 0 0 wired raw_id_template P3 ambient CI=false
-culture_traits 78 live+dial 0 78 wired raw_id_in_label P1 TSV audited; not fail-gated
-religion_traits 40 live+dial 0 40 wired raw_id_in_label P1 TSV audited; not fail-gated
-clan_traits 29 live+dial 0 29 wired raw_id_in_label P1 TSV audited; not fail-gated
-language_traits 26 live+dial 0 26 wired raw_id_in_label P1 TSV audited; not fail-gated
+decisions_library 127 live+dial 0 37 wired 37_action_phrases P3 expanded 0.28.27; camera-a-inventory.tsv
+spells 12 live+dial 0 6 wired display_names P3 Signal spectacle; 6 FX B
+powers 339 live+dial 0 41 wired display_names P3 Signal widened 0.28.27; ambient CI=false
+items 111 live+dial 0 42 wired gains_verb P3 Signal mythril/adamantine/silver/steel/bone
+genes 47 live+dial 0 7 wired display_names P3 warfare/damage/mutagen Signal
+phenotypes 52 live+dial 0 0 wired display_names P3 all B
+world_laws_library 49 live+dial 0 17 wired display_names P3 dramatic + forever_/cursed/army…
+subspecies_traits 204 live+dial 0 21 wired display_names P3 dramatic + mutation/death/blood
+biome_library 29 live+dial 0 0 wired display_names P3 ambient CI=false
+culture_traits 78 live+dial 0 27 wired display_names P3 dramatic widened 0.28.27
+religion_traits 40 live+dial 0 6 wired display_names P3
+clan_traits 29 live+dial 0 13 wired display_names P3
+language_traits 26 live+dial 0 2 wired display_names P3
buildings 618 patch_only n/a consume_and_civ_complete partial EventReason.BuildingEat_BuildingComplete P3 civ complete/wonder A; destroy demoted B
boats n/a patch_only n/a trade_unload wired EventReason.Boat P3 load skipped
combat n/a csharp_policy n/a pair_keys wired EventReason.Fight P3
-architecture_library 53 none n/a 0 covered_by_building_activity n/a P3
-city_build_orders 3 none n/a 0 covered_by_building_activity n/a P3
-kingdoms_traits 5 none n/a 0 covered_by_trait_meta_feeds n/a P3
-items_modifiers 59 none n/a 0 covered_by_item_feed n/a P3
-combat_action_library 11 none n/a 0 covered_by_spell_combat_feeds n/a P2 only if distinct beats
-loyalty_library 29 none n/a 0 covered_by_worldlog_happiness n/a P3
-opinion_library 24 none n/a 0 covered_by_worldlog_happiness n/a P3
-knowledge_library 11 none n/a 0 covered_by_worldlog_happiness n/a P3
-communication_library 10 none n/a 0 covered_by_worldlog_happiness n/a P3
-story_library 1 none n/a 0 covered_by_worldlog_happiness n/a P3
-citizen_job_library 13 none n/a 0 covered_by_activity_dossier n/a P3
-professions 5 none n/a 0 covered_by_activity_dossier n/a P3
-personalities 5 none n/a 0 covered_by_activity_dossier n/a P3
-tasks_actor 217 none n/a 0 activity_taxonomy n/a P3 unmappedTasks=0
-actor_library 322 scoring_overlay n/a 0 species_discovery n/a P3 taxonomy PASS
-plot_category_library 9 none n/a 0 category_only n/a P3
-AllianceManager.removeObject n/a worldlog 0 via_alliance_dissolved worldlog_covers authored_worldlog P3 not mutation gap
-KingdomManager.removeObject n/a worldlog 0 via_kingdom_destroyed worldlog_covers authored_worldlog P3 not mutation gap
-building_complete_wonder n/a patch n/a civ_complete Building.completeConstruction EventReason.BuildingComplete P3 temple/statue spectacle strength
-World.era_manager_poll n/a wired n/a era_change WorldAgeManager.startNextAge EventCatalog.Era P3 update→startNextAge already emits
-World.earthquake_manager_poll n/a wired n/a quake_sticky Earthquake.startQuake EarthquakeActive P3 holds while isQuakeActive
-combat_action_library 11 covered_by_spell_combat n/a 0 CombatActionLibrary.tryToCastSpell n/a P3 no separate catalog
-story_knowledge_opinion n/a covered_by_worldlog_happiness n/a 0 n/a n/a P3 Wave3 closed
-personalities_professions 5+5 covered_by_activity_dossier n/a 0 n/a n/a P3 Wave3 closed
+camera_a_inventory 1143 harness 0 219 wired camera-a-inventory.tsv P3 A=219 B=924 domains=13; gate camera_a_inventory_ok
mutation_gaps 0 harness 0 n/a gaps_empty n/a P3 managers=57 libraryAssets=2893
diff --git a/docs/world-event-inventory.md b/docs/world-event-inventory.md
index 8af9164..9a38107 100644
--- a/docs/world-event-inventory.md
+++ b/docs/world-event-inventory.md
@@ -52,10 +52,11 @@ Only true brief FX, cooldowns, civic Aggregates, and god-tool / biome spam stay
| Relationship | 11 | all interesting discrete |
| Plot / Era / War / Book | 28 / 11 / 5 / 12 | all A |
| Trait | 116 | all CI |
-| Decision | 127 live / 14 prose overlays | 12 camera-interesting |
-| Spell / Item / Gene / Phenotype / WorldLaw / SubspeciesTrait | live | Signal-only A (ambient CI=false); phenotypes all B |
-| Culture / Religion / Clan / LanguageTrait | live dials | Signal-only via dramatic heuristic |
-| Power / Biome | live | Signal predicate only / ambient CI=false |
+| Decision | 127 live / 37 prose overlays | **37** camera-interesting (0.28.27) |
+| Spell / Item / Gene / Phenotype / WorldLaw / SubspeciesTrait | live | Signal-only A: spell 6 / item 42 / gene 7 / phenotype 0 / law 17 / subsp 21 |
+| Culture / Religion / Clan / LanguageTrait | live dials | Signal dramatic: 27 / 6 / 13 / 2 |
+| Power / Biome | live | Power Signal **41**; biome 0 (ambient CI=false) |
+| Library+decision inventory dump | 1143 rows | **219 A / 924 B** (`.harness/camera-a-inventory.tsv`) |
| Building / Boat | none | destroy/consume/trade/unload; damage/load skipped |
## Wired hooks (major)