Update payroll cap to enhance staffing flexibility and adjust related documentation and tests
ci / server (push) Failing after 10s
ci / client (push) Successful in 14s

- Increased the `MonthlyPayrollCap` from 40,000 to 100,000, allowing for greater hiring capacity and subject assignments.
- Updated relevant documentation to reflect the new payroll cap and its implications for staffing.
- Adjusted tests to validate the new payroll cap and ensure proper functionality in staffing scenarios, including allocation and remaining payroll calculations.
- Enhanced styling in the UI to accommodate the changes in management screens.
This commit is contained in:
Leonid Pershin
2026-08-19 13:09:45 +03:00
parent 25ce26064d
commit 207e7ecdb3
6 changed files with 39 additions and 21 deletions
+1 -1
View File
@@ -119,7 +119,7 @@ Simulation tunables live under the `Simulation` section of
| `ModsDirectory` | `mods` | pack folders; `core` is required | | `ModsDirectory` | `mods` | pack folders; `core` is required |
| `SaveIntervalSeconds` | 30 | rare clock snapshot; not every tick | | `SaveIntervalSeconds` | 30 | rare clock snapshot; not every tick |
| `MinSaveIntervalMilliseconds` | 1000 | shortest gap between saves caused by pause or speed | | `MinSaveIntervalMilliseconds` | 1000 | shortest gap between saves caused by pause or speed |
| `MonthlyPayrollCap` | 40000 | monthly payroll the player may commit; hires and assignments that would exceed it are rejected | | `MonthlyPayrollCap` | 100000 | monthly payroll the player may commit; hires and assignments that would exceed it are rejected |
| `SchoolWeekDays` | 5 | working days from Monday (5 is MonFri; 6 adds Saturday) | | `SchoolWeekDays` | 5 | working days from Monday (5 is MonFri; 6 adds Saturday) |
## What is deliberately missing ## What is deliberately missing
+2 -2
View File
@@ -244,9 +244,9 @@ those lessons will not happen.
```json ```json
{ {
"allocated": 10000, "allocated": 100000,
"payroll": 5000, "payroll": 5000,
"remaining": 5000, "remaining": 95000,
"uncovered": [ "uncovered": [
{ {
"defName": "Mathematics", "defName": "Mathematics",
+27 -1
View File
@@ -63,8 +63,13 @@ body {
justify-content: center; justify-content: center;
} }
/*
* No cap. A reading column has a sensible maximum width; a management screen does not — it is a
* tree, a table and a timetable grid, and every pixel taken away from them is a pixel of scrolling.
* The menu below keeps its column, because a row of cards centred on a wide display reads better.
*/
.screen.game { .screen.game {
max-width: 1500px; max-width: none;
} }
#status { #status {
@@ -585,6 +590,27 @@ body {
text-transform: uppercase; text-transform: uppercase;
} }
/*
* Blocks on a card need a line between them, not just a caption: needs, family and the timetable
* ran together into one wall. The rule hangs off the caption rather than a wrapper because not
* every block has one — the timetable is a bare heading plus a grid.
*
* The faint hairlines inside a block (rows of a pair grid) stay lighter than this one on purpose:
* strong line means a new block, faint line means the next row.
*/
.people__card .people__section-title {
margin-top: 14px;
padding-top: 11px;
border-top: 1px solid var(--border);
}
/* The first block sits right under the name, where a divider would only add noise. */
.people__card > .people__card-meta + .people__section > .people__section-title {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
/* /*
* Name on the left, value on the right, as many columns as the panel is wide. As a bulleted list * Name on the left, value on the right, as many columns as the panel is wide. As a bulleted list
* fourteen skills ran the card past the fold with three quarters of the width empty. * fourteen skills ran the card past the fold with three quarters of the width empty.
+1 -1
View File
@@ -14,7 +14,7 @@
"SavesDirectory": "saves", "SavesDirectory": "saves",
"ModsDirectory": "mods", "ModsDirectory": "mods",
"SaveIntervalSeconds": 30, "SaveIntervalSeconds": 30,
"MonthlyPayrollCap": 40000, "MonthlyPayrollCap": 100000,
"SchoolWeekDays": 5 "SchoolWeekDays": 5
} }
} }
+1 -1
View File
@@ -53,7 +53,7 @@ public sealed class SimulationOptions
/// Monthly payroll the player may commit. Hire and subject assignment that would exceed it /// Monthly payroll the player may commit. Hire and subject assignment that would exceed it
/// are rejected immediately; money itself does not move. /// are rejected immediately; money itself does not move.
/// </summary> /// </summary>
public float MonthlyPayrollCap { get; set; } = 40_000f; public float MonthlyPayrollCap { get; set; } = 100_000f;
/// <summary> /// <summary>
/// Working days from Monday. Five is MonFri; six adds Saturday; seven is every day. /// Working days from Monday. Five is MonFri; six adds Saturday; seven is every day.
@@ -26,9 +26,9 @@ public class StaffingApiTests(AppHostFixture fixture)
var staffing = await GetStaffingAsync(client, school.Id); var staffing = await GetStaffingAsync(client, school.Id);
Assert.Equal(40_000f, staffing.Allocated); Assert.Equal(100_000f, staffing.Allocated);
Assert.Equal(0f, staffing.Payroll); Assert.Equal(0f, staffing.Payroll);
Assert.Equal(40_000f, staffing.Remaining); Assert.Equal(100_000f, staffing.Remaining);
Assert.Equal(12, staffing.Applicants.Count); Assert.Equal(12, staffing.Applicants.Count);
Assert.Empty(staffing.Staff); Assert.Empty(staffing.Staff);
Assert.Contains(staffing.Uncovered, subject => subject.DefName == "Mathematics"); Assert.Contains(staffing.Uncovered, subject => subject.DefName == "Mathematics");
@@ -121,14 +121,13 @@ public class StaffingApiTests(AppHostFixture fixture)
} }
[Fact] [Fact]
public async Task Hire_PastTheCap_IsRejectedWithTheNumbers() public async Task Hire_IdlePool_FitsUnderTheCap()
{ {
using var client = fixture.App.CreateHttpClient("server"); using var client = fixture.App.CreateHttpClient("server");
await SchoolApiTests.ResetAsync(client); await SchoolApiTests.ResetAsync(client);
var school = await SchoolApiTests.CreateAsync(client, "Штат предел", Start); var school = await SchoolApiTests.CreateAsync(client, "Штат предел", Start);
var staffing = await GetStaffingAsync(client, school.Id); var staffing = await GetStaffingAsync(client, school.Id);
ProblemResponse? problem = null;
while (staffing.Applicants.Count > 0) while (staffing.Applicants.Count > 0)
{ {
var applicant = staffing.Applicants[0]; var applicant = staffing.Applicants[0];
@@ -136,22 +135,15 @@ public class StaffingApiTests(AppHostFixture fixture)
$"/api/schools/{school.Id}/staff/hire", $"/api/schools/{school.Id}/staff/hire",
new { personId = applicant.Id, position = "Teacher" }, new { personId = applicant.Id, position = "Teacher" },
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
if (response.StatusCode == HttpStatusCode.Conflict)
{
problem = await response.Content.ReadFromJsonAsync<ProblemResponse>(TestContext.Current.CancellationToken);
break;
}
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
staffing = await response.Content.ReadFromJsonAsync<StaffingResponse>(TestContext.Current.CancellationToken); staffing = await response.Content.ReadFromJsonAsync<StaffingResponse>(TestContext.Current.CancellationToken);
Assert.NotNull(staffing); Assert.NotNull(staffing);
} }
Assert.Equal("payroll-exceeded", problem?.Code); Assert.Equal(100_000f, staffing.Allocated);
Assert.Equal(40_000f, problem?.Allocated); Assert.True(staffing.Payroll > 0f);
Assert.True(problem?.Payroll <= 40_000f); Assert.True(staffing.Payroll <= staffing.Allocated);
Assert.True(problem?.Attempted > 40_000f); Assert.Equal(12, staffing.Staff.Count);
Assert.True(staffing.Staff.Count >= 1);
} }
[Fact] [Fact]