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

18 KiB
Raw Blame History

Проверенные срезы

Журнал ревью. Фаза с в 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. SchoolWorkerLongRunning-поток, свой 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_IsRejectedGET /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.MatchesSchoolContentUnavailableException).
  • Фаза 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, но следующий проход это увидит.