namespace HSchool.Simulation;
/// Tunables of the authoritative simulation. Bound from the Simulation config section.
public sealed class SimulationOptions
{
public const string SectionName = "Simulation";
private DateTime _defaultStartDate = new(2012, 3, 31, 6, 0, 0, DateTimeKind.Utc);
/// Fixed simulation steps per second.
public int TickRate { get; set; } = 20;
/// How many schools one player may own at the same time.
public int MaxSchools { get; set; } = 2;
/// How many school workers the process may run at once.
public int MaxSchoolsTotal { get; set; } = 16;
/// Base speed of the game clock: real seconds are multiplied by this many game minutes.
public double GameMinutesPerRealSecond { get; set; } = 1d;
/// Prefilled start of a new school; the client shows it in the creation form.
public DateTime DefaultStartDate
{
get => _defaultStartDate;
// Configuration binding yields Kind=Unspecified, which serializes without a "Z" and makes
// the browser read the date in its own time zone. The game calendar is always UTC.
set => _defaultStartDate = DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
///
/// Directory for per-school save files. Relative paths are resolved against the content root.
///
public string SavesDirectory { get; set; } = "saves";
///
/// Directory of mod packs (core and optional add-ons). Relative paths are resolved
/// against the content root.
///
public string ModsDirectory { get; set; } = "mods";
///
/// How often a running school writes its clock to disk. Create and shutdown write
/// immediately; the tick itself never does.
///
public int SaveIntervalSeconds { get; set; } = 30;
///
/// Shortest gap between two saves caused by pause or speed. A client can send those as fast
/// as the socket allows, and each one used to be a file write on the school's own thread.
///
public int MinSaveIntervalMilliseconds { get; set; } = 1000;
///
/// Monthly payroll the player may commit. Hire and subject assignment that would exceed it
/// are rejected immediately; money itself does not move.
///
public float MonthlyPayrollCap { get; set; } = 100_000f;
///
/// How many people may change destination in one tick. Overflow waits for the next tick
/// instead of being dropped — a queue, not a cutoff.
///
public int MaxDecisionsPerTick { get; set; } = 64;
/// How many scene text fragments a portrait may take. Silent defs do not count.
public int PortraitSceneFragmentLimit { get; set; } = 6;
/// How many scene LoRAs a portrait may add beyond model / preset / shot layers.
public int PortraitSceneLoraLimit { get; set; } = 2;
/// How many scene embeddings a portrait may add beyond model / preset / shot layers.
public int PortraitSceneEmbeddingLimit { get; set; } = 2;
///
/// Working days from Monday. Five is Mon–Fri; six adds Saturday; seven is every day.
///
public int SchoolWeekDays { get; set; } = 5;
/// Length of one fixed step.
public double FixedDeltaTime => 1d / TickRate;
public TimeSpan TickInterval => TimeSpan.FromSeconds(1d / TickRate);
public TimeSpan SaveInterval => TimeSpan.FromSeconds(SaveIntervalSeconds);
public TimeSpan MinSaveInterval => TimeSpan.FromMilliseconds(MinSaveIntervalMilliseconds);
}