- Updated `PeopleApiTests` to correctly handle family structures, ensuring tests account for single-parent scenarios and sorting behavior for surnames. - Added a new test to validate roster integrity after the yearly intake, confirming that the composition reflects changes post-intake. - Revised assertions to ensure accurate comparisons of student and family data, improving test reliability. - Enhanced documentation within tests to clarify the purpose and expected outcomes of new functionalities. - Addressed discrepancies between design and implementation regarding the layout of the people management interface.
178 lines
18 KiB
Markdown
178 lines
18 KiB
Markdown
# Проверенные срезы
|
||
|
||
Журнал ревью. Фаза с ✅ в [`README.md`](README.md) — заявка автора; строка здесь — проверенный
|
||
факт: каждое обещание фазы сверено с кодом и тестом, найденное дописано или исправлено.
|
||
|
||
Коммит в «Проверен на» делает журнал самопроверяющимся. Срез перестаёт считаться проверенным,
|
||
как только `git log <коммит>..HEAD -- <пути>` перестаёт быть пустым — поэтому «Пути» обязательны.
|
||
|
||
## Срез 1. Оболочка и карта
|
||
|
||
- **Фазы:** 0–4
|
||
- **Проверен на:** `b135a9c`, 2026-08-19
|
||
- **Пути:** `src/HSchool.Client`, `src/HSchool.Protocol`, `src/HSchool.Content`,
|
||
`src/HSchool.Server/Game`, `src/HSchool.Server/Api/ModEndpoints.cs`,
|
||
`src/HSchool.Server/Api/SchoolEndpoints.cs`, `src/HSchool.Server/Net`,
|
||
`tests/HSchool.Content.Tests`, `tests/HSchool.Protocol.Tests`,
|
||
`tests/HSchool.AppHost.Tests/SchoolApiTests.cs`, `tests/HSchool.AppHost.Tests/GameSocketTests.cs`
|
||
- **Итог:** дописано 2 теста (оба прогнаны на чистом `b135a9c`), расхождений кода с дизайном
|
||
не найдено кроме одного —
|
||
панель «Общие события» из фазы 1 снята с экрана в фазе 8 и решение нигде не записано
|
||
(оставлено открытым, см. ниже)
|
||
|
||
Что подтверждено:
|
||
|
||
- **Фаза 0.** `pixi.js` нет ни в `package.json`, ни в lockfile, ни в импортах; `npm run build`
|
||
проходит.
|
||
- **Фаза 1.** Дерево карты, панель локации со всеми пятью секциями и пустыми состояниями, клик по
|
||
дереву фильтрует на клиенте, `localize()` перекрашивает подписи. Панели «Общие события» на
|
||
экране нет — единственное расхождение среза.
|
||
- **Фаза 2.** `SchoolWorker` — `LongRunning`-поток, свой `PeriodicTimer`, свой `World`, аккумулятор
|
||
с потолком в 5 шагов. Супервизор держит таблицу и не трогает `World` (инвариант 3). Сейв пишется
|
||
на create, на shutdown и по `SaveIntervalSeconds`; настройки коалесцируются. Требуемый фазой тест
|
||
«create → reload → та же школа с тем же временем» — это
|
||
`GameSocketTests.ReloadFromDisk_RestoresAPausedClock` (точное равенство времени, паузы и
|
||
скорости), плюс `SchoolApiTests.ReloadFromDisk_RestoresCreatedSchools`. Независимость школ —
|
||
`GameSocketTests.PausingOneSchool_DoesNotStopAnother`.
|
||
- **Фаза 3.** Все восемь тестов из списка фазы на месте в `HSchool.Content.Tests` (JSONC-фикстуры,
|
||
last-wins по `defName` и по ключу локали, parent/abstract и цикл, патч `add` в `actions`,
|
||
связность, пустая комната, одна комната без двора). Ванильная раскладка валидируется
|
||
(`VanillaCoreTests.CoreDefaultMap_PassesValidation`). Пропавшая папка мода не стартует школу и
|
||
не трогает файл: исключение летит до создания `School`, поэтому `finally` с `Persist()`
|
||
недостижим, а супервизор только снимает школу с таблицы.
|
||
- **Фаза 4.** `GET /api/mods`, `GET /api/catalog?lang=`, `POST /api/schools` с картой и модами,
|
||
снимок карты при OpenSchool в языке Hello — всё покрыто тестами
|
||
(`OpeningASchool_SendsAMapSnapshot`, `LabelsTheSnapshotInTheHelloLocale`,
|
||
`WithACustomMap_ReturnsThatLayout`, `ReloadFromDisk_RestoresACustomMap`). Кодек, `protocol.ts` и
|
||
`docs/protocol.md` описывают один и тот же `MapSnapshot`, байтовые смещения проверены с обеих
|
||
сторон; версия 7 совпадает в `ProtocolConstants` и `PROTOCOL_VERSION`.
|
||
- **Инварианты.** `Content`/`People`/`Schedule`/`Ai` не ссылаются на Arch и ASP.NET, `Simulation` —
|
||
только на Arch; `DateTime.Now`/`UtcNow` в этих проектах нет.
|
||
- **«Things that will bite you».** Ни одна из относящихся к срезу регрессий не вернулась:
|
||
`modal.ts` резолвит промис явно, `.dialog--screen` держит `height: fit-content`,
|
||
`DefaultStartDate` форсирует UTC-kind, аккумулятор работника на месте, `MapSnapshotSize`
|
||
считается от сообщения, `GameClient` переносит оба `WaitToReadAsync` между итерациями, outbox
|
||
роняет старейший кадр, меню патчит карточки через `card.update`.
|
||
|
||
Дописано:
|
||
|
||
- `SchoolApiTests.Catalog_WithAnUnknownMod_IsRejected` — `GET /api/catalog?mods=` с неизвестной
|
||
пачкой описан в `docs/protocol.md` как `400 unknown-mod`, но не проверялся.
|
||
- `SchoolApiTests.ModId_ThatEscapesTheModsFolder_IsRejectedOnBothEndpoints` — инвариант 6: id
|
||
пачки приходит из браузера и подставляется в путь. `ModContent.IsSafePackId` его чистит на обоих
|
||
входах (каталог и создание школы), тестом это закреплено не было.
|
||
|
||
Открыто:
|
||
|
||
- **Панель «Общие события» пропала.** Фаза 1 требует три области, `design/near-term.md` держит их
|
||
в «Что уже зафиксировано», а `design/people.md` описывает список людей как панель «рядом с картой
|
||
и событиями». Коммит `d8f8db6` (фаза 8) заменил панель вкладками Карта/Люди, не сказав об этом;
|
||
ключи `eventsTitle`/`eventsEmpty` остались в `i18n/strings.ts` мёртвыми. Решать надо одно из
|
||
двух: вернуть пустую панель (или третью вкладку) — или записать отказ от неё в дизайн и убрать
|
||
ключи. Молча менять экран ревью не стало.
|
||
|
||
Замечено рядом (за границами среза):
|
||
|
||
- `SchoolRegistry` после фазы 2 в бою не используется: сервер тикает школы работниками, а из
|
||
класса вызывается только `TryNormalizeName`. `SchoolRegistryTests.Tick_AdvancesOnlyRunningSchools`
|
||
проверяет путь, которым сервер больше не ходит.
|
||
- **`PeopleApiTests.Card_IncludesFamilyAndLiveNeeds` зависит от порядка тестов** (фаза 8, срез 2).
|
||
Проверено на чистом checkout `b135a9c` в отдельном worktree: в одиночку класс проходит (8/8),
|
||
в полном прогоне класса падает на `Assert.Equal(2, card.Family.Parents.Count)` — ожидалось 2,
|
||
пришёл 1. Причина: тест берёт `People[0]` и требует у него двух родителей, а состав семьи
|
||
зависит от сида, сид — от id школы, id — от того, сколько школ создали тесты до него (и от
|
||
`saves/index.json` на машине). На машине с накопленными сейвами тест зелёный, на чистом
|
||
клоне — красный. Чинить надо тест: брать ученика, у которого два родителя, а не первого
|
||
попавшегося. Неполные семьи — сознательное решение `design/people.md`, не баг генерации.
|
||
- **`GameSocketTests.OpeningASchoolDuringAMathLesson_...` не выдерживает полный прогон** (срез 4).
|
||
В одиночку проходит за 1 с, в полном прогоне падает с «No presence frame matched within 40
|
||
frames» через 22 с. Похоже на нехватку кадров под нагрузкой, а не на ошибку присутствия.
|
||
- Полный `dotnet test` один раз упал: процесс `HSchool.Simulation.Tests` умер с `0xC0000005`,
|
||
успев отчитаться о 44 тестах из 63, и два `ActivityTests` (фаза 20) не прошли. Повторить не
|
||
удалось. Скорее всего дерево правилось прямо во время прогона (см. ниже).
|
||
- На момент проверки в рабочем дереве шла правка фазы 21 (`Decision.cs`, `LessonLearning.cs`,
|
||
`LessonLearningSystem.cs`, `PresenceSystem.cs`, `gameScreen.ts`), и какое-то время решение не
|
||
компилировалось. Выводам по срезу 1 это не мешает: его файлы правка не трогает, а дописанные
|
||
тесты прогнаны и на чистом `b135a9c`, и на дереве после починки сборки.
|
||
|
||
## Срез 2. Люди
|
||
|
||
- **Фазы:** 5–9
|
||
- **Проверен на:** `5cd5a6d`, 2026-08-19
|
||
- **Пути:** `src/HSchool.People`, `src/HSchool.Content/PeopleDefs.cs`,
|
||
`src/HSchool.Simulation/RosterSpawner.cs`, `src/HSchool.Simulation/School.cs`,
|
||
`src/HSchool.Server/Api/SchoolEndpoints.cs`, `src/HSchool.Server/Game/PersonCardReader.cs`,
|
||
`src/HSchool.Client/src/ui/peoplePanel.ts`, `src/HSchool.Client/src/ui/personCard.ts`,
|
||
`tests/HSchool.People.Tests`, `tests/HSchool.Content.Tests/PeopleDefTests.cs`,
|
||
`tests/HSchool.Simulation.Tests/PeopleInSchoolTests.cs`,
|
||
`tests/HSchool.AppHost.Tests/PeopleApiTests.cs`
|
||
- **Итог:** исправлен один тест, который падал на чистом клоне, укреплён второй, дописан один;
|
||
расхождение с дизайном одно и то же, что в срезе 1 (раскладка оболочки менеджера)
|
||
|
||
Что подтверждено:
|
||
|
||
- **Фаза 5.** Все шесть пунктов списка тестов на месте в `PeopleDefTests` (разбор каждого вида,
|
||
наследование навыка, патч черты, неизвестная ссылка — ошибка каталога, склонение по модели и
|
||
перекрытие явной таблицей), плюс `ExtraPack_AddsANameSetAndTrait` — критерий «мод со своим
|
||
набором имён виден в каталоге». Наборы имён в `GET /api/catalog` проверены в
|
||
`SchoolApiTests.Catalog_LabelsDefsInTheRequestedLanguage`.
|
||
- **Фаза 6.** Все шесть тестов из списка фазы существуют и проходят. Критерий «в проекте нет
|
||
сокетов, ECS и `DateTime.Now`» закреплён самими тестами —
|
||
`Assembly_DoesNotReferenceArchAspNetOrSockets` и `Sources_DoNotUseWallClock`.
|
||
- **Фаза 7.** Одиннадцать кабинетов (`VanillaMap_HasElevenHomeroomsNotTheComputerLab`), нужды
|
||
тикают со школой, ростер публикуется снимком, `PersistPeople` зовётся только когда состав
|
||
изменился — файл не переписывается по таймеру. Ростер, не сходящийся с картой, оставляет школу
|
||
незапущенной (`RosterFit.Matches` → `SchoolContentUnavailableException`).
|
||
- **Фаза 8.** Список читает опубликованный снимок (`loop.FindPeople`), карточка идёт через
|
||
очередь команд с `TaskCompletionSource` — инвариант 3 цел. Все пять требуемых фазой тестов API
|
||
на месте. Клиент: фильтры, сортируемые колонки, пейджер, семейные ссылки, всё через `t(...)`;
|
||
в строке списка только лёгкие поля.
|
||
- **Фаза 9.** Выпуск старшей имеющейся параллели, сохранение вместимости, семьи через поколения и
|
||
уход родителей выпускников покрыты в `YearlyIntakeTests` и `PeopleInSchoolTests`.
|
||
- **Дизайн.** `design/people.md` сверен построчно: неполные семьи ≈8% (тест требует 5–10%),
|
||
фамилия и отчество отцовские даже у одинокой матери, идентификатор ребёнка — счётчик, места
|
||
раздаются вперемешку. Расхождений нет.
|
||
|
||
Исправлено:
|
||
|
||
- **`PeopleApiTests.Card_IncludesFamilyAndLiveNeeds` падал на чистом клоне.** Тест брал первого
|
||
ученика страницы и требовал у него ровно двух родителей. Состав семьи выводится из сида, а сид —
|
||
это id школы (`SchoolWorker.InstallPeople`: `seed = school.Id`), то есть зависит от того,
|
||
сколько школ создали тесты до него и что лежит в `saves/index.json`. Замер по сидам 1–30: у 28
|
||
первый пятиклассник с двумя родителями, у 2 — с одним, и полный прогон на свежем checkout
|
||
`b135a9c` в отдельном worktree ловил как раз такой сид. Одинокие родители — сознательное
|
||
решение дизайна, поэтому неправ был тест: теперь он ищет ученика, у которого есть мать, а
|
||
количество родителей проверяет диапазоном. Проверено тем же способом, каким ловилось: полный
|
||
прогон на чистом `b135a9c` трижды, 60/60.
|
||
- **`PeopleApiTests.List_SortsBySurname` мерил не тем.** Тест требовал ordinal-порядка, а сервер
|
||
ставит Ё на место Е (`RosterBrowser.NameOrder`, тест `SurnameSort_FollowsTheAlphabetNotCodePoints`).
|
||
Совпадали они только потому, что в `core` нет фамилий на Ё — мод с такой фамилией уронил бы
|
||
тест, а не код. Теперь тест складывает Ё в Е так же, как сервер.
|
||
|
||
Дописано:
|
||
|
||
- `PeopleApiTests.ReloadAfterTheYearlyIntake_RestoresTheNewComposition` — критерии фаз 7 и 9
|
||
«перезапуск поднимает тех же людей» и «перезапуск после перехода поднимает **новый** состав» не
|
||
проверялись сквозь сервер. Просто сравнить людей до и после reload недостаточно: генерация
|
||
детерминирована от id школы, поэтому забывший файл сервер собрал бы тот же ростер и тест бы
|
||
ничего не поймал. Тест поэтому ждёт 1 сентября (школа создаётся за 15 игровых минут до
|
||
полуночи, это около трёх секунд реального времени), а после reload требует состав **после**
|
||
набора и явно отличный от исходного.
|
||
|
||
Открыто:
|
||
|
||
- **Раскладка оболочки менеджера разошлась с дизайном** — то же расхождение, что записано в срезе
|
||
1. Фаза 8 обещает панель «Люди» нижним рядом во всю ширину, `design/people.md` фиксирует
|
||
«панель в оболочке менеджера, нижний ряд; не отдельный экран», а на экране она вкладкой рядом с
|
||
картой. Тот же коммит `d8f8db6`, что убрал панель событий. Решается одним решением на оба
|
||
пункта: либо вернуть нижний ряд, либо переписать обе строки дизайна.
|
||
|
||
Замечено рядом (за границами среза):
|
||
|
||
- `design/people.md` и задача фазы 5 говорят «скорость убывания нужд в `core` — ноль», а в
|
||
`defs/needs` теперь `decayPerHour` от 0.05 до 0.15. Это сознательная работа фазы 20, не
|
||
регрессия; строка дизайна просто устарела и относится к срезу 5.
|
||
- Срез 1 успел сдвинуться: коммит `5cd5a6d` тронул `gameScreen.ts` и `strings.ts` (подпись
|
||
идущего человека). По правилу журнала он больше не «проверен на `b135a9c`» — правка маленькая и
|
||
относится к срезу 5, но следующий проход это увидит.
|