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
+5 -2
View File
@@ -494,7 +494,8 @@ internal sealed class GameLoopService(
isNew: false,
save.ModIds,
save.Map,
save.NameSetId);
save.NameSetId,
save.Presence);
worker.Start();
try
@@ -541,7 +542,8 @@ internal sealed class GameLoopService(
bool isNew,
IReadOnlyList<string>? modIds,
MapLayout? map,
string? nameSetId) =>
string? nameSetId,
IReadOnlyList<PresenceSnapshot>? presence = null) =>
new(
id,
name,
@@ -552,6 +554,7 @@ internal sealed class GameLoopService(
modIds,
map,
nameSetId,
presence,
_options,
clients,
metrics,
+3
View File
@@ -27,6 +27,8 @@ internal sealed class SchoolSave
public MapLayout? Map { get; init; }
public string? NameSetId { get; init; }
public IReadOnlyList<PresenceSnapshot>? Presence { get; init; }
}
/// <summary>Allocates school ids that survive a process restart.</summary>
@@ -160,6 +162,7 @@ internal sealed class SchoolStore
ModIds = save.ModIds,
Map = save.Map,
NameSetId = save.NameSetId,
Presence = save.Presence,
});
}
catch (Exception ex)
+6
View File
@@ -33,6 +33,7 @@ internal sealed class SchoolWorker
private readonly IReadOnlyList<string>? _modIds;
private readonly MapLayout? _savedMap;
private readonly string? _nameSetId;
private readonly IReadOnlyList<PresenceSnapshot>? _savedPresence;
private readonly Action<int> _onFailed;
private readonly int _id;
@@ -64,6 +65,7 @@ internal sealed class SchoolWorker
IReadOnlyList<string>? modIds,
MapLayout? savedMap,
string? nameSetId,
IReadOnlyList<PresenceSnapshot>? savedPresence,
SimulationOptions options,
ClientRegistry clients,
GameMetrics metrics,
@@ -81,6 +83,7 @@ internal sealed class SchoolWorker
_modIds = modIds;
_savedMap = savedMap;
_nameSetId = nameSetId;
_savedPresence = savedPresence;
_options = options;
_clients = clients;
_metrics = metrics;
@@ -882,6 +885,8 @@ internal sealed class SchoolWorker
school.InstallPeople(roster, seed, nameSetId, applicants);
InstallTimetable(school);
school.ConfigurePresence(_options.SchoolWeekDays, _options.MaxDecisionsPerTick);
school.RestorePresence(_savedPresence);
return generated;
}
@@ -927,6 +932,7 @@ internal sealed class SchoolWorker
ModIds = school.Catalog?.PackIds,
Map = school.Map,
NameSetId = _nameSetId,
Presence = school.CapturePresence(),
});
}
catch (Exception ex)
+1
View File
@@ -23,6 +23,7 @@ builder.Services
.Validate(options => options.SaveIntervalSeconds is > 0 and <= 3600, "Simulation:SaveIntervalSeconds must be between 1 and 3600.")
.Validate(options => options.MonthlyPayrollCap > 0, "Simulation:MonthlyPayrollCap must be positive.")
.Validate(options => options.SchoolWeekDays is >= 5 and <= 7, "Simulation:SchoolWeekDays must be between 5 and 7.")
.Validate(options => options.MaxDecisionsPerTick is > 0 and <= 10_000, "Simulation:MaxDecisionsPerTick must be between 1 and 10000.")
.ValidateOnStart();
builder.Services.AddSingleton<GameCommandQueue>();
+2 -1
View File
@@ -15,6 +15,7 @@
"ModsDirectory": "mods",
"SaveIntervalSeconds": 30,
"MonthlyPayrollCap": 100000,
"SchoolWeekDays": 5
"SchoolWeekDays": 5,
"MaxDecisionsPerTick": 64
}
}
@@ -5,6 +5,7 @@
"seatThing": "StudentDesk",
"defaultSeats": 16,
"works": ["TeachLesson"],
"travelMinutes": 0.5,
},
{
"defName": "Library",
@@ -14,6 +15,7 @@
],
"positions": ["Librarian"],
"works": ["LibraryWork"],
"travelMinutes": 0.5,
},
{
"defName": "ComputerLab",
@@ -23,5 +25,6 @@
{ "key": "computers", "thing": "Computer", "count": 12 },
],
"works": ["TeachLesson"],
"travelMinutes": 0.5,
},
]
@@ -1,5 +1,5 @@
[
// Walkable rooms that exist to connect the graph: lobby, stairs. Empty on purpose.
{ "defName": "EntranceHall" },
{ "defName": "Stairwell" },
{ "defName": "EntranceHall", "travelMinutes": 1 },
{ "defName": "Stairwell", "travelMinutes": 1.5 },
]
@@ -1,2 +1,2 @@
// Empty on purpose: a corridor is a walkable room with no furniture of its own.
{ "defName": "Corridor" }
{ "defName": "Corridor", "travelMinutes": 1.5 }
@@ -4,4 +4,5 @@
{ "key": "benches", "thing": "Bench", "count": 4 },
],
"works": ["PELesson"],
"travelMinutes": 0.5,
}
@@ -7,4 +7,5 @@
],
"positions": ["Principal"],
"works": ["PrincipalOfficeWork", "TeachLesson", "WalkSchool"],
"travelMinutes": 0.5,
}
@@ -7,6 +7,7 @@
],
"positions": ["Secretary"],
"works": ["OfficeWork"],
"travelMinutes": 0.5,
},
{
"defName": "TeachersRoom",
@@ -14,6 +15,7 @@
{ "key": "table", "thing": "DiningTable" },
{ "key": "chairs", "thing": "Chair", "count": 6 },
],
"travelMinutes": 0.5,
},
{
"defName": "Cafeteria",
@@ -23,9 +25,11 @@
],
"positions": ["CafeteriaCook"],
"works": ["ServeLunch"],
"travelMinutes": 0.5,
},
{
"defName": "Restroom",
"travelMinutes": 0.5,
},
{
"defName": "MedicalOffice",
@@ -35,11 +39,13 @@
],
"positions": ["Nurse"],
"works": ["MedicalDuty"],
"travelMinutes": 0.5,
},
{
"defName": "ChangingRoom",
"slots": [
{ "key": "lockers", "thing": "Locker", "count": 12 },
],
"travelMinutes": 0.5,
},
]
@@ -1 +1 @@
{ "defName": "SchoolYard" }
{ "defName": "SchoolYard", "travelMinutes": 3 }
@@ -2,6 +2,7 @@
{
"defName": "Diligent",
"weight": 8,
"commuteMinutes": 4,
"incompatible": ["Lazy", "AbsentMinded"],
"skillModifiers": [
{ "skill": "Mathematics", "offset": 8 },
@@ -47,6 +48,7 @@
"defName": "Lazy",
"weight": 6,
"incompatible": ["Diligent"],
"commuteMinutes": -4,
"skillModifiers": [
{ "skill": "PhysicalEducation", "offset": -8 },
{ "skill": "Mathematics", "offset": -6 },