Phase 40: clock tempo at 1 min/s, x5/x10 heavy stride.

This commit is contained in:
Leonid Pershin
2026-08-20 07:02:24 +03:00
parent 2a55a025f4
commit 9199ea36d2
16 changed files with 192 additions and 57 deletions
+20 -2
View File
@@ -6,8 +6,8 @@ namespace HSchool.Simulation;
/// </summary>
public static class ClockSpeed
{
/// <summary>×½, ×1, ×2, ×3, ×4.</summary>
public static ReadOnlySpan<double> Multipliers => [0.5d, 1d, 2d, 3d, 4d];
/// <summary>×½, ×1, ×2, ×5, ×10.</summary>
public static ReadOnlySpan<double> Multipliers => [0.5d, 1d, 2d, 5d, 10d];
/// <summary>Index of ×1, the speed a school starts at.</summary>
public const int DefaultIndex = 1;
@@ -18,4 +18,22 @@ public static class ClockSpeed
/// <summary>Multiplier for a validated index; out-of-range values fall back to ×1.</summary>
public static double MultiplierAt(int index) => IsValid(index) ? Multipliers[index] : Multipliers[DefaultIndex];
/// <summary>
/// Calendar impulses between heavy-system ticks. ×5 and ×10 stride so walking and needs keep
/// one game minute per heavy step instead of twenty tiny ones per second.
/// </summary>
public static int HeavySystemsStride(int speedIndex) => speedIndex switch
{
3 => 4,
4 => 2,
_ => 1,
};
/// <summary>
/// Game minutes passed into presence, needs and learning on a heavy tick. High speeds use a
/// fixed one-minute quantum; lower speeds pass the impulse's own advance.
/// </summary>
public static double HeavyGameMinutes(int speedIndex, double impulseMinutes) =>
speedIndex is 3 or 4 ? 1d : impulseMinutes;
}
+40 -16
View File
@@ -116,6 +116,12 @@ public sealed class School : IDisposable
internal int DecisionBudget { get; set; }
internal int HeavySystemsInvocations { get; private set; }
internal double LastHeavyGameMinutes { get; private set; }
private int _impulseCounter;
public int PendingDecisionCount => DecisionQueue.Count;
/// <summary>
@@ -303,24 +309,14 @@ public sealed class School : IDisposable
LastDecisionSlot = null;
}
PresenceSystem.Apply(this, gameMinutes);
foreach (var id in ActivitySystem.Apply(this, gameMinutes))
{
PresenceSystem.Enqueue(this, id);
}
SyncWeather(force: false);
PersonDayLog.Sync(this);
if (Catalog is not null)
_impulseCounter++;
var stride = ClockSpeed.HeavySystemsStride(Clock.SpeedIndex);
if (_impulseCounter % stride == 0)
{
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);
peopleChanged |= ApparelWear.Apply(this, gameMinutes, before);
var heavyMinutes = ClockSpeed.HeavyGameMinutes(Clock.SpeedIndex, gameMinutes);
peopleChanged |= ApplyHeavySystems(heavyMinutes);
}
}
else
@@ -331,6 +327,34 @@ public sealed class School : IDisposable
return peopleChanged;
}
private bool ApplyHeavySystems(double gameMinutes)
{
HeavySystemsInvocations++;
LastHeavyGameMinutes = gameMinutes;
var peopleChanged = false;
PresenceSystem.Apply(this, gameMinutes);
foreach (var id in ActivitySystem.Apply(this, gameMinutes))
{
PresenceSystem.Enqueue(this, id);
}
PersonDayLog.Sync(this);
if (Catalog is not null)
{
var below = PresenceSystem.BelowThreshold(this);
NeedDecay.Apply(World, Catalog, gameMinutes);
WarmthDecay.Apply(this, gameMinutes);
PresenceSystem.EnqueueNewlyUrgent(this, below);
PresenceSystem.DrainDecisions(this);
LessonLearningSystem.Apply(this, gameMinutes);
peopleChanged |= ApparelWear.Apply(this, gameMinutes, Clock.Time.AddMinutes(-gameMinutes));
}
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
+1 -1
View File
@@ -14,7 +14,7 @@ public sealed class SimulationOptions
public int MaxSchools { get; set; } = 6;
/// <summary>Base speed of the game clock: real seconds are multiplied by this many game minutes.</summary>
public double GameMinutesPerRealSecond { get; set; } = 5d;
public double GameMinutesPerRealSecond { get; set; } = 1d;
/// <summary>Prefilled start of a new school; the client shows it in the creation form.</summary>
public DateTime DefaultStartDate