Add rain and snow minutes to the morning commute.

Day plans take a precomputed extra; Ai does not know precipitation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 09:40:24 +03:00
co-authored by Cursor
parent fbac890be2
commit ce71a91a67
11 changed files with 189 additions and 16 deletions
+7 -2
View File
@@ -10,6 +10,10 @@ public readonly record struct DayPlan(DateOnly Day, DateTime? AppearAt, DateTime
public bool Comes => AppearAt is not null;
}
/// <summary>
/// Day plans. Rain and snow arrive as already-counted extra minutes — this project does not
/// know precipitation.
/// </summary>
public static class DayPlans
{
public static DayPlan Build(
@@ -20,7 +24,8 @@ public static class DayPlans
Timetable? timetable,
DateTime time,
int weekDays,
int schoolSeed)
int schoolSeed,
int extraCommuteMinutes = 0)
{
ArgumentNullException.ThrowIfNull(catalog);
ArgumentNullException.ThrowIfNull(walks);
@@ -48,7 +53,7 @@ public static class DayPlans
travel = 0f;
}
var slack = SlackMinutes(catalog, person, schoolSeed, day);
var slack = SlackMinutes(catalog, person, schoolSeed, day) + Math.Max(0, extraCommuteMinutes);
var appear = DateTime.SpecifyKind(utc.Date.Add(firstStart.ToTimeSpan()).AddMinutes(-(travel + slack)), DateTimeKind.Utc);
var walkHome = DateTime.SpecifyKind(utc.Date.Add(lastEnd.ToTimeSpan()), DateTimeKind.Utc);
return new DayPlan(day, appear, walkHome, firstRoom);
@@ -522,6 +522,12 @@ internal static class PeopleDefValidator
$"BehaviorDef '{behavior.DefName}' commute slack must be a non-negative range with min ≤ max.");
}
if (behavior.CommuteRainMinutes < 0 || behavior.CommuteSnowMinutes < 0)
{
throw new ContentLoadException(
$"BehaviorDef '{behavior.DefName}' commute rain and snow minutes cannot be negative.");
}
if (behavior.DutyLessonWeight < 0f
|| behavior.DutyTravelWeight < 0f
|| behavior.NeedWeightAtZero < 0f
+9
View File
@@ -285,6 +285,15 @@ public sealed class BehaviorDef : Def
public int CommuteSlackMax { get; init; }
/// <summary>
/// Extra minutes on a rainy morning. Added to slack when the street is wet. A pack without
/// the field adds nothing so the catalog still loads.
/// </summary>
public int CommuteRainMinutes { get; init; }
/// <summary>Same as <see cref="CommuteRainMinutes"/> when the street is snow.</summary>
public int CommuteSnowMinutes { get; init; }
/// <summary>A new goal must beat the current one by this much before the person switches.</summary>
public float SwitchMargin { get; init; }
@@ -10,6 +10,9 @@
// the same seed still arrives at the same minute.
"commuteSlackMin": 0,
"commuteSlackMax": 6,
// Extra minutes on the road when the street is wet. Snow is slower than rain.
"commuteRainMinutes": 3,
"commuteSnowMinutes": 6,
"switchMargin": 0.15,
// Lesson or posted work. Beats leisure (3) and a need that only just crossed
// the threshold; a toilet at zero (20) still leaves class.
+21
View File
@@ -1,3 +1,5 @@
using HSchool.Content;
namespace HSchool.Simulation;
/// <summary>Street precipitation. Below 0 °C the same roll is snow, above it is rain.</summary>
@@ -17,4 +19,23 @@ public readonly record struct OutdoorWeather(float TemperatureC, Precipitation P
Math.Round(TemperatureC * 10d, MidpointRounding.AwayFromZero),
short.MinValue,
short.MaxValue);
/// <summary>
/// Extra commute minutes for today's street. Ai receives this number; it does not see
/// precipitation. A pack without the fields adds nothing.
/// </summary>
public int ExtraCommuteMinutes(BehaviorDef? rules)
{
if (rules is null)
{
return 0;
}
return Precipitation switch
{
Precipitation.Rain => rules.CommuteRainMinutes,
Precipitation.Snow => rules.CommuteSnowMinutes,
_ => 0,
};
}
}
+2 -1
View File
@@ -217,7 +217,8 @@ internal static class PresenceSystem
school.Timetable,
school.Clock.Time,
school.SchoolWeekDays,
school.PeopleSeed);
school.PeopleSeed,
school.Weather.ExtraCommuteMinutes(school.Catalog!.BehaviorRules));
}
school.DecisionQueue.Clear();