Remove off-queue.md and add new design documentation files including README.md, defs.md, near-term.md, projects.md, runtime.md, people.md, staffing.md, and schedule.md to outline the structure and agreements for the game's design phases.

This commit is contained in:
Leonid Pershin
2026-08-20 12:26:13 +03:00
parent 12cc36b4dd
commit a37a5eb82d
101 changed files with 1015 additions and 921 deletions
+39
View File
@@ -0,0 +1,39 @@
# Фаза 5. Дефы человека
## Зависимости
- [Фаза 3](../01-shell/03-defs-map.md)
## Зачем
Каталог учится описывать людей. Ни одного человека в этой фазе не появляется — только типы,
из которых их потом соберут.
## Задачи
- [x] `SkillDef`, `TraitDef`, `BodyAttributeDef`, `NeedDef`, `NameSetDef` в `HSchool.Content`:
новые `DefKind`, разбор, наследование и патчи на общих правилах
- [x] `BodyAttributeDef` двух видов: число с распределением (рост, вес) и выбор из списка с
весами (цвет волос, цвет глаз); распределение зависит от пола и возраста
- [x] `SkillDef.bodyLimits` — зажимы навыка по свойству тела (`min` / `max`); ссылка на
неизвестный атрибут или его значение — ошибка загрузки, а не тихий пропуск
- [x] `TraitDef`: вес, несовместимости, ограничения по роли и возрасту, модификаторы навыков;
односторонняя несовместимость считается взаимной
- [x] `NameSetDef`: мужские и женские имена, фамилии с родовыми формами, правило отчества,
модель склонения по умолчанию или явная таблица шести падежей на запись
- [x] Ванильное содержание `core`: навыки (предметы + физические), около десяти черт, четыре
нужды со скоростью убывания **ноль**, славянский набор имён
- [x] Локализация новых defs в `ru` и `en`
- [x] `GET /api/catalog` отдаёт список наборов имён — форме создания нужен выбор
- [x] Тесты Content: разбор каждого вида, наследование навыка, патч черты, неизвестная ссылка —
ошибка, склонение по правилу и перекрытие таблицей
## Критерий готовности
- Каталог `core` грузится, все новые defs проходят валидацию
- Мод, добавляющий свой набор имён и пару черт, виден в каталоге
- `dotnet test` проходит; школы по-прежнему пустые
## Стоп
Не генерировать людей. Не трогать ECS, сервер сверх выдачи каталога и клиент сверх выбора набора.
@@ -0,0 +1,44 @@
# Фаза 6. Библиотека генерации
## Зависимости
- [Фаза 5](05-people-defs.md)
## Зачем
Из каталога, карты и сида получаются люди — семьями и детерминированно. Библиотека тестируется
без мира, без сокета и без хоста, как `HSchool.Content` сейчас.
## Задачи
- [x] Новый проект `HSchool.People` и `tests/HSchool.People.Tests`; ссылка только на
`HSchool.Content`, ни Arch, ни ASP.NET
- [x] Записи `Person`, `Family`, `SchoolClass`, `Roster` — простые данные, не сущности
- [x] Ёмкость из карты: `PupilSlots` по кабинетам и `RoomDef.positions` дают, сколько нужно
учеников и каких работников
- [x] Генерация семьями: взрослые → дети нужных возрастов → раскладка детей по параллелям и
взрослых по должностям
- [x] Совмещение ролей: работник может оказаться родителем ученика той же школы
- [x] Порядок бросков зафиксирован: тело первым, навыки после, `bodyLimits` применяются зажимом
- [x] Склонения: модели в коде, явная таблица из def перекрывает модель
- [x] Сид школы приходит снаружи; сид каждой семьи выводится из него
## Тесты, без которых фаза не закрыта
- [x] Тот же сид, карта и набор имён — ровно тот же ростер
- [x] Тринадцатая семья не меняет, как разыграны первые двенадцать (фамилия, состав, имена).
Класс ребёнка при этом меняется — места раздаются вперемешку по школе, иначе братья и
сёстры всегда оказывались одноклассниками; см. [`docs/design/02-people/people.md`](../../design/02-people/people.md)
- [x] Фамилии, родовые формы и отчества внутри семьи согласованы
- [x] Ученик с телосложением «полное» не получает высокую ловкость
- [x] Все ученические места заполнены, все должности закрыты
- [x] Школа из одного кабинета и школа из одиннадцати — обе собираются
## Критерий готовности
- `HSchool.People.Tests` зелёные
- В проекте нет упоминаний сокетов, ECS и `DateTime.Now`
## Стоп
Не класть людей в `World`. Не трогать сервер, клиент и формат сейва.
@@ -0,0 +1,35 @@
# Фаза 7. Люди в школе
## Зависимости
- [Фаза 6](06-people-generator.md)
- [Фаза 2](../01-shell/02-school-worker.md)
## Зачем
Школа создаётся с людьми, они живут в её `World` и переживают перезапуск сервера. Экран пока
не меняется — это фаза про данные.
## Задачи
- [x] Ванильная карта дорабатывается до одиннадцати кабинетов; подписи кабинетов становятся
номерами помещений («204»), а не именами классов
- [x] Компоненты Arch под слои человека; сущности собираются из записей генератора
- [x] Класс как сущность: параллель, литера, закреплённый кабинет
- [x] Нужды тикают вместе со школой — механика есть, скорость в `core` нулевая
- [x] `saves/{id}.people.json`: сид, люди, семьи, классы. Пишется **только** при изменении
состава, не по таймеру; `saves/{id}.json` остаётся маленьким и частым
- [x] Загрузка ростера при старте школы; ростер, не сходящийся с картой, оставляет школу
незапущенной и файл нетронутым — как сейчас с картой
- [x] Воркер публикует неизменяемый снимок ростера, как публикует `SchoolState`
## Критерий готовности
- Создать школу — все ученические места и должности заняты
- Перезапустить сервер — те же люди с теми же именами и характеристиками
- Файл ростера не переписывается каждые тридцать секунд
- `dotnet test` проходит
## Стоп
Ни панели, ни фильтров, ни HTTP списка людей. Никакого поведения: люди существуют и стоят.
@@ -0,0 +1,40 @@
# Фаза 8. Просмотр людей
## Зависимости
- [Фаза 7](07-people-in-school.md)
- [Фаза 1](../01-shell/01-manager-shell.md)
## Зачем
Игрок видит, кто учится и работает в его школе, не теряя из виду часы, карту и события.
## Задачи
- [x] `GET /api/schools/{id}/people`: фильтры (роль, параллель, должность, пол, диапазон
возраста), сортировка, страница. Читает **опубликованный снимок** ростера и не трогает
воркер — инвариант «только поток школы касается школы» остаётся целым
- [x] `GET /api/schools/{id}/people/{personId}`: карточка через мейлбокс воркера с
`TaskCompletionSource`, потому что нужды живые
- [x] Панель «Люди» в оболочке менеджера: фильтры, сортируемые колонки, пейджер. Сделана
**вкладкой рядом с картой**, а не нижним рядом — заодно с экрана ушла пустая панель
событий из фазы 1; см. [`docs/design/02-people/people.md`](../../design/02-people/people.md)
- [x] Строка списка — только лёгкие поля: ФИО, роль, класс или должность, возраст, пол
- [x] Карточка: тело, навыки, черты, нужды, семья со ссылками на родных — по ссылке открывается
карточка родственника
- [x] Строки и подписи через `t(...)`, как всё остальное
- [x] `docs/protocol.md` пополняется в том же коммите, что и обработчики
- [x] Тесты API: фильтр по роли и параллели, сортировка, границы страниц, неизвестная школа,
неизвестный человек
## Критерий готовности
- Открыть школу, отфильтровать учеников пятой параллели, отсортировать по фамилии
- Открыть карточку ученика, увидеть родителей, перейти по ссылке к матери
- Тысяча строк не роняет и не подвешивает панель
- `dotnet test` и клиентские `npm test` / `build` проходят
## Стоп
Не редактировать людей. Не нанимать и не увольнять руками. Не показывать характеристики в списке —
только в карточке.
+34
View File
@@ -0,0 +1,34 @@
# Фаза 9. Годовой набор
## Зависимости
- [Фаза 7](07-people-in-school.md)
## Зачем
Состав перестаёт быть слепком момента создания. Первого сентября классы переходят в следующую
параллель, старший выпускается, приходит новый первый.
## Задачи
- [x] Переход по игровому календарю на 1 сентября: у каждого класса параллель +1
- [x] Выпуск **старшей имеющейся** параллели — школа из четырёх кабинетов не ждёт одиннадцатого
класса, чтобы кого-то выпустить
- [x] Освободившийся кабинет достаётся новому первому классу; вместимость школы не меняется —
её меняет только стройка
- [x] Набор первого класса тем же генератором и теми же семьями: у нового ученика может
оказаться брат или сестра в старшей параллели
- [x] Выпускники уходят из ростера; их родители уходят тоже, если не осталось других детей в
школе и они в ней не работают
- [x] Ростер переписывается один раз за переход, снимок публикуется заново — не по тику
## Критерий готовности
- Перемотать год на ×4: параллели сдвинулись, прежний выпускной класс исчез, появился новый первый
- Вместимость школы до и после перехода совпадает
- Перезапуск сервера после перехода поднимает новый состав
- Тест на школу без одиннадцатой параллели: выпускается старшая имеющаяся
## Стоп
Не второгодники. Не переводы между школами. Не увольнения и наём работников.
+19
View File
@@ -0,0 +1,19 @@
Индекс срезов: [`../README.md`](../README.md).
## Срез 2. Люди
Дизайн: [`../design/people.md`](../../design/02-people/people.md).
Школа наполняется людьми. Ученики, работники и родители существуют, показываются и обновляются
раз в год — но **ничего не делают**: ни расписания, ни перемещений, ни влияния характеристик.
Фазы идут по порядку, кроме 8 и 9: обе зависят только от 7 и могут вестись параллельно
(клиент / симуляция).
| Фаза | Статус | Зачем |
| --- | --- | --- |
| [5. Дефы человека](05-people-defs.md) | ✅ | Навыки, черты, тело, нужды, наборы имён в каталоге |
| [6. Библиотека генерации](06-people-generator.md) | ✅ | `HSchool.People`: семьи из карты и сида |
| [7. Люди в школе](07-people-in-school.md) | ✅ | Сущности в `World`, ростер на диске, 11 кабинетов в `core` |
| [8. Просмотр людей](08-people-browser.md) | ✅ | Панель со списком, фильтрами и карточкой |
| [9. Годовой набор](09-yearly-intake.md) | ✅ | Первое сентября: переход, выпуск, набор |
+44
View File
@@ -0,0 +1,44 @@
# Журнал ревью
Сводка: [`../reviewed.md`](../reviewed.md). Индекс среза: [`README.md`](README.md).
## Срез 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/02-people/people.md`.
- Выпуск старшей имеющейся, вместимость, семьи через поколения, reload после набора.
Дописано:
- `PeopleApiTests.ReloadAfterTheYearlyIntake_RestoresTheNewComposition` — ждёт 1 сентября и
требует состав **после** набора, отличный от исходного (детерминизм от сида иначе скрыл бы
потерянный файл).
Исправлено:
- `Card_IncludesFamilyAndLiveNeeds` брал `People[0]` и требовал двух родителей. Состав зависит
от сида; на чистом клоне первый ученик иногда из неполной семьи. Теперь ищет ученика с матерью
и проверяет диапазон.
- `List_SortsBySurname` требовал ordinal-порядок, сервер ставит Ё на место Е. Совпадали, пока
в `core` не было фамилий на Ё.