Sample outdoor weather from the climate preset so people can freeze and the clock can show it.

Protocol v8 adds tenths of a °C and precipitation to the clock frame; warmth drains from insulation versus place temperature.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 03:49:40 +03:00
co-authored by Cursor
parent d8f4958167
commit 8e7ab46e79
40 changed files with 1008 additions and 39 deletions
+43 -4
View File
@@ -78,9 +78,12 @@ public sealed class School : IDisposable
/// <summary>Country used to generate this school's people. Needed again on 1 September.</summary>
public string? CountryId { get; private set; }
/// <summary>Climate preset rolled at birth. Phase 32 reads it; it cannot change on a live school.</summary>
/// <summary>Climate preset rolled at birth. Weather reads it; it cannot change on a live school.</summary>
public string? ClimatePresetId { get; private set; }
/// <summary>Street temperature and precipitation last committed for the clock and warmth.</summary>
public OutdoorWeather Weather { get; private set; } = OutdoorWeather.None;
/// <summary>Skill everyone generated for this school speaks natively.</summary>
public string? NativeLanguage { get; private set; }
@@ -132,11 +135,12 @@ public sealed class School : IDisposable
ClimatePresetId = climatePresetId;
NativeLanguage = nativeLanguage;
Applicants = applicants;
RosterSpawner.Spawn(World, roster);
RosterSpawner.Spawn(World, roster, Catalog);
PlanDay = null;
LastDecisionSlot = null;
Plans.Clear();
DecisionQueue.Clear();
SyncWeather(force: true);
}
public bool TryStartAction(string personId, string actionId)
@@ -208,6 +212,7 @@ public sealed class School : IDisposable
PlanDay = null;
LastDecisionSlot = null;
NeedDecay.Apply(World, Catalog, (next.Value - before).TotalMinutes);
SyncWeather(force: true);
return new SkipEmptyResult(SkipEmptyError.None, next.Value, peopleChanged);
}
@@ -224,7 +229,7 @@ public sealed class School : IDisposable
Roster = roster;
Applicants = applicants;
var snapshot = PresenceSystem.Capture(this);
RosterSpawner.Replace(World, roster);
RosterSpawner.Replace(World, roster, Catalog);
PresenceSystem.Restore(this, snapshot);
TimetableDirty = true;
}
@@ -274,16 +279,50 @@ public sealed class School : IDisposable
if (Catalog is not null)
{
var below = PresenceSystem.BelowThreshold(this);
SyncWeather(force: false);
NeedDecay.Apply(World, Catalog, gameMinutes);
WarmthDecay.Apply(this, gameMinutes);
PresenceSystem.EnqueueNewlyUrgent(this, below);
PresenceSystem.DrainDecisions(this);
LessonLearningSystem.Apply(this, gameMinutes);
}
}
else
{
SyncWeather(force: false);
}
return peopleChanged;
}
/// <summary>
/// Recomputes the street from the preset, seed and current time. Commits when the clock
/// tenths or precipitation change, so warmth does not jitter every tick. A skip must force
/// the morning sample — yesterday's evening must not stick.
/// </summary>
public void SyncWeather(bool force)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var next = EvaluateWeather();
if (force || next.Tenths != Weather.Tenths || next.Precipitation != Weather.Precipitation)
{
Weather = next;
}
}
private OutdoorWeather EvaluateWeather()
{
if (Catalog is null
|| ClimatePresetId is null
|| !Catalog.ClimatePresets.TryGetValue(ClimatePresetId, out var preset)
|| preset.Abstract)
{
return OutdoorWeather.None;
}
return WeatherSampler.Sample(preset, PeopleSeed, Clock.Time);
}
private bool TryYearlyIntake(DateTime before, DateTime after)
{
if (Roster is null || Catalog is null || CountryId is null)
@@ -301,7 +340,7 @@ public sealed class School : IDisposable
if (changed)
{
var snapshot = PresenceSystem.Capture(this);
RosterSpawner.Replace(World, Roster);
RosterSpawner.Replace(World, Roster, Catalog);
PresenceSystem.Restore(this, snapshot);
TimetableDirty = true;
}