Fix PE change-clothes interruption and post-PE mode detection.

Add appropriateness tests for staging PE kit, wearing form before gym, and returning to everyday clothes after the lesson.
This commit is contained in:
Leonid Pershin
2026-08-20 06:31:41 +03:00
parent c541156614
commit e461a9f3e4
5 changed files with 112 additions and 11 deletions
+4 -3
View File
@@ -166,9 +166,10 @@ internal static class ActivitySystem
ApparelDresser.RedressPerson(school, person, mode, includeHome: false, todaySubjects: subjects); ApparelDresser.RedressPerson(school, person, mode, includeHome: false, todaySubjects: subjects);
} }
private static bool IsChangeClothes(string actionId) => internal static bool IsChangeClothes(string? actionId) =>
actionId.Equals(ApparelActions.ChangeMale, StringComparison.Ordinal) actionId is not null
|| actionId.Equals(ApparelActions.ChangeFemale, StringComparison.Ordinal); && (actionId.Equals(ApparelActions.ChangeMale, StringComparison.Ordinal)
|| actionId.Equals(ApparelActions.ChangeFemale, StringComparison.Ordinal));
private static int Occupied(School school, string nodeId, string thing) private static int Occupied(School school, string nodeId, string thing)
{ {
+20 -3
View File
@@ -56,10 +56,27 @@ internal static class ApparelPresence
return ApparelDresser.ModeForLesson(current?.Subject); return ApparelDresser.ModeForLesson(current?.Subject);
} }
var next = lessons.Where(lesson => lesson.Period > slot.Index).OrderBy(lesson => lesson.Period).FirstOrDefault(); if (slot.Kind == DaySlotKind.Break)
if (next is not null)
{ {
return ApparelDresser.ModeForLesson(next.Subject); var justEnded = lessons.FirstOrDefault(lesson => lesson.Period == slot.Index);
if (justEnded is not null && ApparelDresser.ModeForLesson(justEnded.Subject) == ApparelMode.Pe)
{
return ApparelMode.Everyday;
}
var next = lessons.Where(lesson => lesson.Period > slot.Index).OrderBy(lesson => lesson.Period).FirstOrDefault();
if (next is not null)
{
return ApparelDresser.ModeForLesson(next.Subject);
}
return ApparelMode.Everyday;
}
var nextLesson = lessons.Where(lesson => lesson.Period > slot.Index).OrderBy(lesson => lesson.Period).FirstOrDefault();
if (nextLesson is not null)
{
return ApparelDresser.ModeForLesson(nextLesson.Subject);
} }
var previous = lessons.Where(lesson => lesson.Period < slot.Index).OrderByDescending(lesson => lesson.Period).FirstOrDefault(); var previous = lessons.Where(lesson => lesson.Period < slot.Index).OrderByDescending(lesson => lesson.Period).FirstOrDefault();
+5 -2
View File
@@ -400,15 +400,18 @@ internal static class PresenceSystem
state, state,
(node, thing) => occupied.GetValueOrDefault((node, thing))); (node, thing) => occupied.GetValueOrDefault((node, thing)));
var changing = activity.IsActive && ActivitySystem.IsChangeClothes(activity.ActionId);
if (decision.WalkTo is not null if (decision.WalkTo is not null
&& !decision.WalkTo.Equals(presence.NodeId, StringComparison.Ordinal) && !decision.WalkTo.Equals(presence.NodeId, StringComparison.Ordinal)
&& activity.IsActive) && activity.IsActive
&& !changing)
{ {
activity = PersonActivity.Idle; activity = PersonActivity.Idle;
} }
intent = decision.Intent; intent = decision.Intent;
if (decision.WalkTo is not null) if (decision.WalkTo is not null && !changing)
{ {
presence = PresenceStepper.StartWalk(presence, walks, decision.WalkTo, headingHome: false); presence = PresenceStepper.StartWalk(presence, walks, decision.WalkTo, headingHome: false);
} }
@@ -55,6 +55,51 @@ public class AppropriatenessSimulationTests
item => item.Location == ItemLocations.Worn && item.Def == "FurCoat"); item => item.Location == ItemLocations.Worn && item.Def == "FurCoat");
} }
[Fact]
public void MorningDress_StagesPeKitForTodaysLesson()
{
var (school, pupilId) = StaffedPeClass();
using (school)
{
MorningDress.Apply(school);
var person = school.Roster!.People.Single(item => item.Id == pupilId);
Assert.Contains(
person.Items,
item => item.Def is "PeShirt" or "PeShorts"
&& item.Location is ItemLocations.Locker or ItemLocations.Bag);
}
}
[Fact]
public void PeBreak_CompletesChangeBeforeLesson()
{
var (school, pupilId) = StaffedPeClass();
using (school)
{
MorningDress.Apply(school);
AdvanceTo(school, new DateTime(2012, 4, 3, 9, 30, 0, DateTimeKind.Utc));
var person = school.Roster!.People.Single(item => item.Id == pupilId);
Assert.Contains(
person.Items,
item => item.Location == ItemLocations.Worn && item.Def == "PeShirt");
}
}
[Fact]
public void AfterPeBreak_ModeExpectsEveryday()
{
var (school, pupilId) = StaffedPeClass();
using (school)
{
MorningDress.Apply(school);
AdvanceTo(school, new DateTime(2012, 4, 3, 10, 12, 0, DateTimeKind.Utc));
var person = school.Roster!.People.Single(item => item.Id == pupilId);
var schoolClass = school.Roster.Classes.First(row => row.PupilIds.Contains(pupilId));
Assert.Equal(ApparelMode.Everyday, ApparelPresence.ModeAt(school, person, schoolClass));
Assert.True(ApparelDresser.CurrentIssues(school, person, ApparelMode.Everyday).HasFlag(ApparelIssue.PeForbidden));
}
}
[Fact] [Fact]
public void PeBreak_PupilReachesOwnChangingRoom() public void PeBreak_PupilReachesOwnChangingRoom()
{ {
@@ -71,6 +116,38 @@ public class AppropriatenessSimulationTests
} }
} }
[Fact]
public void PeLesson_WearsPeThenReturnsToEveryday()
{
var (school, pupilId) = StaffedPeClass();
using (school)
{
MorningDress.Apply(school);
AdvanceTo(school, new DateTime(2012, 4, 3, 9, 35, 0, DateTimeKind.Utc));
var person = school.Roster!.People.Single(item => item.Id == pupilId);
Assert.Contains(
person.Items,
item => item.Location == ItemLocations.Worn && item.Def == "PeShirt");
Assert.Contains(
person.Items,
item => item.Location == ItemLocations.Worn && item.Def == "PeShorts");
Assert.DoesNotContain(
person.Items,
item => item.Location == ItemLocations.Worn
&& item.Def is "Shirt" or "TShirt" or "Dress");
AdvanceTo(school, new DateTime(2012, 4, 3, 10, 25, 0, DateTimeKind.Utc));
person = school.Roster.People.Single(item => item.Id == pupilId);
Assert.DoesNotContain(
person.Items,
item => item.Location == ItemLocations.Worn && item.Def == "PeShirt");
Assert.Contains(
person.Items,
item => item.Location == ItemLocations.Worn
&& item.Def is "Shirt" or "TShirt" or "Dress" or "Skirt" or "Pants" or "Jeans" or "Trousers");
}
}
[Fact] [Fact]
public void Boy_DoesNotEnterGirlsChangingRoom() public void Boy_DoesNotEnterGirlsChangingRoom()
{ {
@@ -201,6 +278,7 @@ public class AppropriatenessSimulationTests
[ [
new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1), new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1),
new LessonPlacement(schoolClass.Id, "PhysicalEducation", "t2", "gym-hall", Day: 1, Period: 2), new LessonPlacement(schoolClass.Id, "PhysicalEducation", "t2", "gym-hall", Day: 1, Period: 2),
new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 3),
], ],
[])); []));
school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 10_000); school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 10_000);
@@ -68,7 +68,7 @@ public class PresenceTests
} }
[Fact] [Fact]
public void PhysicalEducationAfterAClassroomLesson_ReachesTheGymOnTheBreak() public void PhysicalEducationBeforeLesson_GoesToChangingRoomOnTheBreak()
{ {
var (catalog, map) = Vanilla(); var (catalog, map) = Vanilla();
var walks = WalkGraph.Build(catalog, map); var walks = WalkGraph.Build(catalog, map);
@@ -78,9 +78,11 @@ public class PresenceTests
var (school, _, pupilId) = StaffedFirstFloorClass(); var (school, _, pupilId) = StaffedFirstFloorClass();
using (school) using (school)
{ {
AdvanceTo(school, new DateTime(2012, 4, 3, 9, 25, 0, DateTimeKind.Utc)); AdvanceTo(school, new DateTime(2012, 4, 3, 9, 24, 0, DateTimeKind.Utc));
var row = school.CapturePresence().Single(item => item.PersonId == pupilId); var row = school.CapturePresence().Single(item => item.PersonId == pupilId);
Assert.Equal("gym-hall", row.NodeId); var person = school.Roster!.People.Single(item => item.Id == pupilId);
var room = person.Female ? "girls-changing-room" : "boys-changing-room";
Assert.Equal(room, row.NodeId);
Assert.Empty(row.Path); Assert.Empty(row.Path);
} }
} }