Implement building completion events and enhance earthquake handling.
- Update building event patches to include completion notifications - Introduce earthquake event handling for ongoing seismic activity - Modify event catalog to reflect changes in ambient interest settings - Add new harness scenarios for library asset label validation - Increment version to 0.28.17 in mod.json
This commit is contained in:
parent
8a4314be6f
commit
03729664be
22 changed files with 1459 additions and 86 deletions
|
|
@ -3753,11 +3753,11 @@ public static class AgentHarness
|
|||
source = "boat";
|
||||
break;
|
||||
case "building":
|
||||
id = "building_destroy";
|
||||
key = "building:destroy:harness:" + EventFeedUtil.SafeId(unit);
|
||||
label = EventReason.BuildingFalls("harness_hall", unit);
|
||||
strength = 88f;
|
||||
category = "Spectacle";
|
||||
id = "building_complete";
|
||||
key = "building:complete:harness:" + EventFeedUtil.SafeId(unit);
|
||||
label = EventReason.BuildingComplete(unit, "temple_human", spectacle: true);
|
||||
strength = 86f;
|
||||
category = "Settlement";
|
||||
source = "building";
|
||||
break;
|
||||
case "spell":
|
||||
|
|
@ -7041,6 +7041,205 @@ public static class AgentHarness
|
|||
+ $"familyBad={familyBad} siblingBad={siblingBad} sample='{(ids.Count > 0 ? ActivityAssetCatalog.FormatCitizenJobIdLabel(ids[0]) : "")}'";
|
||||
break;
|
||||
}
|
||||
case "library_asset_labels_ok":
|
||||
{
|
||||
// Every live library id used by EventReason.Library must resolve to a tip
|
||||
// without snake_case, and item tips must not use "forges" except forge-y ids.
|
||||
int total = 0;
|
||||
int empty = 0;
|
||||
int underscored = 0;
|
||||
int forgeBad = 0;
|
||||
var lines = new List<string> { "kind\tid\tdisplay\tsample_label" };
|
||||
IReadOnlyList<string> kinds = LibraryAssetNames.AllKinds;
|
||||
for (int k = 0; k < kinds.Count; k++)
|
||||
{
|
||||
string kind = kinds[k];
|
||||
List<string> ids = LibraryAssetNames.EnumerateLiveIds(kind);
|
||||
for (int i = 0; i < ids.Count; i++)
|
||||
{
|
||||
string id = ids[i];
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
total++;
|
||||
string display = LibraryAssetNames.DisplayName(kind, id);
|
||||
string sample = EventReason.Library(null, kind, id);
|
||||
lines.Add(kind + "\t" + id + "\t" + display + "\t" + sample);
|
||||
if (string.IsNullOrEmpty(display))
|
||||
{
|
||||
empty++;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(display) && display.IndexOf('_') >= 0)
|
||||
{
|
||||
underscored++;
|
||||
}
|
||||
|
||||
if (kind == "item")
|
||||
{
|
||||
bool forgey = id.IndexOf("forge", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| id.IndexOf("smith", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| id.IndexOf("anvil", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
string verb = LibraryAssetNames.ItemVerb(id);
|
||||
if (!forgey
|
||||
&& string.Equals(verb, "forges", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
forgeBad++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
System.IO.File.WriteAllLines(
|
||||
System.IO.Path.Combine(HarnessDir(), "library-asset-labels.tsv"),
|
||||
lines);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// diagnostic dump only
|
||||
}
|
||||
|
||||
pass = total > 0 && empty == 0 && underscored == 0 && forgeBad == 0;
|
||||
detail =
|
||||
$"assets={total} empty={empty} underscored={underscored} forgeBad={forgeBad} "
|
||||
+ $"kinds={kinds.Count}";
|
||||
break;
|
||||
}
|
||||
case "library_ambient_policy_ok":
|
||||
{
|
||||
// Signal-only libraries: ambient dials must be B; phenotypes all B;
|
||||
// spectacle spells stay A; utility spells stay B.
|
||||
int dialBad = 0;
|
||||
int phenotypeCamera = 0;
|
||||
int spellFxCamera = 0;
|
||||
int spellSpectacleMissing = 0;
|
||||
int signalCamera = 0;
|
||||
var lines = new List<string>
|
||||
{
|
||||
"domain\tid\tcreates_interest\tstrength\tnotes"
|
||||
};
|
||||
|
||||
string[] signalOnlyKeys =
|
||||
{
|
||||
"spell", "power", "item", "gene", "phenotype", "worldLaw", "subspeciesTrait",
|
||||
"biome", "cultureTrait", "religionTrait", "clanTrait", "languageTrait"
|
||||
};
|
||||
for (int i = 0; i < signalOnlyKeys.Length; i++)
|
||||
{
|
||||
string key = signalOnlyKeys[i];
|
||||
if (EventCatalogConfig.TryGetLibraryDefaults(key, out LibraryCatalogDefaults dial)
|
||||
&& dial != null
|
||||
&& dial.AmbientCreatesInterest)
|
||||
{
|
||||
dialBad++;
|
||||
lines.Add(key + "\t*\t\t\tambientCreatesInterest_true");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string id in EventCatalog.Phenotype.AuthoredIds)
|
||||
{
|
||||
DiscreteEventEntry e = EventCatalog.Phenotype.GetOrFallback(id);
|
||||
lines.Add("phenotypes\t" + id + "\t" + e.CreatesInterest + "\t"
|
||||
+ e.EventStrength.ToString("0.#") + "\t");
|
||||
if (e.CreatesInterest)
|
||||
{
|
||||
phenotypeCamera++;
|
||||
}
|
||||
}
|
||||
|
||||
string[] fxSpells =
|
||||
{
|
||||
"cast_silence", "cast_grass_seeds", "spawn_vegetation", "spawn_skeleton",
|
||||
"cast_shield", "cast_cure"
|
||||
};
|
||||
for (int i = 0; i < fxSpells.Length; i++)
|
||||
{
|
||||
DiscreteEventEntry e = EventCatalog.Spell.GetOrFallback(fxSpells[i]);
|
||||
lines.Add("spells\t" + fxSpells[i] + "\t" + e.CreatesInterest + "\t"
|
||||
+ e.EventStrength.ToString("0.#") + "\tfx");
|
||||
if (e.CreatesInterest)
|
||||
{
|
||||
spellFxCamera++;
|
||||
}
|
||||
}
|
||||
|
||||
string[] spectacleSpells =
|
||||
{
|
||||
"summon_lightning", "summon_tornado", "cast_curse", "cast_fire",
|
||||
"cast_blood_rain", "teleport"
|
||||
};
|
||||
for (int i = 0; i < spectacleSpells.Length; i++)
|
||||
{
|
||||
DiscreteEventEntry e = EventCatalog.Spell.GetOrFallback(spectacleSpells[i]);
|
||||
lines.Add("spells\t" + spectacleSpells[i] + "\t" + e.CreatesInterest + "\t"
|
||||
+ e.EventStrength.ToString("0.#") + "\tspectacle");
|
||||
if (!e.CreatesInterest)
|
||||
{
|
||||
spellSpectacleMissing++;
|
||||
}
|
||||
else
|
||||
{
|
||||
signalCamera++;
|
||||
}
|
||||
}
|
||||
|
||||
// Spot-check Signal classes still have camera rows after demote.
|
||||
int geneSignal = 0;
|
||||
foreach (string id in EventCatalog.Gene.AuthoredIds)
|
||||
{
|
||||
if (EventCatalog.Gene.GetOrFallback(id).CreatesInterest)
|
||||
{
|
||||
geneSignal++;
|
||||
}
|
||||
}
|
||||
|
||||
int lawSignal = 0;
|
||||
foreach (string id in EventCatalog.WorldLaw.AuthoredIds)
|
||||
{
|
||||
if (EventCatalog.WorldLaw.GetOrFallback(id).CreatesInterest)
|
||||
{
|
||||
lawSignal++;
|
||||
}
|
||||
}
|
||||
|
||||
int itemSignal = 0;
|
||||
foreach (string id in EventCatalog.Item.AuthoredIds)
|
||||
{
|
||||
if (EventCatalog.Item.GetOrFallback(id).CreatesInterest)
|
||||
{
|
||||
itemSignal++;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
System.IO.File.WriteAllLines(
|
||||
System.IO.Path.Combine(HarnessDir(), "library-ambient-policy.tsv"),
|
||||
lines);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// diagnostic dump only
|
||||
}
|
||||
|
||||
pass = dialBad == 0
|
||||
&& phenotypeCamera == 0
|
||||
&& spellFxCamera == 0
|
||||
&& spellSpectacleMissing == 0
|
||||
&& geneSignal > 0
|
||||
&& lawSignal > 0
|
||||
&& itemSignal > 0
|
||||
&& signalCamera == spectacleSpells.Length;
|
||||
detail =
|
||||
$"dialBad={dialBad} phenotypeCamera={phenotypeCamera} spellFxCamera={spellFxCamera} "
|
||||
+ $"spellSpectacleMissing={spellSpectacleMissing} geneSignal={geneSignal} "
|
||||
+ $"lawSignal={lawSignal} itemSignal={itemSignal} spectacleOk={signalCamera}";
|
||||
break;
|
||||
}
|
||||
case "dossier_not_contains":
|
||||
{
|
||||
string needle = string.IsNullOrEmpty(cmd.value) ? cmd.label : cmd.value;
|
||||
|
|
|
|||
|
|
@ -316,6 +316,8 @@ public static class DomainEventHarness
|
|||
&& spells.Passed && powers.Passed && decisions.Passed && items.Passed
|
||||
&& subspTraits.Passed && genes.Passed && phenotypes.Passed
|
||||
&& worldLaws.Passed && biomes.Passed
|
||||
&& cultureTraits.Passed && religionTraits.Passed
|
||||
&& clanTraits.Passed && languageTraits.Passed
|
||||
&& relMissing == 0 && relTotal > 0
|
||||
&& decisionProseMissing == 0 && decisionStrengthMissing == 0
|
||||
&& decisionProseTotal > 0;
|
||||
|
|
@ -323,7 +325,10 @@ public static class DomainEventHarness
|
|||
$"{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}; "
|
||||
+ $"{worldLaws.Detail}; {biomes.Detail}; "
|
||||
+ $"{cultureTraits.Detail}; {religionTraits.Detail}; "
|
||||
+ $"{clanTraits.Detail}; {languageTraits.Detail}; "
|
||||
+ $"relationship: authored={relTotal} missing={relMissing}; "
|
||||
+ $"decision_prose: camera={decisionProseTotal} missing={decisionProseMissing} "
|
||||
+ $"strengthMissing={decisionStrengthMissing} catalog={EventCatalogConfig.LoadedFromFile}";
|
||||
return new DomainEventAuditResult { Passed = passed, Detail = detail };
|
||||
|
|
|
|||
|
|
@ -399,7 +399,8 @@ public static class EventCatalogConfig
|
|||
|
||||
string[] keys =
|
||||
{
|
||||
"spell", "power", "item", "gene", "phenotype", "worldLaw", "subspeciesTrait", "biome"
|
||||
"spell", "power", "item", "gene", "phenotype", "worldLaw", "subspeciesTrait", "biome",
|
||||
"cultureTrait", "religionTrait", "clanTrait", "languageTrait"
|
||||
};
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
|
|
@ -415,7 +416,8 @@ public static class EventCatalogConfig
|
|||
SignalStrength = ExtractJsonFloat(obj, "signalStrength", 80f),
|
||||
Category = ExtractJsonString(obj, "category") ?? "Event",
|
||||
Label = ExtractJsonString(obj, "label") ?? "{a} · {id}",
|
||||
AmbientCreatesInterest = ExtractJsonBool(obj, "ambientCreatesInterest", true)
|
||||
// Large libraries default Signal-only; JSON must opt into ambient A.
|
||||
AmbientCreatesInterest = ExtractJsonBool(obj, "ambientCreatesInterest", false)
|
||||
};
|
||||
string[] ids = ExtractStringArray(obj, "signalIds");
|
||||
for (int j = 0; j < ids.Length; j++)
|
||||
|
|
|
|||
|
|
@ -46,9 +46,11 @@ public static class EventLivePipelinesHarness
|
|||
RunDisastersViaWorldLog(unit, fails);
|
||||
RunWar(unit, fails);
|
||||
RunEra(fails);
|
||||
RunEarthquake(unit, fails);
|
||||
RunBook(unit, fails);
|
||||
RunPlot(unit, fails);
|
||||
RunBuildingDestroy(unit, fails);
|
||||
RunBuildingComplete(unit, fails);
|
||||
RunBoatSmoke(unit, fails);
|
||||
RunCombatSmoke(unit, fails);
|
||||
RunMeta(unit, fails);
|
||||
|
|
@ -579,6 +581,69 @@ public static class EventLivePipelinesHarness
|
|||
}
|
||||
}
|
||||
|
||||
private static void RunEarthquake(Actor unit, List<string> fails)
|
||||
{
|
||||
InterestDropLog.Clear();
|
||||
InterestRegistry.ForceExpireContaining("earthquake:", Time.unscaledTime);
|
||||
try
|
||||
{
|
||||
WorldTile tile = unit != null ? unit.current_tile : null;
|
||||
if (tile == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
tile = World.world?.tiles_list?.GetRandom();
|
||||
}
|
||||
catch
|
||||
{
|
||||
tile = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (tile == null)
|
||||
{
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"earthquake",
|
||||
"earthquake",
|
||||
"unsupported",
|
||||
"Earthquake.startQuake",
|
||||
"no_tile");
|
||||
return;
|
||||
}
|
||||
|
||||
Earthquake.startQuake(tile, EarthquakeType.SmallDisaster);
|
||||
if (InterestRegistry.CountKeysContaining("earthquake:") > 0)
|
||||
{
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"earthquake",
|
||||
"earthquake",
|
||||
"ok",
|
||||
"Earthquake.startQuake",
|
||||
"busy_bypass=ForceLiveEventFeeds;active=" + Earthquake.isQuakeActive());
|
||||
}
|
||||
else
|
||||
{
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"earthquake",
|
||||
"earthquake",
|
||||
"fail",
|
||||
"Earthquake.startQuake",
|
||||
"no_a_interest");
|
||||
fails.Add("earthquake:earthquake no interest key");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
fails.Add("earthquake:earthquake " + ex.Message);
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"earthquake",
|
||||
"earthquake",
|
||||
"fail",
|
||||
"Earthquake.startQuake",
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunBook(Actor unit, List<string> fails)
|
||||
{
|
||||
InterestDropLog.Clear();
|
||||
|
|
@ -1494,6 +1559,208 @@ public static class EventLivePipelinesHarness
|
|||
// Domains without AuthoredIds rows in ledger seed - combat/boat/building claimed in Run*Smoke.
|
||||
}
|
||||
|
||||
private static void RunBuildingComplete(Actor unit, List<string> fails)
|
||||
{
|
||||
InterestDropLog.Clear();
|
||||
InterestRegistry.ForceExpireContaining("building:complete", Time.unscaledTime);
|
||||
try
|
||||
{
|
||||
Building building = FindCivBuildingNear(unit) ?? TrySpawnCivBuilding(unit);
|
||||
if (building == null)
|
||||
{
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"building",
|
||||
"building_complete",
|
||||
"unsupported",
|
||||
"Building.completeConstruction",
|
||||
"no_civ_building");
|
||||
return;
|
||||
}
|
||||
|
||||
MethodInfo complete = AccessTools.Method(typeof(Building), "completeConstruction");
|
||||
if (complete == null)
|
||||
{
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"building",
|
||||
"building_complete",
|
||||
"unsupported",
|
||||
"Building.completeConstruction",
|
||||
"no_method");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (unit != null && building.current_tile != null)
|
||||
{
|
||||
AccessTools.Method(typeof(Actor), "setCurrentTile", new[] { typeof(WorldTile) })
|
||||
?.Invoke(unit, new object[] { building.current_tile });
|
||||
unit.current_position = building.current_position;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
complete.Invoke(building, null);
|
||||
if (InterestRegistry.CountKeysContaining("building:complete") > 0)
|
||||
{
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"building",
|
||||
"building_complete",
|
||||
"ok",
|
||||
"Building.completeConstruction",
|
||||
"busy_bypass=ForceLiveEventFeeds");
|
||||
}
|
||||
else
|
||||
{
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"building",
|
||||
"building_complete",
|
||||
"fail",
|
||||
"Building.completeConstruction",
|
||||
"no_a_interest_after_complete");
|
||||
fails.Add("building:building_complete no interest key");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
fails.Add("building:building_complete " + ex.Message);
|
||||
EventLiveCoverageLedger.Claim(
|
||||
"building",
|
||||
"building_complete",
|
||||
"fail",
|
||||
"Building.completeConstruction",
|
||||
ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static Building FindCivBuildingNear(Actor unit)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (World.world?.buildings == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Building best = null;
|
||||
float bestDist = float.MaxValue;
|
||||
Vector3 origin = unit != null ? unit.current_position : Vector3.zero;
|
||||
foreach (Building b in World.world.buildings)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (b == null || !b.isAlive() || b.asset == null
|
||||
|| !LibraryAssetNames.IsCivBuilding(b.asset))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 pos = Vector3.zero;
|
||||
try
|
||||
{
|
||||
if (b.current_tile != null)
|
||||
{
|
||||
pos = b.current_tile.posV3;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
pos = Vector3.zero;
|
||||
}
|
||||
|
||||
float d = (pos - origin).sqrMagnitude;
|
||||
bool spectacle = LibraryAssetNames.IsSpectacleBuilding(b.asset.id, b.asset);
|
||||
// Prefer temples/statues when scoring distance ties.
|
||||
float score = d - (spectacle ? 1000f : 0f);
|
||||
if (score < bestDist)
|
||||
{
|
||||
bestDist = score;
|
||||
best = b;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Building TrySpawnCivBuilding(Actor unit)
|
||||
{
|
||||
try
|
||||
{
|
||||
WorldTile tile = unit != null ? unit.current_tile : null;
|
||||
if (tile == null || World.world?.buildings == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MethodInfo add = AccessTools.Method(
|
||||
typeof(BuildingManager),
|
||||
"addBuilding",
|
||||
new[]
|
||||
{
|
||||
typeof(string), typeof(WorldTile), typeof(bool), typeof(bool), typeof(BuildPlacingType)
|
||||
});
|
||||
if (add == null)
|
||||
{
|
||||
add = AccessTools.Method(typeof(BuildingManager), "addBuilding");
|
||||
}
|
||||
|
||||
if (add == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] candidates = { "statue", "temple_human", "bonfire", "house_human_1", "house" };
|
||||
for (int i = 0; i < candidates.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
BuildingAsset asset = AssetManager.buildings?.get(candidates[i]);
|
||||
if (asset == null || !LibraryAssetNames.IsCivBuilding(asset))
|
||||
{
|
||||
// Still try known spectacle ids even if type check fails early.
|
||||
if (candidates[i] != "statue" && !candidates[i].StartsWith("temple_", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
object built = add.GetParameters().Length >= 5
|
||||
? add.Invoke(
|
||||
World.world.buildings,
|
||||
new object[] { candidates[i], tile, false, false, BuildPlacingType.New })
|
||||
: add.Invoke(World.world.buildings, new object[] { candidates[i], tile });
|
||||
if (built is Building b && b.isAlive())
|
||||
{
|
||||
return b;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// try next
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void RunBuildingDestroy(Actor unit, List<string> fails)
|
||||
{
|
||||
InterestDropLog.Clear();
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("spell", 48f, 88f, "Spectacle", "{a} casts {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("spell", 48f, 88f, "Spectacle", "{a} casts {id}", false);
|
||||
HashSet<string> signals = d.SignalIds.Count > 0
|
||||
? d.SignalIds
|
||||
: new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
|
|
@ -70,9 +70,41 @@ public static partial class EventCatalog
|
|||
d.AmbientStrength,
|
||||
signals,
|
||||
d.SignalStrength,
|
||||
signalPredicate: IsSpectacleSpell,
|
||||
ambientCreatesInterest: d.AmbientCreatesInterest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spectacle casts own the camera; brief FX / utility (silence, shield, cure, grass) stay B.
|
||||
/// </summary>
|
||||
private static bool IsSpectacleSpell(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string s = id.ToLowerInvariant();
|
||||
if (s.Contains("silence")
|
||||
|| s.Contains("shield")
|
||||
|| s.Contains("cure")
|
||||
|| s.Contains("grass")
|
||||
|| s.Contains("vegetation")
|
||||
|| s.Contains("skeleton"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return s.StartsWith("summon_", StringComparison.Ordinal)
|
||||
|| s.Contains("meteor")
|
||||
|| s.Contains("tornado")
|
||||
|| s.Contains("lightning")
|
||||
|| s.Contains("teleport")
|
||||
|| s.Contains("curse")
|
||||
|| s.Contains("blood_rain")
|
||||
|| s.Contains("cast_fire");
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AuthoredIds
|
||||
{
|
||||
get
|
||||
|
|
@ -85,7 +117,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("spell", 48f, 88f, "Spectacle", "{a} casts {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("spell", 48f, 88f, "Spectacle", "{a} casts {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 55f);
|
||||
}
|
||||
}
|
||||
|
|
@ -186,8 +218,11 @@ public static partial class EventCatalog
|
|||
}
|
||||
|
||||
string s = id.ToLowerInvariant();
|
||||
// Top-tier materials from live items library (mythril/adamantine) plus named relics.
|
||||
return s.Contains("legendary")
|
||||
|| s.Contains("mythic")
|
||||
|| s.Contains("mythril")
|
||||
|| s.Contains("adamantine")
|
||||
|| s.Contains("artifact")
|
||||
|| s.Contains("relic")
|
||||
|| s.Contains("divine")
|
||||
|
|
@ -207,7 +242,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("item", 36f, 80f, "Item", "{a} forges {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("item", 36f, 80f, "Item", "{a} gains {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLiveItemIds,
|
||||
|
|
@ -232,7 +267,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("item", 36f, 80f, "Item", "{a} forges {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("item", 36f, 80f, "Item", "{a} gains {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 40f);
|
||||
}
|
||||
}
|
||||
|
|
@ -283,7 +318,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("subspeciesTrait", 34f, 78f, "Evolution", "{a} evolves {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("subspeciesTrait", 34f, 78f, "Evolution", "{a} evolves {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLiveSubspeciesTraitIds,
|
||||
|
|
@ -308,7 +343,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("subspeciesTrait", 34f, 78f, "Evolution", "{a} evolves {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("subspeciesTrait", 34f, 78f, "Evolution", "{a} evolves {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 36f);
|
||||
}
|
||||
}
|
||||
|
|
@ -335,7 +370,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("gene", 30f, 70f, "Genetics", "{a} gains gene {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("gene", 30f, 70f, "Genetics", "{a} gains gene {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLiveGeneIds,
|
||||
|
|
@ -360,7 +395,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("gene", 30f, 70f, "Genetics", "{a} gains gene {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("gene", 30f, 70f, "Genetics", "{a} gains gene {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 30f);
|
||||
}
|
||||
}
|
||||
|
|
@ -387,7 +422,8 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("phenotype", 28f, 60f, "Genetics", "{a} shows {id}", true);
|
||||
// Skin/shade phenotypes are ambient noise - no Signal predicate, all B.
|
||||
LibraryCatalogDefaults d = LibOr("phenotype", 28f, 60f, "Genetics", "{a} shows {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLivePhenotypeIds,
|
||||
|
|
@ -411,7 +447,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("phenotype", 28f, 60f, "Genetics", "{a} shows {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("phenotype", 28f, 60f, "Genetics", "{a} shows {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 28f);
|
||||
}
|
||||
}
|
||||
|
|
@ -438,7 +474,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("worldLaw", 40f, 70f, "WorldLaw", "Law changed: {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("worldLaw", 40f, 70f, "WorldLaw", "Law changed: {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLiveWorldLawIds,
|
||||
|
|
@ -447,10 +483,27 @@ public static partial class EventCatalog
|
|||
d.AmbientStrength,
|
||||
signalIds: null,
|
||||
d.SignalStrength,
|
||||
signalPredicate: id => id != null && (id.Contains("war") || id.Contains("death") || id.Contains("plague")),
|
||||
signalPredicate: IsDramaticWorldLaw,
|
||||
ambientCreatesInterest: d.AmbientCreatesInterest);
|
||||
}
|
||||
|
||||
private static bool IsDramaticWorldLaw(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string s = id.ToLowerInvariant();
|
||||
return s.Contains("war")
|
||||
|| s.Contains("death")
|
||||
|| s.Contains("plague")
|
||||
|| s.Contains("hunger")
|
||||
|| s.Contains("rebell")
|
||||
|| s.Contains("disaster")
|
||||
|| s.Contains("angry");
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AuthoredIds
|
||||
{
|
||||
get
|
||||
|
|
@ -463,7 +516,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("worldLaw", 40f, 70f, "WorldLaw", "Law changed: {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("worldLaw", 40f, 70f, "WorldLaw", "Law changed: {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 40f);
|
||||
}
|
||||
}
|
||||
|
|
@ -568,7 +621,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("cultureTrait", 34f, 74f, "Culture", "{a} culture gains {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("cultureTrait", 34f, 74f, "Culture", "{a} culture gains {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLiveCultureTraitIds,
|
||||
|
|
@ -593,7 +646,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("cultureTrait", 34f, 74f, "Culture", "{a} culture gains {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("cultureTrait", 34f, 74f, "Culture", "{a} culture gains {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 34f);
|
||||
}
|
||||
}
|
||||
|
|
@ -620,7 +673,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("religionTrait", 34f, 74f, "Religion", "{a} faith gains {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("religionTrait", 34f, 74f, "Religion", "{a} faith gains {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLiveReligionTraitIds,
|
||||
|
|
@ -645,7 +698,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("religionTrait", 34f, 74f, "Religion", "{a} faith gains {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("religionTrait", 34f, 74f, "Religion", "{a} faith gains {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 34f);
|
||||
}
|
||||
}
|
||||
|
|
@ -672,7 +725,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("clanTrait", 32f, 72f, "Clan", "{a} clan gains {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("clanTrait", 32f, 72f, "Clan", "{a} clan gains {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLiveClanTraitIds,
|
||||
|
|
@ -697,7 +750,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("clanTrait", 32f, 72f, "Clan", "{a} clan gains {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("clanTrait", 32f, 72f, "Clan", "{a} clan gains {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 32f);
|
||||
}
|
||||
}
|
||||
|
|
@ -724,7 +777,7 @@ public static partial class EventCatalog
|
|||
|
||||
Entries.Clear();
|
||||
_appliedGeneration = EventCatalogConfig.Generation;
|
||||
LibraryCatalogDefaults d = LibOr("languageTrait", 30f, 68f, "Language", "{a} tongue gains {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("languageTrait", 30f, 68f, "Language", "{a} tongue gains {id}", false);
|
||||
LiveLibraryInterest.EnsureSeeded(
|
||||
Entries,
|
||||
ActivityAssetCatalog.EnumerateLiveLanguageTraitIds,
|
||||
|
|
@ -749,7 +802,7 @@ public static partial class EventCatalog
|
|||
public static DiscreteEventEntry GetOrFallback(string id)
|
||||
{
|
||||
Ensure();
|
||||
LibraryCatalogDefaults d = LibOr("languageTrait", 30f, 68f, "Language", "{a} tongue gains {id}", true);
|
||||
LibraryCatalogDefaults d = LibOr("languageTrait", 30f, 68f, "Language", "{a} tongue gains {id}", false);
|
||||
return LiveLibraryInterest.Lookup(Entries, id, d.Category, d.Label, 30f);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@ public sealed class DiscreteEventEntry
|
|||
public string MakeLabel(string a, string b)
|
||||
{
|
||||
string template = string.IsNullOrEmpty(LabelTemplate) ? "{id}" : LabelTemplate;
|
||||
string displayId = string.IsNullOrEmpty(Id) ? "" : EventReason.HumanizeId(Id);
|
||||
return template
|
||||
.Replace("{id}", Id ?? "")
|
||||
.Replace("{id}", displayId)
|
||||
.Replace("{a}", a ?? "")
|
||||
.Replace("{b}", b ?? "");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -814,43 +814,102 @@ public static class EventReason
|
|||
|
||||
public static string Library(Actor a, string kind, string assetId)
|
||||
{
|
||||
string an = NameOrSomeone(a);
|
||||
string id = HumanizeId(assetId);
|
||||
string k = HumanizeId(kind);
|
||||
string k = (kind ?? "").Trim().ToLowerInvariant();
|
||||
string id = LibraryAssetNames.DisplayName(k, assetId);
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return an + " triggers " + (string.IsNullOrEmpty(k) ? "an event" : k);
|
||||
id = HumanizeId(assetId);
|
||||
}
|
||||
|
||||
switch ((kind ?? "").Trim().ToLowerInvariant())
|
||||
bool hasActor = a != null && a.isAlive();
|
||||
string an = hasActor ? NameOrSomeone(a) : "";
|
||||
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
if (!hasActor)
|
||||
{
|
||||
return string.IsNullOrEmpty(k) ? "An event" : HumanizeId(k);
|
||||
}
|
||||
|
||||
return an + " triggers " + (string.IsNullOrEmpty(k) ? "an event" : HumanizeId(k));
|
||||
}
|
||||
|
||||
switch (k)
|
||||
{
|
||||
case "spell":
|
||||
return an + " casts " + id;
|
||||
return hasActor ? an + " casts " + id : id + " is cast";
|
||||
case "item":
|
||||
return an + " forges " + id;
|
||||
return hasActor
|
||||
? an + " " + LibraryAssetNames.ItemVerb(assetId) + " " + id
|
||||
: id + " is gained";
|
||||
case "decision":
|
||||
return Decision(a, assetId);
|
||||
case "gene":
|
||||
return an + " gains gene " + id;
|
||||
return hasActor ? an + " gains gene " + id : "Gene " + id;
|
||||
case "phenotype":
|
||||
return an + " shows phenotype " + id;
|
||||
return hasActor ? an + " shows " + id : "Phenotype " + id;
|
||||
case "power":
|
||||
return an + " channels " + id;
|
||||
return hasActor ? an + " channels " + id : "God power: " + id;
|
||||
case "worldlaw":
|
||||
case "world_law":
|
||||
return "Law changed: " + id;
|
||||
case "biome":
|
||||
return "Biome shifts: " + id;
|
||||
case "subspecies_trait":
|
||||
return an + " gains " + id;
|
||||
return hasActor ? an + " evolves " + id : id + " evolves";
|
||||
case "culture_trait":
|
||||
return an + "'s culture gains " + id;
|
||||
return hasActor ? an + "'s culture gains " + id : "Culture gains " + id;
|
||||
case "religion_trait":
|
||||
return an + "'s faith gains " + id;
|
||||
return hasActor ? an + "'s faith gains " + id : "Faith gains " + id;
|
||||
case "clan_trait":
|
||||
return an + "'s clan gains " + id;
|
||||
return hasActor ? an + "'s clan gains " + id : "Clan gains " + id;
|
||||
case "language_trait":
|
||||
return an + "'s language gains " + id;
|
||||
return hasActor ? an + "'s language gains " + id : "Language gains " + id;
|
||||
default:
|
||||
return an + " · " + id;
|
||||
return hasActor ? an + " · " + id : id;
|
||||
}
|
||||
}
|
||||
|
||||
public static string Earthquake(Actor near)
|
||||
{
|
||||
if (near != null && near.isAlive())
|
||||
{
|
||||
string n = Name(near);
|
||||
if (!string.IsNullOrEmpty(n))
|
||||
{
|
||||
return n + " is caught in an earthquake";
|
||||
}
|
||||
}
|
||||
|
||||
return "Earthquake shakes the land";
|
||||
}
|
||||
|
||||
/// <summary>Civ construction finished - temple/statue spectacle vs ordinary house.</summary>
|
||||
public static string BuildingComplete(Actor near, string buildingId, bool spectacle)
|
||||
{
|
||||
string id = LibraryAssetNames.DisplayName("building", buildingId);
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
id = HumanizeId(buildingId);
|
||||
}
|
||||
|
||||
string what = string.IsNullOrEmpty(id)
|
||||
? (spectacle ? "a wonder" : "a building")
|
||||
: (spectacle ? "wonder " + id : id);
|
||||
|
||||
if (near != null && near.isAlive())
|
||||
{
|
||||
string n = Name(near);
|
||||
if (!string.IsNullOrEmpty(n))
|
||||
{
|
||||
return n + " finishes " + what;
|
||||
}
|
||||
}
|
||||
|
||||
return spectacle ? "Wonder finished: " + (string.IsNullOrEmpty(id) ? "structure" : id)
|
||||
: "Building finished: " + (string.IsNullOrEmpty(id) ? "structure" : id);
|
||||
}
|
||||
|
||||
public static string Decision(Actor a, string decisionId)
|
||||
{
|
||||
string an = NameOrSomeone(a);
|
||||
|
|
@ -869,8 +928,10 @@ public static class EventReason
|
|||
public static string Apply(string template, Actor a, Actor b = null, string id = "")
|
||||
{
|
||||
string t = string.IsNullOrEmpty(template) ? "{a}" : template;
|
||||
string displayId = string.IsNullOrEmpty(id) ? "" : HumanizeId(id);
|
||||
// Prefer humanized id in catalog templates so `{id}` never dumps snake_case.
|
||||
return t
|
||||
.Replace("{id}", id ?? "")
|
||||
.Replace("{id}", displayId)
|
||||
.Replace("{a}", Name(a) ?? "")
|
||||
.Replace("{b}", Name(b) ?? "");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public static class DeferredInterestFeed
|
|||
"power:" + entry.Id,
|
||||
"power",
|
||||
entry.Category,
|
||||
EventReason.HumanizeId(entry.Id),
|
||||
EventReason.Library(null, "power", entry.Id),
|
||||
entry.Id,
|
||||
entry.EventStrength,
|
||||
position,
|
||||
|
|
@ -127,7 +127,7 @@ public static class DeferredInterestFeed
|
|||
"worldlaw:" + entry.Id,
|
||||
"worldlaw",
|
||||
entry.Category,
|
||||
EventReason.HumanizeId(entry.Id),
|
||||
EventReason.Library(null, "worldlaw", entry.Id),
|
||||
entry.Id,
|
||||
entry.EventStrength,
|
||||
position,
|
||||
|
|
@ -197,7 +197,7 @@ public static class DeferredInterestFeed
|
|||
"biome:" + entry.Id,
|
||||
"biome",
|
||||
entry.Category,
|
||||
EventReason.HumanizeId(entry.Id),
|
||||
EventReason.Library(null, "biome", entry.Id),
|
||||
entry.Id,
|
||||
entry.EventStrength,
|
||||
position,
|
||||
|
|
|
|||
|
|
@ -95,4 +95,66 @@ public static class MetaInterestFeed
|
|||
follow.current_position,
|
||||
follow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ongoing earthquake while <see cref="Earthquake.isQuakeActive"/> - covers god-tool quakes
|
||||
/// and disaster-spawned quakes (WorldLog may also fire for the latter).
|
||||
/// </summary>
|
||||
public static void EmitEarthquake(WorldTile tile)
|
||||
{
|
||||
if (!AgentHarness.LiveFeedsAllowed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 pos = Vector3.zero;
|
||||
try
|
||||
{
|
||||
if (tile != null)
|
||||
{
|
||||
pos = tile.posV3;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
pos = Vector3.zero;
|
||||
}
|
||||
|
||||
if (pos == Vector3.zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Camera.main != null)
|
||||
{
|
||||
Vector3 cam = Camera.main.transform.position;
|
||||
pos = new Vector3(cam.x, cam.y, 0f);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
pos = new Vector3(1f, 1f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (pos == Vector3.zero)
|
||||
{
|
||||
pos = new Vector3(1f, 1f, 0f);
|
||||
}
|
||||
|
||||
Actor follow = WorldActivityScanner.FindNearestAliveUnit(pos, 80f);
|
||||
string label = EventReason.Earthquake(follow);
|
||||
EventFeedUtil.Register(
|
||||
"earthquake:active",
|
||||
"earthquake",
|
||||
"Disaster",
|
||||
label,
|
||||
"earthquake",
|
||||
95f,
|
||||
pos,
|
||||
follow,
|
||||
locationOnly: follow == null,
|
||||
minWatch: 8f,
|
||||
maxWatch: 45f,
|
||||
completion: InterestCompletionKind.EarthquakeActive);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
using ai.behaviours;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
/// <summary>Building damage/destroy/eat interest patches from mutation discovery.</summary>
|
||||
/// <summary>Building damage/destroy/eat/complete interest patches from mutation discovery.</summary>
|
||||
[HarmonyPatch]
|
||||
public static class BuildingEventPatches
|
||||
{
|
||||
|
|
@ -49,6 +50,58 @@ public static class BuildingEventPatches
|
|||
_ = __instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Civ construction finished (houses, temples, statues).
|
||||
/// Nature/tree completes stay silent - not settlement spectacle.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(Building), nameof(Building.completeConstruction))]
|
||||
[HarmonyPostfix]
|
||||
public static void PostfixCompleteConstruction(Building __instance)
|
||||
{
|
||||
if (!AgentHarness.LiveFeedsAllowed || __instance?.asset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LibraryAssetNames.IsCivBuilding(__instance.asset))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string buildingId = "";
|
||||
try
|
||||
{
|
||||
buildingId = __instance.asset.id ?? "";
|
||||
}
|
||||
catch
|
||||
{
|
||||
buildingId = "";
|
||||
}
|
||||
|
||||
bool spectacle = LibraryAssetNames.IsSpectacleBuilding(buildingId, __instance.asset);
|
||||
float strength = spectacle ? 86f : 72f;
|
||||
Vector3 pos = BuildingWorldPos(__instance);
|
||||
Actor follow = WorldActivityScanner.FindNearestAliveUnit(pos, 48f);
|
||||
string label = EventReason.BuildingComplete(follow, buildingId, spectacle);
|
||||
string key = "building:complete:"
|
||||
+ (string.IsNullOrEmpty(buildingId) ? "unknown" : buildingId)
|
||||
+ ":"
|
||||
+ EventFeedUtil.SafeId(follow);
|
||||
|
||||
EventFeedUtil.Register(
|
||||
key,
|
||||
"building",
|
||||
"Settlement",
|
||||
label,
|
||||
string.IsNullOrEmpty(buildingId) ? "building_complete" : buildingId,
|
||||
strength,
|
||||
pos,
|
||||
follow,
|
||||
locationOnly: follow == null,
|
||||
minWatch: 6f,
|
||||
maxWatch: spectacle ? 28f : 18f);
|
||||
}
|
||||
|
||||
private static void Emit(string eventId, float strength, Actor actor, string label)
|
||||
{
|
||||
if (!AgentHarness.LiveFeedsAllowed)
|
||||
|
|
@ -67,4 +120,28 @@ public static class BuildingEventPatches
|
|||
actor.current_position,
|
||||
actor);
|
||||
}
|
||||
|
||||
private static Vector3 BuildingWorldPos(Building building)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (building.current_tile != null)
|
||||
{
|
||||
return building.current_tile.posV3;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fall through
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return building.current_position;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,22 @@ public static class MetaEventPatches
|
|||
MetaInterestFeed.EmitEra(ReadCurrentEraId());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ongoing quake hold. Age change is already covered by <see cref="PostfixStartNextAge"/>
|
||||
/// (called from <c>WorldAgeManager.update</c> when progress completes).
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(Earthquake), nameof(Earthquake.startQuake))]
|
||||
[HarmonyPostfix]
|
||||
public static void PostfixStartQuake(WorldTile pTile)
|
||||
{
|
||||
if (!Earthquake.isQuakeActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MetaInterestFeed.EmitEarthquake(pTile);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(CultureManager), nameof(CultureManager.newCulture))]
|
||||
[HarmonyPostfix]
|
||||
public static void PostfixNewCulture(Culture __result)
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ internal static class HarnessScenarios
|
|||
return EventSuite();
|
||||
case "domain_event_audit":
|
||||
return DomainEventAudit();
|
||||
case "library_asset_labels_ok":
|
||||
return LibraryAssetLabelsOk();
|
||||
case "event_tracking":
|
||||
return EventTracking();
|
||||
case "family_trigger_smoke":
|
||||
|
|
@ -1938,10 +1940,22 @@ internal static class HarnessScenarios
|
|||
{
|
||||
Step("dea0", "wait_world"),
|
||||
Step("dea1", "assert", expect: "domain_event_audit"),
|
||||
Step("dea1b", "assert", expect: "library_asset_labels_ok"),
|
||||
Step("dea1c", "assert", expect: "library_ambient_policy_ok"),
|
||||
Step("dea2", "snapshot")
|
||||
};
|
||||
}
|
||||
|
||||
private static List<HarnessCommand> LibraryAssetLabelsOk()
|
||||
{
|
||||
return new List<HarnessCommand>
|
||||
{
|
||||
Step("lal0", "wait_world"),
|
||||
Step("lal1", "assert", expect: "library_asset_labels_ok"),
|
||||
Step("lal2", "snapshot")
|
||||
};
|
||||
}
|
||||
|
||||
private static List<HarnessCommand> EventTracking()
|
||||
{
|
||||
return new List<HarnessCommand>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ public enum InterestCompletionKind
|
|||
/// <summary>Sticky family pack while members / alpha remain resolvable.</summary>
|
||||
FamilyPack = 9,
|
||||
/// <summary>Sticky status outbreak while clustered carriers remain resolvable.</summary>
|
||||
StatusOutbreak = 10
|
||||
StatusOutbreak = 10,
|
||||
/// <summary>Sticky while <c>Earthquake.isQuakeActive()</c> (ongoing shake poll).</summary>
|
||||
EarthquakeActive = 11
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ public static class InterestCompletion
|
|||
case InterestCompletionKind.StatusOutbreak:
|
||||
return StatusOutbreakStillActive(candidate);
|
||||
|
||||
case InterestCompletionKind.EarthquakeActive:
|
||||
return EarthquakeStillActive(candidate);
|
||||
|
||||
case InterestCompletionKind.ActivityActive:
|
||||
return ActivityStillActive(candidate);
|
||||
|
||||
|
|
@ -71,6 +74,19 @@ public static class InterestCompletion
|
|||
}
|
||||
}
|
||||
|
||||
private static bool EarthquakeStillActive(InterestCandidate c)
|
||||
{
|
||||
_ = c;
|
||||
try
|
||||
{
|
||||
return Earthquake.isQuakeActive();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CombatStillActive(InterestCandidate c)
|
||||
{
|
||||
Actor unit = c.FollowUnit;
|
||||
|
|
|
|||
|
|
@ -3045,6 +3045,7 @@ public static class InterestDirector
|
|||
case InterestCompletionKind.CombatActive:
|
||||
case InterestCompletionKind.WarFront:
|
||||
case InterestCompletionKind.PlotActive:
|
||||
case InterestCompletionKind.EarthquakeActive:
|
||||
classCap = w.maxWatchCombat;
|
||||
break;
|
||||
case InterestCompletionKind.FamilyPack:
|
||||
|
|
|
|||
314
IdleSpectator/LibraryAssetNames.cs
Normal file
314
IdleSpectator/LibraryAssetNames.cs
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IdleSpectator;
|
||||
|
||||
/// <summary>
|
||||
/// Localized / humanized display names for live AssetManager library ids.
|
||||
/// Used by library event Labels so watch tips never show raw snake_case when the game has a name.
|
||||
/// </summary>
|
||||
public static class LibraryAssetNames
|
||||
{
|
||||
private static readonly string[] LibraryKinds =
|
||||
{
|
||||
"spell", "power", "item", "gene", "phenotype", "worldlaw", "subspecies_trait",
|
||||
"culture_trait", "religion_trait", "clan_trait", "language_trait", "biome", "building"
|
||||
};
|
||||
|
||||
public static IReadOnlyList<string> AllKinds => LibraryKinds;
|
||||
|
||||
/// <summary>Spectator-facing name for a library asset id (localized when available).</summary>
|
||||
public static string DisplayName(string kind, string assetId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetId))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
object asset = TryGetAsset(kind, assetId);
|
||||
string localized = TryLocalizedName(asset);
|
||||
if (!string.IsNullOrEmpty(localized))
|
||||
{
|
||||
return localized;
|
||||
}
|
||||
|
||||
return EventReason.HumanizeId(assetId);
|
||||
}
|
||||
|
||||
public static object TryGetAsset(string kind, string assetId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(kind) || string.IsNullOrEmpty(assetId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string id = assetId.Trim();
|
||||
string k = kind.Trim().ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
switch (k)
|
||||
{
|
||||
case "spell":
|
||||
return AssetManager.spells?.get(id);
|
||||
case "power":
|
||||
return AssetManager.powers?.get(id);
|
||||
case "item":
|
||||
return AssetManager.items?.get(id);
|
||||
case "gene":
|
||||
return AssetManager.gene_library?.get(id);
|
||||
case "phenotype":
|
||||
return AssetManager.phenotype_library?.get(id);
|
||||
case "worldlaw":
|
||||
case "world_law":
|
||||
return AssetManager.world_laws_library?.get(id);
|
||||
case "subspecies_trait":
|
||||
return AssetManager.subspecies_traits?.get(id);
|
||||
case "culture_trait":
|
||||
return AssetManager.culture_traits?.get(id);
|
||||
case "religion_trait":
|
||||
return AssetManager.religion_traits?.get(id);
|
||||
case "clan_trait":
|
||||
return AssetManager.clan_traits?.get(id);
|
||||
case "language_trait":
|
||||
return AssetManager.language_traits?.get(id);
|
||||
case "biome":
|
||||
return AssetManager.biome_library?.get(id);
|
||||
case "building":
|
||||
return AssetManager.buildings?.get(id);
|
||||
case "trait":
|
||||
return AssetManager.traits?.get(id);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> EnumerateLiveIds(string kind)
|
||||
{
|
||||
string k = (kind ?? "").Trim().ToLowerInvariant();
|
||||
switch (k)
|
||||
{
|
||||
case "spell":
|
||||
return ActivityAssetCatalog.EnumerateLiveSpellIds();
|
||||
case "power":
|
||||
return ActivityAssetCatalog.EnumerateLivePowerIds();
|
||||
case "item":
|
||||
return ActivityAssetCatalog.EnumerateLiveItemIds();
|
||||
case "gene":
|
||||
return ActivityAssetCatalog.EnumerateLiveGeneIds();
|
||||
case "phenotype":
|
||||
return ActivityAssetCatalog.EnumerateLivePhenotypeIds();
|
||||
case "worldlaw":
|
||||
case "world_law":
|
||||
return ActivityAssetCatalog.EnumerateLiveWorldLawIds();
|
||||
case "subspecies_trait":
|
||||
return ActivityAssetCatalog.EnumerateLiveSubspeciesTraitIds();
|
||||
case "culture_trait":
|
||||
return ActivityAssetCatalog.EnumerateLiveCultureTraitIds();
|
||||
case "religion_trait":
|
||||
return ActivityAssetCatalog.EnumerateLiveReligionTraitIds();
|
||||
case "clan_trait":
|
||||
return ActivityAssetCatalog.EnumerateLiveClanTraitIds();
|
||||
case "language_trait":
|
||||
return ActivityAssetCatalog.EnumerateLiveLanguageTraitIds();
|
||||
case "biome":
|
||||
return ActivityAssetCatalog.EnumerateLiveBiomeIds();
|
||||
case "building":
|
||||
return ActivityAssetCatalog.EnumerateLiveBuildingIds();
|
||||
default:
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Item watch verb from live asset shape: equipment/weapons/armor gain; forge-y ids forge; else gain.
|
||||
/// </summary>
|
||||
public static string ItemVerb(string itemId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(itemId))
|
||||
{
|
||||
return "gains";
|
||||
}
|
||||
|
||||
string s = itemId.ToLowerInvariant();
|
||||
if (s.Contains("forge") || s.Contains("smith") || s.Contains("anvil"))
|
||||
{
|
||||
return "forges";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ItemAsset item = AssetManager.items?.get(itemId.Trim());
|
||||
if (item != null)
|
||||
{
|
||||
string subtype = item.equipment_subtype ?? "";
|
||||
string material = item.material ?? "";
|
||||
if (!string.IsNullOrEmpty(subtype)
|
||||
|| item.equipment_value > 0
|
||||
|| s.StartsWith("armor_", StringComparison.Ordinal)
|
||||
|| s.StartsWith("weapon_", StringComparison.Ordinal)
|
||||
|| s.StartsWith("helmet_", StringComparison.Ordinal)
|
||||
|| s.StartsWith("boots_", StringComparison.Ordinal)
|
||||
|| s.StartsWith("ring_", StringComparison.Ordinal)
|
||||
|| s.StartsWith("amulet_", StringComparison.Ordinal))
|
||||
{
|
||||
return "gains";
|
||||
}
|
||||
|
||||
_ = material;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fall through
|
||||
}
|
||||
|
||||
return "gains";
|
||||
}
|
||||
|
||||
public static bool IsSpectacleBuilding(string buildingId, BuildingAsset asset)
|
||||
{
|
||||
string id = (buildingId ?? "").ToLowerInvariant();
|
||||
if (id.StartsWith("temple_", StringComparison.Ordinal)
|
||||
|| id == "statue"
|
||||
|| id.Contains("statue")
|
||||
|| id.Contains("monument")
|
||||
|| id.Contains("wonder"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string group = asset != null ? (asset.group ?? "") : "";
|
||||
if (group.IndexOf("temple", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| group.IndexOf("wonder", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsCivBuilding(BuildingAsset asset)
|
||||
{
|
||||
if (asset == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return asset.building_type == BuildingType.Building_Civ;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
string type = asset.type ?? "";
|
||||
return type.IndexOf("civ", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string TryLocalizedName(object asset)
|
||||
{
|
||||
if (asset == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
MethodInfo translated = asset.GetType().GetMethod(
|
||||
"getTranslatedName",
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (translated != null && translated.GetParameters().Length == 0)
|
||||
{
|
||||
string name = translated.Invoke(asset, null) as string;
|
||||
if (IsUsableDisplay(name, asset))
|
||||
{
|
||||
return ActivityAssetCatalog.TitleCaseWords(name.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fall through
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
MethodInfo locale = asset.GetType().GetMethod(
|
||||
"getLocaleID",
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (locale != null && locale.GetParameters().Length == 0)
|
||||
{
|
||||
string localeId = locale.Invoke(asset, null) as string;
|
||||
if (!string.IsNullOrEmpty(localeId))
|
||||
{
|
||||
string text = LocalizedTextManager.getText(localeId);
|
||||
if (IsUsableDisplay(text, asset) && !string.Equals(text, localeId, StringComparison.Ordinal))
|
||||
{
|
||||
return ActivityAssetCatalog.TitleCaseWords(text.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fall through
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private static bool IsUsableDisplay(string name, object asset)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string trimmed = name.Trim();
|
||||
if (trimmed.Length == 0 || trimmed.IndexOf('_') >= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PropertyInfo idProp = asset.GetType().GetProperty("id");
|
||||
FieldInfo idField = asset.GetType().GetField("id");
|
||||
string id = idProp != null
|
||||
? idProp.GetValue(asset) as string
|
||||
: idField != null
|
||||
? idField.GetValue(asset) as string
|
||||
: null;
|
||||
if (!string.IsNullOrEmpty(id) && string.Equals(trimmed, id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// keep name
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -99,6 +99,8 @@ public static class MutationDiscoveryHarness
|
|||
"BookManager.generateNewBook",
|
||||
"BookManager.burnBook",
|
||||
"WorldAgeManager.startNextAge",
|
||||
"Earthquake.startQuake",
|
||||
"Earthquake.spawnQuake",
|
||||
"CultureManager.newCulture",
|
||||
"ReligionManager.newReligion",
|
||||
"LanguageManager.newLanguage",
|
||||
|
|
@ -118,6 +120,7 @@ public static class MutationDiscoveryHarness
|
|||
"BehDealDamageToTargetBuilding.execute",
|
||||
"BehConsumeTargetBuilding.execute",
|
||||
"Building.startDestroyBuilding",
|
||||
"Building.completeConstruction",
|
||||
"BehBoatTransportUnloadUnits.execute",
|
||||
"BehBoatMakeTrade.execute",
|
||||
"BehBoatTransportDoLoading.execute",
|
||||
|
|
@ -454,7 +457,7 @@ public static class MutationDiscoveryHarness
|
|||
|| name == "units"
|
||||
|| name == "subspecies"
|
||||
|| name == "plots"
|
||||
// Meta genesis Harmony covers new*; ongoing poll is optional fill.
|
||||
// Meta genesis Harmony covers new*; era/quake have dedicated wires.
|
||||
|| name == "alliances"
|
||||
|| name == "clans"
|
||||
|| name == "cultures"
|
||||
|
|
@ -467,7 +470,9 @@ public static class MutationDiscoveryHarness
|
|||
|| name == "kingdoms"
|
||||
|| name == "items"
|
||||
|| name == "buildings"
|
||||
|| name == "diplomacy";
|
||||
|| name == "diplomacy"
|
||||
|| name == "era_manager"
|
||||
|| name == "earthquake_manager";
|
||||
managersTsv.Append("World").Append('\t')
|
||||
.Append(name).Append('\t')
|
||||
.Append("0").Append('\t')
|
||||
|
|
|
|||
|
|
@ -3606,7 +3606,7 @@
|
|||
"signalStrength": 86,
|
||||
"category": "Spectacle",
|
||||
"label": "{a} casts {id}",
|
||||
"ambientCreatesInterest": true,
|
||||
"ambientCreatesInterest": false,
|
||||
"signalIds": [
|
||||
"summon_lightning",
|
||||
"summon_tornado",
|
||||
|
|
@ -3628,36 +3628,36 @@
|
|||
"ambientStrength": 34,
|
||||
"signalStrength": 76,
|
||||
"category": "Item",
|
||||
"label": "{a} forges {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"label": "{a} gains {id}",
|
||||
"ambientCreatesInterest": false
|
||||
},
|
||||
"gene": {
|
||||
"ambientStrength": 28,
|
||||
"signalStrength": 64,
|
||||
"category": "Genetics",
|
||||
"label": "{a} gains gene {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"ambientCreatesInterest": false
|
||||
},
|
||||
"phenotype": {
|
||||
"ambientStrength": 26,
|
||||
"signalStrength": 55,
|
||||
"category": "Genetics",
|
||||
"label": "{a} shows {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"ambientCreatesInterest": false
|
||||
},
|
||||
"worldLaw": {
|
||||
"ambientStrength": 36,
|
||||
"signalStrength": 68,
|
||||
"category": "WorldLaw",
|
||||
"label": "Law changed: {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"ambientCreatesInterest": false
|
||||
},
|
||||
"subspeciesTrait": {
|
||||
"ambientStrength": 32,
|
||||
"signalStrength": 72,
|
||||
"category": "Evolution",
|
||||
"label": "{a} evolves {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"ambientCreatesInterest": false
|
||||
},
|
||||
"biome": {
|
||||
"ambientStrength": 22,
|
||||
|
|
@ -3671,28 +3671,28 @@
|
|||
"signalStrength": 74,
|
||||
"category": "Culture",
|
||||
"label": "{a} culture gains {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"ambientCreatesInterest": false
|
||||
},
|
||||
"religionTrait": {
|
||||
"ambientStrength": 34,
|
||||
"signalStrength": 74,
|
||||
"category": "Religion",
|
||||
"label": "{a} faith gains {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"ambientCreatesInterest": false
|
||||
},
|
||||
"clanTrait": {
|
||||
"ambientStrength": 32,
|
||||
"signalStrength": 72,
|
||||
"category": "Clan",
|
||||
"label": "{a} clan gains {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"ambientCreatesInterest": false
|
||||
},
|
||||
"languageTrait": {
|
||||
"ambientStrength": 30,
|
||||
"signalStrength": 68,
|
||||
"category": "Language",
|
||||
"label": "{a} tongue gains {id}",
|
||||
"ambientCreatesInterest": true
|
||||
"ambientCreatesInterest": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "IdleSpectator",
|
||||
"author": "dazed",
|
||||
"version": "0.28.13",
|
||||
"version": "0.28.17",
|
||||
"description": "AFK Idle Spectator (I) + Lore (L). Killer button retargets dossier to the killer.",
|
||||
"GUID": "com.dazed.idlespectator"
|
||||
}
|
||||
|
|
|
|||
212
docs/event-catalog-discovery.md
Normal file
212
docs/event-catalog-discovery.md
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# Event catalog discovery
|
||||
|
||||
Live gap map of AssetManager libraries, mutation hooks, and IdleSpectator event-catalog coverage.
|
||||
Expansion of [`event-catalog.json`](../IdleSpectator/event-catalog.json) should cite this ledger, not guessed id lists.
|
||||
|
||||
Audit date: 2026-07-17.
|
||||
WorldBox harness refresh: `mutation_discovery`, `domain_event_audit`, `activity_happiness_audit`, `activity_status_audit`, `world_log_audit`, `disaster_war_audit`, `activity_taxonomy_audit` (all PASS).
|
||||
Artifacts: `IdleSpectator/.harness/` (especially `mutation_*.tsv`, `domain_event_audit.tsv`).
|
||||
Machine worksheet: [`event-catalog-discovery.tsv`](event-catalog-discovery.tsv).
|
||||
|
||||
## Verdict
|
||||
|
||||
**Id inventory is complete for every EventCatalog domain.**
|
||||
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:
|
||||
|
||||
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`.
|
||||
|
||||
Tips here means watch tip / orange reason Labels from interest feeds, not dossier chip hover tooltips.
|
||||
|
||||
## How to read priorities
|
||||
|
||||
| Priority | Meaning |
|
||||
|----------|---------|
|
||||
| P0 | Missing wire or authored sync that drops a real lifecycle beat |
|
||||
| P1 | Tip/prose quality for already-seeded high-visibility classes |
|
||||
| P2 | New surfaces or Signal tuning after P0/P1 |
|
||||
| P3 | Intentional Layer B / covered-by another feed |
|
||||
|
||||
## Authored 1:1 domains
|
||||
|
||||
Live audits: `missingAuthored=0` / `orphan=0` for all closed libraries.
|
||||
|
||||
| Domain | Live | Authored | Catalog mode | Camera policy (approx) | Priority |
|
||||
|--------|-----:|---------:|--------------|------------------------|----------|
|
||||
| happiness | 67 | 67 | authored | Signal 49 / Aggregate 13 / Ambient 5 | P3 sync done |
|
||||
| status | 58 | 58 | authored | CreatesInterest 42; B FX 16 | P3 sync done |
|
||||
| worldLog | 41 | 41 | authored | CreatesInterest 40 (`auto_tester` B) | P3 sync done |
|
||||
| plots | 28 | 28 | authored | all A | P3 sync done |
|
||||
| eras | 11 | 11 | authored | all A | P3 sync done |
|
||||
| books | 12 | 12 | authored | all A | P3 sync done |
|
||||
| traits | 116 | 116 | authored | all A | P3 sync done |
|
||||
| disasters | 17 | 17 | authored | via WorldLog pairs | P3 sync done |
|
||||
| warTypes | 5 | 5 | authored | dials | P3 sync done |
|
||||
| relationship | n/a | 11 | authored discrete | all A | P3 sync done |
|
||||
|
||||
Happiness Ambient (stay B): `got_poked`, `got_caught`, `paid_tax`, `just_ate`, `just_pooped`.
|
||||
Status B: recovery_*, egg gain, spell_silence/boost, on_guard, just_ate, afterglow, slowness, cough, shield, dodge, dash, flicked.
|
||||
|
||||
## Live + dial domains
|
||||
|
||||
Seeded via `LiveLibraryInterest.EnsureSeeded` + JSON `libraries.*` dials.
|
||||
`authored=1` in `domain_event_audit.tsv` means live-seeded entry exists, not a per-id JSON prose row.
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
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`.
|
||||
|
||||
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`.
|
||||
|
||||
## Patch-only / no catalog rows
|
||||
|
||||
| Surface | Live / notes | Wire status | Priority |
|
||||
|---------|--------------|-------------|----------|
|
||||
| buildings | 618 | consume A; ambient destroy demoted B; **no complete/wonder A** | P0 complete/wonder |
|
||||
| boats | patch | trade/unload A; load B | P3 |
|
||||
| combat | policy in `EventCatalog.Combat` | scanner + fight feeds | P3 |
|
||||
| architecture / city_build_orders | 53 / 3 | covered_by_building_activity | P3 |
|
||||
| kingdoms_traits | 5 | covered_by_trait_meta_feeds | P3 |
|
||||
| items_modifiers | 59 | covered_by_item_feed | P3 |
|
||||
| combat_action_library | 11 | covered_by_spell_combat_feeds | P2 only if distinct beats |
|
||||
| loyalty / opinion / knowledge / communication / story | 29/24/11/10/1 | covered_by_worldlog_happiness | P3 |
|
||||
| citizen_job / professions / personalities | 13/5/5 | Activity/dossier labels | P3 |
|
||||
| tasks_actor | 217 | Activity taxonomy (unmappedTasks=0) | P3 |
|
||||
| actor_library | 322 | SpeciesDiscovery / voice (taxonomy PASS) | P3 |
|
||||
| plot_category_library | 9 | categories only | P3 |
|
||||
|
||||
WorldLog already covers end beats that inventory once flagged as open wire work:
|
||||
`alliance_dissolved`, `kingdom_destroyed`, `kingdom_shattered`, `kingdom_fractured`, `city_destroyed`.
|
||||
Manager `removeObject` is intentionally not event-worthy in `MutationDiscoveryHarness` because those WorldLog rows exist.
|
||||
|
||||
## Mutation discovery
|
||||
|
||||
| Metric | Value |
|
||||
|--------|------:|
|
||||
| managers | 57 |
|
||||
| managerMethods | 189 |
|
||||
| actorMethods | 256 |
|
||||
| beh | 93 |
|
||||
| libraries | 39 |
|
||||
| libraryAssets | 2893 |
|
||||
| gaps | 0 |
|
||||
|
||||
`World.era_manager` / `World.earthquake_manager` report `null` on this build (unwired PollState, not gap rows).
|
||||
Age change remains wired via `WorldAgeManager.startNextAge`.
|
||||
Ongoing era/earthquake poll is still optional fill (P2), not a missing id catalog row.
|
||||
|
||||
Unwired ManagerEnd / Beh rows exist in TSVs but are filtered out of `mutation_gaps` by design (Beh → activity path; `removeObject` → WorldLog).
|
||||
|
||||
## Tip quality gap (main expansion lever)
|
||||
|
||||
`EventReason` / discrete `MakeLabel` replace `{id}` with the raw asset id.
|
||||
Status/trait Activity and dossier paths already use locale/`getTranslatedName` helpers (`LiveEnsemble.StatusDisplayName`, trait display names).
|
||||
Library event Labels do not.
|
||||
|
||||
Examples of weak watch tips today:
|
||||
|
||||
- `{a} culture gains xenophobic` instead of a localized culture-trait name
|
||||
- `{a} forges armor_leather` (wrong verb + raw id)
|
||||
- `{a} gains gene warfare_3`
|
||||
- `God power: atomic_bomb` (readable but not localized)
|
||||
|
||||
Recommended class fix: resolve `{id}` through live asset display names inside Label builders for library domains (same pattern as status), not thousands of JSON rows.
|
||||
|
||||
## Audit hygiene
|
||||
|
||||
`DomainEventHarness` diffs culture/religion/clan/language traits into `domain_event_audit.tsv` but omits those diffs from `Passed`.
|
||||
If meta traits are first-class EventCatalog domains (they are), the pass condition should include them.
|
||||
|
||||
## Expansion waves
|
||||
|
||||
### Wave 0 - done by this audit
|
||||
|
||||
Authored 1:1 sync and live seed inventory: no missing ids.
|
||||
|
||||
### Wave 1 (first implementation PR) - tip quality + building complete
|
||||
|
||||
Status (2026-07-17): **implemented** in mod `0.28.14`.
|
||||
|
||||
1. `LibraryAssetNames` + `EventReason.Library` resolve live display names (localized when available, else humanized).
|
||||
2. Item verb is `gains` by default; `forges` only for forge/smith/anvil ids. Catalog template `{a} gains {id}`.
|
||||
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`.
|
||||
|
||||
### Wave 2 - Signal / ambient A tuning
|
||||
|
||||
Status (2026-07-17): **implemented** in mod `0.28.16`.
|
||||
|
||||
1. `ambientCreatesInterest=false` for spell/item/gene/phenotype/worldLaw/subspecies/meta trait dials (Signal predicates / signalIds own Layer A).
|
||||
2. Spells: `IsSpectacleSpell` keeps summon/meteor/tornado/lightning/teleport/curse/blood/fire as A; silence/shield/cure/grass/vegetation/skeleton stay B.
|
||||
3. World laws: dramatic class includes hunger/rebellion/disaster/angry (plus war/death/plague).
|
||||
4. Phenotypes: all B (no Signal predicate).
|
||||
5. Items: Signal for mythril/adamantine (+ named relic tokens); leather/iron ambient B.
|
||||
6. JSON loader now ingests culture/religion/clan/language trait dials; default ambient CI is false.
|
||||
7. Gate: `library_ambient_policy_ok` (nested in `domain_event_audit`).
|
||||
|
||||
Era change was already wired (`WorldAgeManager.update` → `startNextAge`).
|
||||
Earthquake ongoing hold: **implemented** (`Earthquake.startQuake` → `EarthquakeActive` sticky) in `0.28.17`.
|
||||
|
||||
### Wave 3 - new surfaces only if playtests demand
|
||||
|
||||
Status (2026-07-17): **triaged closed** - no new EventCatalog domains.
|
||||
|
||||
| Surface | Live | Decision |
|
||||
|---------|------|----------|
|
||||
| combat_action_library (11) | melee/range/spell/dodge/… | Covered by combat scanner + `tryToCastSpell` → spell catalog |
|
||||
| story / knowledge / communication / loyalty / opinion | small libs | Covered by WorldLog / happiness civic paths |
|
||||
| personalities / professions | 5 / 5 | Activity/dossier labels only |
|
||||
| architecture / city_build_orders | build helpers | Building complete / consume path |
|
||||
|
||||
Do not mass-author powers/items/buildings into JSON.
|
||||
|
||||
## First implementation PR recommendation
|
||||
|
||||
**Title focus:** Improve library event tip labels and wire building completion.
|
||||
|
||||
**Scope:**
|
||||
|
||||
- Display-name resolution for library Labels (class fix)
|
||||
- Item label template improvement
|
||||
- Building complete / wonder Layer A path + harness gate
|
||||
- `domain_event_audit` pass includes culture/religion/clan/language trait diffs
|
||||
- Refresh this doc + [`world-event-inventory.md`](world-event-inventory.md) counts
|
||||
|
||||
**Out of scope for that PR:** mass JSON rows; scoring-model changes; dossier chip tooltips.
|
||||
|
||||
## Refresh commands
|
||||
|
||||
```bash
|
||||
./scripts/worldbox-ctl.sh status
|
||||
./scripts/harness-run.sh --no-launch mutation_discovery
|
||||
./scripts/harness-run.sh --no-launch domain_event_audit
|
||||
./scripts/harness-run.sh --no-launch activity_happiness_audit
|
||||
./scripts/harness-run.sh --no-launch activity_status_audit
|
||||
./scripts/harness-run.sh --no-launch world_log_audit
|
||||
./scripts/harness-run.sh --no-launch disaster_war_audit
|
||||
./scripts/harness-run.sh --no-launch activity_taxonomy_audit
|
||||
```
|
||||
52
docs/event-catalog-discovery.tsv
Normal file
52
docs/event-catalog-discovery.tsv
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
library_or_hook live_count catalog_mode missing_ids camera_a wire_status tip_quality priority notes
|
||||
happiness_library 67 authored 0 49_Signal wired authored_prose P3 Ambient5 Aggregate13; audit PASS
|
||||
status 58 authored 0 42 wired authored_prose P3 16 CreatesInterest=false FX; audit PASS
|
||||
world_log_library 41 authored 0 40 wired authored_labels P3 auto_tester B; audit PASS
|
||||
plots_library 28 authored 0 28 wired authored_labels P3 domain_event_audit PASS
|
||||
era_library 11 authored 0 11 wired authored_labels P3 age change via WorldAgeManager; era_manager field null
|
||||
book_types 12 authored 0 12 wired authored_labels P3
|
||||
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
|
||||
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
|
||||
mutation_gaps 0 harness 0 n/a gaps_empty n/a P3 managers=57 libraryAssets=2893
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
Source of truth for what IdleSpectator covers across the **whole** game.
|
||||
Refresh via live libraries + `mutation_discovery` / domain audits under `.harness/`.
|
||||
Full gap map and expansion waves: [`event-catalog-discovery.md`](event-catalog-discovery.md).
|
||||
|
||||
Audit date: 2026-07-16.
|
||||
Audit date: 2026-07-17.
|
||||
|
||||
## Policy
|
||||
|
||||
|
|
@ -24,57 +25,65 @@ Only true brief FX, cooldowns, civic Aggregates, and god-tool / biome spam stay
|
|||
|
||||
| Library | ~Count | Owner |
|
||||
|---------|-------:|-------|
|
||||
| happiness_library | 67 | EventCatalog.Happiness |
|
||||
| status | 58 | EventCatalog.Status |
|
||||
| world_log_library | 41 | EventCatalog.WorldLog |
|
||||
| happiness_library | 67 | EventCatalog.Happiness (1:1 authored) |
|
||||
| status | 58 | EventCatalog.Status (1:1 authored) |
|
||||
| world_log_library | 41 | EventCatalog.WorldLog (1:1 authored) |
|
||||
| disasters | 17 | via WorldLog |
|
||||
| war_types_library | 5 | EventCatalog.WarType |
|
||||
| traits | 116 | EventCatalog.Trait (all CI; strength ranks) |
|
||||
| plots_library | 28 | EventCatalog.Plot |
|
||||
| era_library | 11 | EventCatalog.Era |
|
||||
| book_types | 12 | EventCatalog.Book (all A) |
|
||||
| decisions / powers / spells / items | 127 / 339 / 12 / 111 | EventCatalog.Libraries |
|
||||
| decisions / powers / spells / items | 127 / 339 / 12 / 111 | EventCatalog.Libraries / Decision |
|
||||
| subspecies_traits / genes / phenotypes | 204 / 47 / 52 | Libraries |
|
||||
| buildings | 618 | patches (consume/destroy A; damage skipped) |
|
||||
| world_laws / biomes | 49 / 29 | Libraries |
|
||||
| culture / religion / clan / language traits | 78 / 40 / 29 / 26 | EventCatalog.Libraries (live+dial) |
|
||||
| buildings | 618 | patches (consume A; damage/ambient destroy skipped) |
|
||||
| actor_library | 322 | SpeciesDiscovery / scoring |
|
||||
| culture/religion/clan/language traits | 78 / 40 / 29 / 26 | **no event catalog yet** |
|
||||
| tasks_actor | 217 | Activity taxonomy |
|
||||
|
||||
## Layer A sizes (policy)
|
||||
|
||||
| Domain | Authored | Layer A |
|
||||
|--------|--------:|--------:|
|
||||
| Domain | Authored / seeded | Layer A |
|
||||
|--------|------------------:|--------|
|
||||
| Happiness | 67 | ~49 Signal (5 Ambient FX; 13 Aggregate civic) |
|
||||
| Status | 58 | ~42 CreatesInterest (FX/cooldowns + egg gain stay B) |
|
||||
| WorldLog | 41 | ~40 |
|
||||
| Relationship | 11 | all interesting discrete (seeker/pack at low strength) |
|
||||
| Relationship | 11 | all interesting discrete |
|
||||
| Plot / Era / War / Book | 28 / 11 / 5 / 12 | all A |
|
||||
| Trait | 116 | all CI (no noticeScoreMin hard drop) |
|
||||
| Spell / Item / Gene / Phenotype / WorldLaw / SubspeciesTrait | live | ambient A at low strength; Signal bumps spectacle |
|
||||
| Power / Decision / Biome | live | Signal predicate only (tools / idle AI / terrain) |
|
||||
| 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 |
|
||||
| Building / Boat | none | destroy/consume/trade/unload; damage/load skipped |
|
||||
|
||||
## Wired hooks (major)
|
||||
|
||||
Happiness, status (+ egg-loss hatch), WorldLog, wars, plots (new/cancel/join/leave/finish),
|
||||
meta genesis (culture/religion/language/clan/army/alliance/city/kingdom),
|
||||
books, traits, deferred libraries, boats (trade/unload), buildings (consume/destroy),
|
||||
relationship (lover/child/family/baby/alpha), Family.setAlpha, SpeciesDiscovery.
|
||||
books, traits, deferred libraries, boats (trade/unload), buildings (consume; civ complete/wonder; ambient destroy demoted),
|
||||
relationship (lover/child/family/baby/alpha), Family.setAlpha, SpeciesDiscovery,
|
||||
meta trait gain (`MetaObjectWithTraits.addTrait` pipelines),
|
||||
earthquake sticky (`Earthquake.startQuake` while active).
|
||||
|
||||
## Known gaps / thin areas
|
||||
|
||||
| Gap | Why it matters | Status |
|
||||
|-----|----------------|--------|
|
||||
| Culture/religion/clan/language trait gain | Meta drama libraries exist | **Live** (MetaObjectWithTraits.addTrait in pipelines) |
|
||||
| Building complete / wonder finish | Settlement spectacle | **Open** |
|
||||
| Alliance/kingdom destroy managers | End beats if WorldLog thin | **Open** |
|
||||
| Era/earthquake poll (ongoing) | Age change is wired; poll not | **Open** |
|
||||
| Building complete / wonder finish | Settlement spectacle | **Live** (`Building.completeConstruction`, civ only) |
|
||||
| Library tip Labels use raw `{id}` | Watch tips look untracked / ugly | **Live** (`LibraryAssetNames` + humanize) |
|
||||
| Large ambient-A libraries (items/genes/phenotypes/subspecies/meta) | Camera noise with weak prose | **Live** (Signal-only A; `library_ambient_policy_ok`) |
|
||||
| Meta-trait diffs not fail-gated in `domain_event_audit` | Regressions easy to miss | **Live** (fail-gated + `library_asset_labels_ok`) |
|
||||
| Era/earthquake ongoing poll | Age via `startNextAge`; quake sticky via `Earthquake.startQuake` | **Live** |
|
||||
| Alliance/kingdom destroy managers | End beats covered by WorldLog (`alliance_dissolved`, `kingdom_destroyed`, …) | **Closed via WorldLog** |
|
||||
|
||||
## Stay B (true noise)
|
||||
|
||||
- Happiness: `got_poked`, `got_caught`, `paid_tax`, `just_ate`, `just_pooped` + all Aggregates
|
||||
- Status: dash/dodge/shield/recovery_*/spell_*/cough/flicked/just_ate/on_guard/afterglow/slowness + egg gain
|
||||
- Libraries: Power ambient (god paint tools), Decision ambient (idle AI; interesting decisions are Signal), Biome ambient
|
||||
- Patches: `building_damage`, `boat_load`
|
||||
- Patches: `building_damage`, ambient building destroy, `boat_load`
|
||||
|
||||
## Scoring notes
|
||||
|
||||
|
|
@ -88,4 +97,9 @@ CharacterSignificance includes favorite/king/leader/renown/kills/level and speci
|
|||
./scripts/harness-run.sh --no-launch mutation_discovery
|
||||
./scripts/harness-run.sh --no-launch domain_event_audit
|
||||
./scripts/harness-run.sh --no-launch activity_happiness_audit
|
||||
./scripts/harness-run.sh --no-launch activity_status_audit
|
||||
./scripts/harness-run.sh --no-launch world_log_audit
|
||||
./scripts/harness-run.sh --no-launch disaster_war_audit
|
||||
```
|
||||
|
||||
See also [`event-catalog-discovery.md`](event-catalog-discovery.md) for the prioritized expansion waves.
|
||||
|
|
|
|||
Loading…
Reference in a new issue