diff --git a/IdleSpectator/ActivityActionComposer.cs b/IdleSpectator/ActivityActionComposer.cs
index 9663f0e..8133278 100644
--- a/IdleSpectator/ActivityActionComposer.cs
+++ b/IdleSpectator/ActivityActionComposer.cs
@@ -86,7 +86,7 @@ public static class ActivityActionComposer
&& BeatTemplates.TryGetValue(key, out string[] beatBag)
&& voice.AssetKind != ActivityAssetKind.Vehicle)
{
- return Pick(beatBag, voice, key, subjectId, lineIndex);
+ return Pick(beatBag, subjectId, lineIndex);
}
string verb = ActivityVerbMap.Resolve(key);
@@ -110,7 +110,8 @@ public static class ActivityActionComposer
}
string[] bag = ActivityActionLexicon.GetBag(voice, verb, hasTarget, ctx.Terrain);
- return Pick(bag, voice, verb, subjectId, lineIndex);
+ string phrase = Pick(bag, subjectId, lineIndex);
+ return ActivityModifierVoiceOverlay.Apply(phrase, voice, verb, subjectId, lineIndex);
}
private static ProseVoice EnsureVoice(ActivityContext ctx)
@@ -135,8 +136,6 @@ public static class ActivityActionComposer
private static string Pick(
string[] bag,
- ProseVoice voice,
- string verb,
long subjectId,
int lineIndex)
{
@@ -150,29 +149,8 @@ public static class ActivityActionComposer
return bag[0];
}
- int slot = VerbSlot(verb);
- int bit = voice.AssetKind == ActivityAssetKind.Vehicle
- ? 0
- : ((voice.FlavorOrdinal >> slot) & 1);
int actorVariation = (int)((subjectId ^ (lineIndex * 397L)) & 1L);
- int index = (bit ^ actorVariation) % Math.Min(2, bag.Length);
+ int index = actorVariation % Math.Min(2, bag.Length);
return bag[index];
}
-
- private static int VerbSlot(string verb)
- {
- switch ((verb ?? "").ToLowerInvariant())
- {
- case "move": return 0;
- case "wait": return 1;
- case "play": return 2;
- case "eat": return 3;
- case "sleep": return 4;
- case "hunt": return 5;
- case "fight": return 6;
- case "social": return 7;
- case "laugh": return 8;
- default: return 9;
- }
- }
}
diff --git a/IdleSpectator/ActivityActionLexicon.cs b/IdleSpectator/ActivityActionLexicon.cs
index 537add9..d780f69 100644
--- a/IdleSpectator/ActivityActionLexicon.cs
+++ b/IdleSpectator/ActivityActionLexicon.cs
@@ -19,15 +19,19 @@ public static class ActivityActionLexicon
return VehicleBag(v);
}
+ string[] authored = ActivitySpeciesVoiceCatalog.GetBag(voice.BaseSpeciesId, v, terrain);
+ if (authored != null && authored.Length > 0)
+ {
+ return authored;
+ }
+
+ // Safe fallback for unknown future assets that are not yet in the authored catalog.
if (voice.Modifier != ActivityModifier.None)
{
string[] modifier = ModifierBag(voice, v, hasTarget);
if (modifier != null) return modifier;
}
- string[] profile = ProfileBag(voice.ProfileTag, v, hasTarget);
- if (profile != null) return profile;
-
string[] body = BodyBag(voice.BaseBodyTag, v, hasTarget, terrain);
return body ?? GenericBag(v, hasTarget);
}
@@ -126,77 +130,6 @@ public static class ActivityActionLexicon
};
}
- private static string[] ProfileBag(string profile, string verb, bool hasTarget)
- {
- if (string.IsNullOrEmpty(profile)) return null;
- if (profile == "dragon")
- {
- return verb switch
- {
- "move" => new[] { "advances at an unhurried pace{place_at}", "moves as if the world should make way{place_at}" },
- "sleep" => new[] { "coils into a guarded sleep{place_at}", "rests with one eye on the world{place_at}" },
- "eat" => new[] { "feeds with terrifying appetite{place_at}", "devours a meal at leisure{place_at}" },
- "laugh" => new[] { "rumbles with deep laughter{place_at}", "laughs in a dangerous rolling growl{place_at}" },
- "hunt" => Targeted(hasTarget, "descends on {target}{place_at}", "keeps {target} in sight{place_at}", "circles in search of prey{place_at}", "hunts across a wide range{place_at}"),
- _ => null
- };
- }
-
- if (profile == "fairy")
- {
- return verb switch
- {
- "move" => new[] { "flits onward in bright little arcs{place_at}", "drifts ahead on glittering wings{place_at}" },
- "play" => new[] { "loops and twirls in sparkling arcs{place_at}", "flutters about mischievously{place_at}" },
- "laugh" => new[] { "chimes with tiny laughter{place_at}", "laughs in a bright little trill{place_at}" },
- _ => null
- };
- }
-
- if (profile == "lemon_snail")
- {
- return verb switch
- {
- "move" => new[] { "glides ahead at an unhurried pace{place_at}", "follows a slow, winding trail{place_at}" },
- "play" => new[] { "turns in slow, winding circles{place_at}", "sways gently from side to side{place_at}" },
- "eat" => new[] { "nibbles through a meal slowly{place_at}", "takes a slow, careful meal{place_at}" },
- _ => null
- };
- }
-
- if (profile == "cat")
- {
- return verb switch
- {
- "move" => new[] { "pads softly along{place_at}", "slinks ahead, watching the way{place_at}" },
- "play" => new[] { "bats, twists, and springs aside{place_at}", "springs into sudden leaps{place_at}" },
- _ => null
- };
- }
-
- if (profile == "buffalo")
- {
- return verb switch
- {
- "move" => new[] { "trudges onward with steady weight{place_at}", "ambles ahead in slow, heavy steps{place_at}" },
- "play" => new[] { "charges, stops, and wheels around{place_at}", "stomps and bounds with clumsy enthusiasm{place_at}" },
- _ => null
- };
- }
-
- if (profile == "crabzilla")
- {
- return verb switch
- {
- "move" => new[] { "takes enormous sideways strides{place_at}", "scuttles onward like a moving fortress{place_at}" },
- "fight" => Targeted(hasTarget, "brings a colossal claw against {target}{place_at}", "crashes into {target} like a siege engine{place_at}", "fights with city-shaking force{place_at}", "clashes like a moving fortress{place_at}"),
- _ => null
- };
- }
-
- return null;
- }
-
private static string[] BodyBag(
ActivityBodyTag body,
string verb,
diff --git a/IdleSpectator/ActivityModifierVoiceOverlay.cs b/IdleSpectator/ActivityModifierVoiceOverlay.cs
new file mode 100644
index 0000000..9c43a9a
--- /dev/null
+++ b/IdleSpectator/ActivityModifierVoiceOverlay.cs
@@ -0,0 +1,184 @@
+using System;
+
+namespace IdleSpectator;
+
+///
+/// Adds a meaningful transformed-variant tone without replacing the recovered base mob voice.
+/// Native authored forms keep their own complete voice and do not pass through this overlay.
+///
+public static class ActivityModifierVoiceOverlay
+{
+ private const string ContextTokens = "{place_at}{job_as}";
+
+ public static string Apply(
+ string basePhrase,
+ ProseVoice voice,
+ string verb,
+ long subjectId,
+ int lineIndex)
+ {
+ if (string.IsNullOrEmpty(basePhrase)
+ || voice == null
+ || voice.Modifier == ActivityModifier.None
+ || voice.LiveSpeciesId.Equals(voice.BaseSpeciesId, StringComparison.OrdinalIgnoreCase))
+ {
+ return basePhrase;
+ }
+
+ string[] bag = OverlayBag(voice.Modifier, voice.BaseBodyTag, verb);
+ if (bag == null || bag.Length == 0)
+ {
+ return basePhrase;
+ }
+
+ int index = (int)((subjectId ^ (lineIndex * 397L)) & 1L) % bag.Length;
+ string core = basePhrase.EndsWith(ContextTokens, StringComparison.Ordinal)
+ ? basePhrase.Substring(0, basePhrase.Length - ContextTokens.Length)
+ : basePhrase;
+ return core + bag[index] + ContextTokens;
+ }
+
+ private static string[] OverlayBag(
+ ActivityModifier modifier,
+ ActivityBodyTag body,
+ string verb)
+ {
+ return modifier switch
+ {
+ ActivityModifier.Undead => UndeadBag(body, verb),
+ ActivityModifier.Elemental => ElementalBag(verb),
+ ActivityModifier.Mush => MushBag(verb),
+ ActivityModifier.Tumor => TumorBag(verb),
+ ActivityModifier.Alien => AlienBag(verb),
+ ActivityModifier.Construct => ConstructBag(verb),
+ _ => null
+ };
+ }
+
+ private static string[] UndeadBag(ActivityBodyTag body, string verb)
+ {
+ if (verb == "move")
+ {
+ return body switch
+ {
+ ActivityBodyTag.Canine => V(
+ ", its paws landing in stiff, uneven beats",
+ ", its muzzle fixed ahead as its gait falters"),
+ ActivityBodyTag.Feline => V(
+ ", each silent step held rigid too long",
+ ", its once-fluid gait broken into sharp jolts"),
+ ActivityBodyTag.Bird => V(
+ ", its lifeless wings beating out of rhythm",
+ ", each ragged motion ending in a hard twitch"),
+ ActivityBodyTag.Insect => V(
+ ", its joints clicking in lifeless sequence",
+ ", each small step snapping into the next"),
+ ActivityBodyTag.Plant => V(
+ ", its withered growth dragging behind",
+ ", every deadened limb moving in rigid turns"),
+ ActivityBodyTag.Dragon => V(
+ ", its ruined frame carrying deathly weight",
+ ", every ancient joint grinding through the motion"),
+ _ => V(
+ ", its gait broken by stiff, uneven jolts",
+ ", every step landing with lifeless weight")
+ };
+ }
+
+ return verb switch
+ {
+ "wait" => V(
+ ", holding an unnaturally rigid posture",
+ ", without breath or any easy motion"),
+ "play" => V(
+ ", repeating each motion in empty jerks",
+ ", the movement breaking into stiff, empty rhythms"),
+ "eat" => V(
+ ", driven by a hunger that never eases",
+ ", consuming each bite without satisfaction"),
+ "sleep" => V(
+ ", lying rigid even at rest",
+ ", never fully losing its restless tension"),
+ "hunt" => V(
+ ", pursuing without tiring",
+ ", never slowing as the chase continues"),
+ "fight" => V(
+ ", striking without fear or pain",
+ ", pressing the attack with deathless persistence"),
+ "social" => V(
+ ", answering with hollow, halting sounds",
+ ", holding close in an uneasy silence"),
+ "laugh" => V(
+ ", the sound collapsing into a dry rasp",
+ ", each note ending in a hollow groan"),
+ "work" => V(
+ ", continuing in tireless, rigid motions",
+ ", never pausing as its joints grind on"),
+ _ => V(
+ ", its body moving with lifeless stiffness",
+ ", each motion marked by restless undeath")
+ };
+ }
+
+ private static string[] ElementalBag(string verb)
+ {
+ return verb switch
+ {
+ "wait" => V(", flickering without becoming still", ", its glow rising and falling"),
+ "sleep" => V(", dimming to a banked glow", ", its brightness settling low"),
+ "eat" => V(", drawing the meal into its inner heat", ", feeding the flame within"),
+ "fight" => V(", shedding sparks with every strike", ", flaring brighter through the clash"),
+ _ => V(", edged by quick tongues of flame", ", trailing brief curls of heat")
+ };
+ }
+
+ private static string[] MushBag(string verb)
+ {
+ return verb switch
+ {
+ "wait" => V(", faint spores drifting from every pause", ", its soft growth gently pulsing"),
+ "eat" => V(", drawing nourishment through fibrous growth", ", feeding in slow, spreading pulses"),
+ "fight" => V(", bursting spores through every impact", ", its soft mass recoiling and swelling"),
+ _ => V(", shedding a faint wake of spores", ", its softened form flexing with each motion")
+ };
+ }
+
+ private static string[] TumorBag(string verb)
+ {
+ return verb switch
+ {
+ "wait" => V(", its swollen mass quivering in place", ", uneven pulses passing through its body"),
+ "eat" => V(", absorbing the meal into its growing mass", ", folding each bite into hungry tissue"),
+ "fight" => V(", its mass surging behind every blow", ", new growth tightening through the clash"),
+ _ => V(", its form shifting in uneven pulses", ", swollen tissue rolling through each motion")
+ };
+ }
+
+ private static string[] AlienBag(string verb)
+ {
+ return verb switch
+ {
+ "wait" => V(", holding a posture that is difficult to read", ", its unfamiliar joints locking at odd angles"),
+ "social" => V(", answering in precise, unfamiliar signals", ", mirroring the exchange with uncanny timing"),
+ "fight" => V(", changing angles without warning", ", every strike arriving with uncanny precision"),
+ _ => V(", following an unfamiliar rhythm", ", each motion precise but difficult to predict")
+ };
+ }
+
+ private static string[] ConstructBag(string verb)
+ {
+ return verb switch
+ {
+ "wait" => V(", every mechanism settling into a fixed hold", ", its joints locking into measured stillness"),
+ "sleep" => V(", its moving parts falling dormant", ", the last small mechanisms winding down"),
+ "fight" => V(", every joint driving the next strike", ", its rigid frame absorbing the impact"),
+ "work" => V(", repeating each step with exact precision", ", its mechanisms keeping a flawless cadence"),
+ _ => V(", every joint moving in measured sequence", ", its mechanisms keeping a precise cadence")
+ };
+ }
+
+ private static string[] V(string first, string second)
+ {
+ return new[] { first, second };
+ }
+}
diff --git a/IdleSpectator/ActivityProse.cs b/IdleSpectator/ActivityProse.cs
index 39cef46..be842b3 100644
--- a/IdleSpectator/ActivityProse.cs
+++ b/IdleSpectator/ActivityProse.cs
@@ -624,6 +624,19 @@ public static class ActivityProse
.Replace(" toward {target}", "")
.Replace(" after {target}", "")
.Replace(" at {target}", "")
+ .Replace(" against {target}", "")
+ .Replace(" before {target}", "")
+ .Replace(" beside {target}", "")
+ .Replace(" near {target}", "")
+ .Replace(" around {target}", "")
+ .Replace(" past {target}", "")
+ .Replace(" behind {target}", "")
+ .Replace(" under {target}", "")
+ .Replace(" over {target}", "")
+ .Replace(" across {target}", "")
+ .Replace(" from {target}", "")
+ .Replace(" into {target}", "")
+ .Replace(" upon {target}", "")
.Replace(" {target}", "")
.Replace("{target}", "someone");
}
diff --git a/IdleSpectator/ActivitySpeciesVoice.cs b/IdleSpectator/ActivitySpeciesVoice.cs
new file mode 100644
index 0000000..c4da63a
--- /dev/null
+++ b/IdleSpectator/ActivitySpeciesVoice.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+
+namespace IdleSpectator;
+
+public enum ActivityVoiceSource
+{
+ Unknown,
+ AuthoredSpecies,
+ TaxonomyFallback,
+ Vehicle
+}
+
+///
+/// Complete authored action vocabulary for one verified base mob.
+/// Contextual clauses such as actor, target, terrain, place, and job stay outside this type.
+///
+public sealed class ActivitySpeciesVoice
+{
+ public static readonly string[] CoreVerbs =
+ {
+ "move", "wait", "play", "eat", "sleep",
+ "hunt", "fight", "social", "laugh", "work"
+ };
+
+ private readonly Dictionary _bags;
+ private readonly Dictionary _liquidBags =
+ new Dictionary(StringComparer.Ordinal);
+
+ public string Id { get; }
+
+ internal ActivitySpeciesVoice(string id, Dictionary bags)
+ {
+ Id = id ?? "";
+ _bags = bags ?? new Dictionary(StringComparer.Ordinal);
+ }
+
+ public string[] GetBag(string verb, ActivityTerrain terrain)
+ {
+ string key = verb ?? "";
+ if (terrain == ActivityTerrain.Liquid
+ && _liquidBags.TryGetValue(key, out string[] liquid)
+ && liquid != null
+ && liquid.Length > 0)
+ {
+ return liquid;
+ }
+
+ return _bags.TryGetValue(key, out string[] bag) ? bag : null;
+ }
+
+ public bool HasCompleteCore()
+ {
+ for (int i = 0; i < CoreVerbs.Length; i++)
+ {
+ if (!_bags.TryGetValue(CoreVerbs[i], out string[] bag)
+ || bag == null
+ || bag.Length < 2
+ || string.IsNullOrEmpty(bag[0])
+ || string.IsNullOrEmpty(bag[1]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ internal void SetLiquidBag(string verb, string[] bag)
+ {
+ if (!string.IsNullOrEmpty(verb) && bag != null && bag.Length > 0)
+ {
+ _liquidBags[verb] = bag;
+ }
+ }
+}
diff --git a/IdleSpectator/ActivitySpeciesVoiceCatalog.cs b/IdleSpectator/ActivitySpeciesVoiceCatalog.cs
new file mode 100644
index 0000000..a7d34f7
--- /dev/null
+++ b/IdleSpectator/ActivitySpeciesVoiceCatalog.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+
+namespace IdleSpectator;
+
+/// Authoritative authored voice registry keyed by verified base actor asset id.
+public static partial class ActivitySpeciesVoiceCatalog
+{
+ private static readonly Dictionary Voices = Build();
+
+ private static Dictionary Build()
+ {
+ var voices = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ AddCivVoices(voices);
+ AddMammalVoices(voices);
+ AddAnimalVoices(voices);
+ AddInvertebrateVoices(voices);
+ AddFantasyVoices(voices);
+ return voices;
+ }
+
+ public static bool TryGet(string baseSpeciesId, out ActivitySpeciesVoice voice)
+ {
+ return Voices.TryGetValue(baseSpeciesId ?? "", out voice);
+ }
+
+ public static bool HasCompleteVoice(string baseSpeciesId)
+ {
+ return TryGet(baseSpeciesId, out ActivitySpeciesVoice voice) && voice.HasCompleteCore();
+ }
+
+ public static string VoiceKey(string baseSpeciesId)
+ {
+ return TryGet(baseSpeciesId, out ActivitySpeciesVoice voice) ? voice.Id : "";
+ }
+
+ public static string[] GetBag(string baseSpeciesId, string verb, ActivityTerrain terrain)
+ {
+ return TryGet(baseSpeciesId, out ActivitySpeciesVoice voice)
+ ? voice.GetBag(verb, terrain)
+ : null;
+ }
+
+ public static List AuthoredIds()
+ {
+ var ids = new List(Voices.Keys);
+ ids.Sort(StringComparer.Ordinal);
+ return ids;
+ }
+
+ private static void Add(
+ Dictionary voices,
+ string id,
+ string[] move,
+ string[] wait,
+ string[] play,
+ string[] eat,
+ string[] sleep,
+ string[] hunt,
+ string[] fight,
+ string[] social,
+ string[] laugh,
+ string[] work)
+ {
+ var bags = new Dictionary(StringComparer.Ordinal)
+ {
+ ["move"] = Contextualize(move),
+ ["wait"] = Contextualize(wait),
+ ["play"] = Contextualize(play),
+ ["eat"] = Contextualize(eat),
+ ["sleep"] = Contextualize(sleep),
+ ["hunt"] = Contextualize(hunt),
+ ["fight"] = Contextualize(fight),
+ ["social"] = Contextualize(social),
+ ["laugh"] = Contextualize(laugh),
+ ["work"] = Contextualize(work)
+ };
+ voices[id] = new ActivitySpeciesVoice(id, bags);
+ }
+
+ private static void AddLiquid(
+ Dictionary voices,
+ string id,
+ string verb,
+ string first,
+ string second)
+ {
+ if (voices.TryGetValue(id, out ActivitySpeciesVoice voice))
+ {
+ voice.SetLiquidBag(verb, Contextualize(V(first, second)));
+ }
+ }
+
+ private static string[] V(string first, string second)
+ {
+ return new[] { first, second };
+ }
+
+ private static string[] Contextualize(string[] phrases)
+ {
+ if (phrases == null)
+ {
+ return null;
+ }
+
+ var result = new string[phrases.Length];
+ for (int i = 0; i < phrases.Length; i++)
+ {
+ result[i] = (phrases[i] ?? "") + "{place_at}{job_as}";
+ }
+
+ return result;
+ }
+}
diff --git a/IdleSpectator/ActivitySpeciesVoices.Animals.cs b/IdleSpectator/ActivitySpeciesVoices.Animals.cs
new file mode 100644
index 0000000..ef256a1
--- /dev/null
+++ b/IdleSpectator/ActivitySpeciesVoices.Animals.cs
@@ -0,0 +1,445 @@
+using System.Collections.Generic;
+
+namespace IdleSpectator;
+
+public static partial class ActivitySpeciesVoiceCatalog
+{
+ private static void AddAnimalVoices(Dictionary voices)
+ {
+ Add(
+ voices,
+ "chicken",
+ V(
+ "takes high, quick steps with head bobbing",
+ "hurries along with wings tucked tight"),
+ V(
+ "balances on one foot while its comb tilts",
+ "holds still and turns one bright eye"),
+ V(
+ "flutters both wings in a brief burst",
+ "hops and tosses its head with beak raised"),
+ V(
+ "pecks up morsels with brisk jabs",
+ "pins a bite beneath one foot and tears at it"),
+ V(
+ "tucks its beak beneath a folded wing",
+ "settles with feathers puffed around its body"),
+ V(
+ "scratches twice before darting toward {target}",
+ "tilts the head toward {target} and pecks forward"),
+ V(
+ "leaps with both spurs aimed at {target}",
+ "beats both wings before pecking at {target}"),
+ V(
+ "clucks while brushing shoulders with another",
+ "circles close with soft beak taps"),
+ V(
+ "breaks into rapid clucks with beak wide",
+ "shakes its comb through a rattling cackle"),
+ V(
+ "rakes with both feet in alternating strokes",
+ "carries material pinched in its beak"));
+
+ Add(
+ voices,
+ "crab",
+ V(
+ "scuttles sideways on sharply jointed legs",
+ "keeps its shell level through a quick sidestep"),
+ V(
+ "holds both claws ready while its eyestalks pivot",
+ "rests on bent legs with mouthparts ticking"),
+ V(
+ "taps its claws together and spins sideways",
+ "weaves between its own raised pincers"),
+ V(
+ "passes small bites from pincer to mouthparts",
+ "tears a morsel apart between mismatched claws"),
+ V(
+ "folds its legs beneath the rim of its shell",
+ "tucks both pincers close and stills its eyestalks"),
+ V(
+ "probes toward {target} with one open pincer",
+ "advances sideways toward {target} with claws spread"),
+ V(
+ "snaps both pincers at {target}",
+ "raises one heavy claw and sidesteps toward {target}"),
+ V(
+ "waves one claw while its antennae sweep forward",
+ "touches pincers with another in measured taps"),
+ V(
+ "clicks its pincers in a quick clattering run",
+ "chatters its mouthparts beneath raised eyestalks"),
+ V(
+ "sorts loose material between precise pincers",
+ "grips and shifts a load one sidestep at a time"));
+
+ Add(
+ voices,
+ "crocodile",
+ V(
+ "crawls on splayed legs with belly held low",
+ "drives forward while its heavy tail counterbalances"),
+ V(
+ "lies motionless with only its eyes shifting",
+ "holds its long jaw slightly parted"),
+ V(
+ "rolls its armored body and lashes its tail",
+ "makes a short feint with jaws agape"),
+ V(
+ "clamps a meal between interlocking teeth",
+ "tilts its broad head back to gulp a bite"),
+ V(
+ "rests its chin with legs drawn against its flanks",
+ "stills beneath ridged eyelids and folded jaws"),
+ V(
+ "creeps toward {target} with belly pressed low",
+ "freezes before surging toward {target}"),
+ V(
+ "parts long jaws before snapping at {target}",
+ "lashes an armored tail toward {target}"),
+ V(
+ "bumps an armored snout against another",
+ "rumbles low while resting jaw beside jaw"),
+ V(
+ "claps its jaws around a deep throat rumble",
+ "shudders through a run of gravelly bellows"),
+ V(
+ "shoves a load with its plated snout",
+ "drags a load in the hinge of its jaws"));
+
+ Add(
+ voices,
+ "frog",
+ V(
+ "springs forward on long folded hind legs",
+ "bounds with forefeet tucked beneath its chest"),
+ V(
+ "squats still while its round eyes swivel",
+ "holds its crouch as its throat pulses"),
+ V(
+ "pops through a chain of short, crooked hops",
+ "bounces in place with toes spread wide"),
+ V(
+ "flicks its tongue around a morsel",
+ "swallows a bite with a slow double blink"),
+ V(
+ "folds all four legs beneath its body",
+ "lowers its eyelids over still, round eyes"),
+ V(
+ "swivels both eyes toward {target} before springing",
+ "crouches low before flicking a sticky tongue toward {target}"),
+ V(
+ "kicks both hind feet at {target}",
+ "thrusts both splayed forefeet toward {target}"),
+ V(
+ "pulses its throat through a carrying croak",
+ "answers another with quick chirps and toe taps"),
+ V(
+ "croaks in a bouncing, uneven chain",
+ "puffs its throat sac around a chirping trill"),
+ V(
+ "pushes a load with its blunt head",
+ "braces its hind legs and shifts a load forward"));
+
+ Add(
+ voices,
+ "ostrich",
+ V(
+ "covers distance in long, springing strides",
+ "paces forward while its tall neck sways"),
+ V(
+ "stands high with its neck drawn straight",
+ "shifts between long legs while its head scans"),
+ V(
+ "fans its short wings and prances in a circle",
+ "bobs its long neck between skipping steps"),
+ V(
+ "pinches up a morsel with its flat beak",
+ "cranes its neck high to swallow a bite"),
+ V(
+ "folds its long legs beneath a feathered body",
+ "rests with its neck curved across its back"),
+ V(
+ "turns the long neck toward {target}",
+ "rushes toward {target} in lengthening strides"),
+ V(
+ "swings a long leg toward {target}",
+ "drives a two-toed kick at {target}"),
+ V(
+ "weaves its neck beside another's raised neck",
+ "fans loose wing feathers through a measured display"),
+ V(
+ "booms with its neck stretched upright",
+ "clacks its broad beak between hollow calls"),
+ V(
+ "carries material in its beak",
+ "nudges a load forward with its feathered breast"));
+
+ Add(
+ voices,
+ "penguin",
+ V(
+ "waddles in short steps with flippers angled out",
+ "rocks forward from heel to heel"),
+ V(
+ "stands upright with both flippers tucked",
+ "shifts its weight across broad webbed feet"),
+ V(
+ "spins with its stiff flippers spread",
+ "drops to its belly and scoots with both feet"),
+ V(
+ "snaps its narrow beak around a bite",
+ "tilts its head back to swallow a morsel"),
+ V(
+ "stands with its beak tucked beneath one flipper",
+ "rests on its belly with flippers held close"),
+ V(
+ "leans toward {target} with beak aligned",
+ "waddles toward {target} in rapid steps"),
+ V(
+ "slaps both flippers at {target}",
+ "leans forward and pecks at {target}"),
+ V(
+ "taps beaks with another between low calls",
+ "presses shoulder to shoulder in a compact cluster"),
+ V(
+ "brays with its beak lifted high",
+ "shakes its chest through a run of sharp honks"),
+ V(
+ "shoves a load ahead with its chest",
+ "carries material at the tip of its beak"));
+ AddLiquid(
+ voices,
+ "penguin",
+ "move",
+ "cuts through the water with powerful flipper strokes",
+ "torpedoes beneath the surface with feet trailing");
+ AddLiquid(
+ voices,
+ "penguin",
+ "wait",
+ "treads water with small strokes of both feet",
+ "bobs at the surface with flippers spread");
+ AddLiquid(
+ voices,
+ "penguin",
+ "play",
+ "porpoises above the surface with flippers driving",
+ "loops underwater and twists through rising bubbles");
+ AddLiquid(
+ voices,
+ "penguin",
+ "sleep",
+ "rests afloat with its beak tucked low",
+ "dozes while bobbing at the surface");
+
+ Add(
+ voices,
+ "piranha",
+ V(
+ "propels its compact body with quick tail beats",
+ "glides forward with paired fins angled wide"),
+ V(
+ "holds position with its tail twitching",
+ "hangs still while its gill covers pulse"),
+ V(
+ "wheels in a tight circle with fins flared",
+ "darts forward and pivots on a rigid tail flick"),
+ V(
+ "shears off a bite with triangular teeth",
+ "nips rapid mouthfuls with its jaw working"),
+ V(
+ "slows its tail until only its gills move",
+ "hangs nearly motionless with fins folded close"),
+ V(
+ "darts after {target} with gill covers flaring",
+ "darts toward {target} with jaws open"),
+ V(
+ "bites at {target} with interlocking teeth",
+ "whips the tail and snaps toward {target}"),
+ V(
+ "aligns beside another with fins matching pace",
+ "circles another while flicking its paired fins"),
+ V(
+ "clacks its triangular teeth in a rapid chatter",
+ "works its lower jaw through a clicking rattle"),
+ V(
+ "nudges a load with its blunt snout",
+ "grips material between its teeth"));
+ AddLiquid(
+ voices,
+ "piranha",
+ "move",
+ "slices through the water with rapid tail beats",
+ "darts beneath the surface with fins tucked");
+ AddLiquid(
+ voices,
+ "piranha",
+ "wait",
+ "hovers in the water with fins fanning",
+ "faces the current while its tail makes tiny corrections");
+ AddLiquid(
+ voices,
+ "piranha",
+ "play",
+ "zigzags through the water in sudden bursts",
+ "spirals beneath the surface with fins flared");
+ AddLiquid(
+ voices,
+ "piranha",
+ "sleep",
+ "drifts underwater with its tail barely moving",
+ "rests below the surface with gills pulsing");
+
+ Add(
+ voices,
+ "seal",
+ V(
+ "inches forward by bunching its heavy body",
+ "pulls itself along with broad foreflippers"),
+ V(
+ "props its chest up while whiskers quiver",
+ "rests on folded flippers with muzzle raised"),
+ V(
+ "rolls onto its side and bats with both flippers",
+ "arches its body through a clumsy half-turn"),
+ V(
+ "grips a meal between pointed teeth",
+ "tears off a bite with a sharp head shake"),
+ V(
+ "curls with its flippers pressed against its sides",
+ "rests its whiskered muzzle on one foreflipper"),
+ V(
+ "turns a whiskered muzzle toward {target}",
+ "lunges toward {target} with jaws ready"),
+ V(
+ "bares pointed teeth before snapping at {target}",
+ "surges chest-first and bites toward {target}"),
+ V(
+ "touches whiskers with another between low grunts",
+ "leans close and answers another with a short bark"),
+ V(
+ "barks in bouncing, chesty bursts",
+ "huffs through its whiskers between blunt honks"),
+ V(
+ "pushes a load with its broad chest",
+ "grips a load between its teeth and pulls"));
+ AddLiquid(
+ voices,
+ "seal",
+ "move",
+ "swims through the water with sweeping hind-flipper strokes",
+ "dives beneath the surface with foreflippers tucked");
+ AddLiquid(
+ voices,
+ "seal",
+ "wait",
+ "floats upright with its whiskered muzzle above water",
+ "treads water with slow hind-flipper sweeps");
+ AddLiquid(
+ voices,
+ "seal",
+ "play",
+ "rolls underwater with bubbles streaming past its whiskers",
+ "slaps the surface and dives through the splash");
+ AddLiquid(
+ voices,
+ "seal",
+ "sleep",
+ "dozes afloat with its muzzle tipped above water",
+ "rests at the surface with flippers drifting");
+
+ Add(
+ voices,
+ "snake",
+ V(
+ "slithers forward in deep S-shaped bends",
+ "glides by rippling its ribs beneath smooth scales"),
+ V(
+ "coils with its head raised above the loops",
+ "holds still while a forked tongue samples nearby scents"),
+ V(
+ "loops its body around itself in quick turns",
+ "darts its head between shifting coils"),
+ V(
+ "opens its hinged jaw around a meal",
+ "works a bite backward with alternating jaw steps"),
+ V(
+ "coils tightly with its head at the center",
+ "rests its jaw across a loop of scaled body"),
+ V(
+ "flicks a forked tongue toward {target}",
+ "glides toward {target} with head aligned"),
+ V(
+ "strikes with open jaws at {target}",
+ "coils and snaps toward {target}"),
+ V(
+ "rests in parallel coils beside another",
+ "follows another's motion with matched tongue flicks"),
+ V(
+ "hisses in short bursts with jaws parted",
+ "flicks its tongue through a breathy rattle"),
+ V(
+ "loops around a load and pulls",
+ "braces its coils to shift a load"));
+
+ Add(
+ voices,
+ "turtle",
+ V(
+ "plods on clawed feet while its shell rocks",
+ "extends its neck and pulls its shell forward"),
+ V(
+ "withdraws its head halfway beneath the shell rim",
+ "blinks slowly with all four feet braced"),
+ V(
+ "rocks its shell from side to side",
+ "paws forward with alternating forefeet"),
+ V(
+ "clips off a bite with its hard beak",
+ "cranes its neck to reach a morsel"),
+ V(
+ "draws its limbs beneath the shell",
+ "rests its chin just inside the shell rim"),
+ V(
+ "extends the neck toward {target}",
+ "holds the hard beak open while lunging toward {target}"),
+ V(
+ "drives forward shell-first toward {target}",
+ "bites at {target} with a hard beak"),
+ V(
+ "stretches its neck beside another",
+ "taps its shell rim against another's shell"),
+ V(
+ "clicks its beak between throaty chirps",
+ "bobs its head through a rasping chuckle"),
+ V(
+ "shoves a load with the front of its shell",
+ "braces its claws and pushes a load forward"));
+ AddLiquid(
+ voices,
+ "turtle",
+ "move",
+ "paddles through the water with all four feet",
+ "glides beneath the surface with its shell level");
+ AddLiquid(
+ voices,
+ "turtle",
+ "wait",
+ "floats at the surface with its nostrils raised",
+ "holds beneath the water with feet spread wide");
+ AddLiquid(
+ voices,
+ "turtle",
+ "play",
+ "rolls underwater with its shell turning slowly",
+ "paddles in a circle just below the surface");
+ AddLiquid(
+ voices,
+ "turtle",
+ "sleep",
+ "rests underwater with limbs tucked beneath its shell",
+ "dozes at the surface with its chin above water");
+ }
+}
diff --git a/IdleSpectator/ActivitySpeciesVoices.Civs.cs b/IdleSpectator/ActivitySpeciesVoices.Civs.cs
new file mode 100644
index 0000000..262c0e0
--- /dev/null
+++ b/IdleSpectator/ActivitySpeciesVoices.Civs.cs
@@ -0,0 +1,465 @@
+using System.Collections.Generic;
+
+namespace IdleSpectator;
+
+public static partial class ActivitySpeciesVoiceCatalog
+{
+ private static void AddCivVoices(Dictionary voices)
+ {
+ Add(voices, "civ_acid_gentleman",
+ V("paces with back held straight", "advances in an upright stride"),
+ V("holds chin high and shoulders level", "repeats each gesture at the same angle"),
+ V("practices an elaborate bow", "turns with one arm held across the chest"),
+ V("divides the meal into equal portions", "takes one bite between upright pauses"),
+ V("reclines with back still straight", "rests with every limb folded together"),
+ V("stalks {target} in even steps", "closes on {target} behind a rigid guard"),
+ V("slashes at {target} with a caustic hand", "parries {target} with a rigid forearm"),
+ V("offers another a deep bow", "trades clipped greetings with nearby company"),
+ V("lets out a clipped chuckle", "muffles a short laugh behind one hand"),
+ V("measures the task twice", "keeps records in matching groups"));
+
+ Add(voices, "civ_alpaca",
+ V("steps lightly with chin held high", "pads onward in a woolly sway"),
+ V("plants all four feet and listens", "chews slowly with ears turned forward"),
+ V("bounds sideways in sudden hops", "prances through a woolly little dance"),
+ V("pulls mouthfuls from the meal", "chews each bite in a steady rhythm"),
+ V("tucks long legs beneath thick fleece", "settles into an upright doze"),
+ V("tracks {target} with ears pricked", "presses after {target} in springing strides"),
+ V("kicks at {target} with both hind feet", "shoulders {target} behind dense fleece"),
+ V("leans close for a soft-nosed greeting", "hums in low pulses beside nearby company"),
+ V("bursts into a breathy warble", "shakes all over with a muffled chuckle"),
+ V("carries the load", "sorts material between both forefeet"));
+
+ Add(voices, "civ_armadillo",
+ V("scuttles with plated back bobbing", "trots onward in a low quick rhythm"),
+ V("sits back on haunches to listen", "holds still inside a half-curled shell"),
+ V("curls fully and spins", "uncurls and scampers in a tight loop"),
+ V("breaks the meal apart", "snuffles up each bite"),
+ V("curls tight beneath overlapping plates", "sleeps fully enclosed by the shell"),
+ V("sniffs out {target} with lowered snout", "trundles after {target} beneath a lowered shell"),
+ V("rams {target} in a plated roll", "claws at {target} from a crouched guard"),
+ V("taps shells in a brisk greeting", "chitters softly with nearby company"),
+ V("uncurls with a rattling little laugh", "chitters until the back plates shake"),
+ V("reinforces the construction", "hauls a load against one plated shoulder"));
+
+ Add(voices, "civ_bear",
+ V("lumbers forward on heavy paws", "advances with a broad rolling gait"),
+ V("rises tall with head held high", "rests heavily on broad forepaws"),
+ V("rears and sways from paw to paw", "bounds through a spinning charge"),
+ V("scoops the meal with both paws", "licks the meal from broad claws"),
+ V("curls around one heavy forepaw", "drops into a deep rumbling sleep"),
+ V("follows {target} by scent", "surges after {target} with head low"),
+ V("mauls at {target} with sweeping claws", "rears over {target} and crashes down"),
+ V("claps another on the shoulder", "shares a booming greeting with nearby company"),
+ V("roars with belly-deep amusement", "huffs into a rumbling chuckle"),
+ V("lifts the load above the chest", "braces the construction with both forepaws"));
+
+ Add(voices, "civ_beetle",
+ V("clicks onward on lacquered legs", "traces a straight line on quick feet"),
+ V("locks every joint and watches", "turns both feelers in slow arcs"),
+ V("spins on tightly planted legs", "clambers over its own folded limbs"),
+ V("clips the meal apart", "works steadily along each bite"),
+ V("folds all legs beneath a hard wing case", "rests sealed beneath the carapace"),
+ V("feels out {target} with raised antennae", "skitters after {target} in a straight line"),
+ V("drives a horn at {target}", "meets {target} behind a polished carapace"),
+ V("touches antennae in greeting", "joins nearby company with evenly spaced clicks"),
+ V("rattles wing cases through a laugh", "clicks out a quick dry chuckle"),
+ V("sorts material with repeated leg motions", "fits the construction edge to edge"));
+
+ Add(voices, "civ_buffalo",
+ V("plods ahead with horns level", "advances in heavy rolling steps"),
+ V("stands solidly and chews the cud", "waits without shifting a hoof"),
+ V("bucks in a broad turning arc", "charges forward before wheeling back"),
+ V("tears off a mouthful", "grinds the meal with slow jaw strokes"),
+ V("kneels heavily with horns lowered", "sleeps inside a guard of curved horns"),
+ V("closes on {target} at a steady run", "drives after {target} with horns forward"),
+ V("gores at {target} from a planted stance", "buffets {target} with a massive brow"),
+ V("presses foreheads in greeting", "forms a horn-outward circle with nearby company"),
+ V("snorts into a rolling thunderous laugh", "bellows between blunt chuckles"),
+ V("pulls the load", "drives the construction into place"));
+
+ Add(voices, "civ_candy_man",
+ V("skips along on glossy heels", "twirls onward in quick steps"),
+ V("straightens up and waits", "stands with stripes held vertically"),
+ V("spins in quick circles", "hops through a springy little dance"),
+ V("nibbles around the meal", "snaps off one bite"),
+ V("curls into a compact crinkling shape", "rests with glossy limbs folded together"),
+ V("feints toward {target} in a wide turn", "dashes after {target} in zigzag steps"),
+ V("clubs at {target} with a hardened fist", "pelts {target} with rapid hand strikes"),
+ V("greets another with a full turn", "offers quick bows to nearby company"),
+ V("tinkles with a high laugh", "crackles into rapid giggles"),
+ V("groups the work with both hands", "folds the craft in repeated motions"));
+
+ Add(voices, "civ_capybara",
+ V("ambles onward on evenly placed feet", "takes broad slow steps"),
+ V("sits on folded haunches and observes", "rests a blunt chin against the chest"),
+ V("bobs the head in a low rhythm", "rolls slowly from side to side"),
+ V("chews each bite in slow jaw strokes", "holds the meal between both forepaws"),
+ V("sinks into a close compact curl", "dozes with nose tucked to the chest"),
+ V("trails {target} in even steps", "closes on {target} without breaking stride"),
+ V("shoves {target} with a solid shoulder", "snaps at {target} from a low brace"),
+ V("shifts aside for nearby company", "rests flank to flank beside another"),
+ V("chuckles in soft bubbling pulses", "squeaks once before a low laugh"),
+ V("keeps a measured working pace", "moves the load with both forepaws"));
+
+ Add(voices, "civ_cat",
+ V("threads onward on silent paws", "saunters ahead with tail high"),
+ V("sits upright and washes one paw", "watches nearby motion through narrowed eyes"),
+ V("pounces on its own twitching tail", "springs sideways with back arched"),
+ V("takes bites from the meal", "pins each bite beneath one paw"),
+ V("turns three circles before curling tight", "tucks nose beneath one flank"),
+ V("stalks {target} belly-low", "springs after {target} in sudden silence"),
+ V("rakes at {target} with quick claws", "arches against {target} with teeth bared"),
+ V("winds around another in greeting", "offers nearby company a slow blink"),
+ V("chirps into a short laugh", "purrs until the sound breaks into chuckles"),
+ V("sorts material with paw tips", "checks the work from every angle"));
+
+ Add(voices, "civ_chicken",
+ V("struts in quick head-bobbing steps", "hurries onward on scratching feet"),
+ V("stands high and fluffs every feather", "freezes with one eye turned sideways"),
+ V("flaps into a short hopping turn", "hops and pivots on quick feet"),
+ V("pecks the meal repeatedly", "scratches twice before each bite"),
+ V("tucks the beak beneath one wing", "settles into a rounded sleeping crouch"),
+ V("rushes after {target} with wings spread", "tracks {target} in darting circles"),
+ V("spurs at {target} in a flapping leap", "pecks at {target} between sharp sidesteps"),
+ V("clucks over civic news with another", "gathers near company with rapid calls"),
+ V("cackles in a rapid rising burst", "erupts into breathless clucks"),
+ V("sorts material with the beak", "scratches briskly at the task"));
+
+ Add(voices, "civ_cow",
+ V("walks with a broad pendulous sway", "takes steady repeated steps"),
+ V("chews while standing hipshot", "waits without shifting a hoof"),
+ V("trots in a broad uneven circle", "flicks the tail and kicks up both heels"),
+ V("pulls in a mouthful", "chews the meal in repeated cycles"),
+ V("folds down into a heavy resting curl", "rests with muzzle against one flank"),
+ V("follows {target} with steady nostrils", "trots after {target} with lowered head"),
+ V("hooks at {target} with a sweeping horn", "kicks at {target} from a braced stance"),
+ V("greets another with a mellow call", "stands shoulder to shoulder with nearby company"),
+ V("moos into a long wobbling laugh", "shakes the dewlap with deep amusement"),
+ V("draws a steady load", "works the task at an even pace"));
+
+ Add(voices, "civ_crab",
+ V("sidesteps briskly with claws held high", "scuttles onward on quick jointed legs"),
+ V("settles into a low armored crouch", "raises both eyestalks and waits"),
+ V("waves alternating claws in a quick dance", "spins through a tight sideways circle"),
+ V("snips the meal into mouthfuls", "holds each bite between polished pincers"),
+ V("tucks every limb beneath the shell", "rests fully closed inside the shell"),
+ V("circles {target} in tightening sidesteps", "scuttles after {target} with claws open"),
+ V("pinches at {target} with one heavy claw", "hammers {target} behind a raised shell"),
+ V("waves a claw in brisk greeting", "trades measured taps with nearby company"),
+ V("clacks both claws in choppy amusement", "bubbles out a sideways little laugh"),
+ V("cuts material with paired claws", "carries the load in paired claws"));
+
+ Add(voices, "civ_crocodile",
+ V("prowls low with tail sweeping behind", "slides onward on splayed feet"),
+ V("lies motionless with jaws slightly open", "waits with one eye open"),
+ V("spins around its sweeping tail", "snaps between quick turns"),
+ V("gulps down a bite", "pins the meal before tearing it apart"),
+ V("sinks flat into a resting pose", "sleeps with one eye barely closed"),
+ V("creeps toward {target} in complete stillness", "bursts after {target} from a dead stop"),
+ V("clamps down on {target} with crushing jaws", "whips the tail across {target}"),
+ V("rumbles a low greeting to another", "rests flank to flank with nearby company"),
+ V("chuffs through a deep toothy laugh", "rattles the throat through low chuckles"),
+ V("drags the load", "guards the work without moving"));
+
+ Add(voices, "civ_dog",
+ V("trots ahead with tail swinging", "bounds onward with ears streaming"),
+ V("sits with ears trained forward", "waits with paws tapping"),
+ V("bows low before springing in a circle", "chases its own tail in bounding loops"),
+ V("devours the meal in rapid bites", "holds a bite between both forepaws"),
+ V("circles three times and drops with a sigh", "sleeps with paws twitching"),
+ V("tracks {target} nose-first", "races after {target} with ears streaming"),
+ V("bites at {target} and holds fast", "surges into {target} with one shoulder"),
+ V("greets another with repeated sniffs", "leans one flank against nearby company"),
+ V("pants into a barking laugh", "yips through repeated chuckles"),
+ V("carries the load at a trot", "watches the work with ears forward"));
+
+ Add(voices, "civ_fox",
+ V("slips forward on black paws", "weaves onward in a crossing line"),
+ V("wraps the tail once around the feet", "listens with ears angled apart"),
+ V("pounces on its own sweeping tail", "feints one way before springing another"),
+ V("nibbles around the meal first", "takes one bite at a time"),
+ V("curls nose-deep into a sweeping tail", "folds into a narrow sleeping curl"),
+ V("trails {target} with angled steps", "cuts ahead of {target} with a sudden dash"),
+ V("feints past {target} and snaps back", "slashes at {target} from an oblique angle"),
+ V("trades low whispers with another", "bows to nearby company with ears raised"),
+ V("barks out a quick laugh", "masks a short chuckle with the tail tip"),
+ V("keeps records in narrow groups", "checks each part of the work"));
+
+ Add(voices, "civ_frog",
+ V("hops forward in compact arcs", "springs onward in quick bounds"),
+ V("squats with throat pulsing slowly", "holds still and blinks"),
+ V("leapfrogs over its own folded legs", "bounces through a springy little turn"),
+ V("flicks each bite straight into the mouth", "swallows a bite with a round-eyed blink"),
+ V("folds into a tiny crouched form", "dozes with chin resting on both hands"),
+ V("bounds after {target} in long leaps", "waits before springing toward {target}"),
+ V("kicks at {target} with both feet", "lashes at {target} with a whipping tongue"),
+ V("croaks a greeting to another", "joins nearby company in rhythmic calls"),
+ V("ribbits into a bubbling laugh", "inflates the throat with booming amusement"),
+ V("reaches upper parts of the construction", "presses the work with both hands"));
+
+ Add(voices, "civ_garlic_man",
+ V("marches with papery layers rustling", "bobs onward on knobby feet"),
+ V("smooths one loose outer layer", "stands bundled beneath folded layers"),
+ V("spins with papery arms spread", "bobs from side to side in a rustling dance"),
+ V("pulls apart the meal segment by segment", "crunches one bite at a time"),
+ V("folds into its own dry layers", "gathers every loose layer before sleeping"),
+ V("follows {target} by the faintest scent", "rolls after {target} in a papery rush"),
+ V("buffets {target} with a knotted head", "strikes at {target} with both hands"),
+ V("greets another with a full-body bob", "rustles beside nearby company"),
+ V("rustles into a crackling laugh", "splits into quick giggles"),
+ V("groups material between both hands", "layers the craft with knobby hands"));
+
+ Add(voices, "civ_goat",
+ V("clatters onward on nimble split hooves", "picks a quick high-stepping route"),
+ V("balances on four feet while waiting", "chews with one ear cocked"),
+ V("leaps high simply to twist around", "bounds sideways on springy legs"),
+ V("tears off a mouthful", "samples every part of the meal"),
+ V("kneels into a compact sleeping curl", "sleeps with legs tucked tightly"),
+ V("scrambles after {target} without slowing", "charges {target} with chin tucked"),
+ V("butts {target} with curled horns", "kicks at {target} from a sudden pivot"),
+ V("nuzzles another in brisk greeting", "joins nearby company with noisy bleats"),
+ V("bleats out a brash hiccupping laugh", "snorts between sharp little chuckles"),
+ V("works the task from a high stance", "clears material with quick bites"));
+
+ Add(voices, "civ_hyena",
+ V("lope-walks with sloping shoulders", "circles onward without slowing"),
+ V("sits crookedly with jaws parted", "sniffs with head turning side to side"),
+ V("bounds in looping bursts", "spins with jaws open and shoulders lowered"),
+ V("cracks through one bite first", "guards the meal between broad jaws"),
+ V("drops into a loose sleeping sprawl", "sleeps with one ear twitching"),
+ V("runs {target} down in repeated stages", "fans wide before closing on {target}"),
+ V("crushes at {target} with heavy jaws", "harrows {target} with darting bites"),
+ V("shoulder-bumps another in greeting", "calls nearby company into a close group"),
+ V("whoops into a cascading laugh", "cackles until the flanks heave"),
+ V("breaks material into separate parts", "works the task with repeated jaw motions"));
+
+ Add(voices, "civ_lemon_man",
+ V("zips along with a springy step", "rolls onward before popping upright"),
+ V("polishes the rind with one hand", "waits with arms folded over a pointed chest"),
+ V("bounces lightly on rounded feet", "spins on one pointed toe"),
+ V("takes a bite and puckers", "presses the meal before another bite"),
+ V("rests with limbs tucked against the rind", "curls around the pointed crown"),
+ V("pursues {target} in quick cutting turns", "rolls after {target} at sudden speed"),
+ V("sprays a stinging burst at {target}", "jabs at {target} with the pointed crown"),
+ V("greets another with a brisk nod", "pivots toward each nearby speaker"),
+ V("squeaks into a sharp fizzy laugh", "puckers before bursting into giggles"),
+ V("presses the material with both palms", "groups the work into even sections"));
+
+ Add(voices, "civ_liliar",
+ V("glides with layered frills whispering", "drifts onward in measured steps"),
+ V("folds slender hands beneath the chin", "stands statuesque with head raised"),
+ V("weaves layered arms through a slow turn", "unfurls through a spinning dance"),
+ V("takes one bite between slender fingers", "eats the meal in equal morsels"),
+ V("closes layered frills around the body", "rests upright with hands folded together"),
+ V("tracks {target} without blinking", "sweeps toward {target} along a curved path"),
+ V("lashes at {target} with a supple stem", "cuts at {target} with sharpened leaves"),
+ V("exchanges a sweeping bow with another", "turns nearby company into a facing circle"),
+ V("chimes with a light silvery laugh", "hides a trilling chuckle behind slim fingers"),
+ V("braids material into the craft", "sets the construction with both hands"));
+
+ Add(voices, "civ_monkey",
+ V("scampers ahead with arms swinging low", "bounds onward in long-armed strides"),
+ V("squats and picks through its fur", "hangs by the tail while watching"),
+ V("swings between its own planted hands", "somersaults with the tail curled high"),
+ V("takes the meal in quick bites", "stuffs both cheeks before chewing"),
+ V("curls up with tail wound around the body", "sleeps with both hands beneath the head"),
+ V("swings toward {target} before dropping close", "scrambles after {target} in quick bounds"),
+ V("clubs at {target} with heavy fists", "grapples {target} with hands and tail"),
+ V("grooms another during rapid chatter", "waves both hands at nearby company"),
+ V("chatters into a rolling shriek of laughter", "slaps both knees and howls"),
+ V("handles several parts of the task", "reaches upper parts of the construction"));
+
+ Add(voices, "civ_penguin",
+ V("waddles forward with flippers held out", "slides onward on a rounded belly"),
+ V("rocks from heel to heel", "stands in a close inward-facing huddle"),
+ V("spins belly-down in a tight circle", "bobs through a brisk flipper dance"),
+ V("tips back a morsel", "pecks the meal apart with a narrow bill"),
+ V("tucks the bill beneath one flipper", "sleeps upright with flippers folded"),
+ V("slides after {target} in bounding lunges", "waddles toward {target} in short strides"),
+ V("jabs at {target} with a stiff beak", "flipper-slaps {target} from a planted stance"),
+ V("bows stiffly to another", "huddles shoulder to shoulder with nearby company"),
+ V("honks into a stuttering laugh", "wobbles through short chuckles"),
+ V("slides the load with steady force", "checks each part of the task in order"));
+
+ Add(voices, "civ_piranha",
+ V("darts onward in brisk fin-led bursts", "weaves ahead with snapping turns"),
+ V("hovers with fins making tiny corrections", "holds position with jaws slightly open"),
+ V("loops around its own flicking tail", "snaps between quick turns"),
+ V("shears a bite from the meal", "tears each morsel into bites"),
+ V("settles with fins tucked close", "rests almost motionless"),
+ V("homes in on {target} with sudden speed", "circles {target} before rushing close"),
+ V("tears at {target} with flashing teeth", "darts past {target} in repeated bites"),
+ V("clicks teeth in brisk greeting", "moves tightly with nearby company"),
+ V("chatters into a sharp staccato laugh", "snaps the jaws between quick giggles"),
+ V("trims material in rapid cuts", "sorts the task into short stages"));
+
+ Add(voices, "civ_rabbit",
+ V("hops along with ears streaming back", "bounds onward in quick doubles"),
+ V("sits tall and rotates both ears", "twitches the nose while waiting"),
+ V("leaps high and twists in midair", "boxes with alternating forepaws"),
+ V("nibbles the meal into scalloped edges", "holds a bite between both paws"),
+ V("folds long ears over the shoulders", "rests in a compact sleeping crouch"),
+ V("zigzags after {target} in rapid bounds", "tracks {target} with ears pitched forward"),
+ V("kicks at {target} with both hind feet", "boxes at {target} with flashing forepaws"),
+ V("touches noses twice in greeting", "shares rapid civic news with another"),
+ V("snickers through a trembling nose", "thumps once before breaking into laughter"),
+ V("carries the load in quick hops", "weaves material with both forepaws"));
+
+ Add(voices, "civ_rat",
+ V("scurries onward with whiskers forward", "threads ahead in low quick steps"),
+ V("sits back and combs both whiskers", "waits with nose twitching rapidly"),
+ V("rolls between its own nimble paws", "spins and darts back with a squeak"),
+ V("gnaws the meal repeatedly", "cups a bite between both paws"),
+ V("curls tightly around the tail", "sleeps with forepaws tucked beneath the chin"),
+ V("sniffs a winding trail toward {target}", "cuts ahead of {target} with a quick dash"),
+ V("bites at {target} from below", "swipes at {target} with sharp claws"),
+ V("trades whispered civic news with another", "inclines its whiskers toward nearby company"),
+ V("squeaks into a high laugh", "wrings both paws through shrill giggles"),
+ V("sorts material by size", "checks each part of the task"));
+
+ Add(voices, "civ_rhino",
+ V("stomps onward behind a lowered horn", "pushes ahead in broad heavy steps"),
+ V("stands immovable with ears raised", "rests thick chin against the chest"),
+ V("bucks through a heavy turning charge", "tosses the head and stamps both feet"),
+ V("grinds through the meal", "scoops up bites with the lower lip"),
+ V("drops heavily into a resting crouch", "sleeps with the horn angled clear"),
+ V("charges straight at {target}", "pounds after {target} in heavy steps"),
+ V("drives the horn at {target}", "tramples toward {target} behind thick hide"),
+ V("bumps shoulders twice in greeting", "stands solidly beside nearby company"),
+ V("bellows into a rough booming laugh", "snorts until the heavy sides shake"),
+ V("breaks material apart", "pushes the construction into place"));
+
+ Add(voices, "civ_scorpion",
+ V("skitters forward beneath an arched tail", "advances on eight jointed feet"),
+ V("holds both pincers open and still", "waits with the stinger raised"),
+ V("waves both claws in alternating rhythm", "spins beneath a curling tail"),
+ V("snips the meal into paired portions", "draws each bite between small mouthparts"),
+ V("folds pincers beneath the plated body", "rests with the tail curled over the back"),
+ V("angles both pincers toward {target}", "skitters after {target} with tail cocked"),
+ V("stings at {target} over a raised guard", "seizes {target} between heavy pincers"),
+ V("crosses claws in a salute", "holds a fixed distance from nearby company"),
+ V("clicks both pincers in dry amusement", "trembles the tail through a rasping laugh"),
+ V("handles material between both pincers", "cuts and grips each part of the task"));
+
+ Add(voices, "civ_seal",
+ V("galumphs ahead on broad flippers", "slides onward with whiskers streaming"),
+ V("props upright and balances on the belly", "rests whiskered chin against the chest"),
+ V("spins in a rolling turn", "claps flippers after a full-body flop"),
+ V("swallows a morsel whole", "pins the meal beneath one flipper"),
+ V("flops into a loose sleeping curl", "dozes with nose resting on the tail"),
+ V("slides after {target} in bounding lunges", "tracks {target} with twitching whiskers"),
+ V("body-slams {target} from a rolling start", "bites at {target} behind raised flippers"),
+ V("claps a loud welcome to another", "leans into nearby company whisker-first"),
+ V("barks into a bouncing breathless laugh", "claps through a series of honking chuckles"),
+ V("moves the load with both flippers", "smooths the work with both flippers"));
+
+ Add(voices, "civ_sheep",
+ V("trots in a soft close-stepping rhythm", "follows a steady path with fleece bobbing"),
+ V("stands with fleece softly settling", "chews with drooping ears"),
+ V("bounds high on springy legs", "pushes nose-first into a woolly tumble"),
+ V("crops the meal in even bites", "chews beneath a woolly fringe"),
+ V("folds into a deep woolly curl", "sleeps with muzzle against one flank"),
+ V("follows {target} along a steady line", "rushes after {target} with head lowered"),
+ V("rams {target} with a thick brow", "kicks at {target} behind a woolly guard"),
+ V("gathers close for low civic chatter", "greets nearby company with soft bleats"),
+ V("bleats into a soft wavering laugh", "shivers the fleece with low chuckles"),
+ V("sorts and groups material", "moves the load in even motions"));
+
+ Add(voices, "civ_snake",
+ V("glides forward in polished curves", "threads onward without a sound"),
+ V("coils tightly and watches", "raises the head with tongue flicking slowly"),
+ V("loops around its own rising coils", "sways through a winding rhythm"),
+ V("works the meal down in repeated gulps", "unhinges the jaw around a bite"),
+ V("knots into a compact sleeping coil", "rests beneath overlapping loops"),
+ V("tastes the trail toward {target}", "flows after {target} without a sound"),
+ V("strikes at {target} from a tight coil", "wraps {target} in tightening loops"),
+ V("loops into a low bow", "shares low whispers with another"),
+ V("hisses into a winding laugh", "rattles the tail through short chuckles"),
+ V("threads material in repeated loops", "winds material into the craft"));
+
+ Add(voices, "civ_turtle",
+ V("plods onward beneath a ridged shell", "takes each step at a measured pace"),
+ V("draws halfway into the shell and waits", "rests chin on the shell rim"),
+ V("rocks carefully from side to side", "spins slowly on the curved shell"),
+ V("clips off a bite with a beaked mouth", "chews the meal in slow jaw strokes"),
+ V("withdraws every limb beneath the shell", "settles into a low resting pose"),
+ V("follows {target} without changing pace", "intercepts {target} along a direct line"),
+ V("snaps at {target} from behind the shell", "drives the shell edge into {target}"),
+ V("nods through a long greeting", "shares quiet company without crowding another"),
+ V("wheezes into a slow creaking laugh", "bobs the head through quiet chuckles"),
+ V("checks each construction joint", "carries a load beneath the shell"));
+
+ Add(voices, "civ_unicorn",
+ V("canters with mane streaming", "prances onward on lifted hooves"),
+ V("stands with the horn lifted", "smooths the mane with repeated turns"),
+ V("dances an even stepping pattern", "tosses the mane through a high rear"),
+ V("selects one bite at a time", "takes morsels from the meal"),
+ V("folds long legs beneath the body", "rests with the horn angled high"),
+ V("gallops after {target} in a direct line", "closes on {target} with horn lowered"),
+ V("thrusts the horn at {target}", "strikes at {target} with raised hooves"),
+ V("offers another a deep bow", "leads nearby company in high steps"),
+ V("trills into a clear ringing laugh", "tosses the mane through short chuckles"),
+ V("engraves the craft with the horn tip", "draws a load with high steps"));
+
+ Add(voices, "civ_wolf",
+ V("trots forward with shoulders rolling", "ranges onward without slowing"),
+ V("sits with tail wrapped close", "tests every nearby scent"),
+ V("bows low before bounding sideways", "chases its own tail in a quick circle"),
+ V("tears off a bite", "holds the meal beneath one paw"),
+ V("turns into a tight sleeping circle", "rests with muzzle tucked to one flank"),
+ V("courses after {target} along the flank", "drives {target} toward a tighter line"),
+ V("slashes at {target} with repeated bites", "knocks {target} down from the side"),
+ V("touches muzzles in a close greeting", "raises a gathering call to nearby company"),
+ V("yips into a rough rising laugh", "howls between long chuckles"),
+ V("patrols around the work", "moves the load in close formation"));
+
+ Add(voices, "dwarf",
+ V("strides on short powerful legs", "marches onward with beard tucked close"),
+ V("plants both feet and folds thick arms", "waits with chin resting on one fist"),
+ V("stamps through a compact turning dance", "drops into a deep braced crouch"),
+ V("cuts an even bite and chews", "wipes the meal from a braided mustache"),
+ V("drops into a stout sleeping curl", "sleeps beneath a beard spread wide"),
+ V("tracks {target} with eyes fixed forward", "closes on {target} behind a low guard"),
+ V("hammers at {target} with heavy fists", "hooks {target} with a thick forearm"),
+ V("clasps forearms with another", "trades craft boasts with nearby company"),
+ V("booms with a body-shaking laugh", "snorts through a bristling beard"),
+ V("aligns the construction", "drives the craft in a steady rhythm"));
+
+ Add(voices, "elf",
+ V("walks with a light even stride", "passes onward in near-silent steps"),
+ V("stands listening with head slightly bowed", "rests long fingers against one palm"),
+ V("balances through a turning step", "springs lightly from foot to foot"),
+ V("divides the meal into equal portions", "takes bites with long fingers"),
+ V("reclines with limbs folded together", "sleeps with one hand lightly raised"),
+ V("follows {target} with soundless steps", "draws a direct line toward {target}"),
+ V("strikes at {target} with a swift hand", "cuts at {target} with a slender hand"),
+ V("exchanges brief greetings with another", "shares civic lore with nearby company"),
+ V("laughs in a clear ascending ripple", "muffles a short chuckle with long fingers"),
+ V("shapes the craft with fingertips", "keeps records of the work"));
+
+ Add(voices, "human",
+ V("hurries onward with arms swinging", "threads ahead in alternating steps"),
+ V("shifts weight with eyes forward", "pauses to catch a breath"),
+ V("hops through a quick uneven rhythm", "turns a small stumble into a low bow"),
+ V("divides the meal before taking a bite", "alternates bites with short pauses"),
+ V("curls with knees drawn to the chest", "rests with hands folded over the chest"),
+ V("follows {target} through repeated turns", "runs down {target} with eyes fixed forward"),
+ V("strikes at {target} with ready hands", "holds firm against {target} from a balanced stance"),
+ V("trades names and civic news with another", "waves nearby company into the conversation"),
+ V("breaks into an open laugh", "slaps one knee through a broad grin"),
+ V("checks the record", "moves briskly from task to task"));
+
+ Add(voices, "orc",
+ V("stalks forward in long heavy strides", "shoulders onward with tusks high"),
+ V("squats with fists resting on the thighs", "stands with fists held at the sides"),
+ V("wrestles against its own braced strength", "stomps through a rough turning dance"),
+ V("tears the meal apart with both hands", "crunches through each bite"),
+ V("drops into a heavy sleeping sprawl", "sleeps with one fist tightly closed"),
+ V("runs {target} down with pounding steps", "closes on {target} behind raised forearms"),
+ V("chops at {target} with repeated hand strikes", "headbutts {target} between heavy blows"),
+ V("greets another with a forearm clash", "swaps loud boasts with nearby company"),
+ V("barks out a harsh booming laugh", "bares both tusks in rumbling amusement"),
+ V("heaves material into place", "drives the construction with both arms"));
+ }
+}
diff --git a/IdleSpectator/ActivitySpeciesVoices.Fantasy.cs b/IdleSpectator/ActivitySpeciesVoices.Fantasy.cs
new file mode 100644
index 0000000..611e0cb
--- /dev/null
+++ b/IdleSpectator/ActivitySpeciesVoices.Fantasy.cs
@@ -0,0 +1,441 @@
+using System.Collections.Generic;
+
+namespace IdleSpectator;
+
+public static partial class ActivitySpeciesVoiceCatalog
+{
+ private static void AddFantasyVoices(Dictionary voices)
+ {
+ Add(voices, "UFO",
+ V("glides in a soundless level line", "banks on a seamless silver rim"),
+ V("hovers without sway", "holds its lights in a fixed pattern"),
+ V("spins its rim in a bright calibration", "winks its lower lights in sequence"),
+ V("draws energy into its hull", "sips a thin charge through its underside"),
+ V("dims every light and hangs inert", "settles into a low-power hover"),
+ V("hunts {target} with a narrow scanning beam", "hunts {target} by pulsing its sensor ring"),
+ V("attacks {target} with a precise lower ray", "attacks {target} with a hot lance"),
+ V("answers nearby signals with colored flashes", "circles once while broadcasting soft tones"),
+ V("trills in quick electronic notes", "warbles through a rising chord"),
+ V("maps its task with scanning light", "aligns its panels over a precision task"));
+
+ Add(voices, "alien",
+ V("bounds forward on springing legs", "scuttles sideways with its torso upright"),
+ V("crouches low while its eyes keep moving", "folds its long limbs into a compact perch"),
+ V("taps both hands in a rapid rhythm", "braids its narrow fingers in quick patterns"),
+ V("nibbles a meal in tiny bites", "draws energy through its clicking mouthparts"),
+ V("tucks its head between raised shoulders", "curls its limbs around its chest"),
+ V("hunts {target} with clicking mouthparts", "hunts {target} with darting eyes"),
+ V("attacks {target} with a hooked wrist", "attacks {target} from springing legs"),
+ V("touches fingertips and trades soft clicks", "mirrors another's posture in greeting"),
+ V("chatters in bubbling bursts", "chirps until its throat sac trembles"),
+ V("sorts material by shape with four quick hands", "fits task material with narrow fingers"));
+
+ Add(voices, "angle",
+ V("slides point-first along a rigid path", "turns through a perfectly sharp corner"),
+ V("balances on one luminous vertex", "hangs motionless between intersecting lines"),
+ V("folds and unfolds into crisp new shapes", "traces a tiny polygon with luminous edges"),
+ V("absorbs energy through its luminous edges", "draws energy into its center"),
+ V("flattens into a dim, closed figure", "rests with every edge neatly aligned"),
+ V("hunts {target} with straight geometric feelers", "hunts {target} from a rotating vertex"),
+ V("attacks {target} with a razor-straight edge", "attacks {target} by closing its angles"),
+ V("meets another outline edge to edge", "signals by changing the count of its corners"),
+ V("chimes with laughter from every taut edge", "chimes a laugh in two exact intervals"),
+ V("scores precise boundaries across task material", "squares a precision task one line at a time"));
+
+ Add(voices, "assimilator",
+ V("pulls itself forward on braided tendrils", "rolls its shifting bulk in measured surges"),
+ V("anchors several feelers and studies nearby motion", "compresses into a listening mound"),
+ V("cycles through borrowed gestures with imperfect timing", "forms and dissolves little grasping hands"),
+ V("wraps a meal in a clear membrane", "draws energy beneath its rippling surface"),
+ V("seals itself inside a toughened outer skin", "slows its inner currents to a faint pulse"),
+ V("hunts {target} with tasting filaments", "hunts {target} through borrowed noses"),
+ V("attacks {target} with a hardened wedge-limb", "attacks {target} by splitting and snapping shut"),
+ V("echoes nearby calls in layered voices", "offers a borrowed face that melts into another"),
+ V("bursts into a chorus of mismatched chuckles", "repeats one bright laugh from several mouths"),
+ V("weaves task material into useful shapes", "repairs its own surface with matched fibers"));
+
+ Add(voices, "civ_crystal_golem",
+ V("strides with heavy faceted steps", "advances as inner light shifts between joints"),
+ V("plants both feet and becomes statue-still", "rests broad hands against its angular knees"),
+ V("refracts its inner light between its palms", "makes colored glints dance across its facets"),
+ V("draws energy through glowing seams", "feeds energy into its core"),
+ V("dims its core behind crossed stone arms", "locks each joint and enters a glassy stillness"),
+ V("hunts {target} by reading vibrations through one fist", "hunts {target} with a prism-bright gaze"),
+ V("attacks {target} with a massive crystalline forearm", "attacks {target} with a faceted shoulder"),
+ V("flashes slow colors through its chest", "touches knuckles and shares a resonant hum"),
+ V("booms with a core-deep resonance", "tinkles from every edge in a bright cascade"),
+ V("sets a heavy load with unerring weight", "cuts hard material along clean shining planes"));
+
+ Add(voices, "cold_one",
+ V("stalks on stiff frost-rimmed limbs", "skates forward without lifting its feet"),
+ V("stands rigid while cold vapor leaks between its teeth", "hunches beneath a mantle of hard rime"),
+ V("carves tiny spirals into its own rime", "grows and snaps an icicle from one fingertip"),
+ V("crushes a meal between pale jaws", "drinks energy through a whitening palm"),
+ V("folds into a compact frost-crusted crouch", "goes still beneath a shell of opaque ice"),
+ V("hunts {target} by feeling warmth through spread fingers", "hunts {target} with a heat-seeking stare"),
+ V("attacks {target} with frost-rimmed claws", "attacks {target} with a numbing palm"),
+ V("greets with a breath of glittering vapor", "clicks frozen teeth in a measured exchange"),
+ V("crackles like ice under strain", "gives a thin laugh of chiming shards"),
+ V("freezes task material into a firm join", "shapes task material with repeated icy claw strokes"));
+
+ Add(voices, "crabzilla",
+ V("thunders sideways on towering jointed legs", "hauls its armored bulk ahead with earthshaking steps"),
+ V("settles its vast shell onto folded limbs", "holds both colossal pincers beneath its shell"),
+ V("clacks its pincers in a booming rhythm", "crosses colossal pincers in a measured display"),
+ V("pulverizes a meal between grinding plates", "scoops a meal toward hidden mouthparts"),
+ V("tucks every limb beneath its fortress shell", "rests with eye stalks drawn into armored sockets"),
+ V("hunts {target} with sweeping eye stalks", "hunts {target} through vibrations in its leg tips"),
+ V("attacks {target} with a colossal pincer", "attacks {target} with an immense plated claw"),
+ V("raises both pincers toward another", "clacks a greeting for nearby company"),
+ V("rattles its shell with a booming clatter", "clacks its mouthparts in rolling bursts"),
+ V("levels task material with measured pincer taps", "packs material flat beneath a broad claw"));
+
+ Add(voices, "crystal_sword",
+ V("floats point-first in a gleaming rush", "sweeps ahead with its hilt held level"),
+ V("hangs upright with its point barely lowered", "rests suspended inside a faint prismatic shimmer"),
+ V("twirls through a rapid sequence of cuts", "bounces colored light from edge to edge"),
+ V("draws energy along its fuller", "absorbs energy through its jewel-set guard"),
+ V("sinks point-down into a dormant hover", "dims its facets and stills every tremor"),
+ V("hunts {target} with its edge turned forward", "hunts {target} with its testing point"),
+ V("attacks {target} in a clean cutting arc", "attacks {target} with its ringing flat"),
+ V("salutes by lifting its hilt", "crosses guards and trades a clear ringing note"),
+ V("sings with a high crystalline peal", "rings out in a bright descending cadence"),
+ V("trims task material with exact strokes", "scores material using its shining point"));
+
+ Add(voices, "demon",
+ V("strides on smoking hooves", "lunges ahead with wings drawn tight"),
+ V("squats with claws curled beneath its body", "folds leathery wings around a red-hot core"),
+ V("flicks its own embers between hooked claws", "snaps its tail through its own sparks"),
+ V("tears a meal with blackened fangs", "gulps energy until its throat glows"),
+ V("hangs upside down behind wrapped wings", "dozes in a crouch as smoke curls from its nostrils"),
+ V("hunts {target} through heat-sensing nostrils", "hunts {target} with horns lowered"),
+ V("attacks {target} with a burning claw", "attacks {target} with its barbed tail"),
+ V("butts horns with another in greeting", "shares a red-hot snarl with nearby company"),
+ V("cackles in a cracked furnace roar", "barks a burst of scorching laughter"),
+ V("brands task material with clawed sigils", "heats task material with beating wings"));
+
+ Add(voices, "dragon",
+ V("surges forward on vast beating wings", "pads ahead with its plated tail held clear"),
+ V("coils around its foreclaws and watches", "perches high on folded haunches"),
+ V("chases its own tail with thunderous wingbeats", "snaps twice at its own wingtip"),
+ V("rips a meal with serrated teeth", "swallows a meal in one gulp"),
+ V("curls beneath the shelter of one broad wing", "rests its horned head across crossed forelegs"),
+ V("hunts {target} through flared nostrils", "hunts {target} with unblinking eyes"),
+ V("attacks {target} with both hind talons", "attacks {target} with a narrow torrent"),
+ V("touches brow horns in greeting", "rumbles low while fanning one wing"),
+ V("laughs in rolling thunder from its chest", "chuffs bright sparks between rolling growls"),
+ V("moves weighty loads in closed talons", "fuses material with a measured breath"));
+
+ Add(voices, "druid",
+ V("walks with a crooked staff marking each step", "paces lightly beneath a leaf-woven cloak"),
+ V("leans on the staff and listens", "kneels with both palms pressed down"),
+ V("braids the cloak's leaf tips around the staff", "coaxes the staff's living grain into curls"),
+ V("chews a meal in slow bites", "draws energy through measured mouthfuls"),
+ V("rests beneath the hood with hands in sleeves", "curls around the crooked staff"),
+ V("hunts {target} through the staff's sensing grain", "hunts {target} with searching root-magic"),
+ V("attacks {target} with thorny growth", "attacks {target} with grasping vines"),
+ V("bows the leaf-woven hood toward another", "greets nearby company with a rustling cloak"),
+ V("chuckles through a rustling hood", "laughs softly as the leaf-woven cloak flutters"),
+ V("binds task material with fresh fibers", "guides material into an orderly weave"));
+
+ Add(voices, "evil_mage",
+ V("sweeps forward behind a trailing black robe", "glides with a crooked staff held before the face"),
+ V("stands inside a ring of hovering runes", "hunches over the staff while shadows gather close"),
+ V("makes its dark sparks chase one another", "shuffles glowing symbols between its hands"),
+ V("draws energy through clenched teeth", "draws energy through the crooked staff"),
+ V("reclines in a shell of folded shadow", "dozes upright while the staff keeps watch"),
+ V("hunts {target} through a circling violet eye", "hunts {target} with crawling symbols"),
+ V("attacks {target} with crackling violet force", "attacks {target} with the hooked staff"),
+ V("bows with the face hidden by the hood", "trades layered whispers over clasped sleeves"),
+ V("snickers behind one raised hand", "laughs in a dry, echoing rasp"),
+ V("inks task material with a smoking fingertip", "distills murky energy for a waiting task"));
+
+ Add(voices, "fairy",
+ V("zips ahead on shimmering wings", "darts in a loop while its own glow pulses"),
+ V("hovers with toes tucked beneath a tiny skirt", "perches lightly with wings folded upright"),
+ V("blows a ring of its own sparkling motes", "loops through its own shimmer in a quick dance"),
+ V("sips a meal in tiny mouthfuls", "nibbles a meal held in both hands"),
+ V("folds bright wings around a curled body", "dozes while suspended in a soft glow"),
+ V("hunts {target} in widening flight circles", "hunts {target} with shimmering wingbeats"),
+ V("attacks {target} with a needle-thin flash", "attacks {target} with its own sparkling motes"),
+ V("touches wingtips with another in greeting", "trades spiraling ribbons of its own colored light"),
+ V("giggles in tiny bell-like bursts", "trills a laugh in quick pulses"),
+ V("ties task material into delicate loops", "polishes material with its own sparkling motes"));
+
+ Add(voices, "fire_elemental",
+ V("strides as a column of folding flame", "streams forward in licking orange ribbons"),
+ V("gathers into a steady upright blaze", "burns low around a white-hot center"),
+ V("flicks sparks into spinning hoops", "splits off a flame that chases its hands"),
+ V("devours fuel in a sudden flare", "draws energy inward until its core whitens"),
+ V("banks its body into a bed of embers", "closes around its core in a dim red shell"),
+ V("hunts {target} by reaching with thin flames", "hunts {target} through trembling tongues"),
+ V("attacks {target} with a roaring arm of fire", "attacks {target} in a ring of searing force"),
+ V("mingles its sparks with another in greeting", "bows to nearby company beneath a bright crown"),
+ V("roars with the pop of burning fuel", "crackles in a rising cascade"),
+ V("heats material to a visible glow", "seals a join with one white-hot fingertip"));
+
+ Add(voices, "fire_elemental_blob",
+ V("wobbles forward in molten hops", "oozes ahead while flames lick from its crown"),
+ V("puddles into a quivering orange mound", "holds still as bubbles roll beneath its skin"),
+ V("pops little fire bubbles from its surface", "splits into two wobbling lobes and rejoins"),
+ V("rolls fuel into its glowing body", "melts a meal beneath a drooping fold"),
+ V("flattens into a dim ember-shape", "forms a cooling crust around its liquid center"),
+ V("hunts {target} with a glowing pseudopod", "hunts {target} with a forked lick of flame"),
+ V("attacks {target} with a clinging heat-glob", "attacks {target} by expanding its fiery body"),
+ V("bumps warm sides with another", "waves a small flaming lobe toward nearby company"),
+ V("gurgles through a popping laugh", "burps a chain of bright crackles"),
+ V("fills task seams with molten material", "presses its soft heat around a task join"));
+
+ Add(voices, "fire_elemental_horse",
+ V("gallops on hooves of compact flame", "canters with a blazing mane streaming backward"),
+ V("stands with one ember-bright hoof cocked", "lowers its head as the mane settles to embers"),
+ V("prances while its own hooves pulse brightly", "tosses its mane in a shower of its own sparks"),
+ V("crops fuel with glowing teeth", "draws energy through flared nostrils"),
+ V("folds its legs beneath a banked fiery body", "rests with its muzzle tucked against a warm flank"),
+ V("hunts {target} with smoke-lined nostrils", "hunts {target} through one sensitive forehoof"),
+ V("attacks {target} with burning hooves", "attacks {target} with a sweeping tail of flame"),
+ V("touches glowing muzzles with another", "nickers to nearby company as its mane flickers"),
+ V("whinnies in a rising furnace note", "snorts a laughing burst of sparks"),
+ V("hauls a load in a heat-hazed stride", "stamps material firm with controlled hot hooves"));
+
+ Add(voices, "fire_elemental_slug",
+ V("glides on a ribbon of molten glow", "inches forward beneath two wavering flame feelers"),
+ V("draws into a low smoldering coil", "rests with both feelers dimmed to red points"),
+ V("waves its feelers through its own curling sparks", "blows bright bubbles from its glowing mantle"),
+ V("rasps a meal with its heated mouth", "absorbs energy through its broad underside"),
+ V("seals itself beneath a cooled black crust", "tucks its feelers into a banked orange body"),
+ V("hunts {target} with sweeping hot feelers", "hunts {target} with heat-sensitive feelers"),
+ V("attacks {target} with a strip of clinging flame", "attacks {target} with a hot spark"),
+ V("touches feelers with another", "draws a curling ember-sign toward nearby company"),
+ V("sizzles in wet, bubbly chuckles", "pops softly along its glowing mantle"),
+ V("lays a narrow bead across task material", "smooths task joins with its heated foot"));
+
+ Add(voices, "fire_elemental_snake",
+ V("slithers in a swift incandescent coil", "whips forward as a narrow line of flame"),
+ V("loops around its glowing core", "raises its head above a banked spiral"),
+ V("chases a spark through its own coils", "ties its blazing body into a loose knot"),
+ V("swallows fuel that glows down its length", "draws energy through a flickering forked tongue"),
+ V("curls into a dim ring of embers", "hides its head beneath overlapping fiery coils"),
+ V("hunts {target} with a split flame tongue", "hunts {target} through sensitive burning coils"),
+ V("attacks {target} in a searing straight line", "attacks {target} within burning coils"),
+ V("braids one loop beside another in greeting", "flicks a paired spark toward nearby company"),
+ V("hisses with a bright crackling cadence", "rattles its tail in popping bursts"),
+ V("threads heat through task material", "coils around a task join until it fuses cleanly"));
+
+ Add(voices, "fire_skull",
+ V("drifts forward beneath a ragged flame", "bobs ahead with jaws aglow"),
+ V("hovers with empty sockets fixed ahead", "settles low as its crown burns blue"),
+ V("spins until its own flame forms a ring", "chatters its jaw to scatter its own sparks"),
+ V("bites fuel and crushes it into flame", "inhales energy through its hollow nose"),
+ V("dims its crown and floats jaw-down", "closes its mouth around the last red glow"),
+ V("hunts {target} with hollow sockets fixed forward", "hunts {target} through snapping teeth"),
+ V("attacks {target} with blazing jaws", "attacks {target} with a compact fiery bolt"),
+ V("clacks teeth toward another in greeting", "bumps brows and shares a tongue of flame"),
+ V("cackles through a loose rattling jaw", "howls with fire roaring from its mouth"),
+ V("scorches task material with a measured bite", "carries a hot task load between its teeth"));
+
+ Add(voices, "ghost",
+ V("drifts with a trailing translucent hem", "passes forward in a wavering glide"),
+ V("hangs still with hands folded through its chest", "fades to a faint outline while watching"),
+ V("loops through its own misty train", "ripples its translucent outline without touching it"),
+ V("draws energy through its hollow form", "absorbs energy from its own aura"),
+ V("thins into an almost invisible veil", "folds into a dim floating silhouette"),
+ V("hunts {target} with one spectral hand cupped", "hunts {target} through its wavering outline"),
+ V("attacks {target} with a chilling hand", "attacks {target} with a face-stretching wail"),
+ V("passes one hand through another in greeting", "bows toward nearby company as its outline brightens"),
+ V("warbles in an airy, echoing laugh", "moans upward into a trembling chuckle"),
+ V("lifts a task load with unseen pressure", "guides task material without contact"));
+
+ Add(voices, "god_finger",
+ V("walks on its tip in deliberate little hops", "slides upright with the nail leading"),
+ V("balances rigidly on one rounded tip", "curls slightly and holds a hooked pose"),
+ V("draws circles with its nail", "taps out an alternating five-beat rhythm"),
+ V("draws energy through its rounded tip", "absorbs energy through the nail"),
+ V("lies flat with the nail dimmed", "bends into a relaxed hook and goes still"),
+ V("hunts {target} with its nail pointed sharply", "hunts {target} through a sensitive fingertip"),
+ V("attacks {target} with a sudden flick", "attacks {target} with a gleaming nail"),
+ V("crooks itself toward another in greeting", "taps another tip in quick acknowledgment"),
+ V("drums a rolling laugh against itself", "wiggles through a silent pantomime of laughter"),
+ V("nudges task material into exact position", "presses firmly to set finished work"));
+
+ Add(voices, "greg",
+ V("lopes ahead with elbows flung wide", "shuffles sideways in a lopsided gait"),
+ V("squats on its heels and stares without blinking", "stands slack-jawed with hands dangling"),
+ V("makes faces at its own waggling fingers", "hops twice and freezes in a crooked pose"),
+ V("crams a meal between its teeth", "licks every fingertip after a mouthful"),
+ V("sprawls flat with limbs at mismatched angles", "curls around its knees and snores through its nose"),
+ V("hunts {target} with abrupt zigzag sniffs", "hunts {target} by peering under its own arm"),
+ V("attacks {target} with windmilling fists", "attacks {target} with a lunging headbutt"),
+ V("waves with both hands far too close", "greets by copying every gesture a beat late"),
+ V("honks out a belly laugh", "wheezes until its shoulders bounce"),
+ V("stacks task material in uneven columns", "hammers task material in an irregular rhythm"));
+
+ Add(voices, "jumpy_skull",
+ V("boings forward on a springing jaw", "hops in quick arcs with teeth clenched"),
+ V("balances on its lower teeth and quivers", "sits jaw-first with sockets darting about"),
+ V("bounces in place to a clacking beat", "snaps at its own rebounding jaw"),
+ V("catches a meal between chattering teeth", "grinds a meal between its teeth"),
+ V("rests upside down on the crown of its cranium", "wedges its jaw shut and stops bouncing"),
+ V("hunts {target} in tight hopping circles", "hunts {target} with one tilted socket"),
+ V("attacks {target} in a teeth-first arc", "attacks {target} with a ricocheting cranium"),
+ V("bumps craniums with another", "chatters a greeting toward nearby company"),
+ V("cackles in rapid jaw-clacking bursts", "rattles with a hollow, breathless guffaw"),
+ V("pounds task material with its crown", "carries a task load clenched between its teeth"));
+
+ Add(voices, "living_house",
+ V("shuffles forward on creaking foundation stones", "lurches ahead as doors and shutters flap"),
+ V("settles squarely and braces every corner", "stands with chimney straight and windows fixed ahead"),
+ V("opens and closes its shutters in rhythm", "rocks its roofline in a slow, creaking dance"),
+ V("draws fuel through its front door", "feeds a meal into its grinding cellar"),
+ V("locks its door and dims every window", "sags gently beneath a resting roof"),
+ V("hunts {target} through swiveling windows", "hunts {target} through a listening chimney"),
+ V("attacks {target} with a heavy door", "attacks {target} behind a jutting porch"),
+ V("waves both shutters toward another", "rings an inner bell for nearby company"),
+ V("rattles every window in a booming laugh", "creaks from cellar to rafters in a long chuckle"),
+ V("repairs task material with an inner beam", "stacks material with measured foundation steps"));
+
+ Add(voices, "living_plants",
+ V("walks on a braid of probing roots", "pulls itself forward with curling vines"),
+ V("anchors its roots and lifts every leaf", "folds broad fronds around a knotted stem"),
+ V("twirls seed pods on thin tendrils", "claps two broad leaves in a crisp rhythm"),
+ V("draws energy through fine root hairs", "unfurls leaves while absorbing energy"),
+ V("closes every leaf around its central bud", "sinks into a rooted, slow-pulsing rest"),
+ V("hunts {target} with pollen-sensitive tendrils", "hunts {target} through wandering roots"),
+ V("attacks {target} with thorned vines", "attacks {target} behind layered leaves"),
+ V("entwines tendrils in a slow greeting", "opens a bright bloom toward nearby company"),
+ V("rustles in a quick leafy chuckle", "shakes dry pods in a rattling laugh"),
+ V("weaves task material with flexible stems", "binds material with tightening roots"));
+
+ Add(voices, "mush_animal",
+ V("scampers on stubby feet beneath a broad cap", "bounds forward as soft gills puff with each landing"),
+ V("sits back on its haunches and lowers its cap", "crouches with spongy ears tucked close"),
+ V("pounces on its own drifting spores", "rolls onto its back and pedals short feet"),
+ V("nibbles a meal with flat little teeth", "laps a meal with its tongue"),
+ V("curls beneath its own lowered cap", "nestles into a squat ball with gills closed"),
+ V("hunts {target} with a porous snout", "hunts {target} through trembling gills"),
+ V("attacks {target} with its broad cap", "attacks {target} beneath a blinding spore puff"),
+ V("rubs caps with another in greeting", "sniffs another and chirps through its gills"),
+ V("squeaks in damp, bubbly bursts", "chuffs until its broad cap wobbles"),
+ V("packs task material with quick forepaws", "carries a task load balanced on its cap"));
+
+ Add(voices, "mush_unit",
+ V("marches on pale stalk-like legs", "strides with a broad cap tilted forward"),
+ V("stands with arms folded beneath hanging gills", "tilts its broad cap as the gills pulse"),
+ V("tosses a spore puff from palm to palm", "tips the broad cap through an exaggerated dance"),
+ V("chews a meal with a grinding mouth", "drinks a meal in slow mouthfuls"),
+ V("squats until the cap covers the whole body", "rests upright as the gills pulse slowly"),
+ V("hunts {target} with a veil of sensing spores", "hunts {target} with tapping fingertips"),
+ V("attacks {target} with a hardened forearm", "attacks {target} beneath a dense spore cloud"),
+ V("tips the cap in measured greeting", "presses palms and exchanges a faint spore plume"),
+ V("chuckles in low, papery puffs", "laughs until spores shake from the gills"),
+ V("cultivates task material into sturdy panels", "presses material into useful molded shapes"));
+
+ Add(voices, "necromancer",
+ V("paces with a bone-tipped staff clicking beside each step", "glides as its own magic stirs the robe"),
+ V("bends over a circle of pale runes", "stands rigid while the staff whispers back"),
+ V("makes finger bones dance across one palm", "conducts a tiny procession of rattling fragments"),
+ V("draws energy through one raised hand", "draws wan energy through the bone-tipped staff"),
+ V("sleeps upright inside a ring of orbiting bones", "rests the brow against the staff as pale lights orbit"),
+ V("hunts {target} through a floating jaw's whispers", "hunts {target} with a cold spectral wisp"),
+ V("attacks {target} with conjured grasping hands", "attacks {target} with grave-pale force"),
+ V("greets another with a tap of the staff", "trades layered whispers with nearby company"),
+ V("laughs in a papery, hollow rasp", "chuckles while its orbiting bones knock together"),
+ V("threads task material into jointed frames", "inscribes pale commands along material"));
+
+ Add(voices, "plague_doctor",
+ V("hurries with coat tails snapping behind", "steps beneath a long beaked mask"),
+ V("stands with gloved hands clasped at the chest", "tilts the mask while its beak filters sound"),
+ V("drums gloved fingers along one sleeve", "balances the long beak above folded gloves"),
+ V("takes measured bites behind the mask", "draws energy through the narrow beak"),
+ V("reclines without removing mask or gloves", "dozes seated with both gloves folded"),
+ V("hunts {target} through the mask's round lenses", "hunts {target} by turning the long beak"),
+ V("attacks {target} with both gloved hands", "attacks {target} with a thrust of the long beak"),
+ V("nods toward another and raises one glove", "bows the long beaked mask toward nearby company"),
+ V("chuckles dryly behind the hollow beak", "lets out a muffled laugh through layered cloth"),
+ V("measures task material into labeled portions", "sterilizes task material with a controlled flame"));
+
+ Add(voices, "printer",
+ V("trundles forward on humming rollers", "slides along while its paper tray rattles"),
+ V("idles with a steady green indicator", "pauses as internal gears tick into alignment"),
+ V("feeds out a strip covered in dancing marks", "whirs through an alternating self-test pattern"),
+ V("draws ink from a sealed cartridge", "pulls a blank sheet through its turning rollers"),
+ V("powers down with its tray tucked closed", "dims its display while the print head parks"),
+ V("hunts {target} with a traveling scan bar", "hunts {target} through its narrow glass slit"),
+ V("attacks {target} with a rapid sheet volley", "attacks {target} with its snapping tray"),
+ V("beeps twice and flashes its status light", "exchanges neatly printed greeting slips"),
+ V("chatters its gears in a mechanical laugh", "chirps a laugh through sequenced tones"),
+ V("copies precise symbols onto task material", "collates task material into squared stacks"));
+
+ Add(voices, "skeleton",
+ V("clatters forward on bare jointed feet", "strides with loose ribs clicking together"),
+ V("stands with both hands hooked into its rib cage", "sits in a neatly folded pile of bones"),
+ V("juggles three knuckles through its ribs", "removes its jaw and makes it chatter by hand"),
+ V("gnaws a meal with bare teeth", "pours energy through its empty mouth"),
+ V("stacks its bones into a tidy dormant heap", "leans against its own shin and goes slack"),
+ V("hunts {target} with empty sockets lowered", "hunts {target} with a detached ear bone"),
+ V("attacks {target} with a sharpened forearm", "attacks {target} by separating and reassembling"),
+ V("rattles finger bones toward another", "clicks jaws with nearby company"),
+ V("cackles with its jaw flapping wide", "shakes from skull to toes in hollow laughter"),
+ V("sorts task material by length", "fastens material with strips pulled through its ribs"));
+
+ Add(voices, "tumor_monster_animal",
+ V("lopes on uneven limbs beneath pulsing growths", "scrambles forward with one swollen shoulder leading"),
+ V("huddles low as nodules throb along its back", "rests its heavy head on a cluster of folded limbs"),
+ V("chases a twitching tendril around its flank", "paws at bubbles moving beneath its hide"),
+ V("tears a meal with crowded teeth", "draws energy through several puckered mouths"),
+ V("curls around its largest pulsing mass", "slumps into a pulsing heap of slow contractions"),
+ V("hunts {target} through a ring of wet nostrils", "hunts {target} with sensitive nodules"),
+ V("attacks {target} behind a hardened growth", "attacks {target} with several mouths"),
+ V("nuzzles another with a broad sensory lobe", "trades low pulses through touching growths"),
+ V("gurgles from several throats at once", "wheezes in a wet, hiccupping laugh"),
+ V("packs task material with fleshy fibers", "drags material using a hooked extra limb"));
+
+ Add(voices, "tumor_monster_unit",
+ V("lumbers upright on mismatched legs", "pulls a swollen frame forward with an oversized arm"),
+ V("hunches while clustered eyes blink in sequence", "braces both hands against a pulsing abdomen"),
+ V("makes two smaller hands wrestle across its chest", "drums a rhythm on hardened nodules"),
+ V("chews a meal through a sideways mouth", "feeds energy into a pulsing growth"),
+ V("folds extra limbs around its swollen torso", "rests with every eye closing one after another"),
+ V("hunts {target} with clustered eyes", "hunts {target} using a thin tasting tendril"),
+ V("attacks {target} with its oversized fist", "attacks {target} between hardened lobes"),
+ V("raises a small palm toward another", "touches sensory tendrils in a pulsing greeting"),
+ V("laughs in overlapping throaty pitches", "snorts through several vents in uneven rhythm"),
+ V("molds dense tissue around task material", "uses three hands to bind material under tension"));
+
+ Add(voices, "white_mage",
+ V("walks beneath a robe edged in steady light", "glides with a smooth staff held upright"),
+ V("stands inside a soft ring of ordered sigils", "rests both hands atop a pearl-bright staff"),
+ V("folds little stars from threads of light", "makes small sparks orbit the staff"),
+ V("draws energy through an open palm", "draws energy through the staff"),
+ V("sleeps within a dim shell of pale light", "bows the hood and lets the staff stand guard"),
+ V("hunts {target} with a searching luminous mote", "hunts {target} through the staff's crystal"),
+ V("attacks {target} with a shining barrier", "attacks {target} with cleansing light"),
+ V("offers a pearl glow toward another", "bows as two pale halos briefly overlap"),
+ V("laughs in a clear, ringing cadence", "chuckles as bright motes bob around the hood"),
+ V("mends task material with woven light", "purifies material in a pearl-white glow"));
+
+ Add(voices, "zombie",
+ V("shambles forward with one foot dragging", "lurches ahead on stiff, reaching arms"),
+ V("sways in place with its jaw hanging loose", "slumps forward until a sudden twitch straightens it"),
+ V("reaches repeatedly for its dangling sleeve", "rocks side to side in an uneven imitation of dancing"),
+ V("bites into a meal with broken teeth", "gnaws a meal without closing its lips"),
+ V("collapses in a limp heap", "dozes standing with its forehead pressed down"),
+ V("hunts {target} through a ruined nose", "hunts {target} with an unbroken forward stare"),
+ V("attacks {target} with both stiff hands", "attacks {target} with broken teeth"),
+ V("groans face to face with another", "pats another with a cold, stiff hand"),
+ V("gurgles through a crooked jaw", "barks a hoarse chuckle"),
+ V("hauls a load with stiff arms", "presses task material with its full weight"));
+
+ Add(voices, "snowman",
+ V("waddles on a rolling rounded base", "pivots forward as its packed sections sway"),
+ V("balances its stacked form without swaying", "holds stick-like limbs beside its rounded body"),
+ V("twirls both stick-like limbs in broad circles", "rotates its rounded sections in opposite directions"),
+ V("absorbs energy through its packed body", "compacts a meal into its rounded middle"),
+ V("settles each packed section into a dormant stack", "draws its stick-like limbs against its cold body"),
+ V("hunts {target} by rotating its rounded upper body", "hunts {target} through pulsing cold-body effects"),
+ V("attacks {target} with sweeping stick-like limbs", "attacks {target} with a burst of its own packed snow"),
+ V("waves one stick-like limb toward another", "tips its rounded upper body toward nearby company"),
+ V("shakes its stacked sections in a powdery chuckle", "crunches through a soft, body-rumbling laugh"),
+ V("compacts task material with its rounded body", "pushes a load with both stick-like limbs"));
+ }
+}
diff --git a/IdleSpectator/ActivitySpeciesVoices.Invertebrates.cs b/IdleSpectator/ActivitySpeciesVoices.Invertebrates.cs
new file mode 100644
index 0000000..e01bbab
--- /dev/null
+++ b/IdleSpectator/ActivitySpeciesVoices.Invertebrates.cs
@@ -0,0 +1,261 @@
+using System.Collections.Generic;
+
+namespace IdleSpectator;
+
+public static partial class ActivitySpeciesVoiceCatalog
+{
+ private static void AddInvertebrateVoices(Dictionary voices)
+ {
+ Add(
+ voices,
+ "acid_blob",
+ V("rolls its leading lobe forward", "pours its mass into another low glide"),
+ V("holds its rim in a shallow quiver", "settles into a wide motionless pool"),
+ V("flicks small beads from its rippling edge", "folds two rising lobes over each other"),
+ V("spreads a thin feeding film across its meal", "draws morsels inward beneath its surface"),
+ V("flattens until only its skin trembles", "gathers into a low mound and stills"),
+ V("extends a narrow feeler toward {target}", "streams forward after {target}"),
+ V("lashes out at {target} with a sudden liquid fold", "rears its front edge at {target} and slaps it down"),
+ V("touches rims and trades slow surface pulses", "presses close and mirrors another's ripples"),
+ V("burbles as rings race across its surface", "pops a chain of little bubbles"),
+ V("pushes a broad lobe steadily at the task", "divides its mass around the task"));
+
+ Add(
+ voices,
+ "ant_black",
+ V("keeps a straight course on quick jointed steps", "scurries forward with antennae sweeping"),
+ V("halts with both feelers angled ahead", "sets all six feet and samples with its antennae"),
+ V("darts aside and circles back", "crosses and uncrosses its paired forelegs"),
+ V("clips bites from its meal", "braces its forelegs and rasps at a morsel"),
+ V("tucks its legs beneath a compact crouch", "lowers its feelers and becomes still"),
+ V("advances toward {target} with rapid antenna taps", "follows after {target} in tight turns"),
+ V("locks mandibles on {target} and drives from its hind legs", "ducks low at {target} before snapping its jaws upward"),
+ V("meets another with brisk feeler strokes", "passes close to another and exchanges antenna taps"),
+ V("rattles its jaws in a clipped little rhythm", "shivers its feelers through a dry chuckle"),
+ V("hauls a load with its body pitched forward", "sorts material into a straight row"));
+
+ Add(
+ voices,
+ "ant_blue",
+ V("advances in measured steps with wide feeler sweeps", "moves forward in alternating angular turns"),
+ V("balances high on straightened legs", "pauses to trace a slow arc with each feeler"),
+ V("pivots in tight loops on alternating feet", "rears and catches its forefeet in a paired clasp"),
+ V("rotates a morsel between paired mouthparts", "pares bites from its meal"),
+ V("folds into a narrow resting stance", "rests with its antennae laid along its head"),
+ V("triangulates toward {target} with alternating feeler taps", "takes an angular zigzag toward {target}"),
+ V("sidesteps at {target} and pinches from the flank", "sets its feet wide at {target} and closes both jaws"),
+ V("draws paired symbols with crossing antennae", "paces beside another in matching steps"),
+ V("ticks its mouthparts in a rising cadence", "taps its forefeet in a quick alternating pattern"),
+ V("taps each load with both feelers before shifting it", "fits material into a straight line"));
+
+ Add(
+ voices,
+ "ant_green",
+ V("bounds forward with its forelegs held overhead", "threads ahead beneath a balanced abdomen"),
+ V("rests its forelegs across raised mandibles", "stops to groom its feelers with hooked wrists"),
+ V("spins beneath raised forelegs", "vaults forward and doubles back"),
+ V("saws at a morsel with its mandibles", "holds a morsel aloft while shaving off bites"),
+ V("curls around its tucked forelegs", "settles with its legs clasped close to its body"),
+ V("tests toward {target} with one lifted foreleg", "rushes after {target} with its jaws held open"),
+ V("swings its raised forelegs at {target}", "hooks at {target} and pulls sideways"),
+ V("opens its jaws while facing another", "links feelers with another and turns in a brief circle"),
+ V("scrapes its jaws together in bouncing beats", "drums an alternating pattern with its middle feet"),
+ V("cuts material into narrow sections", "carries sections of a load one by one"));
+
+ Add(
+ voices,
+ "ant_red",
+ V("charges in short bursts with jaws spread", "races forward and brakes on splayed forelegs"),
+ V("rocks over its braced feet with feelers rigid", "holds a low stance with mandibles parted"),
+ V("pounces forward and kicks backward", "rushes in tight circles with its abdomen raised"),
+ V("tears brisk bites from its meal", "pins its meal and bites in rapid strokes"),
+ V("drops into a tight crouch without relaxing its jaws", "folds its limbs close and rests low"),
+ V("surges after {target} in rapid bursts", "cuts toward {target} with jaws spread"),
+ V("lunges at {target} with mandibles wide", "clamps on {target} and twists its whole body"),
+ V("bumps heads and trades forceful antenna strokes", "marches shoulder to shoulder with another"),
+ V("clacks its jaws in a rapid rolling burst", "stamps all six feet in a crackling rhythm"),
+ V("drives a load forward from behind", "grips the task and pulls without pause"));
+
+ Add(
+ voices,
+ "bee",
+ V("zips ahead on rapidly beating wings", "bobs forward in a humming flight"),
+ V("hovers in place with dangling legs", "lands and combs its wings with its hind legs"),
+ V("loops upward and spirals back down", "dances in tight figures with a wagging abdomen"),
+ V("unfurls its tongue and draws from a meal", "packs pollen against its hind legs"),
+ V("folds its wings flat and tucks in its legs", "rests with its head lowered between its forelegs"),
+ V("angles its feelers toward {target}", "accelerates after {target} with a deepening buzz"),
+ V("dives at {target} with its abdomen curled beneath it", "buffets at {target} with wings and kicking legs"),
+ V("touches mouthparts with another in a brief exchange", "faces another while waggling its abdomen"),
+ V("buzzes in a tumbling uneven trill", "shakes its abdomen through an uneven hum"),
+ V("brushes pollen into compact bundles", "ferries a gathered load in steady flights"));
+
+ Add(
+ voices,
+ "beetle",
+ V("trundles forward beneath closed wing cases", "clambers ahead as its hooked feet lift in sequence"),
+ V("draws in its legs and lowers its plated back", "stands braced while its feelers make short sweeps"),
+ V("rolls onto its back and pumps all six legs", "tips onto its side and rocks upright again"),
+ V("anchors its feet and chews its meal", "works its jaws steadily across a morsel"),
+ V("seals its wings and settles behind folded legs", "wedges its head low and becomes still"),
+ V("follows after {target} with its feelers forward", "lumbers toward {target} in a straight line"),
+ V("rams at {target} with its lowered head", "opens its wing cases at {target} and shoves with all six legs"),
+ V("knocks feelers against another's plated brow", "circles another and taps shell against shell"),
+ V("clicks its wing cases in hollow bursts", "rasps its feet against its plated sides"),
+ V("leans its hardened back into a load", "grips and drags material with hooked forefeet"));
+
+ Add(
+ voices,
+ "bioblob",
+ V("inches forward as its front edge swells", "heaves its rounded membrane into a springy roll"),
+ V("balances as a domed pulse rises and falls", "rests while slow ripples cross its skin"),
+ V("bounces twice and catches itself in a wide wobble", "pinches off a bead and reels it back inside"),
+ V("wraps a membrane pocket around each morsel", "draws nutrients inward with a deep contracting pulse"),
+ V("dims its inner motion and sags into a low mound", "draws its membrane close around a still center"),
+ V("points a pulsing bulge toward {target}", "bounds after {target} with quick whole-body squeezes"),
+ V("hardens its front at {target} into a blunt swell", "compresses and springs at {target} as one mass"),
+ V("joins surfaces and trades alternating pulses", "echoes another's pulse from rim to rim"),
+ V("warbles through a chain of membrane pops", "jiggles until ripples cross its membrane in rings"),
+ V("forms gripping pockets around the task", "moves material with alternating squeezes"));
+
+ Add(
+ voices,
+ "butterfly",
+ V("floats ahead on broad alternating wingbeats", "loops forward with legs tucked close"),
+ V("holds its wings upright and still", "opens and closes its wings in slow measured fans"),
+ V("spirals upward and glides back down", "dips and rises on alternating wingbeats"),
+ V("uncurls its feeding tube and takes from a meal", "steadies on four legs while drawing nutrients"),
+ V("folds its wings tight and draws in its legs", "rests behind the narrow line of closed wings"),
+ V("follows after {target} in rising uneven loops", "glides toward {target} with quick wingbeats"),
+ V("beats its wings hard at {target}", "veers at {target} and kicks with slender legs"),
+ V("circles another in paired rising arcs", "rests alongside another with wing edges nearly touching"),
+ V("flutters in a loose cascade of wingbeats", "bobs through an uneven airy chuckle"),
+ V("carries a load on its legs", "makes repeated passes over the task"));
+
+ Add(
+ voices,
+ "flower_bud",
+ V("hitches its rootlets forward beneath a bending stalk", "leans and draws its stalk after its roots"),
+ V("holds its closed petals above a steady stalk", "anchors its rootlets and lets its leaves settle"),
+ V("twirls its stalk until the outer petals fan wide", "bobs its closed crown in quick springing arcs"),
+ V("presses root hairs outward and draws nutrients in", "cups a morsel beneath its lowest leaves"),
+ V("folds every petal inward around its center", "bows its crown and slackens its leaves"),
+ V("angles its crown toward {target}", "pulls its rootlets toward {target} in a tightening curve"),
+ V("whips its stalk at {target} with its crown closed", "braces its roots at {target} and sweeps its leaves outward"),
+ V("touches leaf tips and trades a tremor with another", "inclines its crown beside another's petals"),
+ V("shakes its petals in a crisp papery rattle", "nods through a cascade of rustling leaves"),
+ V("winds fine roots around the task", "uses paired leaves to lift and place material"));
+
+ Add(
+ voices,
+ "fly",
+ V("shoots forward and stops in midair", "skims ahead on blurred, glassy wings"),
+ V("hovers with its forelegs rubbing together", "holds still and wipes each eye with a bent foreleg"),
+ V("darts in a square and retraces every turn", "loops upside down before righting in a snap"),
+ V("sets its feet and sponges at a meal", "lowers its mouthparts for quick repeated tastes"),
+ V("draws its legs close with wings laid flat", "tucks its legs close and stills its wing buzz"),
+ V("closes toward {target} by sharp angles", "tracks after {target} with abrupt hovering stops"),
+ V("slams sideways at {target} with a burst of wing speed", "grapples with {target} while its wings whine"),
+ V("faces another and drums with its forefeet", "orbits another in close mirrored darts"),
+ V("buzzes in a broken, hiccupping run", "rubs its wings into a thin trill"),
+ V("shuttles material in rapid trips", "uses its forefeet to turn the task bit by bit"));
+
+ Add(
+ voices,
+ "garl",
+ V("lurches ahead as a layered bulb rocks over its roots", "sets fibrous roots, then hauls its stalk forward"),
+ V("settles its layered base and holds its stalk upright", "fans its narrow leaves above a rooted pause"),
+ V("spins on its bulb while loose skins flutter", "whips its leaf tips in alternating circles"),
+ V("threads fine roots around a meal and draws inward", "parts its papery layers to receive morsels"),
+ V("closes its leaves around a bowed stalk", "rests its bulb on one side with roots curled beneath"),
+ V("tilts its stalk toward {target}", "scrambles after {target} on bunching root fibers"),
+ V("swings its dense bulb at {target} in a low arc", "lashes at {target} with stiff leaves"),
+ V("braids leaf tips briefly with another's", "presses layered sides together and rustles"),
+ V("shudders until its dry skins chatter", "flicks its leaves in an alternating crackle"),
+ V("hooks the task with bundled root fibers", "wedges its layered base behind a load"));
+
+ Add(
+ voices,
+ "grasshopper",
+ V("vaults ahead on folded hind legs", "springs forward and lands on braced forefeet"),
+ V("crouches with its hind legs folded beneath its abdomen", "holds still while its feelers trace broad arcs"),
+ V("bounds straight up and twists before landing", "kicks both hind legs and leaps after them"),
+ V("clips bites with side-working jaws", "grips a morsel between its forefeet and chews"),
+ V("folds its long hind legs tight against its sides", "rests low with feelers laid back"),
+ V("launches toward {target} in successive long bounds", "leans toward {target} and springs after {target}"),
+ V("drives both hind feet at {target} in a sharp kick", "leaps at {target} and drops forefeet-first"),
+ V("crosses feelers with another in alternating taps", "sits beside another and rubs a leg along one wing"),
+ V("scrapes out a skipping chirr", "pulses its hind legs through an alternating rattle"),
+ V("kicks loose material into a gathered load", "carries material between its forefeet in short hops"));
+
+ Add(
+ voices,
+ "lemon_snail",
+ V("glides forward on a rippling muscular foot", "extends its eyestalks and slides beneath its shell"),
+ V("rests with its eyestalks half withdrawn", "holds its shell level while the foot stops rippling"),
+ V("waves both long feelers in crossing loops", "rocks its shell from side to side without advancing"),
+ V("rasps small strokes across the meal", "pins a morsel beneath the front of its foot"),
+ V("withdraws beneath the lip of its shell", "seals close with every feeler tucked away"),
+ V("stretches its eyestalks toward {target}", "glides after {target} with its head extended"),
+ V("swings its shell sideways at {target}", "rears its head and drives at {target} under its shell"),
+ V("touches feeler tips and circles shell to shell", "glides alongside another in a close parallel line"),
+ V("bobs its eyestalks in a slow alternating rhythm", "rocks its shell through a scraping chuckle"),
+ V("pushes material with the broad front of its foot", "anchors its shell and rasps steadily at the task"));
+
+ Add(
+ voices,
+ "lil_pumpkin",
+ V("rolls its ribbed body as a short vine pulls ahead", "scampers on curling tendrils beneath its bobbing crown"),
+ V("sets its round body down among coiled tendrils", "holds its crown upright while the vine tips rest"),
+ V("bounces on its rounded base and spins a tendril", "catches one curling vine between two others"),
+ V("draws a morsel beneath its vine and presses close", "uses a split tendril to guide a morsel inward"),
+ V("curls every tendril against its ribbed sides", "tips onto one flank and lets its crown droop"),
+ V("uncurls an extended vine toward {target}", "rolls after {target} with tendrils reaching ahead"),
+ V("butts its hard rounded body at {target}", "snaps a coiled tendril at {target}"),
+ V("loops tendrils briefly around another's vine", "rests side by side and taps crowns with another"),
+ V("rattles its hollow body from within", "bounces as its hollow body knocks"),
+ V("winds tendrils around the task and pulls", "braces its round body behind a shifting load"));
+
+ Add(
+ voices,
+ "sand_spider",
+ V("skates forward on eight low-splayed legs", "scuttles sideways with its body held low"),
+ V("freezes with each leg bent at a different angle", "settles low while its front legs test faint tremors"),
+ V("juggles its forefeet in alternating pairs", "spins once on four braced feet"),
+ V("pins its meal beneath folded forelegs", "works its mouthparts while holding the morsel still"),
+ V("draws all eight legs into a tight resting knot", "flattens its body and folds its forelegs inward"),
+ V("feels toward {target} through widely spread feet", "stalks toward {target} in alternating sideways steps"),
+ V("rears at {target} and strikes with the front pair", "hooks at {target} and pivots sideways"),
+ V("taps front feet in a measured exchange", "faces another and mirrors each lifted leg"),
+ V("drums eight feet in a tumbling cadence", "shakes its mouthparts through a dry clicking fit"),
+ V("draws fine strands around the task", "pulls bound material backward beneath its body"));
+
+ Add(
+ voices,
+ "scorpion",
+ V("paces forward with pincers raised and tail balanced", "sidesteps ahead on eight compact legs"),
+ V("rests low beneath the arch of its tail", "holds both pincers open without a tremor"),
+ V("passes one pincer beneath the other", "circles its own tail tip in tight steps"),
+ V("shears a morsel apart between opposing pincers", "holds its meal in one claw and cuts with the other"),
+ V("lowers its tail and folds its claws beneath its head", "settles with every leg tucked under its plated body"),
+ V("tests toward {target} with open pincers", "tracks after {target} with its tail poised overhead"),
+ V("snaps both claws at {target} before driving its tail downward", "snaps one pincer at {target} and counters with the other"),
+ V("clasps pincers and steps through a paired turn", "touches claw tips in measured alternating taps"),
+ V("clacks its pincers in a brisk hollow rhythm", "rattles its plated tail in uneven pulses"),
+ V("grips the task between both claws", "drags material backward with its tail held clear"));
+
+ Add(
+ voices,
+ "smore",
+ V("shuffles forward on a bundle of springy stalks", "tilts its stacked crown and steps on bending shoots"),
+ V("rests its layered crown above folded shoots", "sets its fibrous base and lets each stalk straighten"),
+ V("bounces its stacked layers in alternating tilts", "twirls a ribbonlike leaf around its crown"),
+ V("presses one layer around each morsel", "draws nutrients between two yielding layers"),
+ V("compresses its crown and folds its shoots inward", "settles into a squat stack with leaves tucked close"),
+ V("leans its upper layer toward {target}", "hurries after {target} on flexing stalks"),
+ V("springs its stacked body at {target}", "claps two broad layers at {target}"),
+ V("rests one broad layer against another's crown", "crosses ribbonlike leaves with another"),
+ V("wobbles until its stacked layers flap together", "rustles its leaves through a bouncing chuckle"),
+ V("pinches the task between broad yielding layers", "uses bundled stalks to shove material into place"));
+ }
+}
diff --git a/IdleSpectator/ActivitySpeciesVoices.Mammals.cs b/IdleSpectator/ActivitySpeciesVoices.Mammals.cs
new file mode 100644
index 0000000..a04474f
--- /dev/null
+++ b/IdleSpectator/ActivitySpeciesVoices.Mammals.cs
@@ -0,0 +1,303 @@
+using System.Collections.Generic;
+
+namespace IdleSpectator;
+
+public static partial class ActivitySpeciesVoiceCatalog
+{
+ private static void AddMammalVoices(Dictionary voices)
+ {
+ Add(
+ voices,
+ "alpaca",
+ V("paces on padded feet with its long neck held upright", "steps lightly while its fleece sways"),
+ V("stands chewing with its ears turning", "holds still behind a curtain of fleece"),
+ V("bounds sideways on stiff legs", "nudges and skips in a woolly burst"),
+ V("clips a bite with split lips", "chews steadily with a rolling jaw"),
+ V("folds its long legs beneath a fleece-covered body", "rests with its neck curved over tucked legs"),
+ V("raises its head near {target} and scans with upright ears", "advances toward {target} with its neck stretched high"),
+ V("lashes out near {target} with a sharp forward kick", "braces its padded feet before {target} and spits"),
+ V("touches noses beneath a pair of upright ears", "hums softly beside nearby company"),
+ V("lets out a bright, breathy hum", "burbles while chewing cud"),
+ V("carries the load on a straight, fleece-lined back", "kneels on padded joints beside the task"));
+
+ Add(
+ voices,
+ "armadillo",
+ V("scuttles low beneath overlapping armor bands", "trots with claws ticking under a plated back"),
+ V("pauses with its pointed snout raised", "sits low while its armored bands settle"),
+ V("scrabbles in a quick circle with oversized claws", "pops upright and paddles its foreclaws"),
+ V("roots with a narrow snout and digs out each bite", "laps up morsels between rapid sniffs"),
+ V("curls its plated body around its soft underside", "dozes with nose and feet tucked under armor"),
+ V("samples scents near {target} with claws flexed", "searches around {target} with rapid sniffs"),
+ V("hunkers near {target} behind its shell and drives forward", "swipes at {target} with powerful digging claws"),
+ V("sniffs along another armored flank", "touches pointed snouts in a brief greeting"),
+ V("chatters in a dry, rapid burst", "squeaks from beneath its plated brow"),
+ V("excavates with alternating strokes of broad foreclaws", "pushes loose material aside with its armored shoulder"));
+
+ Add(
+ voices,
+ "bandit",
+ V("slips ahead on quiet, measured steps", "keeps low with one hand held close"),
+ V("leans back and watches through narrowed eyes", "checks each side with short head turns"),
+ V("rolls one wrist and flexes quick fingers", "practices a feint and a smooth retreat"),
+ V("takes bites without lowering the guard", "eats quickly with one hand shielding the meal"),
+ V("sleeps curled tightly with both hands tucked in", "rests lightly with chin lowered to the chest"),
+ V("tracks movement near {target} from a crouch", "advances toward {target} with quick hands raised"),
+ V("slashes toward {target} from a low stance and withdraws", "turns aside near {target} and counters with the off hand"),
+ V("trades a brief hand sign at close range", "leans in to exchange a few short words"),
+ V("gives a muffled chuckle through closed lips", "snickers and taps two fingers against the chin"),
+ V("sorts material with quick fingertips", "checks the task with rapid hand movements"));
+
+ Add(
+ voices,
+ "bear",
+ V("lumbers forward with heavy shoulders rolling", "advances on broad plantigrade paws"),
+ V("rises upright and works its broad nose", "sits back on its haunches with forepaws hanging"),
+ V("clasps both forepaws and rolls onto one shoulder", "rolls onto its back and bats with broad paws"),
+ V("tears off a bite with strong jaws", "scoops the meal inward with long curved claws"),
+ V("curls into a dense mound with paws over its muzzle", "settles heavily on its side and slows its breathing"),
+ V("follows the scent near {target} with muzzle sweeping side to side", "charges toward {target} with nose low"),
+ V("rears near {target} and brings both forepaws down", "drives at {target} behind a roar and a heavy swipe"),
+ V("rubs muzzle and shoulder against another broad flank", "sniffs another face with small rounded ears forward"),
+ V("huffs out a deep, rolling chortle", "grumbles in a sequence of breathy pulses"),
+ V("hauls with both forepaws and a braced back", "shoves the task onward with massive shoulders"));
+
+ Add(
+ voices,
+ "buffalo",
+ V("moves in a heavy rolling stride beneath a horned brow", "trots with its shoulder hump rising and falling"),
+ V("stands broadside while its tail flicks", "holds its head low and works its jaw"),
+ V("bucks and twists with all four hooves briefly clear", "bounds forward and tosses its curved horns"),
+ V("tears off a bite with a wide muzzle", "chews steadily while its beard brushes its chest"),
+ V("folds its legs beneath a massive chest", "rests flank-down with its horned head upright"),
+ V("works its broad nostrils near {target} and follows", "pushes toward {target} with muzzle low and horns level"),
+ V("drops its horned head near {target} and drives forward", "swings its heavy skull at {target} in a short arc"),
+ V("presses shoulder to shoulder with nearby company", "grooms a nearby hide with slow tongue strokes"),
+ V("bellows in a deep, chesty rumble", "snorts and answers with a booming grunt"),
+ V("leans into the load with neck and shoulder hump", "drags steadily while broad hooves brace"));
+
+ Add(
+ voices,
+ "capybara",
+ V("pads along on short legs with webbed toes splayed", "ambles forward with a blunt muzzle leading"),
+ V("sits squarely with half-lidded eyes and rounded ears", "rests its barrel-shaped body on folded feet"),
+ V("bobs up and scampers in a compact circle", "hops back on short legs and bobs again"),
+ V("nibbles the meal quickly with broad orange incisors", "chews a bite along one side of the jaw"),
+ V("lies flat with its blunt chin between its forefeet", "dozes in a compact heap with ears still twitching"),
+ V("sniffs near {target} in short deliberate bursts", "moves toward {target} with whiskers spread"),
+ V("bares broad incisors near {target} and lunges low", "braces before {target} and snaps sharply"),
+ V("touches blunt muzzles and settles beside nearby company", "grooms a nearby coat with careful front teeth"),
+ V("purrs in a bubbling series of soft clicks", "whistles once and follows with a throaty chuckle"),
+ V("grips and shifts material with incisors and forefeet", "pushes the task ahead with a broad chest"));
+
+ Add(
+ voices,
+ "cat",
+ V("pads forward on silent, tucked claws", "threads ahead with tail held in a supple curve"),
+ V("sits upright and wraps its tail around neat paws", "freezes with whiskers spread and ears pivoting"),
+ V("pounces onto its forepaws and bats from side to side", "springs sideways with back arched and tail high"),
+ V("pins a morsel with one paw and tears it with its teeth", "licks its lips before taking another bite"),
+ V("curls nose to tail with paws hidden beneath", "kneads twice before folding into a compact curl"),
+ V("stalks toward {target} with scapulae rising under the coat", "waits near {target} before a sudden pounce"),
+ V("lashes at {target} with unsheathed foreclaws", "flattens its ears near {target} and strikes rapidly"),
+ V("greets with an upright tail and a cheek rub", "grooms a nearby coat with rough, measured licks"),
+ V("chatters out a bright trill", "purrs around a short chirrup"),
+ V("hooks the task closer with a careful paw", "inspects the work with whiskers forward and tail twitching"));
+
+ Add(
+ voices,
+ "cow",
+ V("walks with an unhurried sway on cloven hooves", "steps forward while its broad muzzle bobs"),
+ V("stands ruminating with its tail switching", "rests its weight on three legs and chews"),
+ V("kicks up its hind heels in a heavy skip", "dips its broad forehead and skips sideways"),
+ V("draws in a bite with its rough tongue", "chews cud in slow circular strokes"),
+ V("folds its legs and settles on a broad belly", "lies on its side with muzzle resting low"),
+ V("follows the scent near {target} with muzzle extended", "advances toward {target} behind lowered horns"),
+ V("swings its horned head at {target} and stamps a forehoof", "drives toward {target} with forehead lowered"),
+ V("licks a nearby neck with a broad rough tongue", "stands flank to flank and exchanges low calls"),
+ V("moos in a rising, resonant call", "utters a deep lowing chuckle"),
+ V("leans its broad chest into the load", "pulls steadily with cloven hooves planted"));
+
+ Add(
+ voices,
+ "dog",
+ V("trots ahead with nose active and tail balancing each turn", "pads forward in an even four-beat gait"),
+ V("sits on its haunches with ears trained forward", "waits with head tilted and tail tapping"),
+ V("bows on its forelegs and bounds sideways", "bounds in a circle with tail sweeping"),
+ V("holds the meal between both forepaws and gnaws", "bites repeatedly before licking its muzzle"),
+ V("circles twice and curls with nose beneath tail", "sprawls on its side with paws twitching"),
+ V("tracks the scent near {target} with nose sweeping low", "surges toward {target} in long bounding strides"),
+ V("bares its teeth near {target} and lunges from braced forelegs", "snaps at {target}, pivots, and drives in again"),
+ V("greets with a nose touch and a broadly wagging tail", "sniffs closely before settling alongside"),
+ V("pants through a breathy, barking chuckle", "lets out a short yip followed by a huff"),
+ V("carries the task firmly between its jaws", "digs in with forepaws and pulls backward"));
+
+ Add(
+ voices,
+ "fox",
+ V("trots lightly with its brush held level", "slips forward on narrow paws with ears pricked"),
+ V("sits with its brush curled around slender legs", "stands listening while its pointed ears swivel"),
+ V("pounces high and lands forepaws-first", "whips around after its own sweeping brush"),
+ V("nips at the meal with a narrow muzzle", "pins the meal beneath one paw and chews"),
+ V("curls tightly beneath its sweeping brush", "rests with pointed muzzle tucked against its chest"),
+ V("tilts its head near {target} before pouncing", "stalks toward {target} with ears fixed ahead"),
+ V("darts at {target} with a quick bite and springs clear", "twists near {target} and snaps from a narrow stance"),
+ V("touches pointed muzzles and circles with brush raised", "grooms a nearby ear with delicate nibbles"),
+ V("gekker-calls in a rapid, rasping burst", "lets out a high yip followed by a throaty chatter"),
+ V("carries material delicately in its jaws", "scrapes at the task with quick alternating forepaws"));
+
+ Add(
+ voices,
+ "goat",
+ V("picks a nimble path on hard cloven hooves", "bounds forward with short springing steps"),
+ V("stands chewing while horizontal pupils scan", "balances squarely with beard stirring beneath its chin"),
+ V("hops onto stiff legs and twists in place", "rears briefly and taps its horns together"),
+ V("takes a bite with mobile split lips", "strips off a bite and chews with a sideways jaw"),
+ V("tucks its legs beneath a narrow, deep chest", "rests chin-down with horns angled back"),
+ V("tests scents near {target} with quick sniffs", "bounds toward {target} with horns tipped forward"),
+ V("rears near {target} and crashes down behind its horns", "drives a compact headbutt at {target} from planted hooves"),
+ V("rubs horn bases against a nearby neck", "nibbles carefully along another coat"),
+ V("bleats in a broken, chuckling cadence", "snorts and answers with a nasal trill"),
+ V("pushes with its forehead while hooves grip", "carries a small load between nimble jaws"));
+
+ Add(
+ voices,
+ "hyena",
+ V("lopes ahead with high shoulders and a sloping back", "pads forward on powerful forequarters"),
+ V("stands squarely with rounded ears tracking every sound", "sits back beneath a heavy neck and watches"),
+ V("bounds in a crooked circle with jaws gaping", "feints forward and wheels away on long forelegs"),
+ V("cracks a bite with massive premolars", "tears and swallows with rapid jaw strokes"),
+ V("lies belly-down with heavy jaws across its paws", "curls on one flank beneath raised shoulders"),
+ V("quarters near {target} with nose low", "paces toward {target} in an endurance lope"),
+ V("clamps onto {target} with crushing jaws and braces", "rushes at {target} behind powerful shoulders and snaps"),
+ V("greets with muzzle low and measured flank sniffing", "grooms a nearby coat with short front-tooth nibbles"),
+ V("whoops and cackles in a rising cascade", "chatters out a breathless, ringing laugh"),
+ V("drags the task with jaws locked and shoulders driving", "crushes material between broad teeth"));
+
+ Add(
+ voices,
+ "monkey",
+ V("scampers on all fours with its long tail balancing", "bounds ahead and reaches down with deft hands"),
+ V("squats on its haunches and scans with quick head turns", "perches upright while its fingers pick through its coat"),
+ V("claps its hands and flicks its tail for balance", "leaps, lands, and flicks its tail for balance"),
+ V("turns the meal with nimble thumbs before biting", "holds a morsel in both hands and nibbles"),
+ V("curls on its side with hands tucked beneath its chin", "dozes in a compact crouch with tail wrapped close"),
+ V("searches near {target} with quick eyes and reaching hands", "bounds toward {target} in a four-limbed rush"),
+ V("grapples with {target} using both hands", "leaps at {target} with a slap before springing back"),
+ V("picks carefully through a nearby coat", "touches faces and chatters at close range"),
+ V("hoots through a rapid string of chattering notes", "bares its teeth in a breathy, bouncing chuckle"),
+ V("turns and fits material with nimble fingers", "carries material clasped against its chest"));
+
+ Add(
+ voices,
+ "rabbit",
+ V("bounds forward on long hind feet", "hops in quick arcs with ears held upright"),
+ V("sits tall with nose pulsing and ears rotating", "crouches low on folded hind legs"),
+ V("leaps straight up and twists in midair", "darts in a tight circle before kicking out"),
+ V("clips each bite with sharp front incisors", "nibbles rapidly while its jaw circles"),
+ V("settles into a compact loaf with feet hidden", "lies stretched out with ears laid along its back"),
+ V("sniffs near {target} with nose twitching rapidly", "bounds toward {target} in sudden accelerating leaps"),
+ V("boxes at {target} with rapid forepaw strikes", "kicks toward {target} with both powerful hind feet"),
+ V("touches noses and settles flank to flank", "grooms a nearby forehead with tiny licks"),
+ V("honks softly and chatters its teeth", "lets out a bright squeal between quick nose twitches"),
+ V("scrapes and gathers with alternating forepaws", "carries small material between its incisors"));
+
+ Add(
+ voices,
+ "raccoon",
+ V("ambles forward with masked face low and ringed tail trailing", "walks on nimble plantigrade paws with back arched"),
+ V("sits upright and rubs its sensitive forepaws together", "pauses with black fingers spread and ears forward"),
+ V("rolls onto one shoulder with both paws raised", "pounces, tumbles, and rights its ringed tail"),
+ V("feels over each morsel before taking a bite", "holds the meal in both forepaws and chews neatly"),
+ V("curls around its ringed tail with masked muzzle tucked", "dozes on its side with forepaws folded together"),
+ V("probes near {target} with sensitive fingers and a pointed nose", "follows the scent toward {target} with marked face low"),
+ V("grabs at {target} with both forepaws and bites sharply", "rears near {target} and slashes with black claws"),
+ V("sniffs another marked face and explores a nearby coat with both paws", "sits close and grooms with careful fingerlike claws"),
+ V("trills through a raspy series of chuckles", "chatters and chirrs beneath its dark facial markings"),
+ V("sorts and turns material with deft black paws", "pries at the task with sensitive fingers"));
+
+ Add(
+ voices,
+ "rat",
+ V("scurries low with naked tail balancing each turn", "trots in quick close steps with whiskers fanned"),
+ V("sits upright and washes its muzzle between pink paws", "freezes with whiskers trembling and round ears raised"),
+ V("clasps its forepaws and chatters its incisors", "darts forward, wheels, and flicks its long tail"),
+ V("holds a morsel in both paws and gnaws with orange incisors", "nibbles in rapid bites while whiskers brush the meal"),
+ V("curls into a tight ball around its bare tail", "lies belly-down with pointed muzzle on folded paws"),
+ V("searches near {target} with whiskers sweeping both sides", "scurries toward {target} with nose held low"),
+ V("lunges at {target} and bites with chisel incisors", "rears near {target} to box rapidly with both forepaws"),
+ V("touches noses and grooms around a nearby ear", "huddles close while whiskers cross and tails coil"),
+ V("bruxes its incisors in a soft, crackling chuckle", "squeaks through a rapid series of chirps"),
+ V("gnaws the task with tireless incisors", "carries material tucked beneath its chin"));
+
+ Add(
+ voices,
+ "rhino",
+ V("tramps forward on three-toed feet beneath a heavy horn", "moves with thick shoulders rolling under folded hide"),
+ V("stands nearly motionless while small ears swivel", "rests its massive head with horn angled low"),
+ V("bucks into a short heavy trot", "pivots on stout legs and tosses its horn"),
+ V("crops a bite with a broad square lip", "chews a bite behind thick folded lips"),
+ V("folds its stout legs and settles onto thick hide", "lies flank-down with horn and chin resting low"),
+ V("sniffs near {target} with ears flicking independently", "charges toward {target} with horn level"),
+ V("charges at {target} behind its lowered horn", "swings its massive skull near {target} and stamps forward"),
+ V("touches horns lightly before standing side by side", "rubs thick shoulder hide against a nearby flank"),
+ V("snorts in a rough, rumbling burst", "squeals once and follows with a throaty puff"),
+ V("pushes the load with horn base and broad forehead", "leans its full weight into the task"));
+
+ Add(
+ voices,
+ "sheep",
+ V("walks in close steps beneath a thick fleece", "trots with narrow hooves and wool bouncing"),
+ V("stands chewing with ears angled out from the wool", "settles squarely while its dense fleece rises and falls"),
+ V("springs forward on stiff legs and lands together", "butts lightly, backs away, and hops again"),
+ V("crops a bite with a split upper lip", "chews cud in slow sideways turns"),
+ V("folds its legs beneath a round fleece-covered body", "rests with muzzle tucked against warm wool"),
+ V("lifts its muzzle near {target} and follows", "advances toward {target} with woolly forehead lowered"),
+ V("backs away from {target} and drives forward behind curled horns", "braces narrow hooves near {target} and strikes with its forehead"),
+ V("presses into nearby company until fleeces touch", "nuzzles beneath a nearby woolly cheek"),
+ V("bleats in a wavering, breathy cadence", "answers with a short nasal baa"),
+ V("pulls steadily with fleece bunching over its shoulders", "nudges the task ahead with a woolly brow"));
+
+ Add(
+ voices,
+ "unicorn",
+ V("canters with a single spiral horn cutting a clean line", "steps high on bright cloven hooves with mane flowing"),
+ V("stands poised with horn upright and ears angled forward", "rests one hind hoof while its long mane settles"),
+ V("arches its neck and bounds on light hooves", "rears, paws once, and lands beneath its streaming mane"),
+ V("takes a bite with mobile lips", "chews slowly beneath the shadow of its spiral horn"),
+ V("folds its legs and rests with horn angled safely aside", "lies with its muzzle nestled against a flowing mane"),
+ V("tracks movement near {target} with horn aligned", "surges toward {target} in a light canter"),
+ V("drives at {target} with spiral horn lowered", "rears near {target} and strikes with both forehooves"),
+ V("touches muzzles beneath an arched neck", "combs a nearby mane with careful teeth"),
+ V("nickers in a clear, bell-like trill", "whinnies through a bright rippling cadence"),
+ V("lifts and guides the task with the base of its horn", "pulls in a balanced stride on cloven hooves"));
+
+ Add(
+ voices,
+ "wolf",
+ V("lopes forward with long legs and level back", "trots in an efficient line with tail held low"),
+ V("stands with ears pricked and nose raised", "sits tall on its haunches and watches"),
+ V("bows low on the forelegs before bounding sideways", "wrestles with open jaws and careful pawing"),
+ V("holds the meal between forepaws and tears sideways", "crunches the meal with strong jaws before licking its muzzle"),
+ V("curls tightly with bushy tail over its nose", "lies chest-down with chin between outstretched paws"),
+ V("runs toward {target} with nose low and stride lengthening", "fans out near {target} at an efficient lope"),
+ V("rushes at {target} with jaws open and shoulders braced", "bites at {target}, releases, and circles on quick paws"),
+ V("greets with muzzle licks and a low sweeping tail", "leans flank-first against nearby company"),
+ V("pants out a rough, breathy chuff", "breaks into a rising series of yips and howls"),
+ V("drags the load with jaws set and legs driving", "scrapes and clears with powerful forepaws"));
+
+ Add(
+ voices,
+ "worm",
+ V("extends its front segments and draws the rest forward", "crawls in rippling contractions along its bristled underside"),
+ V("holds its tapered front still while the body contracts", "rests in a loose curve with segments barely shifting"),
+ V("loops its flexible body and straightens in a quick ripple", "coils tightly before easing straight"),
+ V("draws the meal into its mouth opening", "rasps at the meal with steady muscular pulls"),
+ V("settles into a compact coil with segments relaxed", "lies fully extended with only faint body pulses"),
+ V("probes near {target} with a tapered front and rippling muscles", "crawls toward {target} in slow curves"),
+ V("whips near {target} with its front segments and recoils", "braces near {target} and lashes forward"),
+ V("presses along another segmented body", "twines briefly before crawling alongside"),
+ V("makes a faint rasp through contracting segments", "rustles in a quick sequence of dry body ripples"),
+ V("pulls loose material backward beneath its body", "works forward by bracing each band of tiny bristles"));
+ }
+}
diff --git a/IdleSpectator/ActivityVoice.cs b/IdleSpectator/ActivityVoice.cs
index 102ebf8..ee1fec6 100644
--- a/IdleSpectator/ActivityVoice.cs
+++ b/IdleSpectator/ActivityVoice.cs
@@ -88,9 +88,8 @@ public sealed class ProseVoice
public bool BaseIsCiv { get; }
public bool BaseIsHumanoid { get; }
public bool BaseIsAnimal { get; }
- public string FlavorKey { get; }
- public int FlavorOrdinal { get; }
- public string ProfileTag { get; }
+ public string VoiceKey { get; }
+ public ActivityVoiceSource Source { get; }
public bool BaseRecoveryExpected { get; }
public bool BaseRecoverySucceeded { get; }
@@ -106,9 +105,8 @@ public sealed class ProseVoice
bool baseIsCiv,
bool baseIsHumanoid,
bool baseIsAnimal,
- string flavorKey,
- int flavorOrdinal,
- string profileTag,
+ string voiceKey,
+ ActivityVoiceSource source,
bool baseRecoveryExpected,
bool baseRecoverySucceeded)
{
@@ -123,9 +121,8 @@ public sealed class ProseVoice
BaseIsCiv = baseIsCiv;
BaseIsHumanoid = baseIsHumanoid;
BaseIsAnimal = baseIsAnimal;
- FlavorKey = string.IsNullOrEmpty(flavorKey) ? LiveSpeciesId : flavorKey;
- FlavorOrdinal = flavorOrdinal;
- ProfileTag = profileTag ?? "";
+ VoiceKey = voiceKey ?? "";
+ Source = source;
BaseRecoveryExpected = baseRecoveryExpected;
BaseRecoverySucceeded = baseRecoverySucceeded;
}
@@ -144,9 +141,8 @@ public sealed class ProseVoice
false,
false,
false,
- speciesId,
- 0,
"",
+ ActivityVoiceSource.Unknown,
false,
false);
}
diff --git a/IdleSpectator/ActivityVoiceHarness.cs b/IdleSpectator/ActivityVoiceHarness.cs
index 551c06c..7c0e180 100644
--- a/IdleSpectator/ActivityVoiceHarness.cs
+++ b/IdleSpectator/ActivityVoiceHarness.cs
@@ -42,7 +42,22 @@ public static class ActivityVoiceHarness
"patient citrus",
"game",
"through play",
- "in play"
+ "in play",
+ "blank persistence",
+ "seared meal",
+ "sugared meal",
+ "sweet meal",
+ "raw meal",
+ "tough meal",
+ "patient care",
+ "with determination",
+ "playfully",
+ "with delight",
+ "cheerful",
+ "eagerly",
+ "contentedly",
+ "stubborn focus",
+ "stubborn persistence"
};
private static readonly string[] UngroundedTerrainFragments =
@@ -64,9 +79,11 @@ public static class ActivityVoiceHarness
{
List ids = ActivityAssetCatalog.EnumerateLiveActorAssetIds();
var rows = new StringBuilder(32768);
- rows.AppendLine("id\tbase\tkind\tmodifier\tfamily\tbody\teffective\tecology\tordinal\trecovery\tfingerprint");
+ rows.AppendLine("id\tbase\tkind\tmodifier\tfamily\tbody\teffective\tecology\tsource\tvoice_key\trecovery\tfingerprint");
int missing = 0;
+ int missingAuthored = 0;
+ int orphanAuthored = 0;
int defaults = 0;
int emptyProse = 0;
int leaks = 0;
@@ -77,9 +94,15 @@ public static class ActivityVoiceHarness
int vehicleMismatch = 0;
int awkwardProse = 0;
int terrainLeaks = 0;
+ int variantBaseFail = 0;
+ int authoredPhraseFail = 0;
string sample = "";
string vehicleFingerprint = null;
var fingerprints = new Dictionary(StringComparer.Ordinal);
+ var fingerprintsById = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var voicesById = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var liveBases = new HashSet(StringComparer.OrdinalIgnoreCase);
+ var validatedBases = new HashSet(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < ids.Count; i++)
{
@@ -93,6 +116,21 @@ public static class ActivityVoiceHarness
}
ProseVoice voice = ActivityVoiceResolver.Resolve(asset);
+ voicesById[id] = voice;
+ if (voice.AssetKind != ActivityAssetKind.Vehicle)
+ {
+ liveBases.Add(voice.BaseSpeciesId);
+ if (voice.Source != ActivityVoiceSource.AuthoredSpecies
+ || !ActivitySpeciesVoiceCatalog.HasCompleteVoice(voice.BaseSpeciesId))
+ {
+ missingAuthored++;
+ AddSample(ref sample, id + ":voice-source=" + voice.Source);
+ }
+ else if (validatedBases.Add(voice.BaseSpeciesId))
+ {
+ authoredPhraseFail += ValidateAuthoredVoice(voice.BaseSpeciesId, ref sample);
+ }
+ }
if (voice.EffectiveActionTag == ActivityBodyTag.Default
|| string.IsNullOrEmpty(voice.BaseFamily)
|| voice.BaseFamily == ActivityVoiceFamilies.Default)
@@ -125,6 +163,7 @@ public static class ActivityVoiceHarness
int localEmpty;
int localLeaks;
string fingerprint = Fingerprint(id, out localEmpty, out localLeaks);
+ fingerprintsById[id] = fingerprint;
emptyProse += localEmpty;
leaks += localLeaks;
string awkward = FindAwkwardPhrase(fingerprint);
@@ -172,11 +211,40 @@ public static class ActivityVoiceHarness
.Append(voice.BaseBodyTag).Append('\t')
.Append(voice.EffectiveActionTag).Append('\t')
.Append(voice.EcologyTag).Append('\t')
- .Append(voice.FlavorOrdinal).Append('\t')
+ .Append(voice.Source).Append('\t')
+ .Append(voice.VoiceKey).Append('\t')
.Append(voice.BaseRecoveryExpected ? voice.BaseRecoverySucceeded.ToString() : "n/a").Append('\t')
.Append(fingerprint.Replace('\t', ' ')).Append('\n');
}
+ foreach (KeyValuePair pair in voicesById)
+ {
+ ProseVoice voice = pair.Value;
+ if (voice.AssetKind == ActivityAssetKind.Vehicle
+ || pair.Key.Equals(voice.BaseSpeciesId, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (!fingerprintsById.TryGetValue(voice.BaseSpeciesId, out string baseFingerprint)
+ || !fingerprintsById.TryGetValue(pair.Key, out string variantFingerprint)
+ || variantFingerprint.Equals(baseFingerprint, StringComparison.Ordinal))
+ {
+ variantBaseFail++;
+ AddSample(ref sample, pair.Key + ":same-as-base=" + voice.BaseSpeciesId);
+ }
+ }
+
+ List authoredIds = ActivitySpeciesVoiceCatalog.AuthoredIds();
+ for (int i = 0; i < authoredIds.Count; i++)
+ {
+ if (!liveBases.Contains(authoredIds[i]))
+ {
+ orphanAuthored++;
+ AddSample(ref sample, authoredIds[i] + ":orphan-voice");
+ }
+ }
+
int pairFail = PairwiseFailures(ref sample);
bool liveContextOk = VerifyLiveContext(ref sample);
try
@@ -191,6 +259,8 @@ public static class ActivityVoiceHarness
bool passed = ids.Count > 50
&& missing == 0
+ && missingAuthored == 0
+ && orphanAuthored == 0
&& defaults == 0
&& emptyProse == 0
&& leaks == 0
@@ -201,6 +271,8 @@ public static class ActivityVoiceHarness
&& vehicleMismatch == 0
&& awkwardProse == 0
&& terrainLeaks == 0
+ && variantBaseFail == 0
+ && authoredPhraseFail == 0
&& pairFail == 0
&& liveContextOk;
return new ActivityVoiceAuditResult
@@ -208,6 +280,8 @@ public static class ActivityVoiceHarness
Passed = passed,
Detail = "total=" + ids.Count
+ " missing=" + missing
+ + " missingAuthored=" + missingAuthored
+ + " orphanAuthored=" + orphanAuthored
+ " defaults=" + defaults
+ " empty=" + emptyProse
+ " leaks=" + leaks
@@ -218,12 +292,68 @@ public static class ActivityVoiceHarness
+ " vehicleMismatch=" + vehicleMismatch
+ " awkwardProse=" + awkwardProse
+ " terrainLeaks=" + terrainLeaks
+ + " variantBaseFail=" + variantBaseFail
+ + " authoredPhraseFail=" + authoredPhraseFail
+ " pairFail=" + pairFail
+ " liveContextOk=" + liveContextOk
+ " sample='" + sample + "'"
};
}
+ private static int ValidateAuthoredVoice(string baseSpeciesId, ref string sample)
+ {
+ int failures = 0;
+ string label = ActivityAssetCatalog.SpeciesDisplayLabel(baseSpeciesId);
+ for (int i = 0; i < ActivitySpeciesVoice.CoreVerbs.Length; i++)
+ {
+ string verb = ActivitySpeciesVoice.CoreVerbs[i];
+ string[] bag = ActivitySpeciesVoiceCatalog.GetBag(
+ baseSpeciesId,
+ verb,
+ ActivityTerrain.Unknown);
+ if (bag == null || bag.Length < 2)
+ {
+ failures++;
+ AddSample(ref sample, baseSpeciesId + ":" + verb + ":incomplete");
+ continue;
+ }
+
+ for (int p = 0; p < bag.Length; p++)
+ {
+ string phrase = bag[p] ?? "";
+ bool targetMissing = (verb == "hunt" || verb == "fight")
+ && phrase.IndexOf("{target}", StringComparison.Ordinal) < 0;
+ bool contextMissing = !phrase.EndsWith(
+ "{place_at}{job_as}",
+ StringComparison.Ordinal);
+ bool actorLeak = phrase.IndexOf("{actor}", StringComparison.Ordinal) >= 0;
+ bool speciesLeak = !string.IsNullOrEmpty(label)
+ && Regex.IsMatch(
+ phrase,
+ @"\b" + Regex.Escape(label) + @"\b",
+ RegexOptions.IgnoreCase);
+ string awkward = FindAwkwardPhrase(phrase);
+ string terrain = FindPhrase(phrase, UngroundedTerrainFragments);
+ if (targetMissing || contextMissing || actorLeak || speciesLeak
+ || !string.IsNullOrEmpty(awkward) || !string.IsNullOrEmpty(terrain))
+ {
+ failures++;
+ AddSample(
+ ref sample,
+ baseSpeciesId + ":" + verb + ":" + p
+ + ":target=" + !targetMissing
+ + ":context=" + !contextMissing
+ + ":actor=" + actorLeak
+ + ":species=" + speciesLeak
+ + ":awkward=" + awkward
+ + ":terrain=" + terrain);
+ }
+ }
+ }
+
+ return failures;
+ }
+
private static string FindAwkwardPhrase(string fingerprint)
{
return FindPhrase(fingerprint, AwkwardProseFragments);
@@ -275,6 +405,8 @@ public static class ActivityVoiceHarness
bool ok = ctx.Voice != null
&& ctx.Voice.LiveSpeciesId == (actor.asset.id ?? "")
&& ctx.Voice.EffectiveActionTag != ActivityBodyTag.Default
+ && (ctx.Voice.AssetKind == ActivityAssetKind.Vehicle
+ || ctx.Voice.Source == ActivityVoiceSource.AuthoredSpecies)
&& ctx.Terrain == expectedTerrain
&& !string.IsNullOrEmpty(line);
if (!ok)
diff --git a/IdleSpectator/ActivityVoiceProfiles.cs b/IdleSpectator/ActivityVoiceProfiles.cs
deleted file mode 100644
index feff24f..0000000
--- a/IdleSpectator/ActivityVoiceProfiles.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace IdleSpectator;
-
-/// Thin flavor identities for creatures whose voice should be unmistakable.
-public static class ActivityVoiceProfiles
-{
- private static readonly Dictionary Profiles =
- new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- ["dragon"] = "dragon",
- ["fairy"] = "fairy",
- ["cat"] = "cat",
- ["dog"] = "dog",
- ["buffalo"] = "buffalo",
- ["lemon_snail"] = "lemon_snail",
- ["human"] = "human",
- ["elf"] = "elf",
- ["dwarf"] = "dwarf",
- ["orc"] = "orc",
- ["crabzilla"] = "crabzilla",
- ["god_finger"] = "god_finger",
- ["greg"] = "greg",
- ["angle"] = "angle",
- ["cold_one"] = "cold",
- ["snowman"] = "cold",
- ["crystal_sword"] = "crystal"
- };
-
- public static string Resolve(string baseSpeciesId)
- {
- if (string.IsNullOrEmpty(baseSpeciesId))
- {
- return "";
- }
-
- return Profiles.TryGetValue(baseSpeciesId, out string profile) ? profile : "";
- }
-}
diff --git a/IdleSpectator/ActivityVoiceResolver.cs b/IdleSpectator/ActivityVoiceResolver.cs
index 3fdd36e..4103561 100644
--- a/IdleSpectator/ActivityVoiceResolver.cs
+++ b/IdleSpectator/ActivityVoiceResolver.cs
@@ -1,15 +1,10 @@
using System;
-using System.Collections.Generic;
namespace IdleSpectator;
/// The only ActorAsset → prose voice classifier.
public static class ActivityVoiceResolver
{
- private static readonly Dictionary FlavorOrdinals =
- new Dictionary(StringComparer.Ordinal);
- private static int _ordinalLibraryCount = -1;
-
public static ProseVoice Resolve(string speciesId)
{
ActorAsset asset = ActivityAssetCatalog.TryGetActorAsset(speciesId);
@@ -24,8 +19,6 @@ public static class ActivityVoiceResolver
}
string liveId = liveAsset.id ?? "";
- int flavorOrdinal = FlavorOrdinal(liveId);
-
if (IsVehicle(liveId))
{
return Build(
@@ -36,7 +29,6 @@ public static class ActivityVoiceResolver
ActivityBodyTag.Vehicle,
ActivityEcologyTag.Water,
ActivityVoiceFamilies.Special,
- flavorOrdinal,
false,
true);
}
@@ -66,7 +58,6 @@ public static class ActivityVoiceResolver
effective,
ecology,
family,
- flavorOrdinal,
recoveryExpected,
recoverySucceeded);
}
@@ -119,7 +110,6 @@ public static class ActivityVoiceResolver
ActivityBodyTag effective,
ActivityEcologyTag ecology,
string family,
- int flavorOrdinal,
bool recoveryExpected,
bool recoverySucceeded)
{
@@ -128,6 +118,12 @@ public static class ActivityVoiceResolver
ActivityBodyTag baseBody = kind == ActivityAssetKind.Vehicle
? ActivityBodyTag.Vehicle
: ResolveBody(baseAsset, liveAsset, modifier);
+ bool authored = ActivitySpeciesVoiceCatalog.HasCompleteVoice(baseId);
+ ActivityVoiceSource source = kind == ActivityAssetKind.Vehicle
+ ? ActivityVoiceSource.Vehicle
+ : authored
+ ? ActivityVoiceSource.AuthoredSpecies
+ : ActivityVoiceSource.TaxonomyFallback;
return new ProseVoice(
liveId,
baseId,
@@ -140,9 +136,8 @@ public static class ActivityVoiceResolver
baseAsset != null && baseAsset.civ,
baseAsset != null && (baseAsset.is_humanoid || baseAsset.civ),
baseAsset != null && baseAsset.default_animal,
- liveId,
- flavorOrdinal,
- ActivityVoiceProfiles.Resolve(baseId),
+ authored ? ActivitySpeciesVoiceCatalog.VoiceKey(baseId) : baseId,
+ source,
recoveryExpected,
recoverySucceeded);
}
@@ -416,51 +411,6 @@ public static class ActivityVoiceResolver
|| EqualsIgnore(id, "crystal_sword");
}
- private static int FlavorOrdinal(string id)
- {
- EnsureFlavorOrdinals();
- if (!string.IsNullOrEmpty(id) && FlavorOrdinals.TryGetValue(id, out int ordinal))
- {
- return ordinal;
- }
-
- return Math.Abs(StableHash(id)) % 1024;
- }
-
- private static void EnsureFlavorOrdinals()
- {
- List ids = ActivityAssetCatalog.EnumerateLiveActorAssetIds();
- if (ids.Count == _ordinalLibraryCount && FlavorOrdinals.Count == ids.Count)
- {
- return;
- }
-
- ids.Sort(StringComparer.Ordinal);
- FlavorOrdinals.Clear();
- for (int i = 0; i < ids.Count; i++)
- {
- FlavorOrdinals[ids[i]] = i;
- }
-
- _ordinalLibraryCount = ids.Count;
- }
-
- private static int StableHash(string value)
- {
- unchecked
- {
- uint h = 2166136261;
- string s = value ?? "";
- for (int i = 0; i < s.Length; i++)
- {
- h ^= s[i];
- h *= 16777619;
- }
-
- return (int)(h & 0x7fffffff);
- }
- }
-
private static bool EqualsIgnore(string a, string b)
{
return !string.IsNullOrEmpty(a) && a.Equals(b, StringComparison.OrdinalIgnoreCase);
diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs
index f9df1ad..06aaf7e 100644
--- a/IdleSpectator/AgentHarness.cs
+++ b/IdleSpectator/AgentHarness.cs
@@ -1711,6 +1711,15 @@ public static class AgentHarness
detail = $"pending={have} want={want}";
break;
}
+ case "pending_discovery_asset":
+ {
+ string assetId = ResolveAssetId(cmd);
+ bool want = string.IsNullOrEmpty(cmd.value) || ParseBool(cmd.value, true);
+ bool have = SpeciesDiscovery.IsPending(assetId);
+ pass = have == want;
+ detail = $"asset={assetId} pending={have} want={want} total={SpeciesDiscovery.PendingCount}";
+ break;
+ }
case "pending_interest":
{
int want = ParseCountExpect(cmd, defaultValue: 0);
@@ -2572,23 +2581,23 @@ public static class AgentHarness
&& beetlePlay.IndexOf(" the beetle", System.StringComparison.OrdinalIgnoreCase) < 0
&& frogPlay.IndexOf(" the frog", System.StringComparison.OrdinalIgnoreCase) < 0
&& rhinoPlay.IndexOf(" the rhino", System.StringComparison.OrdinalIgnoreCase) < 0
- && (beetlePlay.IndexOf("scuttl", System.StringComparison.OrdinalIgnoreCase) >= 0
- || beetlePlay.IndexOf("skitter", System.StringComparison.OrdinalIgnoreCase) >= 0
- || beetlePlay.IndexOf("buzz", System.StringComparison.OrdinalIgnoreCase) >= 0
- || beetlePlay.IndexOf("tiny", System.StringComparison.OrdinalIgnoreCase) >= 0
- || beetlePlay.IndexOf("busy", System.StringComparison.OrdinalIgnoreCase) >= 0
- || beetlePlay.IndexOf("circuit", System.StringComparison.OrdinalIgnoreCase) >= 0)
+ && (beetlePlay.IndexOf("leg", System.StringComparison.OrdinalIgnoreCase) >= 0
+ || beetlePlay.IndexOf("rocks", System.StringComparison.OrdinalIgnoreCase) >= 0
+ || beetlePlay.IndexOf("wing", System.StringComparison.OrdinalIgnoreCase) >= 0)
&& (frogPlay.IndexOf("hop", System.StringComparison.OrdinalIgnoreCase) >= 0
|| frogPlay.IndexOf("bounc", System.StringComparison.OrdinalIgnoreCase) >= 0
- || frogPlay.IndexOf("leap", System.StringComparison.OrdinalIgnoreCase) >= 0
- || frogPlay.IndexOf("splash", System.StringComparison.OrdinalIgnoreCase) >= 0
- || frogPlay.IndexOf("damp", System.StringComparison.OrdinalIgnoreCase) >= 0
- || frogPlay.IndexOf("jump", System.StringComparison.OrdinalIgnoreCase) >= 0);
+ || frogPlay.IndexOf("pops", System.StringComparison.OrdinalIgnoreCase) >= 0
+ || frogPlay.IndexOf("toe", System.StringComparison.OrdinalIgnoreCase) >= 0)
+ && (rhinoPlay.IndexOf("horn", System.StringComparison.OrdinalIgnoreCase) >= 0
+ || rhinoPlay.IndexOf("stout", System.StringComparison.OrdinalIgnoreCase) >= 0
+ || rhinoPlay.IndexOf("buck", System.StringComparison.OrdinalIgnoreCase) >= 0
+ || rhinoPlay.IndexOf("pivot", System.StringComparison.OrdinalIgnoreCase) >= 0);
bool crabLandOk = !string.IsNullOrEmpty(crabLandPlay)
&& crabLandPlay.IndexOf("water", System.StringComparison.OrdinalIgnoreCase) < 0
&& crabLandPlay.IndexOf("wet", System.StringComparison.OrdinalIgnoreCase) < 0
- && (crabLandPlay.IndexOf("scuttl", System.StringComparison.OrdinalIgnoreCase) >= 0
- || crabLandPlay.IndexOf("sidestep", System.StringComparison.OrdinalIgnoreCase) >= 0);
+ && (crabLandPlay.IndexOf("claw", System.StringComparison.OrdinalIgnoreCase) >= 0
+ || crabLandPlay.IndexOf("pincer", System.StringComparison.OrdinalIgnoreCase) >= 0
+ || crabLandPlay.IndexOf("side", System.StringComparison.OrdinalIgnoreCase) >= 0);
bool fishTerrainOk = !string.IsNullOrEmpty(fishLandPlay)
&& !string.IsNullOrEmpty(fishWaterPlay)
&& fishLandPlay.IndexOf("water", System.StringComparison.OrdinalIgnoreCase) < 0
diff --git a/IdleSpectator/HarnessScenarios.cs b/IdleSpectator/HarnessScenarios.cs
index dc98d7d..8bdae65 100644
--- a/IdleSpectator/HarnessScenarios.cs
+++ b/IdleSpectator/HarnessScenarios.cs
@@ -406,7 +406,7 @@ internal static class HarnessScenarios
Step("d23", "age_current", wait: 2f),
Step("d24", "assert", expect: "would_accept_curiosity", value: "true"),
Step("d25", "discovery_drain"),
- Step("d26", "assert", expect: "pending_discovery", value: "0"),
+ Step("d26", "assert", expect: "pending_discovery_asset", value: "false"),
Step("d27", "assert", expect: "presented", asset: "auto", value: "true"),
Step("d28", "director_run", wait: 1.2f),
Step("d29", "assert", expect: "tip_matches_unit"),
@@ -486,8 +486,8 @@ internal static class HarnessScenarios
Step("act13d", "assert", expect: "activity_prose_grounding"),
Step("act13e", "assert", expect: "activity_prose_library"),
Step("act13f", "assert", expect: "activity_taxonomy_audit"),
- Step("act14", "activity_force", asset: "laughing", label: "Laughing", count: 1),
- Step("act14b", "assert", expect: "activity_log_contains", value: "laugh"),
+ Step("act14", "activity_force", asset: "laughing", label: "Laughing", count: 1, value: "angle"),
+ Step("act14b", "assert", expect: "activity_log_contains", value: "chime"),
Step("act14c", "activity_force", asset: "eating", label: "Taking a meal", count: 1),
Step("act15", "activity_force", asset: "farm", label: "Tends the fields", count: 1),
Step("act16", "activity_force", asset: "hunt", label: "Hunts", count: 1, expect: "Barkley"),
diff --git a/IdleSpectator/SpeciesDiscovery.cs b/IdleSpectator/SpeciesDiscovery.cs
index 46c4f49..d105c3b 100644
--- a/IdleSpectator/SpeciesDiscovery.cs
+++ b/IdleSpectator/SpeciesDiscovery.cs
@@ -35,6 +35,11 @@ public static class SpeciesDiscovery
public static int PendingCount => Pending.Count;
+ public static bool IsPending(string assetId)
+ {
+ return !string.IsNullOrEmpty(assetId) && Pending.Exists(p => p.AssetId == assetId);
+ }
+
public static int DrainedThisSession => _drainedThisSession;
public static void OnSpectatorEnabled()
diff --git a/IdleSpectator/mod.json b/IdleSpectator/mod.json
index 98b0f16..f68a1c2 100644
--- a/IdleSpectator/mod.json
+++ b/IdleSpectator/mod.json
@@ -1,7 +1,7 @@
{
"name": "IdleSpectator",
"author": "dazed",
- "version": "0.12.75",
+ "version": "0.12.79",
"description": "AFK Idle Spectator (I) + Lore (L). Detailed living activity prose by default.",
"GUID": "com.dazed.idlespectator"
}