using System; using System.Collections.Generic; namespace IdleSpectator; /// Single deterministic action-selection pipeline. public static class ActivityActionComposer { private static readonly Dictionary BeatTemplates = new Dictionary(StringComparer.Ordinal) { ["BehUnloadResources"] = new[] { "unloads {carrying} at {place}", "delivers {carrying} into store{place_at}" }, ["BehThrowResources"] = new[] { "throws {carrying} toward storage", "tosses {carrying} from the pack" }, ["BehSocializeTalk"] = new[] { "talks{with}{place_at}", "shares words{with}{place_at}" }, ["BehTryToSocialize"] = new[] { "tries to socialize{with}{place_at}", "looks for a conversation{with}{place_at}" }, ["BehPlantCrops"] = new[] { "plants crops{place_at}{job_as}", "sets seed in the soil{place_at}" }, ["BehCityActorFertilizeCrop"] = new[] { "fertilizes the crops{place_at}{job_as}", "feeds the soil{place_at}" }, ["BehBuildTarget"] = new[] { "works the build{place_at}{job_as}", "raises construction{place_at}" }, ["BehPoopOutside"] = new[] { "steps aside for a private moment outdoors", "tends to nature's call outside" }, ["BehPoopInside"] = new[] { "steps aside for a private moment indoors", "tends to nature's call inside" }, ["BehBoatFishing"] = new[] { "fishes from the boat{place_at}", "works the waters from aboard{place_at}" }, ["BehBoatCollectFish"] = new[] { "collects the catch aboard{place_at}", "hauls in the boat's catch{place_at}" }, ["BehClaimZoneForCityActorBorder"] = new[] { "claims border land{place_at}", "stakes new ground for the settlement{place_at}" } }; public static string Compose( ActivityContext ctx, string key, string rawFact, bool hasTarget, long subjectId, int lineIndex) { ctx ??= ActivityContext.Empty; ProseVoice voice = EnsureVoice(ctx); if (!string.IsNullOrEmpty(key) && BeatTemplates.TryGetValue(key, out string[] beatBag) && voice.AssetKind != ActivityAssetKind.Vehicle) { return Pick(beatBag, voice, key, subjectId, lineIndex); } string verb = ActivityVerbMap.Resolve(key); if (string.IsNullOrEmpty(verb)) { string factual = ActivityProse.FactualActionPhrase(rawFact, key, hasTarget); factual = ActivityClauseAssembler.StripSpeciesTokensPublic(factual, ctx); if (!string.IsNullOrEmpty(factual)) { return factual; } string patterned = ActivityProse.PatternActionPhrase(key, hasTarget); if (!string.IsNullOrEmpty(patterned)) { return patterned; } string humanized = ActivityProse.HumanizeKeyPublic(key); verb = string.IsNullOrEmpty(humanized) ? "move" : humanized.ToLowerInvariant(); } string[] bag = ActivityActionLexicon.GetBag(voice, verb, hasTarget); return Pick(bag, voice, verb, subjectId, lineIndex); } private static ProseVoice EnsureVoice(ActivityContext ctx) { if (ctx.Voice != null && ctx.Voice.LiveSpeciesId.Equals(ctx.SpeciesId ?? "", StringComparison.Ordinal)) { return ctx.Voice; } ProseVoice voice = ActivityVoiceResolver.Resolve(ctx.SpeciesId); ctx.Voice = voice; ctx.BaseSpeciesId = voice.BaseSpeciesId; ctx.ModifierTag = ActivityVoiceResolver.ModifierName(voice.Modifier); ctx.Family = voice.BaseFamily; ctx.MannerTag = ActivityVoiceResolver.TagName(voice.EffectiveActionTag); ctx.IsCiv = voice.BaseIsCiv; ctx.IsHumanoid = voice.BaseIsHumanoid; ctx.IsAnimal = voice.BaseIsAnimal; return voice; } private static string Pick( string[] bag, ProseVoice voice, string verb, long subjectId, int lineIndex) { if (bag == null || bag.Length == 0) { return "moves along{place_at}{job_as}"; } if (bag.Length == 1) { 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); 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; } } }