Update AGENTS.md to correct the behavior of presence frames during school pauses, clarify the design documentation regarding mod load order, and add a new test for uncovered teaching positions in the management panel. Enhance reviewed phase details for accuracy and completeness.
ci / server (push) Failing after 3m48s
ci / client (push) Successful in 42s

This commit is contained in:
Leonid Pershin
2026-08-20 02:57:13 +03:00
parent cf086600c3
commit ed80aa5d08
4 changed files with 119 additions and 22 deletions
+4 -3
View File
@@ -176,6 +176,7 @@ say so explicitly in the change description.
one machine and red on a clean clone. Assert a property, not the first pupil of school 1.
- **Host tests share one server and one save folder.** Start each test by clearing the list
through the API (`SchoolApiTests.ResetAsync`).
- **Presence frames do not arrive at all while a school is paused.** They are broadcast only
after a tick step, and a paused clock produces none. Opening still sends one snapshot; a test
that waits for presence on pause will hang.
- **A paused school still emits presence frames.** The worker loop ticks the mailbox even when
the clock does not advance, so occupancy (often empty) and lesson labels from the timetable
keep going out. Opening sends one extra snapshot. A test that waits for *people* on pause
will hang; a test that waits for a lesson label will not.
+10 -18
View File
@@ -1,11 +1,7 @@
# Фундамент: паки, числа, сид, версия и золотые файлы
Договорённость на срез после жизни школы. Не текущий код — то, что решено сделать.
Срез не добавляет игроку ни одной новой кнопки. Он берёт то, что ревью нашло дешёвым и заметным,
и доводит до конца: мод становится настоящим объектом, числа поведения уезжают из кода в данные,
сид перестаёт быть идентификатором школы, у сейва появляется читаемая версия, а регрессии, которые
сейчас ловятся только глазами, начинают ловиться тестом.
Договорённость на срез после жизни школы. Срез закрыт фазами 22–28; таблица внизу — решения.
Абзацы «почему сейчас» ниже описывают дыры, которые срез закрыл, а не текущий код.
## Почему сейчас
@@ -16,17 +12,13 @@
маленький скучный пак на диске, чтобы last-wins, патчи и дорога «игрок выбрал мод» проверялись
настоящей папкой, а не документами в памяти.
**Числа поведения живут в коде вопреки [`ai.md`](ai.md).** Там записано: «Числа поведения —
отдельный деф правил, как `StaffingDef` у штата». В `BehaviorDef` уехали порог нужды, скорость
обучения и разброс на дорогу, а веса целей остались константами в `Decision.cs`. Пока они там, мод
не может перебалансировать поведение, не написав кода, — а это ровно то обещание, ради которого
заводились дефы.
**Числа поведения уехали в `BehaviorDef`.** Порог нужды, обучение, разброс на дорогу и веса целей
(урок, переход, нужда на нуле, обед) — данные. Каталог без дефа падает на константы кода, а не
ломается. Пак может поднять `lunchWeight` выше урока без форка.
**Две регрессии ловятся руками.** Генератор людей переписывали дважды за одну сессию ревью, а
тест на детерминизм сравнивает два прогона *в одном процессе*: он не заметит, если поменяется сам
порядок бросков. [`people.md`](people.md) называет отпечаток «единственным способом поймать
регрессию в генераторе». Так же с сейвом: поле `nativeLanguage` добавили без бампа формата, и то,
что старые сейвы грузятся, держится на комментарии в коде.
**Золотые файлы ловят то, что раньше ловили глазами.** Отпечаток ростера — файл в фикстурах.
Сейв текущего формата и сейв без `nativeLanguage` грузятся тестом хоста. Формат новее своего
школу не стартует и файл не трогает.
## Удостоверение пака
@@ -152,8 +144,8 @@
- Не даёт менять набор модов у живой школы. Каталог по-прежнему замерзает на работнике.
- Не кладёт `defName` комнаты в снимок карты. Клиент смог бы стилизовать типы помещений от модов,
но это версия протокола и правка в трёх местах — не «дёшево», значит не сюда.
- Не заводит UI для порядка модов. Порядок — алфавит плюс зависимости; ручная перестановка ждёт
того дня, когда паков станет больше трёх.
- Не заводит UI для порядка модов. Порядок — выбор игрока плюс устойчивая топологическая
сортировка по `requires`; ручная перестановка ждёт того дня, когда паков станет больше трёх.
## Зафиксировано этим разговором
+66 -1
View File
@@ -519,7 +519,7 @@ Calendar+обеды 20, планировщик 9, TimetableApi 6.
## Срез 5. Жизнь школы
- **Фазы:** 1821
- **Проверен на:** `450495f`, 2026-08-20 (первичная проверка — `21d79cb`; предыдущая перепроверка — `5a00ad7`)
- **Проверен на:** `cbe739a`, 2026-08-20 (первичная проверка — `21d79cb`; предыдущая перепроверка — `5a00ad7`; дерево до коммита правок — `450495f`)
- **Пути:** `src/HSchool.Ai`, `src/HSchool.Simulation/{PresenceSystem,ActivitySystem,LessonLearningSystem,School}.cs`,
`src/HSchool.Protocol` (кадр `Presence`, пропуск в кадре часов),
`src/HSchool.Server/Game/PresenceFrame.cs`, `src/HSchool.Client/src/ui/gameScreen.ts`,
@@ -653,6 +653,71 @@ Calendar+обеды 20, планировщик 9, TimetableApi 6.
Ai 29, Presence+Decision+Activity+NeedDecay+Lunch 31, Presence_RoundTrips 1.
## Срез 6. Фундамент
- **Фазы:** 2228
- **Проверен на:** `cbe739a`, 2026-08-20
- **Пути:** `src/HSchool.Content` (`PackManifest`, `PackLoadOrder`, `BehaviorDef`),
`src/HSchool.Server/mods/{core/pack.jsonc,example}`, `src/HSchool.Server/Game/SchoolStore.cs`,
`src/HSchool.Server/Api/DevEndpoints.cs`, `src/HSchool.Server/Game/SchoolDumpReader.cs`,
`src/HSchool.People/RosterJson.cs`, `src/HSchool.Client/src/ui/*.test.ts`,
`tests/HSchool.Content.Tests/{PackIdentityTests,ExamplePackTests,BehaviorDefTests}.cs`,
`tests/HSchool.People.Tests/GoldenRosterTests.cs`,
`tests/HSchool.AppHost.Tests/{ExamplePackTests,GoldenSaveTests,SchoolSeedTests,ServiceabilityTests}.cs`
- **Итог:** все тесты из списков семи фаз на месте; дописан один клиентский; исправлен ложный
gotcha про паузу; `foundation.md` приведён к закрытому срезу. Хостовые тесты сверены по коду,
не прогоном — Aspire держит DLL.
Что подтверждено:
- **Фаза 22.** Пак без `pack.jsonc` (id вместо названия), подписи `?lang=`, `core` с манифестом,
`400 missing-mod` с id недостающего, зависимость грузится раньше зависимого, цикл — `mod-cycle`,
def без подписи предупреждает и грузится. Порядок — устойчивая топология поверх выбора игрока,
не алфавит; разрешённый список в ответе создания и в сейве.
- **Фаза 23.** `mods/example` на диске (`pack.jsonc`, локали, черты, набор имён, патч, комната,
README). Копируется в вывод тестов как `core`. `GET /api/catalog?mods=example`, create+reload,
патч только с паком, last-wins, карта пака валидна. Ваниль без выбора не меняется.
- **Фаза 24.** Веса в `BehaviorDef` с комментариями, что перевешивают. Таблица ванильных решений,
пак с обедом выше урока уводит класс, каталог без дефа на константах, отрицательный вес роняет
загрузку.
- **Фаза 25.** `golden/roster.txt` и первая несовпавшая строка; правило в README рядом. Сейв
текущего формата и сейв без `nativeLanguage`. Кривая голода `Hunger_FollowsTheSchoolDayCurve`.
`PrimarySchool` требует трёх, второй учитель не закрывает. `teachersShort` в API и в `t(...)`.
- **Фаза 26.** Сид — поле школы, не id. Тот же сид — те же люди; без сида после reload тот же
состав; золотой сейв `seed == id` грузится; переданный сид в списке. На экране школы и в create.
- **Фаза 27.** Format новее — школа не стартует, файл нетронут. Текущий грузится. Дамп через
снимок + мейлбокс (`DumpSchool` → работник), не в `World` с HTTP. `404 unknown-school`.
`HSchool.Simulation.Tests` без параллельности. Дамп в `docs/protocol.md` за тем же
переключателем, что reload.
- **Фаза 28.** `happy-dom` на экранах; фильтры/пейджер, create (`core` нельзя снять, сброс карты,
busy submit), сетка (pin-rejected / no-teacher / unknown-lesson), предел фонда текстом.
Политика в `AGENTS.md`. `npm test` — 46 тестов, ~5 с.
- **Инварианты.** `Simulation` без `DateTime.Now`. Протокол не бампили. Сид в хостовых тестах,
которым нужен состав, передаётся явно.
Дописано:
- `managementPanel.test.ts` — непокрытый предмет показывает, скольких учителей не хватает
(`staffUncoveredShort` с `n: 3`). Задача фазы 25 была в коде без теста экрана.
Исправлено:
- `AGENTS.md` утверждал, что на паузе кадры присутствия не приходят вовсе. Это ложь: работник
крутит цикл и шлёт occupancy + подписи урока из таблицы. Фаза 27 записала заблуждение ревью.
Строка приведена к факту (тест, который ждёт *людей* на паузе, зависнет; тест на подпись урока —
нет).
- В `design/foundation.md` «порядок — алфавит плюс зависимости» расходился с кодом (порядок
игрока + топология). «Почему сейчас» про веса в `Decision.cs` устарел — срез это закрыл.
Замечено рядом:
- `Dump_ReturnsPeopleNodesAndTimetable` принимает `NodeId is not null || Needs.Count > 0`.
Нужды есть у всех, так что узлы на кампусе не проверяются. Дамп поле `nodeId` отдаёт; критерий
«кто где стоит» руками. Усиливать до явки — соседний срез 5, не здесь.
- Срез 7 (одежда) в работе: фазы 29–30 🔄, 31–37 ⬜. В журнал как закрытый не писать.
Content 17, People 2, Ai 3, Hunger curve 1, клиент 46/46. AppHost не гонялся.
## Что из журнала закрыто, 2026-08-19
Ревью нашло семь пунктов; шесть закрыты в этот же день, седьмой снят как несуществующий.
@@ -165,3 +165,42 @@ describe('ManagementPanel payroll cap', () => {
);
});
});
describe('ManagementPanel uncovered', () => {
beforeEach(() => {
setLocale('en');
vi.mocked(fetchStaffing).mockReset();
vi.mocked(fetchTimetable).mockReset();
vi.mocked(fetchStaffing).mockResolvedValue({
...staffing(),
applicants: [],
uncovered: [
{
defName: 'PrimarySchool',
label: 'Primary',
gradeMin: 1,
gradeMax: 4,
hoursPerWeek: 80,
teachersShort: 3,
},
],
});
vi.mocked(fetchTimetable).mockResolvedValue(timetable());
});
afterEach(() => {
document.body.replaceChildren();
setLocale(initialLocale);
});
it('shows how many teachers a subject still needs', async () => {
const panel = new ManagementPanel();
document.body.append(panel.listElement, panel.cardElement);
panel.show(2);
await vi.waitFor(() =>
expect(panel.listElement.textContent).toContain(
t('staffUncoveredShort', { label: 'Primary', min: 1, max: 4, n: 3 }),
),
);
});
});