Files
h-school/docs/phases/reviewed.md
T
Leonid PershinandCursor 1c3ae9ab7e Reject a second live WebSocket for the same session name.
Two tabs with the cookie already set never POST /api/session, so name-online on login did not cover the design rule. Claim the name under the same lock as the online check.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 11:04:56 +03:00

31 KiB
Raw Blame History

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

Журнал ревью. Фаза с в README.md — заявка автора; строка здесь — проверенный факт: каждое обещание фазы сверено с кодом и тестом, найденное дописано или исправлено.

Коммит в «Проверен на» делает журнал самопроверяющимся. Срез перестаёт считаться проверенным, как только git log <коммит>..HEAD -- <пути> перестаёт быть пустым — поэтому «Пути» обязательны.

Закрытые замечания из прошлых проходов сюда не возвращаются. История перепроверок свёрнута в строку «Проверен на». Последняя проверка срезов 1–6 была до фаз 29–30 (страна, одежда): дрейф по путям есть, это очередь следующего прохода, а не задняя датировка.

Оговорки, которые относятся сразу к нескольким срезам (не дублировать ниже):

  • Хостовый OpeningASchoolDuringAMathLesson_... после правки явки не гонялся: Aspire держал DLL (MSB3021). Симуляционные тесты явки зелёные.
  • npm test в клиенте не гоняет tsc. Дубликат поля и любой TS2300 зелёный в vitest и красный на npm run build.
  • GET /api/catalog отдаёт каркас дня без lunchBreaks (только longBreakAfter). Сетке смены не нужны; молча расширять HTTP ревью не стало.

Срез 1. Оболочка и карта

  • Фазы: 04
  • Проверен на: 418a4ea, 2026-08-20
  • Пути: 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
  • Итог: обещания фаз 0–4 целы; дописаны 3 теста; протокол v7 в трёх местах

Что подтверждено:

  • PixiJS нет; дерево карты, пять секций локации, клиентский фильтр, localize(). Оболочка — вкладки Карта/Люди, не три панели; отказ от ленты событий записан в фазе 1 и в design/near-term.md.
  • SchoolWorkerLongRunning, свой World, аккумулятор с потолком 5. Супервизор таблицу держит и World не трогает. Сейв на create/shutdown/SaveIntervalSeconds; ReloadFromDisk_RestoresAPausedClock, PausingOneSchool_DoesNotStopAnother.
  • Восемь тестов каталога/карты в HSchool.Content.Tests. Ванильная раскладка валидна. Пропавшая папка мода не стартует школу и файл не трогает.
  • GET /api/mods, каталог, create с картой и модами, снимок при OpenSchool. core в диалоге создания нельзя снять. Кодек, protocol.ts и docs/protocol.md описывают один MapSnapshot.
  • modal.ts резолвит промис явно; .dialog--screenheight: fit-content; DefaultStartDate UTC; MapSnapshotSize от сообщения; outbox DropOldest; WaitToReadAsync переносится между итерациями; меню патчит card.update.
  • Родной язык — HTTP/JSON без бампа протокола. Старый сейв без поля берёт первый язык набора, не бросает кубик (ReloadWithoutASavedPick_TakesTheFirstLanguageInsteadOfRolling).

Дописано:

  • SchoolApiTests.Catalog_WithAnUnknownMod_IsRejected
  • SchoolApiTests.ModId_ThatEscapesTheModsFolder_IsRejectedOnBothEndpoints
  • SkillGrantTests.ReloadWithoutASavedPick_TakesTheFirstLanguageInsteadOfRolling

Исправлено:

  • Два поля speedIndex в клиентском School (api.ts) — артефакт мержа фазы 26; vitest не ловил, npm run build падал с TS2300.

Срез 2. Люди

  • Фазы: 59
  • Проверен на: bdb1253, 2026-08-20
  • Пути: 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
  • Итог: обещания фаз 5–9 целы; починены два API-теста, дописан один на reload после набора

Что подтверждено:

  • Дефы человека: разбор, наследование, патч, неизвестная ссылка, склонение, мод со своим набором.
  • Генератор: шесть тестов списка фазы, без Arch/ASP.NET/DateTime.Now. Неполные семьи 5–10%. Сид — поле школы, не school.Id; хостовые тесты, которым нужен состав, передают сид явно.
  • Одиннадцать кабинетов, сущности в World, PersistPeople только при смене состава. Штат при создании пуст — это фаза 11.
  • Список с опубликованного снимка, карточка через TaskCompletionSource. Фильтры, пейджер, семейные ссылки, всё через t(...). Вкладка рядом с картой, не нижний ряд — так записано в фазе 8 и в design/people.md.
  • Выпуск старшей имеющейся, вместимость, семьи через поколения, reload после набора.

Дописано:

  • PeopleApiTests.ReloadAfterTheYearlyIntake_RestoresTheNewComposition — ждёт 1 сентября и требует состав после набора, отличный от исходного (детерминизм от сида иначе скрыл бы потерянный файл).

Исправлено:

  • Card_IncludesFamilyAndLiveNeeds брал People[0] и требовал двух родителей. Состав зависит от сида; на чистом клоне первый ученик иногда из неполной семьи. Теперь ищет ученика с матерью и проверяет диапазон.
  • List_SortsBySurname требовал ordinal-порядок, сервер ставит Ё на место Е. Совпадали, пока в core не было фамилий на Ё.

Срез 3. Штат

  • Фазы: 1013
  • Проверен на: bdb1253, 2026-08-20
  • Пути: 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
  • Итог: обещания фаз 10–13 целы; дописаны 3 теста; docs/protocol.md дополнен ?lang=

Что подтверждено:

  • PETeacher нет; кабинеты без учительской должности; места через defaultSeats. Неизвестный навык в предмете роняет каталог. Стартовая дата 31 марта 2012.
  • Пул: детерминизм по сиду и неделе, частичное обновление, устойчивый размер, «сильный просит больше», пустой штат, соискатель вне ростера до найма. Пул 32.
  • Фонд оплаты, деление часов, пол ставки, покрытие. GET /staffing читает снимок, наём и назначение — GameCommand с TaskCompletionSource. MaxWeeklyHours не запрещает назначение, а оставляет предмет непокрытым. PrimarySchool на ванили требует трёх учителей (teachersShort).
  • Экран: деньги, непокрытые, пул, штат, назначение с карточки, отказ по пределу текстом, всё через t(...).
  • Полностью укомплектованная школа влезает в 100 000 впритык (запас 5–13%). Нанять пул целиком без назначений предела достигает. Раскладка «каждому все предметы» — самая дешёвая, не «укомплектованная школа».

Дописано:

  • StaffingApiTests.AssigningPastTheCap_IsRejectedWithTheNumbers — фаза 12 требовала API-тест на отказ по пределу; единственное место было написано через if (over.StatusCode == Conflict) и проходило при любом исходе.
  • ApplicantPoolTests.ConfidentTraits_AskForMoreAndQuietOnesForLess
  • В AssignAndUnassign_HonourTheCap_AndReloadKeepsAssignments — тот же пул с теми же ставками после перезапуска.

Исправлено:

  • docs/protocol.md не упоминал ?lang= у hire/subjects, хотя клиент его шлёт.

Срез 4. Расписание

  • Фазы: 1417
  • Проверен на: bdb1253, 2026-08-20
  • Пути: 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–17 целы; дописан тест на коды отказа пина; две строки дизайна помечены как отменённые срезом 5

Что подтверждено:

  • Календарь: суббота, каникулы и ночь — «вне учебного дня»; 10:20 вторника — третий урок; шестидневка; зима через 1 января. Физкультура в спортзале. Обеденные смены в каркасе дня (младшие после 3-го, старшие после 4-го).
  • Планировщик: четыре запрета, класс не влезает в меньший кабинет, без Arch/ASP.NET/DateTime.Now.
  • Перестроение только при PeopleChanged + TimetableDirty, не по тику. GET /timetable читает снимок, пин — TaskCompletionSource. Reload_RestoresLockedLessons.
  • Сетка, закрепление, тексты отказа (pin-rejected / no-teacher / unknown-lesson), скобки в дереве из присутствия, личное расписание в карточке. Всё через t(...).
  • Занятость узла в протоколе v7 живёт в кадре Presence, не в снимке карты — фаза 19.

Дописано:

  • TimetableApiTests.PinAndUnpinRefusals_CarryTheDocumentedCodesno-teacher, unknown-class, unknown-subject, unknown-room, unknown-lesson, invalid-query.

Исправлено:

  • В design/schedule.md «панель локации из расписания» и «люди находятся по расписанию» помечены как отменённые срезом 5.

Срез 5. Жизнь школы

  • Фазы: 1821
  • Проверен на: cbe739a, 2026-08-20
  • Пути: 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 сбрасывает планы дня. Голод закрыт данными. AppHost после правки не гонялся — Aspire держал DLL.

Что подтверждено:

  • Ходьба: 13 тестов списка фазы 18 (маршрут, travelMinutes, воскресенье/каникулы, звонок, детерминизм, пропуск пустого времени, лето, промотанная неделя).
  • Присутствие: раскладка с обеих сторон, круговой прогон, люди не в снимке карты, пропуск в учебное время отбрасывается. На паузе кадры идут (occupancy + подписи урока); людей в них может не быть.
  • Нужды: декей, обед, стулья, ночь вне школы, неизвестная нужда, детерминизм. Hunger восстанавливается вне школы, в столовой 112 стульев, обед — смена по параллелям. SleepAndHunger_ReturnToMaxOffCampus, AfterASchoolDay_NobodyIsLeftStarving.
  • Решения: восемь тестов фазы 21, в том числе туалет с урока и возврат, потолок отложенных, детерминизм за неделю. HSchool.Ai без Arch/ASP.NET/DateTime.Now.

Дописано:

  • HireAndAssignMath_DuringLessonThree_PutsSomeoneOnCampus
  • AssigningMath_AfterDayPlansWereBuilt_PutsSomeoneOnCampus

Исправлено:

  • School.SetTimetable обнуляет PlanDay. Иначе после назначения предмета без смены численности EnsurePlans считает планы актуальными, AppearAt остаётся null, кампус пуст. Найм это маскировал (численность растёт).
  • OpeningASchoolDuringAMathLesson_... после снятия паузы сразу ждал людей и съедал бюджет на паузные кадры. Теперь ждёт clock.Running, потом людей.

Срез 6. Фундамент

  • Фазы: 2228
  • Проверен на: 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
  • Итог: все тесты из списков семи фаз на месте; дописан один клиентский; foundation.md приведён к закрытому срезу. Хостовые тесты сверены по коду, не прогоном.

Что подтверждено:

  • Пак: манифест, missing-mod, порядок — выбор игрока плюс топология (не алфавит), цикл — mod-cycle, def без подписи предупреждает и грузится.
  • mods/example на диске; каталог/create/reload; патч только с паком; ваниль без выбора не меняется.
  • Веса в BehaviorDef; пак с обедом выше урока уводит класс; отрицательный вес роняет загрузку.
  • Золотой ростер и сейв; PrimarySchool требует трёх; teachersShort в API и в t(...).
  • Сид — поле школы. Format новее — школа не стартует, файл нетронут. Дамп через снимок + мейлбокс, не в World с HTTP. HSchool.Simulation.Tests без параллельности.
  • Клиентские экраны в happy-dom: фильтры/пейджер, create, сетка, предел фонда текстом.

Дописано:

  • managementPanel.test.ts — непокрытый предмет показывает teachersShort.

Исправлено:

  • AGENTS.md утверждал, что на паузе кадры присутствия не приходят. Работник их шлёт (occupancy + подписи). Тест, который ждёт людей на паузе, зависнет; тест на подпись урока — нет.
  • design/foundation.md: порядок загрузки — не алфавит; «почему сейчас» про веса в Decision.cs устарел.

Замечено рядом:

  • Dump_ReturnsPeopleNodesAndTimetable принимает NodeId is not null || Needs.Count > 0. Нужды есть у всех, так что узлы на кампусе не проверяются. Усиливать до явки — срез 5.

Срез 7. Этап A — данные и гардероб

  • Фазы: 2931
  • Проверен на: d5547b7, 2026-08-20
  • Пути: src/HSchool.Content/{CountryDef,ApparelDefs,CatalogLoader}.cs, src/HSchool.People/{DressGenerator,CountryClimate,RosterJson}.cs, src/HSchool.Server/{Game/SchoolStore.cs,Api/{ModEndpoints,SchoolEndpoints}.cs,Api/PeopleModels.cs}, src/HSchool.Client/src/ui/{createSchoolDialog.ts,personCard.ts}, tests/HSchool.Content.Tests/{CountryDefTests,ApparelDefTests,VanillaCoreTests}.cs, tests/HSchool.People.Tests/{DressGeneratorTests,GoldenRosterTests}.cs, tests/HSchool.AppHost.Tests/{SchoolApiTests,InventoryApiTests,GoldenSaveTests,ServiceabilityTests}.cs, src/HSchool.Client/src/ui/createSchoolDialog.test.ts
  • Итог: все 17 тестов из списков фаз на месте; исправлен дубликат peopleTabOverview в strings.ts (ломал npm run build); одно замечание рядом про вкладки карточки

Что подтверждено:

  • 29: CountryDef/ClimatePresetDef; namesets/ убран; ваниль Russia с двумя пресетами; каталог отдаёт countries; create принимает countryId, unknown-country на чужую страну; пресет от сида (CreateSchool_WithTheSameSeed_GetsTheSameClimatePreset); nameSetId: Slavic поднимается через UpgradeOlderSave (GoldenSaveTests, legacy-no-native/); формат без страны не стартует (ServiceabilityTests); диалог — страны и языки (createSchoolDialog.test.ts); золотой ростер обновлён (GoldenRosterTests).
  • 30: поля ThingDef/ColorDef, слои как роли, мебель без layers; ванильная палитра и одежда в VanillaCoreTests; локали ru/en (EveryVanillaLabel_ExistsInBothLocales); учебник один def без subject на def.
  • 31: InventoryItem с def/color/condition/location/subject; DressGenerator детерминирован (DressGeneratorTests, InventoryApiTests.SameSeed_YieldsTheSameShirt); слои без пересечения, платье на два слоя; пол/яркость/учебник/родители/соискатели/масса/Hauling; карточка отдаёт worn+carried, список — нет (InventoryApiTests); старый people.json без items одевается на reload (PeopleJsonWithoutInventory_IsDressedOnReloadAndRewritten); HSchool.People/HSchool.Content без ASP.NET; протокол не тронут.

Дописано:

  • (нет новых тестов — списки фаз закрыты существующими)

Исправлено:

  • Дубликат ключа peopleTabOverview в strings.ts (ru/en): vitest не ловит TS2300/TS1117, npm run build падал; ключ перенесён, peopleTabPortrait рядом с остальными вкладками.
  • Повтор using HSchool.Content в SchoolStore.cs (CS0105).

Замечено рядом:

  • Карточка уже с вкладками (personCard.ts, фаза 31 обещала один свиток — позже по design/inventory.md); портрет/SwarmUI-строки в strings.ts — не этап A.
  • ResolveClimatePreset в SchoolWorker при reload без сохранённого пресета берёт первый пресет страны (schoolSeed: 0, rollIfOmitted: false) — по дизайну для legacy; create кидает пресет из GameLoopService до spawn.

Срез 7. Этап B–C — климат, жизнь одежды и правила

  • Фазы: 3237
  • Проверен на: 5dcdee6, 2026-08-20
  • Пути: src/HSchool.Simulation/{WeatherSampler,WarmthDecay,PlaceClimate,ApparelDresser,ApparelWear,MorningDress,PersonLogBrowser}.cs, src/HSchool.People/LockerAssigner.cs, src/HSchool.Content/{Appropriateness,ClimatePreset,DressRules}.cs, src/HSchool.Ai/ApparelGoals.cs, src/HSchool.Protocol, src/HSchool.Server/Api/{SchoolEndpoints,DressRulesModels}.cs, src/HSchool.Server/Game/{PersonCardReader,SchoolWorker}.cs, src/HSchool.Client/src/{ui/dressRulesPanel.ts,ui/managementPanel.ts,format/weather.ts,net/api.ts}, docs/protocol.md, tests/HSchool.Simulation.Tests, tests/HSchool.People.Tests/LockerAssignerTests.cs, tests/HSchool.Content.Tests/{ClimatePresetTests,AppropriatenessTests,ChangingRoomMapTests}.cs, tests/HSchool.AppHost.Tests/{PersonLogApiTests,DressRulesApiTests}.cs, tests/HSchool.Protocol.Tests, src/HSchool.Client/src/ui/{personCard,dressRulesPanel,managementPanel}.test.ts
  • Итог: фазы 32–37 подтверждены; дописано 5 тестов симуляции; исправлены 2 бага переодевания и 2 регрессии тестов (не протокол)

Что подтверждено:

  • 3236: погода детерминирована; skip выставляет утро; кадр часов v8 (27 байт) в трёх местах; Warmth и техник; шкафчики; износ и утренняя замена; уместность и PE; вкладки карточки и HTTP-лог.
  • 37: вкладка «Правила» в «Управлении»; четыре комбобокса; GET/POST dress-rules с pending; unknown-form/unknown-color400; правило с завтра; ColorAllowed для whiteTopBlackBottom; dressRulesPanel.test.ts — combobox без API до Apply, ошибка текстом; ManagementPanel не ломает штат и сетку.

Дописано (32–36, прошлый проход):

  • AppropriatenessSimulationTests.MorningDress_StagesPeKitForTodaysLesson
  • AppropriatenessSimulationTests.PeBreak_CompletesChangeBeforeLesson
  • AppropriatenessSimulationTests.PeLesson_WearsPeThenReturnsToEveryday
  • AppropriatenessSimulationTests.AfterPeBreak_ModeExpectsEveryday
  • PresenceTests.PhysicalEducationBeforeLesson_GoesToChangingRoomOnTheBreak

Исправлено:

  • PresenceSystem: ходьба по duty не обрывает активный ChangeClothes.
  • ApparelPresence.ModeAt: после перемены following PE — режим everyday.
  • managementPanel.test.ts: панель штата скрывается через hidden, не удаляется из DOM.
  • PortraitApiTests.GetSwarmUiSettings_ReturnsPresets: URL /api/settings/swarmui (CS1503).
  • dressRulesPanel.ts: неиспользуемый импорт clear — TS6133 на npm run build.

Замечено рядом:

  • PhysicalEducationBeforeLesson_… не гоняет полный цикл «зал с формой» — это в AppropriatenessSimulationTests.PeLesson_….
  • Портреты/SwarmUI — вне среза 7, но PortraitApiTests чинился, чтобы AppHost-сборка не падала.

Срез 8. Сессия и темп

  • Фазы: 3840
  • Проверен на: pending, 2026-08-20
  • Пути: src/HSchool.Server/{Session,Api/SessionEndpoints.cs,Api/SchoolAccess.cs,Game/SchoolOwnership.cs,Game/UserStore.cs,Net/ClientRegistry.cs,Net/GameSocketHandler.cs,Game/GameLoopService.cs}, src/HSchool.Simulation/{ClockSpeed.cs,GameClock.cs,School.cs,SimulationOptions.cs}, src/HSchool.Client/src/{ui/sessionGate.ts,ui/mainMenu.ts,ui/gameScreen.ts,ui/schoolCard.ts,net/protocol.ts,net/api.ts,main.ts}, docs/protocol.md, docs/design/session.md, tests/HSchool.AppHost.Tests/{SessionApiTests.cs,SchoolOwnerApiTests.cs,GameSocketTests.cs}, tests/HSchool.Simulation.Tests/{ClockTempoTests.cs,GameClockTests.cs}, src/HSchool.Client/src/ui/{sessionGate,gameScreen,schoolCard,mainMenu}.test.ts
  • Итог: обещания 38–40 на месте; дописано 4 теста; вторая вкладка с той же кукой больше не получает Welcome. Срез 9 (44/45/47) в работе — не ревьюился

Что подтверждено:

  • 38: HSchool:AlphaPassword (пустой — ValidateOnStart); POST/GET/DELETE /api/session; кука HttpOnly, SameSite=Lax, Path=/, Max-Age из SessionCookieDays (14); users.json каноническое имя; повтор другим регистром — та же запись; игровой HTTP без куки — 401; /health и три ручки сессии — без неё; сокет без куки — close PolicyViolation, без Welcome; Hello по-прежнему версия + локаль; клиент — сначала сессия, потом сокет; хостовые тесты логинятся в ResetAsync/LoginAsync. Версия протокола не бампилась (v8).
  • 39: MaxSchools = 2 на игрока, MaxSchoolsTotal = 16 (в AppHost-тестах Total = 7); create пишет owner; limit-reached в коде — school-limit-reached (как в protocol.md); server-full; GET /api/schoolsschools / others, mine, owner или null; Welcome.MaxSchools — слоты игрока; гость: найм 403 not-owner, SetRunning не меняет running; бесхозная в others, удаляется вторым; меню «Мои»/«Чужие»; в чужой школе нет «Управление» и кнопок часов. Портретный POST без RequireManage. Формат сейва не бампился.
  • 40: база 1 игровая минута/с; таблица 0.5, 1, 2, 5, 10 в ClockSpeed, protocol.ts и protocol.md; 20 импульсов ×1 = 1 минута, индексы 3/4 = 5 и 10 минут; на ×10 тяжёлые 10 раз с квантом 1; на ×5 — 5 раз; индекс 5 игнорируется; воркер по-прежнему 20 Гц и потолок догона 5; пять кнопок на экране. HSchool.Simulation без ASP.NET.

Дописано:

  • SessionApiTests.Login_SetsHttpOnlyLaxSessionCookie
  • SessionApiTests.SecondWebSocket_ForAnOnlineName_ClosesWithoutWelcome
  • ClockTempoTests.AtX5_TwentyImpulses_RunHeavyFiveTimesAndAdvanceFiveGameMinutes (и календарь +1 на ×1 в AtX1_…)
  • mainMenu.test.ts — чужая карточка без удаления, бесхозная с удалением

Исправлено:

  • Второй WebSocket того же имени принимался и слал Welcome: POST проверял «онлайн», upgrade — нет. Две вкладки с живой кукой обходили 409 name-online. Теперь ClientRegistry.TryClaimUserName под тем же замком, второй сокет закрывается PolicyViolation без Welcome.

Открыто:

  • POST /api/session с другим именем при живой куке перезаписывает сессию без DELETE. Дизайн: «Без выхода второе имя с той же куки не взять». Кода ошибки нет — новый HTTP-код молча не вводился.
  • AddDataProtection() без ключей рядом с saves/. Рестарт того же профиля Windows куки, скорее всего, не сбрасывает; новый контейнер — может.
  • Текст фазы 39 пишет limit-reached; живой код и protocol.mdschool-limit-reached (так было до среза). Не менялось.
  • Бесхозная карточка показывает «Хозяин: —», не голый прочерк. GET /api/schools/{id} из дизайна нет: mine/owner живут в списке меню.