Enhance AI and simulation components with presence management and routing capabilities
ci / server (push) Failing after 3m39s
ci / client (push) Successful in 14s

- Introduced the `HSchool.Ai` project, responsible for routing, day plans, and presence management.
- Updated the `HSchool.Simulation` project to integrate with the new AI functionalities, improving decision-making and presence tracking.
- Added `travelMinutes` to room and territory definitions, ensuring accurate movement calculations within the simulation.
- Enhanced the `School` class to manage presence and implement empty-time skipping functionality.
- Updated documentation to reflect the new AI features and their impact on school simulation.
- Added tests for presence management and routing to ensure robust functionality and reliability.
This commit is contained in:
Leonid Pershin
2026-08-19 16:33:52 +03:00
parent c16c34a83c
commit 4137400621
50 changed files with 1978 additions and 57 deletions
@@ -86,6 +86,52 @@ public class CalendarTests
Assert.Equal(DaySlot.BreakAfter(2), slot);
}
[Fact]
public void NextWorkMorning_FromSaturdayLandsOnMondaySix()
{
var catalog = LoadVanilla();
var next = SchoolDay.NextWorkMorning(catalog, new DateTime(2012, 4, 7, 10, 0, 0, DateTimeKind.Utc), weekDays: 5);
Assert.Equal(new DateTime(2012, 4, 9, 6, 0, 0, DateTimeKind.Utc), next);
}
[Fact]
public void NextWorkMorning_FromTuesdayNightLandsOnThatMorning()
{
var catalog = LoadVanilla();
var next = SchoolDay.NextWorkMorning(catalog, new DateTime(2012, 4, 3, 3, 0, 0, DateTimeKind.Utc), weekDays: 5);
Assert.Equal(new DateTime(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc), next);
}
[Fact]
public void NextWorkMorning_FromTuesdayEveningLandsOnWednesday()
{
var catalog = LoadVanilla();
var next = SchoolDay.NextWorkMorning(catalog, new DateTime(2012, 4, 3, 22, 0, 0, DateTimeKind.Utc), weekDays: 5);
Assert.Equal(new DateTime(2012, 4, 4, 6, 0, 0, DateTimeKind.Utc), next);
}
[Fact]
public void InWorkWindow_SevenOnAWorkday_IsAlreadyOpen()
{
var catalog = LoadVanilla();
var seven = new DateTime(2012, 4, 3, 7, 0, 0, DateTimeKind.Utc);
Assert.True(SchoolDay.IsWorkday(catalog, seven, weekDays: 5));
Assert.True(SchoolDay.InWorkWindow(catalog, seven, weekDays: 5));
}
[Fact]
public void InWorkWindow_DoesNotNeedTeachers()
{
var catalog = LoadVanilla();
var morning = new DateTime(2012, 4, 3, 10, 0, 0, DateTimeKind.Utc);
Assert.True(SchoolDay.InWorkWindow(catalog, morning, weekDays: 5));
}
[Fact]
public void Subject_UnknownRoom_FailsTheCatalog()
{
@@ -106,7 +106,7 @@ public class CatalogLoaderTests
CatalogLoader.CorePackId,
"rooms",
"office",
"""{ "defName": "Office", "slots": [{ "key": "chair", "thing": "Chair" }], "positions": ["Principal"] }"""),
"""{ "defName": "Office", "slots": [{ "key": "chair", "thing": "Chair" }], "positions": ["Principal"], "travelMinutes": 1 }"""),
]));
Assert.Contains("Chair", ex.Message);
@@ -126,4 +126,28 @@ public class CatalogLoaderTests
Assert.Equal("Мебель", catalog.Label("ru", catalog.Things["Chair"]));
Assert.Equal("Chair", catalog.Label("en", catalog.Things["Chair"]));
}
[Fact]
public void ConcreteRoomWithoutTravelMinutes_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(CatalogLoader.CorePackId, "rooms", "office", """{ "defName": "Office" }"""),
]));
Assert.Contains("travelMinutes", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void ConcreteTerritoryWithoutTravelMinutes_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(CatalogLoader.CorePackId, "territories", "yard", """{ "defName": "Yard" }"""),
]));
Assert.Contains("travelMinutes", ex.Message, StringComparison.OrdinalIgnoreCase);
}
}
@@ -48,7 +48,7 @@ public class InheritanceTests
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(CatalogLoader.CorePackId, "rooms", "office", """{ "defName": "Office" }"""),
PackDocuments.Def(CatalogLoader.CorePackId, "rooms", "office", """{ "defName": "Office", "travelMinutes": 1 }"""),
PackDocuments.Def(CatalogLoader.CorePackId, "things", "chair", """{ "defName": "Chair", "parent": "Office" }"""),
]));
@@ -125,14 +125,14 @@ public class MapValidationTests
CatalogLoader.CorePackId,
"territories",
"yard",
abstractYard ? """{ "defName": "Yard", "abstract": true }""" : """{ "defName": "Yard" }"""),
abstractYard ? """{ "defName": "Yard", "abstract": true }""" : """{ "defName": "Yard", "travelMinutes": 1 }"""),
PackDocuments.Def(CatalogLoader.CorePackId, "buildings", "main", """{ "defName": "Main" }"""),
PackDocuments.Def(CatalogLoader.CorePackId, "floors", "floor", """{ "defName": "Floor" }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"rooms",
"office",
"""{ "defName": "Office", "slots": [{ "key": "seat", "thing": "Chair" }], "positions": ["Principal"] }"""),
"""{ "defName": "Office", "slots": [{ "key": "seat", "thing": "Chair" }], "positions": ["Principal"], "travelMinutes": 1 }"""),
];
private static MapLayout MiniMap(
+1 -1
View File
@@ -62,7 +62,7 @@ public class MapViewTests
new ContentDocument(
CatalogLoader.CorePackId,
"defs/territories/yard.jsonc",
"""{ "defName": "Yard" }"""),
"""{ "defName": "Yard", "travelMinutes": 1 }"""),
new ContentDocument(
CatalogLoader.CorePackId,
"localizations/ru.jsonc",
+2 -1
View File
@@ -74,7 +74,8 @@ public class PatchTests
{
"defName": "Office",
"slots": [ { "key": "seat", "thing": "Chair" } ],
"works": ["TeachLesson", "WalkSchool"]
"works": ["TeachLesson", "WalkSchool"],
"travelMinutes": 1
}
"""),
PackDocuments.Patch(
@@ -57,6 +57,13 @@ public class VanillaCoreTests
Assert.DoesNotContain(map.Rooms, room => room.Label is "1A" or "1B" or "2A" or "2B");
Assert.Equal(16, map.Rooms.Single(room => room.Id == "classroom-101").Seats);
Assert.Empty(map.Rooms.Single(room => room.Id == "classroom-101").Slots);
Assert.Equal(0.5f, catalog.Rooms["Classroom"].TravelMinutes);
Assert.Equal(1.5f, catalog.Rooms["Corridor"].TravelMinutes);
Assert.Equal(1.5f, catalog.Rooms["Stairwell"].TravelMinutes);
Assert.Equal(1f, catalog.Rooms["EntranceHall"].TravelMinutes);
Assert.Equal(3f, catalog.Territories["SchoolYard"].TravelMinutes);
Assert.Equal(4, catalog.Traits["Diligent"].CommuteMinutes);
Assert.Equal(-4, catalog.Traits["Lazy"].CommuteMinutes);
}
/// <summary>