Update decision-making phase and enhance localization for presence tracking
ci / server (push) Failing after 3m40s
ci / client (push) Successful in 14s

- Marked tasks as complete in the decision-making phase documentation, indicating readiness for implementation.
- Updated the README to reflect the completion status of the decision-making phase.
- Enhanced localization strings to include new presence tracking features, improving user experience.
- Revised the game screen logic to display real-time presence status, including walking states for individuals.
- Added tests to validate the new localization strings and presence functionalities, ensuring robust performance.
This commit is contained in:
Leonid Pershin
2026-08-19 21:00:12 +03:00
parent b135a9caad
commit 5cd5a6d258
21 changed files with 1839 additions and 72 deletions
+19 -19
View File
@@ -11,33 +11,33 @@
## Задачи
- [ ] Цели, веса и планирование — целиком в `HSchool.Ai`: на вход значения, на выход решение.
- [x] Цели, веса и планирование — целиком в `HSchool.Ai`: на вход значения, на выход решение.
В `HSchool.Simulation` — только сбор входов и запись ответа
- [ ] Цели и их вес: обязанность — постоянный высокий, нужда — тем больше, чем ближе к нулю,
- [x] Цели и их вес: обязанность — постоянный высокий, нужда — тем больше, чем ближе к нулю,
досуг — низкий и только при отсутствии обязанности
- [ ] Побеждает наибольший вес. Уход с урока получается из сравнения, а не из отдельного правила
- [ ] Планирование «дойти → сделать»: цель выбирает действие, планировщик достраивает дорогу до
- [x] Побеждает наибольший вес. Уход с урока получается из сравнения, а не из отдельного правила
- [x] Планирование «дойти → сделать»: цель выбирает действие, планировщик достраивает дорогу до
подходящей комнаты
- [ ] Начатое доводится до конца: переключение только при заметном перевесе новой цели
- [ ] Решения по событиям — звонок, конец действия, приход в назначенный узел, пересечение
- [x] Начатое доводится до конца: переключение только при заметном перевесе новой цели
- [x] Решения по событиям — звонок, конец действия, приход в назначенный узел, пересечение
нуждой порога, — а не каждый тик
- [ ] Очередь решений с потолком за тик: отложенное принимается следующим тиком, а не пропадает
- [ ] Возвращение к обязанности после закрытия нужды, если урок ещё идёт
- [ ] Навык растёт на уроке: от предмета, с поправкой на черты и на состояние нужд
- [ ] Опоздание и отсутствие видны: в панели локации на уроке не весь класс, в карточке написано,
- [x] Очередь решений с потолком за тик: отложенное принимается следующим тиком, а не пропадает
- [x] Возвращение к обязанности после закрытия нужды, если урок ещё идёт
- [x] Навык растёт на уроке: от предмета, с поправкой на черты и на состояние нужд
- [x] Опоздание и отсутствие видны: в панели локации на уроке не весь класс, в карточке написано,
где человек
- [ ] Строки через `t(...)`, обе локали
- [x] Строки через `t(...)`, обе локали
## Тесты, без которых фаза не закрыта
- [ ] Ученик с нулевым «туалетом» уходит с урока, доходит до санузла и возвращается в кабинет
- [ ] Нужда чуть ниже порога урок не срывает
- [ ] Две почти равные нужды не заставляют метаться: действие доводится до конца
- [ ] На перемене выбирается досуг, на уроке — нет
- [ ] Навык после учебного дня вырос, у голодного — меньше
- [ ] Потолок решений за тик не теряет решения, а сдвигает их
- [ ] Тот же сид и те же действия игрока дают ту же школу через игровую неделю
- [ ] Разбор решения таблицей входов и выходов, без мира и без хоста
- [x] Ученик с нулевым «туалетом» уходит с урока, доходит до санузла и возвращается в кабинет
- [x] Нужда чуть ниже порога урок не срывает
- [x] Две почти равные нужды не заставляют метаться: действие доводится до конца
- [x] На перемене выбирается досуг, на уроке — нет
- [x] Навык после учебного дня вырос, у голодного — меньше
- [x] Потолок решений за тик не теряет решения, а сдвигает их
- [x] Тот же сид и те же действия игрока дают ту же школу через игровую неделю
- [x] Разбор решения таблицей входов и выходов, без мира и без хоста
## Критерий готовности
+1 -1
View File
@@ -90,4 +90,4 @@
| Фаза | Статус | Зачем |
| --- | --- | --- |
| [20. Нужды и действия](20-needs-actions.md) | ✅ | `ActionDef` с полями, декей, восполнение |
| [21. Выбор действия](21-decisions.md) | | Цели и веса, «дойти → сделать», уход с урока, рост навыка |
| [21. Выбор действия](21-decisions.md) | | Цели и веса, «дойти → сделать», уход с урока, рост навыка |
+96
View File
@@ -0,0 +1,96 @@
# Проверенные срезы
Журнал ревью. Фаза с ✅ в [`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`, и на дереве после починки сборки.