Add recess quarrels, rare yard and gym fights, and apologies.

Bullies remember a victim by a trait field, a defender can join, staff break up a fight with a reprimand, and skip empty time clears leftover clashes so they cannot last forever.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 11:31:06 +03:00
co-authored by Cursor
parent f45347b335
commit 395107c959
20 changed files with 1504 additions and 23 deletions
+101
View File
@@ -0,0 +1,101 @@
using HSchool.Content;
using HSchool.People;
namespace HSchool.Ai.Tests;
public class ConflictTests
{
[Fact]
public void Enemies_HaveHigherQuarrelChanceThanFriends()
{
var (catalog, _) = Fixtures.Vanilla();
var rules = catalog.BehaviorRules!;
var enemy = Conflict.QuarrelChance(-55, [], catalog, rules, null, "b");
var friend = Conflict.QuarrelChance(55, [], catalog, rules, null, "b");
Assert.True(enemy > friend);
Assert.Equal(0f, friend);
Assert.True(enemy > 0f);
}
[Fact]
public void Bully_KeepsSameVictimTwoDays_WhileTheyStayOnRoster()
{
var (catalog, _) = Fixtures.Vanilla();
Assert.True(catalog.Traits["Bully"].RemembersVictim);
Assert.False(catalog.Traits["HotTempered"].RemembersVictim);
var bully = Person("bully", ["Bully"]);
var first = Person("p-a", []);
var second = Person("p-b", []);
var roster = new[] { bully, first, second };
var day1 = Conflict.ResolveVictim(bully, roster, catalog, seed: 11);
var day2 = Conflict.ResolveVictim(bully, roster, catalog, seed: 99);
Assert.NotNull(day1);
Assert.Equal(day1, day2);
Assert.Equal(day1, bully.BullyVictimId);
}
[Fact]
public void Apology_RaisesOpinion_ButNotAbovePreQuarrel()
{
var (catalog, _) = Fixtures.Vanilla();
var rules = catalog.BehaviorRules!;
const int baseline = 10;
var lost = -Conflict.QuarrelOpinionDelta(rules);
var afterQuarrel = baseline - lost;
var restored = Conflict.ApologyOpinion(afterQuarrel, baseline, lost, rules);
Assert.True(lost > 3);
Assert.True(restored > afterQuarrel);
Assert.True(restored <= baseline);
}
[Fact]
public void Catalog_HasNoHealthNeed()
{
var (catalog, _) = Fixtures.Vanilla();
Assert.False(Conflict.HasHealthNeed(catalog));
Assert.False(catalog.Needs.ContainsKey("Health"));
}
[Fact]
public void Fight_DoesNotBlockLesson_QuarrelDoes()
{
Assert.True(TalkCircles.BlocksOnLesson(TalkActions.Quarrel));
Assert.True(TalkCircles.BlocksOnLesson(TalkActions.QuarrelYard));
Assert.False(TalkCircles.BlocksOnLesson(TalkActions.Fight));
Assert.False(TalkCircles.BlocksOnLesson(TalkActions.FightGym));
}
private static Person Person(string id, string[] traits)
{
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<string, int>(StringComparer.Ordinal),
Choices = new Dictionary<string, string>(StringComparer.Ordinal),
Skills = new Dictionary<string, int>(StringComparer.Ordinal),
Traits = traits,
Needs = new Dictionary<string, float>(StringComparer.Ordinal),
Opinions = new Dictionary<string, int>(StringComparer.Ordinal),
};
}
}