Files
h-school/docs/phases/reviewed.md
T
Leonid Pershin 21d79cb49b
ci / server (push) Failing after 3m44s
ci / client (push) Successful in 15s
Enhance people management tests and fix related issues
- 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.
2026-08-19 21:31:01 +03:00

178 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Проверенные срезы
Журнал ревью. Фаза с ✅ в [`README.md`](README.md) — заявка автора; строка здесь — проверенный
факт: каждое обещание фазы сверено с кодом и тестом, найденное дописано или исправлено.
Коммит в «Проверен на» делает журнал самопроверяющимся. Срез перестаёт считаться проверенным,
как только `git log <коммит>..HEAD -- <пути>` перестаёт быть пустым — поэтому «Пути» обязательны.
## Срез 1. Оболочка и карта
- **Фазы:** 04
- **Проверен на:** `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. Люди
- **Фазы:** 59
- **Проверен на:** `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, но следующий проход это увидит.