Files
h-school/src/HSchool.Ai/Presence.cs
T
Leonid Pershin 4137400621
ci / server (push) Failing after 3m39s
ci / client (push) Successful in 14s
Enhance AI and simulation components with presence management and routing capabilities
- 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.
2026-08-19 16:33:52 +03:00

80 lines
2.5 KiB
C#

namespace HSchool.Ai;
/// <summary>
/// One person's place on the graph. <see cref="NodeId"/> is null when they are off campus.
/// There is no "on an edge" state — remaining minutes are spent occupying the current node.
/// </summary>
public readonly record struct Presence(
string? NodeId,
float RemainingMinutes,
string? DestinationId,
bool HeadingHome,
string[] Path)
{
public static Presence OffCampus { get; } = new(null, 0f, null, false, []);
public bool IsOnCampus => NodeId is not null;
}
/// <summary>
/// Hot-path movement in the library so tests can prove travel time without a world. Simulation
/// copies the same loop onto components and does not call this every tick.
/// </summary>
public static class PresenceStepper
{
public static Presence Advance(Presence presence, WalkGraph walks, float minutes)
{
ArgumentNullException.ThrowIfNull(walks);
if (presence.NodeId is null || minutes <= 0)
{
return presence;
}
var remaining = presence.RemainingMinutes - minutes;
var node = presence.NodeId;
var path = presence.Path;
var index = 0;
while (remaining <= 0 && index < path.Length)
{
node = path[index];
index++;
remaining += walks.TravelMinutes(node);
}
if (remaining < 0)
{
remaining = 0;
}
var leftover = index >= path.Length ? [] : path[index..];
return presence with { NodeId = node, RemainingMinutes = remaining, Path = leftover };
}
public static Presence StartWalk(Presence presence, WalkGraph walks, string destination, bool headingHome)
{
ArgumentNullException.ThrowIfNull(walks);
if (presence.NodeId is null)
{
var path = walks.Path(walks.TerritoryId, destination);
return new Presence(walks.TerritoryId, 0f, destination, headingHome, [.. path]);
}
var hops = walks.Path(presence.NodeId, destination);
return presence with
{
DestinationId = destination,
HeadingHome = headingHome,
Path = [.. hops],
};
}
public static Presence ArriveOffCampus(Presence presence) =>
presence.NodeId is not null
&& presence.HeadingHome
&& presence.Path.Length == 0
&& presence.RemainingMinutes <= 0
&& presence.NodeId.Equals(presence.DestinationId, StringComparison.Ordinal)
? Presence.OffCampus
: presence;
}