using System; namespace IdleSpectator; /// Single deterministic action-selection pipeline. public static class ActivityActionComposer { public static string Compose( ActivityContext ctx, string key, string rawFact, bool hasTarget, long subjectId, int lineIndex) { ctx ??= ActivityContext.Empty; ProseVoice voice = EnsureVoice(ctx); string verb = ActivityVerbMap.Resolve(key); if (string.IsNullOrEmpty(verb)) { // Future unknown tasks still get the mob's authored work voice. // The exhaustive runtime audit blocks every current task from reaching this branch. verb = "work"; } string[] bag = ActivityActionLexicon.GetBag(voice, verb, hasTarget, ctx.Terrain); string phrase = Pick(bag, subjectId, lineIndex); return ActivityModifierVoiceOverlay.Apply(phrase, 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, 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 actorVariation = (int)((subjectId ^ (lineIndex * 397L)) & 1L); int index = actorVariation % Math.Min(2, bag.Length); return bag[index]; } }