worldbox-observer-mod/IdleSpectator/Story/CausalHeat.cs
2026-07-21 13:35:07 -05:00

134 lines
3.6 KiB
C#

using System.Collections.Generic;
using UnityEngine;
namespace IdleSpectator;
/// <summary>
/// Decaying per-unit heat from featured cast. Soft-spreads to live relations and Chronicle links.
/// </summary>
public static class CausalHeat
{
private struct HeatEntry
{
public float Value;
public float TouchedAt;
}
private static readonly Dictionary<long, HeatEntry> HeatById = new Dictionary<long, HeatEntry>(64);
private static readonly List<long> ScratchIds = new List<long>(16);
private const float FeaturedAmount = 1f;
private const float RelatedSpread = 0.45f;
public static void Clear()
{
HeatById.Clear();
}
public static void NoteFeatured(long unitId, float amount = FeaturedAmount)
{
if (unitId == 0 || amount <= 0f)
{
return;
}
float now = Time.unscaledTime;
AddHeat(unitId, amount, now);
SpreadRelated(unitId, amount * RelatedSpread, now);
}
public static void NoteCast(IList<long> castIds, float amount = FeaturedAmount)
{
if (castIds == null || castIds.Count == 0)
{
return;
}
for (int i = 0; i < castIds.Count; i++)
{
NoteFeatured(castIds[i], amount);
}
}
public static float Get(long unitId)
{
if (unitId == 0 || !HeatById.TryGetValue(unitId, out HeatEntry entry))
{
return 0f;
}
return Decayed(entry, Time.unscaledTime);
}
public static float ScoreBonus(InterestCandidate c)
{
if (c == null)
{
return 0f;
}
StoryWeights s = InterestScoringConfig.Story;
float subject = Get(c.SubjectId);
float related = c.RelatedId != 0 && c.RelatedId != c.SubjectId ? Get(c.RelatedId) : 0f;
return subject * s.causalHeatWeight + related * s.causalRelatedWeight;
}
private static float Decayed(HeatEntry entry, float now)
{
StoryWeights s = InterestScoringConfig.Story;
float halfLife = s.causalHeatHalfLifeSeconds > 1f ? s.causalHeatHalfLifeSeconds : 180f;
float age = Mathf.Max(0f, now - entry.TouchedAt);
return entry.Value * Mathf.Pow(0.5f, age / halfLife);
}
private static void AddHeat(long unitId, float amount, float now)
{
float cur = 0f;
if (HeatById.TryGetValue(unitId, out HeatEntry entry))
{
cur = Decayed(entry, now);
}
HeatById[unitId] = new HeatEntry
{
Value = Mathf.Min(4f, cur + amount),
TouchedAt = now
};
}
private static void SpreadRelated(long unitId, float amount, float now)
{
if (amount < 0.05f)
{
return;
}
Actor actor = EventFeedUtil.FindAliveById(unitId);
if (actor != null)
{
Actor lover = ActorRelation.GetLover(actor);
if (lover != null)
{
AddHeat(EventFeedUtil.SafeId(lover), amount, now);
}
Actor friend = ActorRelation.GetBestFriend(actor);
if (friend != null)
{
AddHeat(EventFeedUtil.SafeId(friend), amount * 0.75f, now);
}
}
ScratchIds.Clear();
Chronicle.EnumerateRelatedIds(unitId, ScratchIds, max: 6);
for (int i = 0; i < ScratchIds.Count; i++)
{
long other = ScratchIds[i];
if (other == 0 || other == unitId)
{
continue;
}
AddHeat(other, amount * 0.5f, now);
}
}
}