dont dedupe important verbs

This commit is contained in:
DazedAnon 2026-07-15 13:37:55 -05:00
parent 8ab8a9880a
commit 7068ac44c5
6 changed files with 140 additions and 30 deletions

View file

@ -849,7 +849,7 @@ public static class ActivityLog
private static bool ShouldSuppressSameVerb(long subjectId, string key)
{
string verb = ResolveVerbKey(key);
if (string.IsNullOrEmpty(verb))
if (string.IsNullOrEmpty(verb) || ActivityVerbMap.IsSameVerbCooldownExempt(verb))
{
return false;
}

View file

@ -128,6 +128,39 @@ public static class ActivityVerbMap
return !string.IsNullOrEmpty(Resolve(key));
}
/// <summary>
/// High-signal verbs skip the ActivityLog same-verb cooldown (combat, danger, romance, etc.).
/// Spammy verbs like play/move/work still dedupe.
/// </summary>
public static bool IsSameVerbCooldownExempt(string verb)
{
if (string.IsNullOrEmpty(verb))
{
return false;
}
switch (verb)
{
case "fight":
case "hunt":
case "flee":
case "lover":
case "steal":
case "loot":
case "heal":
case "ignite":
case "extinguish":
case "fire":
case "harvest_life":
case "possessed":
case "confused":
case "strange_urge":
return true;
default:
return false;
}
}
public static List<string> EnumerateLiveTaskIds()
{
return ActivityAssetCatalog.EnumerateLiveTaskIds();

View file

@ -2006,15 +2006,20 @@ public static class AgentHarness
}
case "dossier_task_refresh":
{
// Stale focus snapshot must recover: blank chip, then sync from live AI.
// Stale focus snapshot must recover: blank dossier TaskText, then sync from live AI.
// Empty live AI is ok - chip may keep a sticky prior label (never cleared on gaps).
Actor focus = MoveCamera.hasFocusUnit() ? MoveCamera._focus_unit : null;
string live = UnitDossier.ReadLiveTask(focus);
string after = WatchCaption.HarnessBlankAndRefreshTask();
string chip = WatchCaption.ShownTaskChipText;
bool chipMatches = string.IsNullOrEmpty(live)
? string.IsNullOrEmpty(chip)
: !string.IsNullOrEmpty(chip);
pass = after == live && chipMatches;
if (string.IsNullOrEmpty(live))
{
pass = string.IsNullOrEmpty(after);
detail = $"live_empty dossier='{after}' sticky_chip='{chip}'";
break;
}
pass = after == live && !string.IsNullOrEmpty(chip);
detail = $"live='{live}' dossier='{after}' chip='{chip}'";
break;
}
@ -2292,6 +2297,32 @@ public static class AgentHarness
$"added={added} want_added=1 suppressed={suppressed} want_suppressed>={keys.Length - 1} keys={keys.Length}";
break;
}
case "activity_verb_exempt":
{
// Combat / high-signal verbs skip same-verb cooldown.
Actor focus = MoveCamera.hasFocusUnit() ? MoveCamera._focus_unit : null;
if (focus == null || !focus.isAlive())
{
pass = false;
detail = "no_live_focus";
break;
}
ActivityLog.ResetSampleCounters();
string[] keys =
{
"task_attack",
"punch_a_building",
"tantrum",
"task_unit_attack"
};
int added = ActivityLog.HarnessSpamSameVerb(focus, keys);
int suppressed = ActivityLog.VerbSuppressedSinceClear;
pass = added == keys.Length && suppressed == 0;
detail =
$"added={added} want_added={keys.Length} suppressed={suppressed} want_suppressed=0";
break;
}
case "activity_prose_sentence":
{
// Ensure mood/task labels become sentences, not "{name} laughing".

View file

@ -614,6 +614,7 @@ internal static class HarnessScenarios
Step("act13f", "assert", expect: "activity_taxonomy_audit"),
Step("act13g", "assert", expect: "activity_status_audit"),
Step("act13h", "assert", expect: "activity_verb_dedupe"),
Step("act13h2", "assert", expect: "activity_verb_exempt"),
Step("act13i", "assert", expect: "dossier_task_refresh"),
Step("act14", "activity_force", asset: "laughing", label: "Laughing", count: 1, value: "angle"),
Step("act14b", "assert", expect: "activity_log_contains", value: "celestial"),

View file

@ -16,6 +16,8 @@ public static class WatchCaption
private const float SexSize = 14f;
private const float LiveMax = 44f;
private const float ChipIcon = 12f;
/// <summary>Fixed nametag task label slot so text swaps do not autofit-tick the header.</summary>
private const float TaskLabelW = 72f;
private const float TraitIcon = 12f;
private const float HistoryIcon = 10f;
private const float HistoryColMinW = 118f;
@ -623,6 +625,7 @@ public static class WatchCaption
/// <summary>
/// Keep the nametag task chip in sync with live AI (focus snapshot can miss brief no-task gaps).
/// Never clears the chip on empty AI gaps - only replaces text when a new task is present.
/// </summary>
private static void RefreshLiveTask()
{
@ -638,19 +641,30 @@ public static class WatchCaption
}
string live = UnitDossier.ReadLiveTask(actor);
// Hold the last shown label through brief no-task gaps so autofit does not collapse.
if (string.IsNullOrEmpty(live))
{
return;
}
string prev = _current.TaskText ?? "";
if (live == prev)
{
return;
}
bool wasShowing = _taskText != null && _taskText.gameObject.activeSelf;
_current.TaskText = live;
ApplyTaskChip(live);
ReplaceTaskChip(live);
// First appearance needs layout; later swaps only change text inside a fixed-width slot.
if (!wasShowing)
{
bool hasBody = _current.UnitId != 0;
bool hasReason = _reasonText != null && _reasonText.gameObject.activeSelf;
Relayout(hasBody, CountActiveTraitSlots(), hasTask: true, hasReason, CountActiveHistorySlots());
}
bool hasBody = _current.UnitId != 0;
bool hasTask = !string.IsNullOrEmpty(live);
bool hasReason = _reasonText != null && _reasonText.gameObject.activeSelf;
Relayout(hasBody, CountActiveTraitSlots(), hasTask, hasReason, CountActiveHistorySlots());
BringHeaderFront();
}
@ -678,29 +692,61 @@ public static class WatchCaption
return actor;
}
private static void ApplyTaskChip(string taskText)
private static string TruncateTaskLabel(string taskText)
{
bool hasTask = !string.IsNullOrEmpty(taskText);
if (string.IsNullOrEmpty(taskText))
{
return "";
}
return taskText.Length > 14 ? taskText.Substring(0, 13) + "..." : taskText;
}
/// <summary>Show or replace the task chip without ever blanking mid-update.</summary>
private static void ReplaceTaskChip(string taskText)
{
if (string.IsNullOrEmpty(taskText))
{
return;
}
if (_taskIcon != null)
{
HudIcons.Apply(_taskIcon, hasTask ? HudIcons.Task() : null);
_taskIcon.gameObject.SetActive(hasTask);
HudIcons.Apply(_taskIcon, HudIcons.Task());
_taskIcon.gameObject.SetActive(true);
}
if (_taskText != null)
{
string task = hasTask ? taskText : "";
if (task.Length > 14)
{
task = task.Substring(0, 13) + "...";
}
_taskText.text = task;
_taskText.gameObject.SetActive(hasTask);
_taskText.text = TruncateTaskLabel(taskText);
_taskText.gameObject.SetActive(true);
}
}
/// <summary>Harness: blank the nametag task, then sync from live AI.</summary>
private static void ApplyTaskChip(string taskText)
{
if (!string.IsNullOrEmpty(taskText))
{
ReplaceTaskChip(taskText);
return;
}
if (_taskIcon != null)
{
HudIcons.Apply(_taskIcon, null);
_taskIcon.gameObject.SetActive(false);
}
if (_taskText != null)
{
_taskText.text = "";
_taskText.gameObject.SetActive(false);
}
}
/// <summary>
/// Harness: simulate a stale empty snapshot, then sync from live AI (replace, never stay blank if live has a task).
/// </summary>
public static string HarnessBlankAndRefreshTask()
{
if (_current != null)
@ -708,7 +754,7 @@ public static class WatchCaption
_current.TaskText = "";
}
ApplyTaskChip("");
// Do not hide the chip here - RefreshLiveTask must replace in place when live task exists.
RefreshLiveTask();
return _current != null ? (_current.TaskText ?? "") : "";
}
@ -1465,9 +1511,8 @@ public static class WatchCaption
_taskText.gameObject.SetActive(true);
PlaceLeftChip(_taskIcon.rectTransform, x, yFromTop + 3f, ChipIcon, ChipIcon);
x += ChipIcon + 1f;
float taskW = Mathf.Clamp(MeasureTextWidth(_taskText, 36f), 20f, 72f);
PlaceLeftChip(_taskText.rectTransform, x, yFromTop, taskW, HeaderH);
x += taskW + 4f;
PlaceLeftChip(_taskText.rectTransform, x, yFromTop, TaskLabelW, HeaderH);
x += TaskLabelW + 4f;
}
else
{
@ -1521,7 +1566,7 @@ public static class WatchCaption
if (hasTask && _taskIcon != null && _taskText != null)
{
x += ChipIcon + 1f;
x += Mathf.Clamp(MeasureTextWidth(_taskText, 36f), 20f, 72f) + 4f;
x += TaskLabelW + 4f;
}
if (_sexIcon != null && _sexIcon.gameObject.activeSelf)

View file

@ -1,7 +1,7 @@
{
"name": "IdleSpectator",
"author": "dazed",
"version": "0.12.100",
"version": "0.12.103",
"description": "AFK Idle Spectator (I) + Lore (L). Detailed living activity prose by default.",
"GUID": "com.dazed.idlespectator"
}