using HSchool.Content; using HSchool.People; namespace HSchool.Ai.Tests; public class WhisperTests { [Fact] public void BlocksOnLesson_AllowsWhisper_NotChat() { Assert.True(TalkCircles.BlocksOnLesson(TalkActions.Chat)); Assert.True(TalkCircles.BlocksOnLesson(TalkActions.StaffChat)); Assert.True(TalkCircles.BlocksOnLesson(TalkActions.PhoneChat)); Assert.True(TalkCircles.BlocksOnLesson(TalkActions.TeacherTalk)); Assert.False(TalkCircles.BlocksOnLesson(TalkActions.Whisper)); } [Fact] public void FixedSeed_DoesNotCatchEveryWhisperWithTeacherChance() { var (catalog, _, _) = World(); var rules = catalog.BehaviorRules!; var caught = 0; const int attempts = 40; for (var i = 0; i < attempts; i++) { var roll = TalkCircles.Roll01(Seed.Mix(1, i, Seed.WhisperSalt)); if (TalkCircles.WhisperCaught(roll, rules.WhisperCatchChance, rules.WhisperCatchMax, 1f)) { caught++; } } Assert.True(caught > 0); Assert.True(caught < attempts); Assert.True(rules.WhisperCatchMax < 1f); } [Fact] public void CatchChance_NeverReachesOne_EvenWithLoudTraits() { var (catalog, _, _) = World(); var multiplier = TalkCircles.WhisperCatchMultiplier(["Outgoing", "Leader"], catalog); Assert.True(multiplier > 1f); var rules = catalog.BehaviorRules!; const int attempts = 80; var missed = 0; for (var i = 0; i < attempts; i++) { var roll = TalkCircles.Roll01(Seed.Mix(2, i, Seed.WhisperSalt)); if (!TalkCircles.WhisperCaught(roll, rules.WhisperCatchChance, rules.WhisperCatchMax, multiplier)) { missed++; } } Assert.True(missed > 0); } [Fact] public void HighPedagogy_AfterPraise_PlusesOpinionMoreThanLow() { var (catalog, _, _) = World(); var rules = catalog.BehaviorRules!; var topic = catalog.Topics[TeacherTopics.Praise]; var from = Person("pupil", ["RussianLanguage"]); var to = Person("teacher", ["RussianLanguage"]); var raw = TalkCircles.OpinionDelta( from, to, topic, 50f, sharedLanguage: true, TalkActions.TeacherTalk, ApparelIssue.None, rules, catalog); Assert.True(raw > 0); var high = TalkCircles.ScaleByPedagogy(raw, 90f, rules); var low = TalkCircles.ScaleByPedagogy(raw, 20f, rules); Assert.True(high > low); } [Fact] public void BoundToLesson_SocialZero_StartsWhisperNotChat() { var (catalog, map, walks) = World(); var classroom = map.Rooms.First(room => room.Def == "Classroom").Id; var decision = DecisionPlanner.Decide( catalog, map, walks, new ActorState( classroom, classroom, false, false, true, false, false, true, classroom, Needs(social: 0f), Intent.None, Talk: new TalkPlannerContext( "p1", new Dictionary(), new Dictionary(), "c1", false, false, catalog.BehaviorRules)), (_, _) => 0); Assert.Equal(TalkActions.Whisper, decision.StartAction); Assert.NotEqual(TalkActions.Chat, decision.StartAction); } [Fact] public void PickTopic_OnLesson_OnlyWhisperTopics() { var (catalog, _, _) = World(); var picker = Person("a", ["RussianLanguage"]); for (var seed = 0; seed < 30; seed++) { var id = TalkCircles.PickTopic(catalog, picker, age: 14, seed, whisperOnLesson: true); Assert.NotNull(id); Assert.True(catalog.Topics[id!].WhisperOnLesson); } } private static Dictionary Needs(float social = 1f) => new(StringComparer.Ordinal) { ["Social"] = social, ["Hunger"] = 1f, ["Sleep"] = 1f, ["Toilet"] = 1f, }; private static (DefCatalog Catalog, MapLayout Map, WalkGraph Walks) World() { var (catalog, map) = Fixtures.Vanilla(); return (catalog, map, WalkGraph.Build(catalog, map)); } private static Person Person(string id, string[] languages) { var skills = languages.ToDictionary(skill => skill, _ => 60, StringComparer.Ordinal); skills["Communication"] = 50; var cases = new CaseTable { Nom = "A", Gen = "A", Dat = "A", Acc = "A", Ins = "A", Pre = "A", }; return new Person { Id = id, FamilyId = "f1", Female = false, BirthDate = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc), Name = new PersonName("A", "B", "C", cases, cases, cases), IsStudent = true, IsStaff = false, IsParent = false, Numbers = new Dictionary(StringComparer.Ordinal), Choices = new Dictionary(StringComparer.Ordinal), Skills = skills, Traits = [], Needs = new Dictionary(StringComparer.Ordinal), Opinions = new Dictionary(StringComparer.Ordinal), }; } }