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
@@ -0,0 +1,48 @@
# Фаза 22. Удостоверение пака
## Зависимости
- [Фаза 4](../01-shell/04-create-editor.md) — моды и каталог уже ездят в create
## Зачем
Мод перестаёт быть именем папки. У него появляется название, версия и список паков, без которых он
не работает, — а у сервера появляется право отказать во внятной форме вместо сломанного каталога.
## Задачи
- [x] `pack.jsonc` в папке пака: `version` строкой, `requires` списком id. Файла нет — пак
по-прежнему валиден: id вместо названия, версия пустая, зависимостей нет
- [x] Название пака — ключ по его id в его же `localizations/<lang>.jsonc`; второго способа
называть вещи не заводить
- [x] `GET /api/mods` принимает `?lang=ru|en` и отдаёт `label`, `version` и `requires` рядом с
`id` и `required`
- [x] У `core` такой же `pack.jsonc` и такое же название в локалях
- [x] Создание школы проверяет, что каждая зависимость выбрана; нет — `400` с кодом и id того,
кого не хватает
- [x] Порядок загрузки выстраивает сервер: устойчивая топологическая сортировка поверх порядка
игрока, `core` всегда первый. Цикл зависимостей — отказ
- [x] Разрешённый порядок виден: пишется в лог при старте школы и возвращается в ответе создания
- [x] Сейв хранит **разрешённый** порядок паков, чтобы школа поднималась тем же каталогом
- [x] Загрузчик пишет предупреждение, когда у конкретного def нет подписи в локали пака
- [x] `docs/protocol.md` и [`docs/design/06-foundation/foundation.md`](../../design/06-foundation/foundation.md) правятся тем же
коммитом, что и обработчики
## Тесты, без которых фаза не закрыта
- [x] Пак без `pack.jsonc` виден в списке, id стоит вместо названия
- [x] Название приходит на языке запроса, у `core` тоже
- [x] Пак с невыбранной зависимостью не создаёт школу; в ответе видно, кого не хватает
- [x] Зависимость, выбранная после зависимого, всё равно грузится раньше
- [x] Цикл зависимостей — отказ, а не зависание
- [x] Def без подписи даёт предупреждение, но не роняет каталог
## Критерий готовности
- В диалоге создания моды подписаны по-человечески, `core` заблокирован как раньше
- Школа с модом поднимается после перезапуска тем же набором и в том же порядке
- `dotnet test` и клиентские `npm test` / `run build` проходят
## Стоп
Не грузить DLL. Не давать менять набор модов у живой школы. Не делать UI перестановки паков.
@@ -0,0 +1,39 @@
# Фаза 23. Настоящий пример мода
## Зависимости
- [Фаза 22](22-mod-identity.md) — пример должен нести удостоверение, как все
## Зачем
Дорога от папки на диске до школы не проверена ни разу: в `mods/` лежит только `core`, а last-wins
и патчи живут в тестах на документах в памяти. Один маленький настоящий пак делает весь путь
проверяемым — и заодно служит образцом для мод-автора.
## Задачи
- [x] Пак `mods/example` рядом с `core`: `pack.jsonc`, свои локали, пара черт, набор имён,
патч чужого def и одна комната
- [x] Пак нарочно скучный: он образец и фикстура, а не контент. Ванильную игру он не меняет,
пока не выбран
- [x] Пак едет в вывод сборки тестов теми же правилами, что и `core`
- [x] Короткий `README.md` внутри пака: что где лежит и как добавить своё
- [x] Ориентир в [`docs/design/01-shell/defs.md`](../../design/01-shell/defs.md) показывает на него как на образец
## Тесты, без которых фаза не закрыта
- [x] `GET /api/catalog?mods=example` отдаёт типы и подписи пака поверх `core`
- [x] Школа создаётся с паком и поднимается с ним после перезапуска
- [x] Патч пака виден в каталоге школы, а без пака его нет
- [x] Одинаковый `defName` в `core` и в паке — побеждает пак, в логе предупреждение
- [x] Карта пака проходит валидацию и годится для создания школы
## Критерий готовности
- Создать школу с включённым паком и увидеть его содержимое внутри школы
- Снять пак — школа создаётся прежней
- `dotnet test` проходит
## Стоп
Не превращать пример в контент-пак: чем он меньше, тем дольше проживёт.
@@ -0,0 +1,36 @@
# Фаза 24. Числа поведения в данные
## Зависимости
- [Фаза 20](../05-ai/20-needs-actions.md) — `BehaviorDef` уже есть
## Зачем
[`docs/design/05-ai/ai.md`](../../design/05-ai/ai.md) обещает: числа поведения — деф правил, как `StaffingDef` у
штата. Порог нужды и разброс на дорогу туда уехали, а веса целей остались константами в коде.
Пока они там, мод, меняющий одно число, вынужден быть форком.
## Задачи
- [x] Веса целей переезжают в `BehaviorDef`: обязанность-урок, обязанность-переход, нужда на нуле,
обед
- [x] Значения по умолчанию — сегодняшние; поведение не должно измениться ни на минуту
- [x] Каталог без `BehaviorDef` работает на константах кода, а не падает
- [x] Валидатор ловит отрицательные веса и порядок, который делает обед сильнее урока
- [x] Комментарий у каждого числа объясняет, что оно перевешивает — иначе мод-автор крутит вслепую
## Тесты, без которых фаза не закрыта
- [x] Ванильные числа дают ровно те же решения, что и до переезда (таблица входов и выходов)
- [x] Пак, поднявший вес обеда выше урока, уводит класс с урока в столовую
- [x] Каталог без `BehaviorDef` принимает решения на значениях по умолчанию
- [x] Отрицательный вес роняет загрузку каталога
## Критерий готовности
- Отпечаток недели из фазы 25 не меняется от самого переезда
- `dotnet test` проходит
## Стоп
Не заводить формулы в данных. Число — да, выражение — нет.
@@ -0,0 +1,43 @@
# Фаза 25. Золотые файлы и мелкие долги
## Зависимости
Нет. Можно вести параллельно с 22–24.
## Зачем
Две регрессии сейчас ловятся только глазами: молчаливая смена генерации людей и сейв, который
перестал грузиться после нового поля. Обе дешёвы в защите и дороги в разборе постфактум.
## Задачи
- [x] Отпечаток ростера файлом в фикстурах: фиксированные сид, карта, набор имён и родной язык
- [x] Тест печатает **первую** несовпавшую строку, а не «строки различаются»
- [x] Правило записано рядом с фикстурой: отпечаток обновляется в том же коммите, что и генерация,
и коммит объясняет, что поменялось
- [x] Файл сейва текущего формата в фикстурах хоста; тест поднимает из него школу
- [x] Рядом с ним — файл сейва **без** новых полей, чтобы «отсутствующее поле имеет разумное
значение» проверялось, а не подразумевалось
- [x] Форма голодного дня закрепляется тестом: приходят сытыми, к своей смене около 0.4, уходят
голодными, к утру снова полны. Числа подобраны замером и сейчас ничем не защищены
- [x] `uncovered` в staffing говорит, скольких учителей не хватает предмету
- [x] Клиент показывает это число в списке непокрытых, через `t(...)`
## Тесты, без которых фаза не закрыта
- [x] Отпечаток ростера совпадает с файлом
- [x] Школа поднимается из сохранённого файла: те же люди, то же время, тот же штат
- [x] Сейв без поля родного языка грузится и не перетасовывает набор
- [x] Кривая голода за учебный день держится в заявленных границах
- [x] `PrimarySchool` на ванильной карте требует трёх учителей, и ответ это говорит
- [x] Назначение второго учителя уменьшает нехватку, но не закрывает её
## Критерий готовности
- Изменить порядок бросков в генераторе — золотой тест краснеет
- Добавить поле в сейв — старый файл по-прежнему грузится
- `dotnet test` и клиентские `npm test` / `run build` проходят
## Стоп
Не превращать золотой файл в снимок всего мира: отпечаток должен читаться человеком.
@@ -0,0 +1,41 @@
# Фаза 26. Свой сид у школы
## Зависимости
- [Фаза 7](../02-people/07-people-in-school.md) — сид уже лежит в файле людей
## Зачем
Сегодня сид генерации — это `school.Id`. Отсюда три неприятности сразу: у двух игроков школа №1
населена одинаково, состав школы зависит от того, сколько школ создали до неё, и воспроизвести
чужой баг нельзя, не повторив последовательность идентификаторов. Ревью споткнулось об это
дважды — тест, зелёный на машине разработчика и красный на чистом клоне, был ровно про это.
Сид уже хранится в файле людей. Не хватает одного: чтобы он был **свой**, а не производный.
## Задачи
- [x] При создании школы сид берётся из генератора случайных чисел сервера, а не из id
- [x] `POST /api/schools` принимает необязательный `seed`; передали — берётся он, нет — бросается
- [x] Сид виден: в ответе `GET /api/schools` и на экране школы, чтобы его можно было переслать
- [x] Существующие школы не меняются: сид читается из файла людей, как и сейчас
- [x] Тесты хоста, которым нужен предсказуемый состав, передают сид явно, а не полагаются на
порядок создания
- [x] `docs/protocol.md` и [`docs/design/02-people/people.md`](../../design/02-people/people.md) правятся тем же коммитом
## Тесты, без которых фаза не закрыта
- [x] Две школы, созданные с одним сидом, населены одинаково; с разными — по-разному
- [x] Школа, созданная без сида, после перезапуска поднимает тот же состав
- [x] Старый сейв, где сид совпадал с id, грузится и состав не меняется
- [x] Переданный сид виден в списке школ
## Критерий готовности
- Создать две школы подряд без сида и увидеть разные фамилии в пятых классах
- Создать школу с чужим сидом и получить ту же школу
- `dotnet test` и клиентские `npm test` / `run build` проходят
## Стоп
Не делать сид редактируемым у живой школы. Не показывать его там, где он мешает.
@@ -0,0 +1,45 @@
# Фаза 27. Версия сейва и взгляд внутрь
## Зависимости
- [Фаза 2](../01-shell/02-school-worker.md) — сейв и дев-поверхность уже есть
## Зачем
Две дыры в обслуживании. Первая: `Format` в сейве штампуется при записи и копируется при чтении,
но никогда не сравнивается — файл из будущей версии загрузится молча и будет понят неправильно, а
места для миграции просто нет. Вторая: посмотреть внутрь живой школы нечем. Всё ревью замеры
делались временными тестами, потому что другого способа узнать, кто где и с какими нуждами, не
существует.
## Задачи
- [x] Загрузка читает `Format`: новее своего — школу не стартовать, файл не трогать, в лог. Как с
пропавшей папкой мода
- [x] Формат старее своего — явный шов для апгрейда: сегодня пустой, но названный и с комментарием
- [x] `GET /api/dev/schools/{id}/dump` — ростер, присутствие, расписание и нужды одним JSON. За тем
же переключателем, что и `reload-schools`, и никогда в проде по умолчанию
- [x] Дамп читает опубликованные снимки и мейлбокс, а не лезет в `World` мимо работника
- [x] Раздел «Things that will bite you» в `AGENTS.md` пополняется тем, что нашло ревью: сид школы
был её id; тесты хоста делят один сервер и одну папку сейвов, поэтому начинают с очистки;
на паузе кадры присутствия не приходят вовсе
- [x] Классы тестов вокруг Arch перестают идти параллельно: `HSchool.Simulation.Tests` получает
запрет параллельности, потому что нативная память Arch этого не любит
- [x] `docs/protocol.md` описывает дамп в разделе дев-ручек
## Тесты, без которых фаза не закрыта
- [x] Сейв с `format` больше текущего оставляет школу незапущенной и файл нетронутым
- [x] Сейв текущего формата грузится как раньше
- [x] Дамп отдаёт людей, их узлы и текущее расписание для живой школы
- [x] Дамп неизвестной школы — `404`
## Критерий готовности
- Подсунуть сейв из будущего — сервер стартует, эта школа не поднимается, в логе понятно почему
- Снять дамп с идущей школы и увидеть, кто где стоит
- `dotnet test` проходит; полный прогон решения стабилен
## Стоп
Не писать миграции, которых пока не нужно. Не открывать дев-ручки без переключателя.
@@ -0,0 +1,41 @@
# Фаза 28. Тесты экранов
## Зависимости
- [Фаза 13](../03-staffing/13-management-tab.md), [Фаза 17](../04-schedule/17-timetable-screen.md) — самые крупные экраны уже есть
## Зачем
`AGENTS.md` говорит: у экранов тестов нет, потому что DOM-окружение стоило бы зависимости,
которой у проекта нет. На первой фазе это было верно — экран состоял из часов. Сейчас в клиенте
несколько тысяч строк с диалогами, сеткой расписания, редактором карты и панелью управления, а
vitest уже стоит: DOM-окружение — это одна девзависимость.
Цель не «покрыть клиент», а закрыть те места, где на клиенте есть **логика**: сборка запроса из
фильтров, разбор кода ошибки в текст, блокировки в диалоге создания.
## Задачи
- [x] `happy-dom` как девзависимость и окружение vitest для тестов экранов
- [x] Тесты панели людей: фильтры собирают правильный запрос, пейджер не уезжает за границы
- [x] Тесты диалога создания: `core` нельзя снять, сброс карты возвращает дефолт, кнопка
блокируется на время запроса
- [x] Тесты сетки расписания: код отказа планировщика превращается в текст, а не в молчание
- [x] Тесты панели управления: отказ по пределу фонда показывается текстом
- [x] Политика тестирования в `AGENTS.md` переписывается: что теперь проверяется тестом, а что
по-прежнему глазами
- [x] Прогон экранов не должен заметно удлинять `npm test`
## Тесты, без которых фаза не закрыта
Сами тесты и есть содержание фазы; закрывают её четыре набора выше.
## Критерий готовности
- `npm --prefix src/HSchool.Client test` гоняет экраны и проходит
- CI не удлинился настолько, чтобы это раздражало
- Сломать `t(...)` в одном из экранов — тест краснеет
## Стоп
Не тащить фреймворк ради тестов. Не проверять вёрстку и стили — только поведение.
+23
View File
@@ -0,0 +1,23 @@
Индекс срезов: [`../README.md`](../README.md).
## Срез 6. Фундамент
Дизайн: [`../design/foundation.md`](../../design/06-foundation/foundation.md).
Срез без единой новой кнопки для игрока. Мод становится настоящим объектом, числа поведения
уезжают из кода в данные, сид перестаёт быть идентификатором, у сейва появляется версия, а то,
что ревью ловило глазами, начинает ловиться тестом. Всё здесь дёшево поодиночке и заметно
упрощает следующие срезы.
Порядок свободный: 25, 26 и 27 ни от чего не зависят, 23 стоит на 22. Начинать разумно с 25 —
она страхует всё остальное.
| Фаза | Статус | Зачем |
| --- | --- | --- |
| [22. Удостоверение пака](22-mod-identity.md) | ✅ | Название, версия, зависимости и порядок загрузки |
| [23. Пример мода](23-example-pack.md) | ✅ | Настоящая папка вместо документов в памяти |
| [24. Числа поведения](24-behavior-numbers.md) | ✅ | Веса целей в `BehaviorDef`, а не в коде |
| [25. Золотые файлы](25-golden-fixtures.md) | ✅ | Отпечаток ростера, старый сейв, кривая голода, нехватка учителей |
| [26. Свой сид](26-school-seed.md) | ✅ | Состав школы перестаёт зависеть от порядка создания |
| [27. Версия сейва и дамп](27-serviceability.md) | ✅ | Формат читается, внутрь школы можно заглянуть |
| [28. Тесты экранов](28-screen-tests.md) | ✅ | DOM-окружение и логика клиента под тестом |
+44
View File
@@ -0,0 +1,44 @@
# Журнал ревью
Сводка: [`../reviewed.md`](../reviewed.md). Индекс среза: [`README.md`](README.md).
## Срез 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/06-foundation/foundation.md`: порядок загрузки — не алфавит; «почему сейчас» про веса в `Decision.cs`
устарел.
Замечено рядом:
- `Dump_ReturnsPeopleNodesAndTimetable` принимает `NodeId is not null || Needs.Count > 0`.
Нужды есть у всех, так что узлы на кампусе не проверяются. Усиливать до явки — срез 5.