Add AI parent meetings after a class last lesson.

Class teachers rarely keep the homeroom; invited parents walk onto the map, opinions nudge, then parents leave OffCampus. No player schedule API.
This commit is contained in:
Leonid Pershin
2026-08-21 13:33:40 +03:00
parent a57e581f83
commit 3288287da9
22 changed files with 1138 additions and 18 deletions
+34
View File
@@ -153,6 +153,14 @@ public sealed class School : IDisposable
internal int NextSummonOrder { get; set; }
/// <summary>
/// Active parent meetings. Worker thread only — never HTTP. Cleared on morning / skip.
/// </summary>
internal List<ParentMeetingSession> ActiveParentMeetings { get; } = [];
/// <summary>Class ids that already rolled a meeting chance today (pass or fail).</summary>
internal HashSet<string> ParentMeetingRolledToday { get; } = new(StringComparer.Ordinal);
/// <summary>
/// A finished or interrupted circle wrote opinions onto the roster. <see cref="Tick"/>
/// returns this so the worker persists <c>people.json</c> — same seam as morning dress.
@@ -314,6 +322,7 @@ public sealed class School : IDisposable
Clock.JumpTo(next.Value);
TalkCircleSystem.AbandonAll(this);
DirectorSummonSystem.ClearAll(this);
ParentMeetingSystem.ClearAll(this);
ResetDayLog();
var peopleChanged = TryYearlyIntake(before, next.Value);
peopleChanged |= TryApplicantRefresh();
@@ -388,6 +397,7 @@ public sealed class School : IDisposable
ResetDayLog();
// Hung summons do not survive the work morning — same clear as skip empty.
DirectorSummonSystem.ClearAll(this);
ParentMeetingSystem.ClearAll(this);
}
peopleChanged = TryYearlyIntake(before, Clock.Time);
@@ -445,6 +455,29 @@ public sealed class School : IDisposable
public string? DirectorSummonPresenceActionId(string personId) =>
DirectorSummonSystem.PresenceActionId(DirectorSummonPresencePhase(personId));
/// <summary>
/// Card label while walking to / sitting in a parent meeting without a live activity yet.
/// </summary>
public string? ParentMeetingPresenceActionId(string personId)
{
if (!ParentMeetingSystem.IsInMeeting(this, personId))
{
return null;
}
var session = ActiveParentMeetings.FirstOrDefault(row =>
row.TeacherId.Equals(personId, StringComparison.Ordinal)
|| row.AttendeeIds.Contains(personId));
if (session is null)
{
return null;
}
return session.InProgress && session.TeacherId.Equals(personId, StringComparison.Ordinal)
? HSchool.Ai.ParentMeetings.MeetingAction
: HSchool.Ai.ParentMeetings.GoingAction;
}
private void RecordWorldEvents(DateTime before, DateTime after)
{
_worldEvents.AddRange(EventSystem.Detect(this, before, after));
@@ -469,6 +502,7 @@ public sealed class School : IDisposable
}
DirectorSummonSystem.Apply(this);
ParentMeetingSystem.Apply(this);
PersonDayLog.Sync(this);