From ed80aa5d08c5b4d3f1c712fe7f46882d34065f83 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 02:57:13 +0300 Subject: [PATCH] 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. --- AGENTS.md | 7 +- docs/design/foundation.md | 28 +++----- docs/phases/reviewed.md | 67 ++++++++++++++++++- .../src/ui/managementPanel.test.ts | 39 +++++++++++ 4 files changed, 119 insertions(+), 22 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3b0e655..0bc6ac6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/docs/design/foundation.md b/docs/design/foundation.md index 9c5a543..7d7c8be 100644 --- a/docs/design/foundation.md +++ b/docs/design/foundation.md @@ -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`; ручная перестановка ждёт того дня, когда паков станет больше трёх. ## Зафиксировано этим разговором diff --git a/docs/phases/reviewed.md b/docs/phases/reviewed.md index a2404ed..783ad89 100644 --- a/docs/phases/reviewed.md +++ b/docs/phases/reviewed.md @@ -519,7 +519,7 @@ Calendar+обеды 20, планировщик 9, TimetableApi 6. ## Срез 5. Жизнь школы - **Фазы:** 18–21 -- **Проверен на:** `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. Фундамент + +- **Фазы:** 22–28 +- **Проверен на:** `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 Ревью нашло семь пунктов; шесть закрыты в этот же день, седьмой снят как несуществующий. diff --git a/src/HSchool.Client/src/ui/managementPanel.test.ts b/src/HSchool.Client/src/ui/managementPanel.test.ts index edb414c..df41a24 100644 --- a/src/HSchool.Client/src/ui/managementPanel.test.ts +++ b/src/HSchool.Client/src/ui/managementPanel.test.ts @@ -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 }), + ), + ); + }); +});