Enhance school day structure and decision-making for lunch breaks
- Updated `ai.md` to clarify the mechanics of hunger restoration and the importance of lunch breaks in the school schedule. - Revised `schedule.md` to detail the new lunch break structure, allowing for separate sittings for different grade levels. - Enhanced `Decision.cs` and `DecisionPlanner.cs` to incorporate logic for lunch breaks, ensuring that students only leave lessons during their designated lunch windows. - Updated `DayFrameDef` and related classes to support multiple lunch breaks and validate their configurations. - Adjusted tests to validate the new decision-making logic regarding lunch breaks and hunger management, ensuring robust functionality. - Improved localization strings to reflect changes in the school day structure and lunch functionalities.
This commit is contained in:
+12
-2
@@ -177,7 +177,16 @@
|
||||
|
||||
Декей включается. Голод — столовая, туалет — санузел, общение — перемена рядом с
|
||||
одноклассниками. Сон **за кадром**: человек вне школы отдыхает, и утром приходит выспавшимся.
|
||||
Ночной школы у нас нет, а изображать её ради одной полоски — лишняя работа.
|
||||
Ночной школы у нас нет, а изображать её ради одной полоски — лишняя работа. **Голод восстанавливается
|
||||
там же**: дома завтракают и ужинают, иначе дефицит копится из дня в день и вся школа через неделю
|
||||
сидит на нуле — так и было, пока это не измерили.
|
||||
|
||||
**Обед — расписание, а не порыв.** Действие с `lunch: true` предлагается только в свою смену
|
||||
(`DayFrameDef.lunchBreaks`), зато в неё идут и те, кто ещё не проголодался до порога. Иначе
|
||||
получается одно из двух: либо класс уходит с урока в столовую, когда приспичило, либо — если
|
||||
ждать порога — вся параллель проскакивает свою смену и голодает до вечера. Смены разводят
|
||||
параллели: в `core` младшие едят после третьего урока, старшие после четвёртого, и каждая такая
|
||||
перемена становится длинной.
|
||||
|
||||
Прогресс-бары нужд в карточке уже нарисованы. В этом срезе они впервые начнут шевелиться.
|
||||
|
||||
@@ -302,7 +311,8 @@
|
||||
| Планирование | Вырожденный GOAP: «дойти → сделать», два предусловия |
|
||||
| Переключение | Начатое доводится до конца, если новая цель не сильно тяжелее |
|
||||
| Частота решений | По событиям: звонок, конец действия, приход, порог нужды |
|
||||
| Нужды | Декей включается; сон вне школы |
|
||||
| Нужды | Декей включается; сон и голод восстанавливаются вне школы |
|
||||
| Обед | Смены по параллелям в каркасе дня; в свою смену едят и не проголодавшиеся |
|
||||
| Последствия | Навык на уроке, видимые опоздание и прогул. Оценок нет |
|
||||
| Игрок | Наблюдает; приказов нет |
|
||||
| Поток присутствия | Своё сообщение, ~2 Гц, по всей школе, фильтр на клиенте |
|
||||
|
||||
@@ -11,9 +11,12 @@ QSP: не «полотно текста + список действий», а п
|
||||
Ориентир по экрану школы (после часов и паузы, которые уже есть):
|
||||
|
||||
- **Карта** — дерево локаций (tree view).
|
||||
- **Общие события** — то, что происходит в школе целиком, не привязано к выбранному узлу.
|
||||
- **Выбранная локация** — что происходит именно там.
|
||||
|
||||
Отдельной панели общих событий нет: пока ленты событий не существует, постоянно пустая треть
|
||||
экрана только мешала, и место отдано вкладкам «Карта» и «Люди». Появится лента — вернётся и
|
||||
панель; решение принято в фазе 8, здесь оно записано.
|
||||
|
||||
Картинок локаций и портретов в этом горизонте нет; позже можно добавить, не ломая панели.
|
||||
Анимация пока только переходы панелей/списков и живые цифры (часы, счётчики). Графики —
|
||||
когда появятся числа, которые ими стоит показывать, не раньше.
|
||||
@@ -30,10 +33,9 @@ QSP: не «полотно текста + список действий», а п
|
||||
|
||||
Это и реализуем. Нового геймплея сверх этого не закладываем.
|
||||
|
||||
**Экран школы.** Часы, пауза, скорость. Три панели:
|
||||
**Экран школы.** Часы, пауза, скорость. Две панели:
|
||||
|
||||
- дерево карты;
|
||||
- общие события — **пусто** (заголовок и пустое состояние);
|
||||
- дерево карты (позже к нему вкладкой встанут «Люди»);
|
||||
- выбранная локация (комната, этаж, корпус **или двор**): имя, предметы, персонажи (пусто),
|
||||
действия на месте (пусто), должности. Секции те же; где данных нет — пустое состояние.
|
||||
|
||||
@@ -49,7 +51,7 @@ QSP: не «полотно текста + список действий», а п
|
||||
|
||||
| Тема | Решение |
|
||||
| --- | --- |
|
||||
| Вид внутри школы | Менеджер: дерево + общие события + содержимое локации |
|
||||
| Вид внутри школы | Менеджер: дерево (вкладкой с «Людьми») + содержимое локации |
|
||||
| Рендер | DOM, не WebGL/Pixi |
|
||||
| Картинки | Нет в этом горизонте |
|
||||
| Анимация | Переходы и живые цифры |
|
||||
|
||||
@@ -290,13 +290,15 @@ Protocol ← Server → Simulation → People → Content
|
||||
|
||||
## Просмотр
|
||||
|
||||
Не отдельный экран. Список людей — **панель в оболочке менеджера**, рядом с картой и событиями:
|
||||
уходя в отдельный экран, игрок теряет из виду часы, локацию и события, а смысл этой оболочки
|
||||
Не отдельный экран. Список людей — **панель в оболочке менеджера**, рядом с картой:
|
||||
уходя в отдельный экран, игрок теряет из виду часы и локацию, а смысл этой оболочки
|
||||
ровно в том, чтобы всё было на одном экране. Ориентир — прогрессивный QSP: текст, списки и ссылки,
|
||||
без сцены.
|
||||
|
||||
Панель «Люди» занимает нижний ряд во всю ширину — фильтрам, колонкам и пейджеру нужна ширина,
|
||||
которой нет в колонке шириной в треть экрана.
|
||||
Панель «Люди» — вторая вкладка той же панели, что и карта: фильтрам, колонкам и пейджеру нужна
|
||||
ширина, а держать список третьим рядом под картой значило бы отдать ему высоту, которой на экране
|
||||
школы нет. Вкладка переключается рядом с деревом, выбранная строка открывает карточку справа —
|
||||
там же, где содержимое локации.
|
||||
|
||||
- **Фильтры**: роль (ученики / работники / родители), параллель и литера, должность, пол,
|
||||
возрастной диапазон.
|
||||
@@ -388,7 +390,7 @@ Protocol ← Server → Simulation → People → Content
|
||||
| Имена и язык UI | Независимы |
|
||||
| Библиотека | `HSchool.People`: зависит от Content, не знает Arch и ASP.NET |
|
||||
| Детерминизм | Сид в сейве, свой сид на семью; родной язык — часть входа |
|
||||
| Просмотр | Панель в оболочке менеджера, нижний ряд; не отдельный экран |
|
||||
| Просмотр | Панель в оболочке менеджера, вкладкой рядом с картой; не отдельный экран |
|
||||
| Список | Фильтры, сортировки, пейджинг; в строке — только лёгкие поля |
|
||||
| Транспорт списка | HTTP по опубликованному снимку ростера |
|
||||
| Транспорт карточки | Запрос в воркер, потому что нужды живые |
|
||||
|
||||
@@ -131,7 +131,8 @@
|
||||
| --- | --- |
|
||||
| Кто строит | Сервер, детерминированно, из плана и назначений |
|
||||
| Правки игрока | Можно; исправленное закрепляется и переживает перестроение |
|
||||
| Каркас дня | `core`: 8:30, до 7 уроков, 45 минут, перемены 10 и одна 20; одна смена |
|
||||
| Каркас дня | `core`: 8:30, до 7 уроков, 45 минут, перемены 10; длинные — те, в которые обедают |
|
||||
| Обеденные смены | Список в каркасе дня: какая параллель ест после какого урока |
|
||||
| Длина недели | В настройках, пять дней по умолчанию |
|
||||
| Каникулы | Дефами, диапазонами дат; в каникулы расписания нет |
|
||||
| Учебный план | Часы из `SubjectDef`, расписание их только расставляет |
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
- [x] Клик по дереву переключает панель локации на клиенте (пока узлы-заглушки или пустое дерево)
|
||||
- [x] Переключение языка обновляет подписи панелей и пустых состояний (`t(...)`)
|
||||
|
||||
> **Отменено в фазе 8.** Панель общих событий убрана с экрана: пока ленты событий нет, постоянно
|
||||
> пустая треть экрана мешала, и место отдано вкладкам «Карта» и «Люди». Решение записано в
|
||||
> [`../design/near-term.md`](../design/near-term.md); появится лента — вернётся и панель.
|
||||
|
||||
## Критерий готовности
|
||||
|
||||
- Школу можно открыть, часы и пауза работают как раньше
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
воркер — инвариант «только поток школы касается школы» остаётся целым
|
||||
- [x] `GET /api/schools/{id}/people/{personId}`: карточка через мейлбокс воркера с
|
||||
`TaskCompletionSource`, потому что нужды живые
|
||||
- [x] Панель «Люди» нижним рядом оболочки менеджера, во всю ширину: фильтры, сортируемые
|
||||
колонки, пейджер
|
||||
- [x] Панель «Люди» в оболочке менеджера: фильтры, сортируемые колонки, пейджер. Сделана
|
||||
**вкладкой рядом с картой**, а не нижним рядом — заодно с экрана ушла пустая панель
|
||||
событий из фазы 1; см. [`../design/people.md`](../design/people.md)
|
||||
- [x] Строка списка — только лёгкие поля: ФИО, роль, класс или должность, возраст, пол
|
||||
- [x] Карточка: тело, навыки, черты, нужды, семья со ссылками на родных — по ссылке открывается
|
||||
карточка родственника
|
||||
|
||||
+111
-5
@@ -9,7 +9,7 @@
|
||||
## Срез 1. Оболочка и карта
|
||||
|
||||
- **Фазы:** 0–4
|
||||
- **Проверен на:** `b135a9c`, 2026-08-19
|
||||
- **Проверен на:** `5a00ad7`, 2026-08-19 (первичная проверка — `b135a9c`)
|
||||
- **Пути:** `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`,
|
||||
@@ -95,10 +95,29 @@
|
||||
компилировалось. Выводам по срезу 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`.
|
||||
## Срез 2. Люди
|
||||
|
||||
- **Фазы:** 5–9
|
||||
- **Проверен на:** `5cd5a6d`, 2026-08-19
|
||||
- **Проверен на:** `5a00ad7`, 2026-08-19 (первичная проверка — `5cd5a6d`)
|
||||
- **Пути:** `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`,
|
||||
@@ -176,10 +195,24 @@
|
||||
идущего человека). По правилу журнала он больше не «проверен на `b135a9c`» — правка маленькая и
|
||||
относится к срезу 5, но следующий проход это увидит.
|
||||
|
||||
### Перепроверка на `5a00ad7`, 2026-08-19
|
||||
|
||||
`5a00ad7` переписал половину генератора: `PersonSampler` (+266), `FamilyFactory` (+52),
|
||||
`YearlyIntake` (+61), новый `NativeLanguages`, новые навыки в `core` (языки, социальные,
|
||||
рабочие). Проверено по изменению:
|
||||
|
||||
- Решение записано заранее — `design/people.md` описывает родные языки и порядок выдачи навыков
|
||||
ещё с `a441ed9`, код догнал документ. Дрейфа нет, это тот редкий случай, когда дизайн шёл первым.
|
||||
- Восемь новых тестов в `SkillGrantTests` закрывают выдачу навыков по параллелям, отсутствие
|
||||
рабочих навыков у учеников и набор без переброса уже выданного. Старые тесты детерминизма,
|
||||
согласованности семьи и годового набора зелёные — генератор переписан, а обещания среза целы.
|
||||
- Исправления среза 2 (карточка семьи, сортировка по фамилии, состав после набора и перезапуска)
|
||||
проходят на новом коде.
|
||||
|
||||
## Срез 3. Штат
|
||||
|
||||
- **Фазы:** 10–13
|
||||
- **Проверен на:** `21d79cb`, 2026-08-19
|
||||
- **Проверен на:** `5a00ad7`, 2026-08-19 (первичная проверка — `21d79cb`)
|
||||
- **Пути:** `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}`,
|
||||
@@ -249,10 +282,38 @@
|
||||
- В `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 человека нанять всех и остаться в пределах суммы
|
||||
уже нельзя.
|
||||
|
||||
## Срез 4. Расписание
|
||||
|
||||
- **Фазы:** 14–17
|
||||
- **Проверен на:** `21d79cb`, 2026-08-19
|
||||
- **Проверен на:** `5a00ad7`, 2026-08-19 (первичная проверка — `21d79cb`)
|
||||
- **Пути:** `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` (пин, перестроение),
|
||||
@@ -319,10 +380,18 @@
|
||||
прогона старых бинарников против новых данных, а не находка. Поэтому весь срез проверялся в
|
||||
отдельном worktree на `21d79cb`: AppHost 63/63 (три прогона подряд), клиент 33/33 и сборка.
|
||||
|
||||
### Перепроверка на `5a00ad7`, 2026-08-19
|
||||
|
||||
Из путей среза коммит тронул только каталог навыков: пять строк ушли из `skills/academic.jsonc`,
|
||||
появились `languages`, `social` и `work`. Предметы ссылаются на навыки долями, и обе защиты на
|
||||
месте и зелёные — `Subject_UnknownSkill_FailsTheCatalog` и `EveryVanillaLabel_ExistsInBothLocales`.
|
||||
Планировщик, каркас дня и API расписания не менялись; дописанный в прошлый проход тест на коды
|
||||
отказа пина проходит.
|
||||
|
||||
## Срез 5. Жизнь школы
|
||||
|
||||
- **Фазы:** 18–21
|
||||
- **Проверен на:** `21d79cb`, 2026-08-19. Фаза 21 закрыта за час до проверки, `core` правился
|
||||
- **Проверен на:** `5a00ad7`, 2026-08-19 (первичная проверка — `21d79cb`). Фаза 21 закрыта за час до проверки, `core` правился
|
||||
параллельно — проверка велась в отдельном worktree на коммите, не на рабочем дереве
|
||||
- **Пути:** `src/HSchool.Ai`, `src/HSchool.Simulation/{PresenceSystem,ActivitySystem,LessonLearningSystem,School}.cs`,
|
||||
`src/HSchool.Protocol` (кадр `Presence`, пропуск в кадре часов),
|
||||
@@ -402,3 +471,40 @@
|
||||
случаях чинить надо тест, а не присутствие: занятость узла берётся из расписания, а явка — из
|
||||
состояния, и совпадать они не обязаны — фаза 21 прямо обещает, что «опоздание и отсутствие
|
||||
видны».
|
||||
|
||||
### Перепроверка на `5a00ad7`, 2026-08-19
|
||||
|
||||
`School.cs` изменился на десять строк — родной язык прокинут в `InstallPeople`. Присутствие,
|
||||
ходьба, действия и решения не тронуты, `decayPerHour` и `needGain` тоже, поэтому измеренная выше
|
||||
картина с голодом остаётся в силе: вопрос «чем закрывать голод» по-прежнему открыт.
|
||||
|
||||
## Что из журнала закрыто, 2026-08-19
|
||||
|
||||
Ревью нашло семь пунктов; шесть закрыты в этот же день, седьмой снят как несуществующий.
|
||||
|
||||
- **Панель «Общие события».** Решено отказаться. `design/near-term.md` и `design/people.md`
|
||||
переписаны под вкладочную оболочку, фазы 1 и 8 помечены сноской, мёртвые ключи
|
||||
`eventsTitle`/`eventsEmpty` убраны из `i18n`. Экран не менялся — он и был правдой.
|
||||
- **Голод упирался в ноль.** Закрыто тремя правками данных и одной правкой поведения:
|
||||
`Hunger` восстанавливается вне школы (дома едят), `decayPerHour` 0.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 клиентских, сборка клиента зелёная.
|
||||
|
||||
Reference in New Issue
Block a user