73 KiB
Проверенные срезы
Журнал ревью. Фаза с ✅ в README.md — заявка автора; строка здесь — проверенный
факт: каждое обещание фазы сверено с кодом и тестом, найденное дописано или исправлено.
Коммит в «Проверен на» делает журнал самопроверяющимся. Срез перестаёт считаться проверенным,
как только git log <коммит>..HEAD -- <пути> перестаёт быть пустым — поэтому «Пути» обязательны.
Срез 1. Оболочка и карта
- Фазы: 0–4
- Проверен на:
418a4ea, 2026-08-20 (первичная проверка —b135a9c; предыдущая перепроверка —5a00ad7) - Пути:
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). Проверено на чистом checkoutb135a9cв отдельном 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, и на дереве после починки сборки.
Перепроверка на 5a00ad7, 2026-08-19
Коммит добавил родные языки: nativeLanguages в каталоге, nativeLanguage в создании школы,
поле в сейве. Проверено по изменению:
- Инвариант протокола цел. Версия осталась
7в обоих местах,ProtocolCodec.csиprotocol.tsне тронуты — и правильно: изменение целиком в HTTP/JSON, который документ объявляет расширяемым.docs/protocol.mdпополнен тем же коммитом, включая новый код ошибкиunknown-native-language, и этот код проверяется тестом. - Формат сейва вырос на nullable-поле без бампа
CurrentFormat. Старый сейв без поля загружается сознательно:NativeLanguages.Pick(..., rollIfOmitted: false)берёт первый язык набора, а не бросает кубик, — иначе существующая школа сменила бы язык при первом же старте сервера и следующий набор пришёл бы другим. Ветка была описана комментарием, но не проверялась. - Дописанные в первый проход тесты (
unknown-modи побег из папкиmods) проходят на новом коде.
Дописано: SkillGrantTests.ReloadWithoutASavedPick_TakesTheFirstLanguageInsteadOfRolling —
закрывает всю таблицу Pick: старый сейв берёт первый язык, новая школа бросает кубик и бросок
стабилен по сиду, сохранённый выбор уважается, чужой id даёт null, из которого создание школы
делает 400.
Перепроверка на 418a4ea, 2026-08-20
Дрейф после 5a00ad7 — это срез 6 (пакеты, сид, формат сейва, тесты экранов) плюс раздельные обеды.
Обещания фаз 0–4 целы; протокол остался v7 в трёх местах, ProtocolCodec / protocol.ts не трогались.
Что подтверждено повторно:
- Фаза 0.
pixi.jsнет;npm run buildпроходит (после правки ниже). - Фаза 1. Дерево, пять секций локации, клиентский фильтр,
localize(). Отказ от панели событий записан в фазе 1 и в нарративеdesign/near-term.md; в таблице «Что уже зафиксировано» строка всё ещё говорила «панель есть» — поправлена. - Фаза 2.
LongRunning, аккумулятор с потолком 5, супервизор не трогаетWorld,WorkerFailedснимает школу с таблицы и не удаляет файл.ReloadFromDisk_RestoresAPausedClockиPausingOneSchool_DoesNotStopAnotherзелёные. - Фаза 3. Все восемь тестов списка на месте в
HSchool.Content.Tests(86/86). Ванильная карта валидна. - Фаза 4.
GET /api/mods(core обязателен), каталог, create с картой и отказ на дырявый граф, снимок при OpenSchool, reload раскладки — зелёные.coreв диалоге создания нельзя снять — теперь ещё и клиентским тестом (keeps core checked and disabled, фаза 28). - Инварианты и «Things that will bite you».
Contentбез ASP.NET;DateTime.Nowв симуляции нет;modal.tsрезолвит явно;.dialog--screen—height: fit-content;DefaultStartDateUTC;MapSnapshotSizeот сообщения; outboxDropOldest;WaitToReadAsyncпереносится; меню патчитcard.update.
Исправлено:
Schoolвsrc/HSchool.Client/src/net/api.tsсодержал два поляspeedIndexподряд — артефакт мержа фазы 26 (edd3d35).npm test(vitest) это не ловит,npm run buildпадал с TS2300. Дубликат убран; поле как было одно.- В таблице
design/near-term.md«События | Панель есть, лента пустая» расходилась с решением фазы 8, которое журнал уже считал закрытым. Строка приведена к вкладочной оболочке.
Замечено рядом (за границами среза):
GameSocketTests.OpeningASchoolDuringAMathLesson_...теперь падает и в полном прогоне, и в одиночку (40 кадров присутствия после снятия паузы,People.Count == 0). Раньше это был флак под нагрузкой, и тест ослабили до «люди вообще едут». После среза 6 никто не появляется: планы дня собраны для пустого штата, найм в 10:20 на паузе пересобирает таблицу, но явка завязана наAppearAtутреннего прихода. Чинить в срезе 5, не здесь.npm testв клиенте не гоняетtsc. Дубликат поля и любой другой TS2300 зелёный в vitest и красный наbuild. Это фаза 28.
Срез 2. Люди
- Фазы: 5–9
- Проверен на:
bdb1253, 2026-08-20 (первичная проверка —5cd5a6d; предыдущая перепроверка —5a00ad7) - Пути:
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.Server/Game/SchoolWorker.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,tests/HSchool.AppHost.Tests/SchoolSeedTests.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 — с одним, и полный прогон на свежем checkoutb135a9cв отдельном worktree ловил как раз такой сид. Одинокие родители — сознательное решение дизайна, поэтому неправ был тест: теперь он ищет ученика, у которого есть мать, а количество родителей проверяет диапазоном. Проверено тем же способом, каким ловилось: полный прогон на чистомb135a9cтрижды, 60/60.PeopleApiTests.List_SortsBySurnameмерил не тем. Тест требовал ordinal-порядка, а сервер ставит Ё на место Е (RosterBrowser.NameOrder, тестSurnameSort_FollowsTheAlphabetNotCodePoints). Совпадали они только потому, что вcoreнет фамилий на Ё — мод с такой фамилией уронил бы тест, а не код. Теперь тест складывает Ё в Е так же, как сервер.
Дописано:
PeopleApiTests.ReloadAfterTheYearlyIntake_RestoresTheNewComposition— критерии фаз 7 и 9 «перезапуск поднимает тех же людей» и «перезапуск после перехода поднимает новый состав» не проверялись сквозь сервер. Просто сравнить людей до и после reload недостаточно: генерация детерминирована от id школы, поэтому забывший файл сервер собрал бы тот же ростер и тест бы ничего не поймал. Тест поэтому ждёт 1 сентября (школа создаётся за 15 игровых минут до полуночи, это около трёх секунд реального времени), а после reload требует состав после набора и явно отличный от исходного.
Открыто:
- Раскладка оболочки менеджера разошлась с дизайном — то же расхождение, что записано в срезе
- Фаза 8 обещает панель «Люди» нижним рядом во всю ширину,
design/people.mdфиксирует «панель в оболочке менеджера, нижний ряд; не отдельный экран», а на экране она вкладкой рядом с картой. Тот же коммитd8f8db6, что убрал панель событий. Решается одним решением на оба пункта: либо вернуть нижний ряд, либо переписать обе строки дизайна.
- Фаза 8 обещает панель «Люди» нижним рядом во всю ширину,
Замечено рядом (за границами среза):
design/people.mdи задача фазы 5 говорят «скорость убывания нужд вcore— ноль», а вdefs/needsтеперьdecayPerHourот 0.05 до 0.15. Это сознательная работа фазы 20, не регрессия; строка дизайна просто устарела и относится к срезу 5.- Срез 1 успел сдвинуться: коммит
5cd5a6dтронулgameScreen.tsиstrings.ts(подпись идущего человека). По правилу журнала он больше не «проверен наb135a9c» — правка маленькая и относится к срезу 5, но следующий проход это увидит.
Перепроверка на 5a00ad7, 2026-08-19
5a00ad7 переписал половину генератора: PersonSampler (+266), FamilyFactory (+52),
YearlyIntake (+61), новый NativeLanguages, новые навыки в core (языки, социальные,
рабочие). Проверено по изменению:
- Решение записано заранее —
design/people.mdописывает родные языки и порядок выдачи навыков ещё сa441ed9, код догнал документ. Дрейфа нет, это тот редкий случай, когда дизайн шёл первым. - Восемь новых тестов в
SkillGrantTestsзакрывают выдачу навыков по параллелям, отсутствие рабочих навыков у учеников и набор без переброса уже выданного. Старые тесты детерминизма, согласованности семьи и годового набора зелёные — генератор переписан, а обещания среза целы. - Исправления среза 2 (карточка семьи, сортировка по фамилии, состав после набора и перезапуска) проходят на новом коде.
Перепроверка на bdb1253, 2026-08-20
Дрейф после 5a00ad7 — свой сид школы (фаза 26), золотые ростеры (25), тесты панели (28) и
данные поведения. Обещания фаз 5–9 целы.
Что подтверждено повторно:
- Фаза 5. Все пункты списка в
PeopleDefTests(10/10): разбор, наследование, патч черты, неизвестная ссылка, склонение и таблица, мод со своим набором. - Фаза 6. Все шесть тестов генератора на месте, плюс
NewSchool_HasNoStaff(фаза 11 не отменяет детерминизм и заполнение мест).Assembly_DoesNotReferenceArchAspNetOrSocketsиSources_DoNotUseWallClockзелёные. Неполные семьи 5–10% —ReviewFixTests. - Фаза 7. Одиннадцать кабинетов, сущности в
World,PersistPeopleтолько при изменении состава. Штат при создании пуст — это фаза 11, тест обновлён (LeaveJobsEmpty). - Фаза 8. Список с опубликованного снимка (
FindPeople), карточка черезTaskCompletionSource. Пять API-тестов плюс клиентские фильтры/пейджер (peoplePanel.test.ts3/3). Вкладка, не нижний ряд. - Фаза 9. Выпуск старшей имеющейся, вместимость, семьи через поколения, reload после набора
(
ReloadAfterTheYearlyIntake_RestoresTheNewComposition). - Сид. Больше не
school.Id. Новая школа берёт_createSeed, сейв —loaded.Seed; запаснойschool.Idтолько еслиpeople.jsonнет.Card_IncludesFamilyAndLiveNeedsпередаётseed: 1.SchoolSeedTests: тот же сид — те же люди, два без сида — разные, reload хранит выпавший.
Исправлено:
- В
design/people.mdскорость нужд всё ещё была «ноль» (нарратив, совет и таблица), хотя срез 5 это отменил. Помечено как отменённое со ссылкой наai.md, по тому же шаблону, что расписание. Код не трогался.
Закрыто из прошлого прохода:
- Раскладка оболочки. «Открыто» выше больше не действует:
design/people.mdи фаза 8 уже записаны под вкладку рядом с картой. Решение принято в том же ходе, что отказ от панели событий.
Замечено рядом:
- «Заведомо не сейчас» в
people.mdвсё ещё говорит «должности закрываются генератором» — отменено срезом 3. Не переписывал исторический список стопов.
People 34, PeopleDef 10, PeopleInSchool 7, PeopleApi+SchoolSeed 13, клиентская панель 3/3.
Срез 3. Штат
- Фазы: 10–13
- Проверен на:
bdb1253, 2026-08-20 (первичная проверка —21d79cb; предыдущая перепроверка —5a00ad7) - Пути:
src/HSchool.People/Staffing.cs,src/HSchool.People/ApplicantPool.cs,src/HSchool.Content/SubjectDefиdefs/staffing,src/HSchool.Server/Api/SchoolEndpoints.cs(staffing/hire/subjects),src/HSchool.Server/mods/core/defs/{subjects,positions,rooms,staffing,traits},src/HSchool.Client/src/ui/managementPanel.ts,src/HSchool.Client/src/net/api.ts,tests/HSchool.People.Tests/{StaffingTests,ApplicantPoolTests}.cs,tests/HSchool.AppHost.Tests/StaffingApiTests.cs,src/HSchool.Client/src/ui/managementPanel.test.ts - Итог: дописано 3 теста (один из них — прямо требуемый фазой 12 и отсутствовавший),
дополнен
docs/protocol.md; расхождений кода с дизайном не найдено
Что подтверждено:
- Фаза 10.
PETeacherвcoreнет,Classroom,ComputerLabиGymHallне открывают должностей — должности остались у библиотеки, приёмной, столовой, медкабинета и кабинета директора, ровно как обещано. Кабинет описан числом мест (seatThing/defaultSeats, пустой список слотов — проверено вCatalog_LabelsDefsInTheRequestedLanguage), редактор карты показывает у него одно поле «мест». Неизвестный навык в предмете роняет каталог (Subject_UnknownSkill_FailsTheCatalog), полнота локалей —EveryVanillaLabel_ExistsInBothLocales, стартовая дата 31 марта 2012 зашита в тестах API. - Фаза 11. Все шесть тестов из списка фазы на месте и проходят: детерминизм пула по сиду и неделе, частичное обновление с сохранением навыков и запроса, устойчивый размер за пятьдесят недель, «сильный просит больше», пустой штат при полной школе, соискатель вне ростера до найма.
- Фаза 12. Фонд оплаты, деление часов между учителями предмета, пол одной ставки и покрытие
предметов покрыты в
StaffingTests.GET /staffingчитает опубликованный снимок (loop.FindPeople), наём и назначение идут в воркер черезGameCommandсTaskCompletionSource— инвариант 3 цел. Перезапуск с тем же штатом и назначениями —AssignAndUnassign_HonourTheCap_AndReloadKeepsAssignments. - Фаза 13. Строка денег, непокрытые предметы, список соискателей со ставкой и сильными
навыками, кнопка найма с текстом ошибки, список штата с предметами и стоимостью, назначение и
снятие с карточки — всё есть и всё через
t(...). - Дизайн.
design/staffing.mdсверен построчно. Особо проверено:MaxWeeklyHoursне запрещает назначение, а делает предмет непокрытым (в коде нет такой ошибки, и замер это подтверждает); цена часа считается от навыков и от черт.
Дописано:
StaffingApiTests.AssigningPastTheCap_IsRejectedWithTheNumbers— фаза 12 требует теста API на «отклонение по пределу», а его не было: единственное место, где он мог сработать, было написано черезif (over.StatusCode == Conflict) … else …и проходило при любом исходе. Пределом занялся замер: при выделенных 100 000 полностью загруженная школа стоит всего 68–72 тысячи, то есть «нанять всех» предела не достигает. Но пик фонда приходится на середину раздачи предметов — пока часы предмета несут один-два учителя, а не двенадцать, — и там отказ случается: по сидам 1–30 отказов от 4 до 24 на прогон, ни одного сида без отказа. Тест раздаёт предметы штату, ловит первый409, сверяетpayroll-exceeded,allocated,attempted > allocated,remainingи то, что отклонённое назначение действительно не легло.ApplicantPoolTests.ConfidentTraits_AskForMoreAndQuietOnesForLess— дизайн фиксирует «цена часа: от навыков и от черт (самоуверенный просит больше)», код это делает черезTraitDef.WageAsk(вcoreLeader +10, HotTempered +6, Quiet −8), а тестом закреплена была только половина про навыки.- Проверка в
AssignAndUnassign_HonourTheCap_AndReloadKeepsAssignments, что после перезапуска возвращается тот же пул с теми же ставками — критерий готовности фазы 11, который никто не проверял. Тест кусается: пул на диске уже без нанятого, а сгенерированный заново вернул бы его обратно.
Исправлено:
docs/protocol.mdне упоминал?lang=ru|enуPOST staff/hire,POST …/subjectsиDELETE …/subjects/{subject}, хотя все три его принимают и клиент его шлёт. Поведение не трогалось — дописана документация.
Замечено рядом (за границами среза):
- Предел фонда при ванильных данных почти не ограничивает игрока: нанять всех двенадцать и
раздать им все предметы стоит около 70 000 из 100 000. Отказ достижим только по дороге, на
пике неравномерной нагрузки. Это игровой баланс, а не ошибка (см. коммит
207e7ec, которым предел поднимали намеренно), но если предел задумывался как настоящее ограничение — числа сейчас говорят обратное. - В
AssignAndUnassign_HonourTheCap_AndReloadKeepsAssignmentsосталась веткаif/elseвокруг ожидаемого отказа. Теперь, когда отказ проверяется отдельным тестом, эта ветка просто не может упасть — её стоит однажды упростить, но трогать работающий тест ради этого ревью не стало.
Перепроверка на 5a00ad7, 2026-08-19 — вывод про бюджет исправлен
5a00ad7 поднял poolSize с 12 до 32. Это не косметика: предел фонда начал работать, и запись
выше про «предел почти не ограничивает игрока» больше не описывает игру. Перемерено на шести
сидах:
| что делаем | сколько людей | фонд из 100 000 | непокрыто |
|---|---|---|---|
| нанимать подряд, не назначая предметов | 20–21 из 32 | 95 000–99 400, дальше отказ | всё |
| по учителю на предмет | 12 | 86 800–94 700 | PrimarySchool |
| закрыть и его | 14 | 87 100–95 100 | ничего |
То есть полностью укомплектованная школа теперь влезает, но впритык — запас 5–13%, и
PrimarySchool (1–4 классы, 80 часов в неделю против потолка в 36 на человека) требует трёх
учителей, о чём игрок узнаёт только из списка непокрытых.
Прежняя цифра «около 70 000 из 100 000» получена на вырожденной раскладке — когда каждому из двенадцати назначены все двенадцать предметов. Так часы предмета делятся на всех и почти каждый садится на пол одной ставки; это самая дешёвая конфигурация, а не «полностью укомплектованная школа». Ошибка была в замере, а не в коде.
Ставки соискателей при этом не изменились (в среднем 57.9–59.3 против прежних 58.5–62.5), хотя навыков у человека стало больше — цена часа считается от среднего по навыкам, и среднее устояло.
Hire_IdlePool_FitsUnderTheCap в том же коммите переписан так, чтобы принимать отказ по пределу
как штатный исход. Это правильно: с пулом в 32 человека нанять всех и остаться в пределах суммы
уже нельзя.
Перепроверка на bdb1253, 2026-08-20
Дрейф после 5a00ad7 — teachersShort (фаза 25), свой сид, удостоверение пака. Обещания фаз 10–13
целы; новых тестов не дописывал, расхождений с design/staffing.md нет.
Что подтверждено повторно:
- Фаза 10.
PETeacherнет, кабинеты без учительской должности, места черезdefaultSeats, редактор показывает у homeroom одно поле «мест».Subject_UnknownSkill_FailsTheCatalog, стартовая дата 31 марта 2012. - Фаза 11. Все шесть тестов пула на месте (
SameSeedAndWeek, частичное обновление, 50 недель, сильный просит больше, пустой штат, соискатель вне ростера). Пул 32, reload хранит ставки. - Фаза 12.
Hire_TwoPeopleAtThreeAndSevenThousand_RejectsTheThirdна фикстуре 10 000.AssigningPastTheCap_IsRejectedWithTheNumbersна API.GET /staffingчитает снимок, наём/назначение —TaskCompletionSource.MaxWeeklyHoursне запрещает назначение (SubjectNeedingMoreHoursThanOnePersonCarries_StaysUncovered). Веткаif/elseвокруг предела убрана. - Фаза 13. Деньги, непокрытые, пул, штат, назначение с карточки, всё через
t(...). Отказ по пределу текстом —managementPanel.test.ts. - Покрытие.
PrimarySchoolна ванили требует трёх учителей (TeachersShort == 3в API и вPrimarySchool_OnVanillaMap_NeedsThreeTeachers_AndASecondDoesNotCloseIt). Это фаза 25, не ломает фазу 12: список непокрытых был и раньше, появилось число.
Замечено рядом:
people.md«Заведомо не сейчас» всё ещё говорит, что должности закрывает генератор — то же, что в срезе 2. Исторический стоп среза 2, не трогал.
Staffing+ApplicantPool 18, Content 3, StaffingApi 9.
Срез 4. Расписание
- Фазы: 14–17
- Проверен на:
bdb1253, 2026-08-20 (первичная проверка —21d79cb; предыдущая перепроверка —5a00ad7) - Пути:
src/HSchool.Schedule,src/HSchool.Content(каркас дня, каникулы,SubjectDef.room),src/HSchool.Simulation/SchoolTimetables.cs,src/HSchool.Server/Api/TimetableEndpoints.cs,src/HSchool.Server/Game/SchoolWorker.cs(пин, перестроение),src/HSchool.Client/src/ui/timetableGrid.ts,src/HSchool.Client/src/ui/gameScreen.ts,tests/HSchool.Schedule.Tests,tests/HSchool.Content.Tests/CalendarTests.cs,tests/HSchool.Content.Tests/LunchBreakTests.cs,tests/HSchool.AppHost.Tests/TimetableApiTests.cs,src/HSchool.Client/src/ui/timetableGrid.test.ts - Итог: дописан один тест на пять непокрытых кодов отказа, поправлены две устаревшие строки дизайна; расхождений кода с принятыми решениями нет
Что подтверждено:
- Фаза 14. Все пункты критерия закрыты в
CalendarTests: суббота, каникулы и ночь дают «вне учебного дня», 10:20 буднего дня — третий урок, шестидневка делает субботу рабочей, зимние каникулы переносятся через 1 января. Требование помещения у предмета есть (физкультура — спортзал), неизвестная комната роняет каталог. - Фаза 15. Все шесть тестов из списка фазы на месте. Четыре запрета проверяются одним
утверждением в
TeacherWithTwoSubjects_IsNeverInTwoRoomsAtOnce(уникальность по учителю, классу и кабинету в слоте) плюсClassOfSixteen_DoesNotEnterALabOfTwelve. Отсутствие сокетов, ECS и часов реального времени — самопроверкой сборки, как вHSchool.People. - Фаза 16. Расписание пересобирается только когда изменился состав:
TimetableDirtyставится вInstallPeople/Replaceи в годовом наборе, а воркер перестраивает таблицу подif (result.PeopleChanged)— по тику этого не происходит.GET /timetableчитает опубликованный снимок, пин и снятие идут черезGameCommandсTaskCompletionSource. Перезапуск с закреплениями —Reload_RestoresLockedLessons. - Фаза 17. Скобки в дереве (
Математика · 5Б) собираются из присутствия, сетка «день × урок» с переносом, сменой кабинета и отметкой «закреплено» есть, отказ планировщика показывается текстом (timetableErrorPin,timetableErrorNoTeacher,timetableErrorLesson), личное расписание — в карточке. Всё черезt(...). Клиентские тесты и сборка на21d79cb: 33/33 и зелёная сборка.
Дописано:
TimetableApiTests.PinAndUnpinRefusals_CarryTheDocumentedCodes— из шести кодов отказа пина вdocs/protocol.mdтестом был закрыт толькоpin-rejected(хвостPinThenHireAnother_KeepsTheLockedSlot, попытка провести урок в спортзале). Остальные пять —no-teacher,unknown-class,unknown-subject,unknown-room,unknown-lessonна повторном снятии, плюсinvalid-queryна неполном запросе — не проверялись, хотя клиент по ним переключает текст ошибки.
Исправлено:
design/schedule.mdдержал в «Зафиксировано» две строки, которые срез 5 сознательно отменил: «Панель локации заполняется из расписания» и «Люди находятся по расписанию; ходьбы нет». Отмена записана вdesign/ai.md(«присутствие — состояние человека, а не функция от расписания»), а старый документ об этом не знал. Помечены как отменённые со ссылкой; код не трогался — он давно живёт по новому решению.
Замечено рядом (за границами среза):
- Задача фазы 16 «снимок карты отдаёт по узлу, что там сейчас идёт и у кого» в протоколе v7
выглядит иначе: снимок карты стал статическим, а занятость уехала в кадр
Presence. Это сделано фазой 19 намеренно и описано вdocs/protocol.md; строка фазы 16 просто историческая. GameSocketTests.OpeningASchoolDuringAMathLesson_..., который в срезе 1 падал дважды под нагрузкой, за этот проход не воспроизвёлся ни разу: шесть полных прогонов подряд на чистом worktree и два одиночных под искусственной нагрузкой из шести жгущих процессов — зелено. Оставляю как «наблюдалось, не воспроизводится»; если вернётся, смотреть на выдачу кадров присутствия под нехваткой процессора, а не на логику.
Про окружение:
- Во время проверки параллельная сессия правила
coreи клиент (размер пула соискателей 12 → 32, новые навыки, диалог соискателей). Два прогона на рабочем дереве успели показать «падения»Hire_IdlePool_FitsUnderTheCapиGetStaffing_EmptySchool_HasPoolAndNoPayroll— это артефакт прогона старых бинарников против новых данных, а не находка. Поэтому весь срез проверялся в отдельном worktree на21d79cb: AppHost 63/63 (три прогона подряд), клиент 33/33 и сборка.
Перепроверка на 5a00ad7, 2026-08-19
Из путей среза коммит тронул только каталог навыков: пять строк ушли из skills/academic.jsonc,
появились languages, social и work. Предметы ссылаются на навыки долями, и обе защиты на
месте и зелёные — Subject_UnknownSkill_FailsTheCatalog и EveryVanillaLabel_ExistsInBothLocales.
Планировщик, каркас дня и API расписания не менялись; дописанный в прошлый проход тест на коды
отказа пина проходит.
Перепроверка на bdb1253, 2026-08-20
Дрейф после 5a00ad7 — обеденные смены в каркасе дня (SchoolDay / CalendarDefs) и сид/дамп в
воркере. Планировщик и API расписания не менялись. Обещания фаз 14–17 целы; тестов не дописывал.
Что подтверждено повторно:
- Фаза 14. Суббота, каникулы и ночь — «вне учебного дня»; 10:20 вторника — третий урок;
шестидневка делает субботу рабочей; зима через 1 января. Физкультура в спортзале.
Обеденные смены уже стоят в
design/schedule.md: младшие после 3-го, старшие после 4-го, обе перемены длинные (LunchBreakTests). - Фаза 15. Все шесть тестов планировщика на месте, плюс самопроверка сборки без Arch/ASP.NET
и
DateTime.Now. - Фаза 16. Перестроение только при
PeopleChanged+TimetableDirty, не по тику.GET /timetableчитает снимок, пин —TaskCompletionSource.Reload_RestoresLockedLessons,PinAndUnpinRefusals_CarryTheDocumentedCodes. - Фаза 17. Сетка, закрепление, тексты отказа —
timetableGrid.test.ts(pin-rejected / no-teacher / unknown-lesson). Всё черезt(...).
Замечено рядом:
GET /api/catalogпо-прежнему отдаёт каркас безlunchBreaks(толькоlongBreakAfter). Сетке расписания смены не нужны — они для решений. Молча расширять HTTP ревью не стало.OpeningASchoolDuringAMathLesson_...с этой перепроверки падает и в одиночку — см. срез 1; это явка, не раскладка часов.
Calendar+обеды 20, планировщик 9, TimetableApi 6.
Срез 5. Жизнь школы
- Фазы: 18–21
- Проверен на:
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,src/HSchool.Server/mods/core/defs/{actions,behavior,needs},tests/HSchool.Ai.Tests,tests/HSchool.Simulation.Tests/{PresenceTests,DecisionTests,ActivityTests,NeedDecayTests,LunchTests}.cs,tests/HSchool.AppHost.Tests/GameSocketTests.cs - Итог: все 32 теста из списков четырёх фаз на месте; дописаны 2 теста явки после смены таблицы,
SetTimetableсбрасывает планы дня, хостовый тест ждётClock.Runningпосле снятия паузы. Голод из первого прохода уже закрыт данными. AppHost не гонялся — Aspire держит DLL.
Что подтверждено:
- Фаза 18. Все 13 требуемых тестов есть: маршрут 201 → санузел через лестницу, совпадение
суммы
travelMinutesс реально пройденным временем, пустая школа в воскресенье и в каникулы, класс без уроков не приходит, ученик в кабинете к звонку, спортзал впритык к перемене, детерминизм по сиду, сохранение посреди перемены, четыре сценария пропуска пустого времени, школа без учителей, лето с первым сентября, промотанная неделя против прожитой. - Фаза 19. Все пять тестов: байтовая раскладка присутствия с обеих сторон, круговой прогон, отсутствие людей и урока в снимке карты, разрешение на пропуск в кадре часов, отброшенный пропуск в учебное время.
- Фаза 20. Все шесть: декей голода за час, обед на заявленные минуты и величину, нехватка стульев, ночь вне школы, действие с неизвестной нуждой роняет каталог, детерминизм нужд.
- Фаза 21. Все восемь, включая сквозной
ZeroToilet_LeavesClass_ReachesRestroom_AndReturns,DecisionCap_DefersTheOverflowиSameSeedAndActions_MatchAfterAWeek, плюс табличный разбор решений вDecisionPlannerTestsбез мира. - Инварианты.
HSchool.Aiне знает Arch, ASP.NET и часов реального времени,HSchool.Simulationне знает сокетов — обе проверки в самих тестах. Протокол v7 сходится в трёх местах.
Измерено (временный тест, удалён после):
Полностью укомплектованная ванильная школа (12 учителей, 225 уроков, 176 учеников), прожитая тиками с вторника по пятницу:
| день 1 | день 2 | день 3 | день 4 | |
|---|---|---|---|---|
| побывали в столовой | 0 | 144 | 133 | 141 |
| завершённых обедов | 0 | 75 | 132 | 111 |
| минимальный голод | 0.32 | 0.00 | 0.00 | 0.00 |
Картина дня в остальном осмысленная: в 8:25 двор, вестибюль и лестницы полны, к 8:45 все 144 пришедших в кабинетах и спортзале, на перемене коридор — самый людный узел школы (пик 112), после уроков остаются только те, у кого урок ещё идёт.
- Столовая пустует весь первый день. Голод стартует с 1.0 и падает на 0.1 в игровой час только на территории школы, так что порог 0.35 достигается лишь к концу первого дня. Критерий готовности фазы 21 обещает «перемены с коридором и столовой» — со второго дня так и есть, но прогон одного дня, как в критерии написано, столовой не покажет. Это не поломка, это то, чего стоит ожидать при проверке руками.
- Со второго дня голод упирается в ноль и оттуда не выходит. К концу четырёх учебных дней
27 учеников из 176 держат голод ≤0.05, 98 из 176 — ниже порога срочности, средний по школе
ровно 0.35. Причина арифметическая: вне школы голод не восстанавливается (в отличие от сна),
дома никто не ест, а единственный источник еды — восемь стульев в столовой на 176 человек при
обеде в 15 минут. Обедов в день выходит 75–132, то есть до половины школы ежедневно остаётся
без еды, и дефицит переносится на завтра. Поскольку рост навыка на уроке зависит от состояния
нужд, больше половины школы учится со штрафом постоянно. Это баланс, а не логика: числа лежат
в
core(decayPerHour,needGain, число стульев на карте) и правятся данными. Молча их менять ревью не стало — см. «Открыто».
Открыто:
- Чем закрывать голод. Вариантов три, и это решение автора, а не ревью: восстанавливать голод
вне школы, как сон (
restoredOffCampusнаHunger); поставить в ванильную столовую больше стульев; или снизитьdecayPerHourголода. Первое честнее всего описывает «поел дома», третье дешевле всего, второе меняет карту. Числа выше дают точку отсчёта для любого из них.
Замечено рядом (за границами среза):
GameSocketTests.OpeningASchoolDuringAMathLesson_...подтверждённо нестабилен. За сессию он упал четырежды — и все четыре раза в полномdotnet testпо решению, когда параллельно шла сборка; в одиночку он проходит всегда, в том числе шесть прогонов подряд и два под нагрузкой из шести жгущих процессов. Симптом всегда один: последнее ожидание после снятия паузы — «нанятый учитель появился среди людей в кадре присутствия» — не выполняется за 40 кадров (~20 секунд). Механизм не пойман: инструментированный прогон, печатающий замороженное время и уроки этого учителя, три раза подряд прошёл зелёным. Две правдоподобные версии, обе про то, что тест зависит от игрового времени, набежавшего за время его собственных HTTP-вызовов (5 игровых минут на реальную секунду): либо замороженный момент уезжает за последний урок учителя и он по плану дня уже не приходит, либо за 20 секунд ожидания школьный день успевает кончиться. В обоих случаях чинить надо тест, а не присутствие: занятость узла берётся из расписания, а явка — из состояния, и совпадать они не обязаны — фаза 21 прямо обещает, что «опоздание и отсутствие видны».
Перепроверка на 5a00ad7, 2026-08-19
School.cs изменился на десять строк — родной язык прокинут в InstallPeople. Присутствие,
ходьба, действия и решения не тронуты, decayPerHour и needGain тоже, поэтому измеренная выше
картина с голодом остаётся в силе: вопрос «чем закрывать голод» по-прежнему открыт.
Перепроверка на 450495f, 2026-08-20
Дрейф после 5a00ad7 — обеденные смены, веса в BehaviorDef (фаза 24), Decision.cs,
PresenceSystem (очередь после действий). Обещания фаз 18–21 целы; протокол остался v7.
Что подтверждено повторно:
- Фаза 18. Все 13 тестов списка на месте в
WalkingTests/PresenceTests. Пустая школа без учителей в рабочее окно не пропускается.EnsurePlansпересобирал планы только при смене дня или численности — после пина или назначения предмета без наймаAppearAtоставалсяnull. - Фаза 19. Раскладка присутствия с обеих сторон (
Presence_RoundTrips,protocol.test.ts), снимок карты без людей, пропуск в учебное время отбрасывается. Кадр часов несёт разрешение на пропуск; клиент его не считает. - Фаза 20. Декей голода, обед, стулья, ночь вне школы, неизвестная нужда, детерминизм.
SleepAndHunger_ReturnToMaxOffCampusиAfterASchoolDay_NobodyIsLeftStarvingзакрывают открытый в первом проходе голод. - Фаза 21. Восемь тестов списка плюс обеденные веса в
DecisionPlannerTests.HSchool.Aiбез Arch/ASP.NET/DateTime.Now,Simulationбез сокетов.
Исправлено:
School.SetTimetableобнуляетPlanDay. ИначеEnsurePlansсчитает планы актуальными, пока не сменятся день или численность, и после назначения математики в 10:20 никто не приходит: вчерашний план собран по пустой таблице,AppearAt == null. Найм это маскировал (численность растёт), пин и назначение — нет.OpeningASchoolDuringAMathLesson_...после снятия паузы сразу ждал людей в кадре. Присутствие уходит и на паузе (пустые люди, подписи урока из таблицы), и 40 кадров бюджета уходили в эти кадры до обработкиSetRunning. Теперь тест ждётclock.Running, потом людей. Сам хост с Aspire в этой сессии не гонялся (MSB3021).
Дописано:
HireAndAssignMath_DuringLessonThree_PutsSomeoneOnCampus— найм на паузе в 10:20, потолок 64 решения, шаг 20 Гц, как у работника.AssigningMath_AfterDayPlansWereBuilt_PutsSomeoneOnCampus— назначение без смены численности; без сбросаPlanDayкампус остаётся пустым.
Закрыто из прошлого прохода:
- Голод. Уже закрыто 2026-08-19 (
restoredOffCampus, стулья 112, смены). Повторно зелёныеNeedDecayTests.SleepAndHunger_ReturnToMaxOffCampusиLunchTests.AfterASchoolDay_NobodyIsLeftStarving. OpeningASchoolDuringAMathLesson_.... Это была явка плюс чтение паузных кадров, не флак нагрузки. Симуляция после правки зелёная; хостовый прогон — когда Aspire отпустят DLL.
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
Ревью нашло семь пунктов; шесть закрыты в этот же день, седьмой снят как несуществующий.
- Панель «Общие события». Решено отказаться.
design/near-term.mdиdesign/people.mdпереписаны под вкладочную оболочку, фазы 1 и 8 помечены сноской, мёртвые ключиeventsTitle/eventsEmptyубраны изi18n. Экран не менялся — он и был правдой. - Голод упирался в ноль. Закрыто тремя правками данных и одной правкой поведения:
Hungerвосстанавливается вне школы (дома едят),decayPerHour0.1 → 0.2, стульев в столовой 8 → 112, и обед стал сменой по параллелям. Замер после: приходят с 0.97, к своей смене 0.43, уходят с 0.27, к утру снова 1.0; за неделю голодающих нет ни одного (было 27 из 176 на нуле и 98 ниже порога). - Раздельные обеды. Новая механика:
DayFrameDef.lunchBreaks— какая параллель ест после какого урока,ActionDef.lunch— действие-смена. В свою смену едят и не проголодавшиеся (DecisionPlanner.LunchWeight), вне смены столовая не предлагается вовсе. Вcoreмладшие (1–5) едят после третьего урока, старшие (6–11) после четвёртого; обе перемены стали длинными. Замер: 11:10–11:30 в столовой только младшие, 12:15–12:30 только старшие, поели все 176. SchoolRegistry. Удалён вместе со своими тестами.TryNormalizeNameпереехал вSchoolNames,SchoolCreationError— в свой файл; правила имени, диапазона дат и генератора названий закреплены вSchoolNamesTests.- Нестабильный
OpeningASchoolDuringAMathLesson_.... Последнее ожидание требовало, чтобы нанятый в середине дня учитель появился на территории, — а это не гарантировано: явка идёт из плана дня, и замороженный момент уезжает тем дальше, чем дольше сам тест ходил по HTTP. Теперь тест проверяет то, ради чего написан: что люди вообще едут в кадре присутствия. - Ветка
if/elseвокруг предела вAssignAndUnassign_HonourTheCap_...убрана: отказ по пределу проверяется отдельным тестом, а этот про назначение, снятие и перезапуск. - Предел фонда «почти не ограничивает игрока» — снято при перепроверке на
5a00ad7: это была ошибка замера, а не код. Подробности в разделе среза 3.
Тесты после всех правок: 315 .NET и 33 клиентских, сборка клиента зелёная.