From 450495f666c9443296ba275292e308c2ae81e63f Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 02:34:16 +0300 Subject: [PATCH] Update design documentation for inventory and climate phases, clarify staffing test results, and add new presence test for hiring and assigning a teacher during lessons. --- AGENTS.md | 1 + docs/design/inventory.md | 429 ++++++++++++++++++ docs/design/people.md | 7 +- docs/phases/29-country.md | 49 ++ docs/phases/30-apparel-defs.md | 43 ++ docs/phases/31-dress-generator.md | 49 ++ docs/phases/32-weather-warmth.md | 45 ++ docs/phases/33-lockers-technician.md | 47 ++ docs/phases/34-wear-home.md | 42 ++ docs/phases/35-appropriateness.md | 51 +++ docs/phases/36-person-card-tabs.md | 48 ++ docs/phases/37-school-rules.md | 41 ++ docs/phases/README.md | 40 ++ docs/phases/reviewed.md | 115 ++++- .../HSchool.Simulation.Tests/PresenceTests.cs | 34 ++ 15 files changed, 1033 insertions(+), 8 deletions(-) create mode 100644 docs/design/inventory.md create mode 100644 docs/phases/29-country.md create mode 100644 docs/phases/30-apparel-defs.md create mode 100644 docs/phases/31-dress-generator.md create mode 100644 docs/phases/32-weather-warmth.md create mode 100644 docs/phases/33-lockers-technician.md create mode 100644 docs/phases/34-wear-home.md create mode 100644 docs/phases/35-appropriateness.md create mode 100644 docs/phases/36-person-card-tabs.md create mode 100644 docs/phases/37-school-rules.md diff --git a/AGENTS.md b/AGENTS.md index e98791a..3b0e655 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,7 @@ way; this file is *how to work in them*. | connection handling, workers, saves | `src/HSchool.Server` | | what runs locally | `src/HSchool.AppHost/AppHost.cs` | | screens, dialogs, formatting, UI language | `src/HSchool.Client/src` | +| clothing, inventory, climate, dress codes | [`docs/design/inventory.md`](docs/design/inventory.md) — slice 7, phases 29–37; no code yet | ## Commands diff --git a/docs/design/inventory.md b/docs/design/inventory.md new file mode 100644 index 0000000..74a9ee0 --- /dev/null +++ b/docs/design/inventory.md @@ -0,0 +1,429 @@ +# Одежда, вещи и климат + +Договорённость на следующий срез, не текущий код. +Люди: [`people.md`](people.md). Типы и карта: [`defs.md`](defs.md). Жизнь школы: [`ai.md`](ai.md). +Экран: [`near-term.md`](near-term.md). Проекты: [`projects.md`](projects.md). + +У человека появляется то, что на нём надето и что он несёт. Школа получает климат и правила +одежды. Игрок это **смотрит** и выбирает правила; надевает не он — система сама приводит гардероб +к уместности, сезону и погоде. + +Срез отвечает на вопросы «во что одеты», «что в сумке», «какая на улице погода» и «что человек +делал сегодня». Магазинов, стирки, краж и выдачи со склада нет. + +## Что меняется в уже принятом + +| Было | Стало | Почему | +| --- | --- | --- | +| Create выбирает набор имён | Create выбирает **страну**; набор имён вложен в неё | Климат, имена и вкус к одежде — одна культура, а не три пикера | +| `ThingDef` — мебель на карте | Тот же вид def, поля отличают размещаемое / носимое / переносимое | Стул и футболка — предметы, но живут в разных местах | +| Карточка — один свиток | Вкладки: обзор, одежда, ноша, сейчас + история | Гардероб в узкую колонку списком навыков не влезает | +| «Управление» — деньги и штат | Плюс вкладка **«Правила»** | Дресс-код — единственное действие игрока в этом срезе | +| Часы несут дату и паузу | Плюс температура и осадки | Погода должна быть видна, не спрятана в карточке | +| Одна раздевалка на 12 шкафчиков | Две (м / ж); шкафчик принадлежит ученику | Переодевание на физкультуру и личное хранение — разные вещи | + +Старые школы без страны поднимаются: нынешний набор имён отображается на ванильную страну, +климат — первый пресет этой страны. Файл не переписывается молча в новый вид, пока школа не +сохранится сама. + +## Страна + +Новый вид def — `CountryDef`. Это то, что игрок выбирает при создании школы. + +В стране лежит всё, что раньше было «набором имён», плюс ссылки на климатические пресеты: + +```jsonc +{ + "defName": "Russia", + "climatePresets": ["TemperateContinental", "ContinentalCold"], + "names": { + "patronymicRule": "Slavic", + "nativeLanguages": ["Russian", "Belarusian", "Ukrainian"], + "relatedLanguageChance": 0.35, + // maleGiven, femaleGiven, surnames — как у нынешнего NameSetDef + }, +} +``` + +Набор имён **перестаёт быть выбираемым видом**. Он не удаляется как форма данных: это вложенный +объект страны. Мод, который хочет другую страну, кладёт свой `CountryDef` целиком, а не «набор +имён плюс климат сбоку». + +Родной язык по-прежнему выбирается из `names.nativeLanguages`, если их больше одного. Это не +третий пикер культуры — это язык внутри уже выбранной страны. Не указан — бросает сид школы, +как сейчас. + +Климатический пресет школа получает **случайный из списка страны**, от того же сида, и +записывает у себя. Повтор create с тем же сидом даёт ту же погоду. Сменить пресет у живой школы +нельзя: это часть рождения, как набор имён раньше. + +В `core` одна страна — `Russia`: нынешний славянский набор вложен в неё, пресеты — умеренно- +континентальные. Другие страны приходят модами. + +Форма создания говорит «Страна», не «Набор имён». `GET /api/catalog` отдаёт `countries` вместо +`nameSets`. `POST /api/schools` принимает `countryId`; неизвестная страна — `400` `unknown-country`. +Старое поле `nameSetId` не принимается. + +## Климат и погода + +Погода — не картинка и не текст. На школе живёт состояние: **уличная температура** и **осадки** +(нет / дождь / снег). Его видно у часов, и от него одеваются. + +Числа живут в `ClimatePresetDef`, не в коде. Страна только ссылается на id. Мод меняет зиму, +не трогая имена. + +Пресет описывает: + +- ход температуры по году (месячные нормы); +- как температура гуляет внутри дня и от нормы (разброс); +- когда и какие осадки — с поправкой на температуру: ниже нуля тот же бросок даёт снег, выше — + дождь. + +Погода пересчитывается не каждый тик, а когда это меняет одежду или надпись у часов: сдвиг на +несколько десятых градуса, смена осадков. Детерминизм: пресет + сид школы + календарная дата и +час. Тот же сид, тот же день — та же улица. + +**В здании теплее, чем на улице**, даже без сотрудника: стены держат. Но комфорт — работа +техника. Без него зимой в кабинетах прохладно, летом душно; с ним держится жилая полоса. +Двор, крыльцо и переход между корпусами — улица. + +Пропуск пустого времени погоду не «перепрыгивает молча»: к утру рабочего дня состояние считается +из даты, как годовой набор уже считается через каникулы. + +## Тепло как нужда + +Новая нужда — **тепло** (`Warmth`). Шкала как у остальных: 0–1, полоска на карточке. + +Она падает, когда одежда не соответствует **температуре места**, где человек сейчас: на морозе +без куртки, в жару в шубе. В комфортной полосе почти не падает. Дома (вне школы) восстанавливается +за кадром, как сон. + +Две черты, несовместимые друг с другом: + +- **теплолюбивый** — комфортная полоса сдвинута вверх, на жару реагирует спокойнее, на холод — + раньше; +- **холодолюбивый** — наоборот. + +Сдвиг — поле на `TraitDef` (смещение комфортной температуры), не список фамилий в коде. Мод +добавляет «мерзлявый» тем же полем. + +Голодный учится хуже уже сейчас. Замёрзший в этом срезе **ещё не** получает штраф к уроку: нужда +видна и двигает переодевание, но таблицу оценок мы не заводим. Когда появятся последствия состояния, +тепло встанет рядом с голодом. + +## Один ThingDef, три роли + +Мебель, одежда и портфель — один вид. Различают поля, а не отдельные классы. Стул без слоёв +по-прежнему ставится на карту; футболка со слоями на карту не ставится. + +| Поле | Зачем | +| --- | --- | +| `layers` | Какие слои занимает. Пусто — не одежда | +| `portable` | Можно нести в сумке / шкафчике / дома | +| `mass` | Килограммы; ноша складывается | +| `insulation` | Насколько греет (и преет в жару) | +| `formality` | Формальность, 0–100 | +| `age` | Допустимый возраст | +| `sex` | `male` / `female` / нет поля = любой | +| `colors` | Какие `ColorDef` допустимы на этом def | +| `skirtLength` | Длина низа; для «короткой» формы | +| `pe` | Форма для физкультуры | +| `subject` | Только у дефа учебника: какой предмет | + +Учебник — **один** `ThingDef`, экземпляр несёт id предмета. Не двенадцать дефов «учебник математики». + +Цвет — поле **экземпляра**, не отдельный def вещи. «Синяя футболка» и «красная футболка» — один +`TShirt` с разным `color`. + +`ColorDef` — свой вид: id, теги уместности (`neutral`, `bright`, `school`). Правила школы +фильтруют теги, а не имена цветов. Мод добавляет «бордовый» файлом, не пересборкой. + +Новые слои — пересборка: слои как роли, код их знает. Подписи — в локалях. + +### Слои + +Код знает девять, в `core` все используются: + +| Слой | Что надевают | +| --- | --- | +| `Underwear` | трусы | +| `Socks` | носки | +| `Bottom` | юбка, штаны, джинсы, брюки | +| `Top` | футболка, рубашка | +| `OverTop` | свитер, жилет | +| `Outer` | куртка, пальто, шуба | +| `Shoes` | обувь | +| `Head` | шапка, кепка | +| `Accessory` | ремень, шарф, значок | + +Платье — **один предмет на несколько слоёв**: `layers: ["Bottom", "Top"]`. Пиджак, если его можно +снять отдельно от брюк, — отдельная вещь на `OverTop` или `Outer`, не «костюм-монолит». + +На одном слое — не больше одной вещи. Надеть свитер поверх рубашки можно: это разные слои. +Надеть две футболки нельзя. + +### Ванильный набор + +Не витрина моды, а достаточный гардероб, чтобы генератор и правила было чем кормить: + +- бельё и носки нескольких цветов; +- низы: юбка (женская), штаны, джинсы, брюки — длины обычная и короткая; +- верх: футболка, рубашка; +- поверх: свитер; +- верхняя: куртка, пальто, шуба; +- обувь: повседневная и зимняя; +- голова: шапка; +- аксессуары: ремень, шарф; +- платье (два слоя); +- форма физкультуры (верх + низ, `pe: true`); +- ноша: портфель, учебник (один def), телефон, бутылка. + +Цвета палитры: белый, чёрный, серый, синий, голубой, красный, зелёный, бежевый. Красный и +зелёный — `bright`; белый, чёрный, серый, синий — `school` и `neutral`. + +## Где лежит вещь + +У экземпляра одно место из четырёх: + +| Место | Что это | +| --- | --- | +| **Надето** | занимает слои | +| **Сумка** | с собой, давит на ношу | +| **Шкафчик** | в школе, не несёт | +| **Дом** | не локация, а состояние «не в школе» | + +Домашнего узла на карте нет и не будет. «Дом» — событие и подпись текущего дела, как «вне школы» +уже есть у присутствия. + +**Шкафчик — слот на карте, привязанный к ученику.** Число шкафчиков в раздевалке = число слотов. +Раздача: ученики этого пола, старшие параллели раньше, при равенстве — стабильный id. Не хватило — +не досталось: запас только в сумке и дома. Сотрудники шкафчик ученика не получают. + +Сменить одежду в школе можно и без шкафчика: снятое уходит в сумку. Шкафчик — чтобы не таскать +форму для физкультуры весь день. + +Две раздевалки на ванильной карте — два узла, мужская и женская, у спортзала. Def разный, чтобы +ИИ шёл в свою. В редакторе шкафчики по-прежнему число в слоте: поставили 40 — сорок учеников +этого пола получат место. + +## Генератор одевает + +Люди больше не рождаются голыми. Генератор, тем же семейным сидом, собирает гардероб: + +- повседневное и уместное полу, возрасту и дефолтным правилам школы; +- сезонную верхнюю одежду; +- форму для физкультуры ученикам, у кого в сетке есть этот предмет; +- учебники **по одному на каждый предмет параллели** (не все книги каталога); +- портфель, телефон и бутылку — с шансом и возрастом (первокласснику телефон не обязателен). + +Родители одеты: на них есть планы появиться в школе, и раздетой карточки быть не должно. +Соискатели — тоже полные люди, значит тоже одеты. + +Цвет бросается из допустимых у def с учётом правил школы. Красную рубашку в школу с политикой +«без яркого» генератор не выдаст на тело; в домашнем запасе яркое может лежать. + +Детерминизм тот же: страна + пресет + сид + карта + язык = те же люди в той же одежде. Золотой +отпечаток ростера обновляется в той фазе, которая начинает одевать. + +## Ноша + +Новый навык `Hauling` («Переноска»), `always`, как сила. Предел массы: + +**предел = f(Strength, Endurance, Hauling)** — точная формула в `BehaviorDef` или соседнем +дефе правил, не константа в коде. + +На карточке — два числа: несёшь / можешь. Перегруз **не** замедляет ходьбу в этом срезе. Вещи +сверх предела генератор не выдаёт в сумку: лишнее остаётся дома или в шкафчике. + +В сумке в учебный день: портфель, учебники **сегодняшних** уроков, телефон, бутылка, то, чему +не хватило шкафчика. Остальные учебники — в шкафчике или дома. Иначе одиннадцатиклассник с +двенадцатью учебниками всегда перегружен, и шкала лжёт. + +## Износ + +У экземпляра одежды прочность 0–1. Пока вещь **на теле в школе**, она чуть падает со временем. +Ходьбу отдельно не считаем. Сумка и шкафчик не изнашиваются. + +На карточке — полоска и подпись по порогам из данных (`целая`, `поношенная`, `порванная`, +`висит лохмотьями`). Пороги — def, не `if` в UI. + +Из дома в одежде ниже порога **не выходят**. Утром вне школы срабатывает событие: ветхая вещь +заменяется свежей того же def и цвета (или ближайшего уместного). Это видно в истории («получил +новую рубашку»), не как поход по узлам карты. + +Пропуск ночи это событие обязан применить: иначе после «пропустить» школа выходит в лохмотьях. + +## Уместность + +Не запрет «юбку надеть нельзя», а оценка. Надеть можно; система сама так не оденет, а если +ситуация сменилась — переоденет. + +Оси, все из данных на вещи, цвете и правилах школы: + +- пол и возраст; +- сезон и текущая погода (утепление vs улица); +- урок: на физкультуре нужна `pe`-форма; +- формальность вещи vs дресс-код; +- цвет vs цветовая политика; +- длина низа vs «короткая» форма. + +«Короткая» форма — про **длину юбки** (и летний короткий рукав), не про «можно без формы». +Малышам короткое неуместно: даже если школа выбрала «короткую», младшие параллели ходят в +обычной. Порог возраста — поле на правиле, не магическая семёрка в коде. + +Цветовая политика **отдельно от строгости**. Строгая форма всё ещё может быть «без яркого» или +«белый верх, чёрный низ». У учителей свой комплект правил: ученику белая рубашка, директору — +не обязательно. + +Смена правил в «Управлении» вступает в силу **со следующего рабочего дня**. Сегодняшняя одежда +не вспыхивает в раздевалках посреди урока. На карточке правил видно: «с завтра». + +## Переодевание + +Дома — мгновенно, событием, в момент ухода/прихода. + +В школе — **действие**: дойти до своей раздевалки и сменить. Снятое — в шкафчик, если он есть, +иначе в сумку. Надетое — из шкафчика или сумки. + +Типичные поводы, все через уже существующий выбор цели: + +- утром пришёл не по погоде (вчера было тепло, ночью ударил мороз) — переодеть верх; +- сейчас физкультура — форма `pe`, после урока — обратно; +- с завтра действует новый дресс-код — утром уже в нём (домашнее мгновенное + проверка при входе). + +Действие занимает раздевалку как комнату, не чужой шкафчик. Вместимость переодевания — сколько +человек одновременно могут здесь действовать; это не путать с числом личных шкафчиков. + +## Техник + +Новая должность и комната: **техник** (`Technician`) в теплоузле / кабинете техника. Комната +открывает должность, как медкабинет — медсестру. Игрок нанимает из пула. + +Пока техник не нанят, здание держит тепло по-плохому: зимой зябко, летом душно. Нанятый сдвигает +внутреннюю температуру в комфортную полосу. Числа — в дефе правил климата, не в `if (month)`. + +Ванильная карта получает эту комнату в основном корпусе. Без неё должность не рождается, и +нанять некого. + +## Что видит игрок + +### Часы + +Рядом с датой: температура улицы и осадки. Клиент не считает погоду из месяца — только рисует +кадр. Кадр часов растёт, версия протокола поднимается. + +### Карточка человека — вкладки + +Не отдельный экран и не вкладка оболочки. То же окно подробностей справа, внутри него вкладки: + +1. **Обзор** — то, что есть сейчас: тело, навыки, черты, нужды (включая тепло), семья, + расписание. +2. **Одежда** — слои, надетое, цвет, прочность с полоской и подписью, уместность к текущей + ситуации (коротко: «к уроку», «холодно», «не по форме»). +3. **Ноша** — сумка, масса несёшь/можешь, шкафчик если есть, домашнее не тащить сюда списком + из пятидесяти строк: дом — кратко «ещё N вещей дома». +4. **Сейчас** — текущее дело на территории школы (`ест`, `идёт`, `учится (математика)`, + `переодевается`). Вне школы — одна подпись «дома», без фальшивого расписания домашней жизни. + Ниже — **история за сегодня**: сортировка, поиск, пейджинг. + +Игрок с карточки ничего не надевает и не забирает. Чистый просмотр. + +История — HTTP, как список людей: `GET .../people/{id}/log`, страница, фильтр. Не сокет и не +общая лента школы. Хранится на работнике за текущие сутки (граница — шесть утра), к новому дню +обнуляется. Пропуск ночи не оставляет вчерашний лог. + +### Управление — «Правила» + +Вкладка рядом с нынешним штатом, не новый верхний режим. Четыре комбобокса: + +| Кого | Форма | Цвет | +| --- | --- | --- | +| Ученики | обычная / короткая / строгая | свободно / без яркого / белый верх чёрный низ | +| Сотрудники | обычная / короткая / строгая | свободно / без яркого / белый верх чёрный низ | + +«Применить» меняет **завтрашнее** правило. Сегодняшний день доигрывается как был. + +## Поток данных + +Инвентарь лежит **на человеке** в `people.json`. Он меняется чаще состава (переодевание, износ), +но реже нужд: писать файл на каждом тике нельзя, писать при смене состава — мало. Шов тот же, что +у найма: запись, когда гардероб изменился содержательно (утро, переодевание, выдача новой вещи), +не каждый кадр. + +Карточка по-прежнему через мейлбокс: нужды и текущее действие живые, одежда едет тем же ответом. +Список людей одежду не несёт. + +История — отдельный GET, чтобы карточку не раздувать сотней строк. + +Погода — в кадре часов. Открытая школа, 20 Гц, потеря старого кадра допустима: как с датой. + +## Где живёт код + +Нового проекта нет. + +| Что | Куда | +| --- | --- | +| `CountryDef`, `ClimatePresetDef`, `ColorDef`, поля `ThingDef`, слои | `HSchool.Content` | +| Генерация гардероба, раздача шкафчиков, учебники параллели | `HSchool.People` | +| Погода от пресета и даты, износ, утренняя замена | `HSchool.Simulation` | +| Действие «переодеться», цель «одеться по погоде / к физкультуре» | `HSchool.Ai` | +| Кадр часов, карточка, лог, правила, create | `HSchool.Server` + клиент | +| Раскладка часов | `HSchool.Protocol` + `protocol.ts` + `docs/protocol.md` | + +## Советы, которые стоит принять сразу + +- Не заводить `ApparelDef`. Поля на `ThingDef` дешевле, чем два загрузчика и вечный вопрос + «а рюкзак — это что». +- Не делать цвет отдельным def вещи. Экземпляр + палитра. +- Не ставить домашний узел на карту. Тридцать тысяч «комнат» семей не окупить. +- Не раздавать шкафчик каждому на ванильной карте. Нехватка — правило, не баг. +- Не вешать перегруз на ходьбу, пока не измерен. Число на карточке сначала. +- Не возвращать общую ленту событий школы. История — у человека, за сегодня. + +## Заведомо не сейчас + +- Магазины, деньги за одежду, стирка, кража, выдача со склада игроком. +- Игрок одевает человека с карточки. +- Штраф к уроку от холода, болезни от промокания, прогул «не в форме» как оценка. +- Смена страны или климатического пресета у живой школы. +- Почасовая анимация погоды, ветер, грязь, зонты как система. +- Шкафчики сотрудников, раздевалки в каждом корпусе сверх двух ванильных. +- История старше суток и школьная лента на весь экран. +- Портреты и спрайты одежды. Слои — текст и цвет, не картинка. + +## Зафиксировано этим разговором + +| Тема | Решение | +| --- | --- | +| Вид def | Один `ThingDef`; одежда и ноша — поля | +| Цвет | Поле экземпляра; `ColorDef` с тегами уместности | +| Костюм / платье | Один предмет на несколько слоёв; снимаемый пиджак — отдельная вещь | +| Слои | Бельё, носки, низ, верх, поверх, верхняя, обувь, голова, аксессуар | +| Уместность | Пол, возраст, сезон/погода, урок (физкультура), формальность, дресс-код, цвет | +| Запрет vs оценка | Оценка; система сама не наденет неуместное | +| Короткая форма | Длина юбки и летний рукав; малышам неуместно — ходят в обычной | +| Цвет vs строгость | Два независимых правила; своё у учеников и у сотрудников | +| Ноша | Навык `Hauling` + сила + выносливость; на карточке число, ходьбу не трогает | +| Учебники | Один def, экземпляр на предмет параллели; в сумке — сегодняшние | +| Прочее в сумке | Портфель, телефон, бутылка | +| Генератор | Одевает всех, включая родителей и соискателей | +| Переодевание дома | Мгновенно, событием | +| Переодевание в школе | Действие в своей раздевалке | +| Дом | Не узел карты; текущее дело «дома» | +| Шкафчик | Слот карты, один ученик; старшие раньше; не хватило — не досталось | +| Раздевалки | Два узла, м/ж; шкафчики добавляются числом в редакторе | +| Износ | Падает на теле в школе; полоска + подписи; из дома в ветхом не выходят | +| Замена одежды | Утром дома событием | +| Страна | `CountryDef`: вложенные имена + ссылки на пресеты климата | +| Create | Пикер страны вместо набора имён; язык — если у страны их несколько | +| Климат | Пресет выбирается сидом из списка страны, на живой школе не меняется | +| Погода | Температура и осадки; у часов; в кадре часов | +| В здании | Теплее улицы всегда; комфорт — если нанят техник | +| Техник | Комната + должность; холод летом и тепло зимой | +| Нужда | `Warmth`; черты теплолюбивый / холодолюбивый | +| Правила | Вкладка в «Управлении»; четыре комбобокса; с завтра | +| Карточка | Вкладки обзор / одежда / ноша / сейчас+история; чистый просмотр | +| История | Только этот человек, только сегодня, HTTP, поиск и пейджинг | +| Инвентарь в сейве | На человеке в `people.json`, писать при содержательной смене | +| Игрок надевает | Нет | +| Погода влияет на ИИ | Да: переодеться. Ходьбу и урок в этом срезе не штрафует | diff --git a/docs/design/people.md b/docs/design/people.md index cf4a750..bb6c334 100644 --- a/docs/design/people.md +++ b/docs/design/people.md @@ -208,6 +208,9 @@ Скорость — данные, не код. Когда появится еда и сон, цифра меняется в JSONC, а не в C#. +Отменено срезом 5: в `core` ненулевой `decayPerHour`, сон и голод восстанавливаются вне школы, +см. [`ai.md`](ai.md). + ## Имена `NameSetDef` — набор имён целиком: мужские имена, женские имена, фамилии, правила отчеств и @@ -340,7 +343,7 @@ Protocol ← Server → Simulation → People → Content - Не отправлять черты и навыки в списке — только в карточке. Список читают глазами, карточку открывают по одному человеку. - Сид на семью, а не один на школу: иначе любая правка генератора переставит всех. -- Ненулевую скорость нужд не включать, пока нечем их восполнять. +- Ненулевую скорость нужд не включать, пока нечем их восполнять — сделано срезом 5, см. [`ai.md`](ai.md). ## Заведомо не сейчас @@ -386,7 +389,7 @@ Protocol ← Server → Simulation → People → Content | Выдача навыков | Не полный каталог: always, родной, предметы года, взрослые лишние, родственные языки | | Черты в `core` | Около десяти | | Нужды в `core` | Сон, голод, туалет, общение | -| Нужды | Машинерия есть, скорость убывания в `core` — ноль | +| Нужды | Машинерия есть; убывание в `core` — срез 5, не ноль, см. [`ai.md`](ai.md) | | Слои по ролям | Одинаковые виды данных; ключи навыков зависят от роли и возраста | | Набор имён | Один на школу, выбор в create, в `core` славянский; прочие — моды | | Родной язык | Один из `nativeLanguages` набора, выбор в create; остальные языки списка — часто низкий навык | diff --git a/docs/phases/29-country.md b/docs/phases/29-country.md new file mode 100644 index 0000000..f2804ec --- /dev/null +++ b/docs/phases/29-country.md @@ -0,0 +1,49 @@ +# Фаза 29. Страна + +## Зависимости + +- [Фаза 5](05-people-defs.md) +- [Фаза 26](26-school-seed.md) + +## Зачем + +Create выбирает страну, а не набор имён. Имена и климатические пресеты перестают быть двумя +несвязанными пикерами. + +## Задачи + +- [ ] `CountryDef` и `ClimatePresetDef` в `HSchool.Content`: страна вкладывает нынешние поля + набора имён в `names`, ссылается на пресеты списками id +- [ ] Набор имён больше не выбираемый вид; папка `namesets/` в `core` уезжает в страну +- [ ] Ваниль: одна страна `Russia` с нынешним славянским набором и хотя бы одним пресетом + (числа погоды можно заглушить — живая улица в фазе 32) +- [ ] `GET /api/catalog` отдаёт `countries` (`defName`, подпись, `nativeLanguages`, + `climatePresets`) вместо `nameSets` +- [ ] `POST /api/schools` принимает `countryId`; неизвестный — `400` `unknown-country`. + `nameSetId` не принимается +- [ ] Пресет климата записывается на школу: случайный из списка страны, от сида. Живой школе + его не сменить +- [ ] Форма создания: подпись «Страна»; язык — если у страны их несколько +- [ ] Старый сейв с `nameSetId: Slavic` поднимается как `Russia` + первый пресет; формат + новее без `countryId` — как с прочими полями сейва, школа не стартует +- [ ] `docs/protocol.md` и `AGENTS.md` (ориентир create / каталог) в том же коммите +- [ ] Золотой отпечаток ростера обновляется, если вход генератора сменил имя поля + +## Тесты, без которых фаза не закрыта + +- [ ] Каталог `core` грузится; страна без имён или с неизвестным пресетом — ошибка загрузки +- [ ] Create с `countryId: Russia` и без языка даёт тот же родной язык, что прежний сид без + `nativeLanguage` +- [ ] Create с неизвестной страной — `400` `unknown-country` +- [ ] Два create с одним сидом получают один и тот же пресет +- [ ] Старый сейв без страны поднимается +- [ ] Диалог создания показывает страны, не наборы имён; смена страны обновляет список языков + +## Критерий готовности + +- Создать школу, выбрав страну; люди и язык как раньше на том же сиде +- `dotnet test` и `npm test` / `build` проходят + +## Стоп + +Не считать живую погоду. Не одевать людей. Не менять кадр часов. diff --git a/docs/phases/30-apparel-defs.md b/docs/phases/30-apparel-defs.md new file mode 100644 index 0000000..311b5c6 --- /dev/null +++ b/docs/phases/30-apparel-defs.md @@ -0,0 +1,43 @@ +# Фаза 30. Слои и палитра + +## Зависимости + +- [Фаза 3](03-defs-map.md) + +## Зачем + +Каталог умеет описать одежду и ношу, не путая их с партой. Никто ещё ничего не носит. + +## Задачи + +- [ ] Поля `ThingDef`: `layers`, `portable`, `mass`, `insulation`, `formality`, `age`, `sex`, + `colors`, `skirtLength`, `pe`. Пустые `layers` — прежняя мебель, карта не ломается +- [ ] Слои — известный набор в коде (`Underwear` … `Accessory`), как роли. Неизвестный слой — + ошибка загрузки +- [ ] `ColorDef`: id и теги (`neutral`, `bright`, `school`). Ссылка вещи на неизвестный цвет — + ошибка +- [ ] Вещь с несколькими `layers` допустима (платье). Пересечение слоёв у двух надетых сразу + валидатор не проверяет — это состояние человека, не каталога +- [ ] Ваниль по [`../design/inventory.md`](../design/inventory.md): бельё, носки, низы, верх, + свитер, куртка/пальто/шуба, обувь, шапка, аксессуары, платье, форма физкультуры, + портфель, учебник, телефон, бутылка +- [ ] Палитра цветов и локали ru/en на все новые def +- [ ] Учебник — один `ThingDef` без зашитого предмета; предмет живёт на экземпляре в фазе 31 +- [ ] Мебель на карте по-прежнему без `layers`; редактор не предлагает надеть стул + +## Тесты, без которых фаза не закрыта + +- [ ] `Chair` без новых полей грузится как раньше +- [ ] Платье с `layers: ["Bottom", "Top"]` проходит; неизвестный слой — ошибка +- [ ] Футболка ссылается на `Red`; отсутствие `Red` в палитре — ошибка +- [ ] Тест полноты локалей зелёный на новый набор +- [ ] Ванильная карта и каталог предметов (мебель) не изменились по вместимости + +## Критерий готовности + +- Каталог `core` грузится, школы создаются, люди голые +- `dotnet test` проходит + +## Стоп + +Не генерировать экземпляры. Не трогать карточку и create сверх того, что уже есть. diff --git a/docs/phases/31-dress-generator.md b/docs/phases/31-dress-generator.md new file mode 100644 index 0000000..acc69d1 --- /dev/null +++ b/docs/phases/31-dress-generator.md @@ -0,0 +1,49 @@ +# Фаза 31. Генератор одевает + +## Зависимости + +- [Фаза 29](29-country.md) +- [Фаза 30](30-apparel-defs.md) +- [Фаза 6](06-people-generator.md) + +## Зачем + +Люди рождаются одетыми. Гардероб детерминирован и лежит на человеке в `people.json`. + +## Задачи + +- [ ] Экземпляр вещи на человеке: `def`, `color`, `condition` (1), место (`worn` / `bag` / + `locker` / `home`), для учебника — `subject` +- [ ] Генератор одевает учеников, сотрудников, родителей и соискателей от семейного сида: + слои без пересечения, пол, возраст, дефолтные правила (обычная форма, без яркого на теле) +- [ ] Учебники — по одному на предмет параллели ученика; в сумке пока могут лежать все, разнос + «сегодняшние vs шкафчик» — в фазе 33/35 +- [ ] Портфель, телефон, бутылка — с возрастом и шансом из данных, не из константы в генераторе +- [ ] Навык `Hauling` (`always`); предел массы в дефе правил; на карточке несёшь/можешь. + Перегруз ходьбу не трогает; в сумку сверх предела генератор не кладёт +- [ ] `GET .../people/{id}` отдаёт надетое и ношу; список людей — нет +- [ ] Карточка пока без вкладок: секции «Одежда» и «Ноша» в том же свитке +- [ ] `people.json` пишется при создании и наборе; золотой отпечаток обновляется в этом коммите + с объяснением, что изменилось +- [ ] Строки через `t(...)` / локали каталога + +## Тесты, без которых фаза не закрыта + +- [ ] Тот же сид, страна, карта и язык → те же id вещей, цвета и места +- [ ] На одном слое не две вещи; платье занимает низ и верх сразу +- [ ] Юбка не выдаётся мужчинам на тело; яркий верх не выдаётся на тело при дефолтных правилах +- [ ] Ученик параллели с математикой имеет учебник этого предмета +- [ ] Родитель и соискатель одеты +- [ ] Масса в сумке ≤ предела +- [ ] Старый `people.json` без инвентаря поднимается: генератор одевает при загрузке и пишет + файл (как пул соискателей в фазе 11) + +## Критерий готовности + +- Открыть карточку ученицы: слои заполнены, цвет виден, ноша с числом +- Повтор create с сидом даёт ту же рубашку +- `dotnet test` и `npm test` / `build` проходят + +## Стоп + +Не считать погоду. Не изнашивать. Не переодевать по ситуации. Не заводить вкладки карточки. diff --git a/docs/phases/32-weather-warmth.md b/docs/phases/32-weather-warmth.md new file mode 100644 index 0000000..8334558 --- /dev/null +++ b/docs/phases/32-weather-warmth.md @@ -0,0 +1,45 @@ +# Фаза 32. Погода и тепло + +## Зависимости + +- [Фаза 29](29-country.md) +- [Фаза 20](20-needs-actions.md) + +## Зачем + +На улице есть температура и осадки. Человек может мёрзнуть. Часы это показывают. + +## Задачи + +- [ ] `ClimatePresetDef` получает рабочие числа: нормы по месяцам, разброс дня, осадки +- [ ] Школа считает улицу из пресета + сида + даты/часа; не каждый тик, а когда меняется + надпись или это сдвинет тепло +- [ ] В здании теплее улицы за счёт стен, без техника ещё не комфорт (комфорт — фаза 33) +- [ ] Двор и крыльцо — улица +- [ ] Нужда `Warmth`: декей от разницы «утепление одежды vs температура места»; вне школы + восстанавливается, как сон +- [ ] Черты теплолюбивый / холодолюбивый: поле смещения комфорта на `TraitDef`, взаимно + несовместимы +- [ ] Кадр часов: температура (десятые доли °C, `i16`) и осадки (`u8`). Версия протокола +1. + `ProtocolCodec.cs`, `protocol.ts`, `docs/protocol.md` в одном коммите +- [ ] Клиент рисует погоду у даты; не считает её из месяца +- [ ] Пропуск пустого времени выставляет погоду утра, не вчерашнюю + +## Тесты, без которых фаза не закрыта + +- [ ] Тот же пресет, сид и timestamp → та же температура и осадки +- [ ] Январь холоднее июля на ванильном пресете +- [ ] Ниже нуля осадки — снег, не дождь (если пресет вообще дал осадки) +- [ ] На морозе без верхней одежды тепло падает; дома за ночь возвращается +- [ ] Кадр часов круглый трип и байтовая раскладка на обеих сторонах +- [ ] Клиент с старой версией протокола отваливается Hello, как обычно + +## Критерий готовности + +- У часов видно «−5 °C, снег» зимним утром +- Полоска тепла на карточке шевелится +- `dotnet test` и `npm test` / `build` проходят + +## Стоп + +Не нанимать техника. Не переодевать по погоде — замёрзший ещё терпит. Не штрафовать урок. diff --git a/docs/phases/33-lockers-technician.md b/docs/phases/33-lockers-technician.md new file mode 100644 index 0000000..f3e644a --- /dev/null +++ b/docs/phases/33-lockers-technician.md @@ -0,0 +1,47 @@ +# Фаза 33. Техник и шкафчики + +## Зависимости + +- [Фаза 31](31-dress-generator.md) +- [Фаза 32](32-weather-warmth.md) +- [Фаза 11](11-applicants.md) + +## Зачем + +В школе можно нанять человека, который держит тепло зимой и прохладу летом. У учеников появляются +личные шкафчики — кому хватило. + +## Задачи + +- [ ] `RoomDef` теплоузла и `PositionDef Technician`; комната открывает должность, как медкабинет +- [ ] Ванильная карта: комната в основном корпусе, связана с коридором +- [ ] Нанятый техник сдвигает внутреннюю температуру в комфортную полосу; без него здание + только демпфирует улицу. Числа — в дефе правил климата +- [ ] Две раздевалки у спортзала: мужская и женская, разные def, свои шкафчики. Старый один + узел `changing-room` заменяется +- [ ] Раздача: ученики этого пола, старшая параллель раньше, при равенстве id. Сотрудникам + шкафчик не дают. Не хватило — место `locker` пустое, вещи в сумке и дома +- [ ] Пересчёт раздачи на годовом наборе (параллели сдвинулись) +- [ ] Редактор: число шкафчиков в слоте по-прежнему меняется; после создания школы раскладка + заморожена, как вся карта +- [ ] Карточка показывает, есть ли шкафчик (ещё без вкладки ноши) + +## Тесты, без которых фаза не закрыта + +- [ ] Новая школа без техника: январский кабинет холоднее, чем с нанятым техником, и теплее + улицы +- [ ] Нанятый техник в июле держит кабинет ниже раскалённой улицы +- [ ] Две раздевалки на ванильной карте, граф связен +- [ ] Шкафчиков 10 на 40 учениц — получают десять старших; одиннадцатая без шкафчика +- [ ] Мальчик не получает слот девичьей раздевалки +- [ ] После 1 сентября раздача пересчитывается, выпускник слот не держит + +## Критерий готовности + +- Нанять техника из пула — в январе в кабинете становится тепло +- Открыть карточку старшеклассника — шкафчик есть; у части младших нет +- `dotnet test` проходит + +## Стоп + +Не переодевать на физкультуру. Не изнашивать. Не делать вкладку «Правила». diff --git a/docs/phases/34-wear-home.md b/docs/phases/34-wear-home.md new file mode 100644 index 0000000..6a7cb89 --- /dev/null +++ b/docs/phases/34-wear-home.md @@ -0,0 +1,42 @@ +# Фаза 34. Износ и дом + +## Зависимости + +- [Фаза 31](31-dress-generator.md) +- [Фаза 18](18-presence-walking.md) + +## Зачем + +Одежда портится на теле в школе. Из дома в лохмотьях не выходят — утром выдают свежую. + +## Задачи + +- [ ] Прочность экземпляра 0–1; падает только у надетого, только пока человек в школе, от + игрового времени. Формула скорости — данные +- [ ] Пороги подписей в дефе (`целая` / `поношенная` / `порванная` / `висит лохмотьями`); + карточка — полоска + подпись, клиент не считает пороги сам +- [ ] Порог «не выходить»: утром вне школы ветхая надетая вещь заменяется свежей того же def + и цвета (или ближайшего уместного). Событие для истории фазы 36: тип и подпись готовы + даже если лог ещё не рисуется +- [ ] Пропуск пустого времени применяет утреннюю замену, иначе после «пропустить» школа в + лохмотьях +- [ ] Сумка и шкафчик не изнашиваются +- [ ] `people.json` пишется после замены, не каждый тик износа: либо порог пересечён, либо + утренняя выдача + +## Тесты, без которых фаза не закрыта + +- [ ] День в школе снижает прочность надетой рубашки на заявленную величину, ±эпсилон +- [ ] Рубашка в сумке за тот же день не меняется +- [ ] Человек с одеждой ниже порога к утру рабочего дня одет в свежую; def совпадает +- [ ] Skip через ночь даёт ту же замену, что прожитая ночь +- [ ] Подпись на карточке меняется вместе с порогом + +## Критерий готовности + +- Промотать несколько игровых недель — полоска одежды опустилась, потом утром обновилась +- `dotnet test` и `npm test` проходят + +## Стоп + +Не показывать историю вкладкой — только состояние вещи. Не переодевать по погоде. diff --git a/docs/phases/35-appropriateness.md b/docs/phases/35-appropriateness.md new file mode 100644 index 0000000..83fc6af --- /dev/null +++ b/docs/phases/35-appropriateness.md @@ -0,0 +1,51 @@ +# Фаза 35. Уместность + +## Зависимости + +- [Фаза 32](32-weather-warmth.md) +- [Фаза 33](33-lockers-technician.md) +- [Фаза 34](34-wear-home.md) +- [Фаза 21](21-decisions.md) + +## Зачем + +Система сама одевает по правилам, погоде и уроку. В школе переодевание — действие в своей +раздевалке; дома — мгновенно. + +## Задачи + +- [ ] Оценка уместности из данных: пол, возраст, погода/утепление, `pe`, формальность, + цветовые теги, длина низа. Код знает оси, пороги — defs и правила школы +- [ ] На школе хранятся правила учеников и сотрудников (форма + цвет). Дефолт: обычная, + без яркого. Смена с экрана — фаза 37; API уже пишет «с завтра» +- [ ] «Короткая» форма: длина юбки / короткий рукав; младше порога возраста ходят в обычной +- [ ] Дома утром: мгновенно подобрать надетое под правила, погоду и сегодняшний план + (учебники только сегодняшних уроков в сумке, остальное — шкафчик или дом) +- [ ] В школе: действие `ChangeClothes` в своей (м/ж) раздевалке. Снятое — в шкафчик, если + есть, иначе в сумку +- [ ] Цель «одеться к физкультуре» и «вернуться с формы» рядом с обязанностью урока; не + каждый тик, а по звонку / приходу +- [ ] Цель «верх не по погоде» — дойти и сменить куртку, если уже в школе +- [ ] Вместимость действия — сколько одновременно переодеваются в комнате; не путать с + числом шкафчиков +- [ ] Игрок по-прежнему не одевает с карточки + +## Тесты, без которых фаза не закрыта + +- [ ] Школа с «без яркого»: утром на теле нет цвета с тегом `bright` +- [ ] «Короткая» + первоклассница → обычная длина; старше порога — можно короткое +- [ ] Перед уроком физкультуры ученик идёт в свою раздевалку и надевает `pe`; после — снимает +- [ ] Мальчик не идёт в женскую раздевалку +- [ ] Без шкафчика снятое оказывается в сумке, не исчезает +- [ ] Морозным утром на теле есть `Outer`; в июле шубы на теле нет +- [ ] Смена правила через API сегодня не меняет одежду до следующего рабочего утра + +## Критерий готовности + +- На физкультуре в зале видно форму, не школьную рубашку +- Смена погоды за день гонит кого-то в раздевалку, не телепортирует куртку +- `dotnet test` проходит + +## Стоп + +Не рисовать вкладку «Правила». Не штрафовать урок за холод и «не по форме». diff --git a/docs/phases/36-person-card-tabs.md b/docs/phases/36-person-card-tabs.md new file mode 100644 index 0000000..f633755 --- /dev/null +++ b/docs/phases/36-person-card-tabs.md @@ -0,0 +1,48 @@ +# Фаза 36. Вкладки карточки + +## Зависимости + +- [Фаза 31](31-dress-generator.md) +- [Фаза 8](08-people-browser.md) +- [Фаза 28](28-screen-tests.md) + +## Зачем + +Гардероб, ноша и сегодняшний след перестают быть секциями в одном свитке. Подробности о человеке +читаются. + +## Задачи + +- [ ] Окно карточки: вкладки **Обзор / Одежда / Ноша / Сейчас** +- [ ] Обзор — прежнее: тело, навыки, черты, нужды, семья, расписание; текущее дело с обзора + уходит на «Сейчас» +- [ ] Одежда: слои, цвет, полоска износа и подпись, короткая уместность к ситуации +- [ ] Ноша: сумка, несёшь/можешь, шкафчик или его отсутствие, кратко «ещё N дома» без полного + домашнего списка +- [ ] Сейчас: на территории школы — подпись действия (`ест`, `идёт`, `учится (математика)`, + `переодевается`). Вне школы — «дома» +- [ ] История за сегодня под текущим делом: поиск, сортировка, пейджинг +- [ ] `GET /api/schools/{id}/people/{personId}/log` — страница и фильтр; не сокет, не лента + школы. Граница суток — шесть утра; skip ночи обнуляет вчерашнее +- [ ] Запись в лог: начало/конец действия, утренняя выдача одежды, переодевание. Хранит + работник, не `people.json` +- [ ] Vitest: переключение вкладок, пейджер лога, «дома» когда activity пустая и человек away +- [ ] `docs/protocol.md` — HTTP лога в том же коммите + +## Тесты, без которых фаза не закрыта + +- [ ] Лог не едет в `GET .../people/{id}` — только отдельным запросом +- [ ] Страница лога не пересекается; неизвестный человек — `404` +- [ ] После 6:00 записей вчера нет +- [ ] Вне школы вкладка «Сейчас» показывает «дома», не пусто +- [ ] Клиентский тест: вкладка «Одежда» не монтирует таблицу лога + +## Критерий готовности + +- Открыть ученика, переключить четыре вкладки, найти в истории сегодняшний обед +- Тысяча строк лога не приезжает разом +- `dotnet test` и `npm test` / `build` проходят + +## Стоп + +Не возвращать панель общих событий школы. Не давать надевать с вкладки «Одежда». diff --git a/docs/phases/37-school-rules.md b/docs/phases/37-school-rules.md new file mode 100644 index 0000000..8473c0d --- /dev/null +++ b/docs/phases/37-school-rules.md @@ -0,0 +1,41 @@ +# Фаза 37. Правила школы + +## Зависимости + +- [Фаза 35](35-appropriateness.md) +- [Фаза 36](36-person-card-tabs.md) +- [Фаза 13](13-management-tab.md) + +## Зачем + +Игрок задаёт, как одевается школа. Это единственное его действие в срезе про одежду. + +## Задачи + +- [ ] В «Управлении» вкладка **«Правила»** рядом со штатом, не новый верхний режим +- [ ] Четыре комбобокса: форма учеников, цвет учеников, форма сотрудников, цвет сотрудников. + Значения — как в [`../design/inventory.md`](../design/inventory.md) +- [ ] Сохранение пишет завтрашнее правило; на экране видно, что действует сегодня и что + вступит завтра +- [ ] HTTP: чтение текущих+отложенных, запись отложенных. Отказ по неизвестному коду правила — + `400`, не тишина +- [ ] На следующее рабочее утро отложенное становится текущим; фаза 35 уже одевает от него +- [ ] Строки через `t(...)`, обе локали +- [ ] Vitest: смена комбобокса не зовёт API до подтверждения; ошибка API видна текстом + +## Тесты, без которых фаза не закрыта + +- [ ] GET правил на новой школе — дефолт обычная / без яркого на обе роли +- [ ] POST «строгая» сегодня не меняет одежду до следующего рабочего утра +- [ ] После утра на теле учеников нет яркого, если отложили «без яркого» +- [ ] Неизвестный код политики — `400` +- [ ] Вкладка не ломает наём и сетку расписания + +## Критерий готовности + +- Выставить ученикам «белая верх чёрный низ», промотать на утро — рубашки белые +- `dotnet test` и `npm test` / `build` проходят + +## Стоп + +Не давать игроку одевать одного человека. Не вводить магазины и оплату формы. diff --git a/docs/phases/README.md b/docs/phases/README.md index cb902b1..fc5c5c9 100644 --- a/docs/phases/README.md +++ b/docs/phases/README.md @@ -113,3 +113,43 @@ | [26. Свой сид](26-school-seed.md) | ✅ | Состав школы перестаёт зависеть от порядка создания | | [27. Версия сейва и дамп](27-serviceability.md) | ✅ | Формат читается, внутрь школы можно заглянуть | | [28. Тесты экранов](28-screen-tests.md) | ✅ | DOM-окружение и логика клиента под тестом | + +## Срез 7. Одежда, вещи и климат + +Дизайн: [`../design/inventory.md`](../design/inventory.md). + +Человек одевается, несёт вещи, мёрзнет и переодевается. Игрок смотрит гардероб и выбирает +правила школы; надевает не он. Погода видна у часов. Срез можно остановить после этапа A — +школа останется одетой, даже если климат ещё не живой. + +**Этап A — данные и гардероб.** Страна вместо набора имён, вещи умеют быть одеждой, генератор +одевает. + +| Фаза | Статус | Зачем | +| --- | --- | --- | +| [29. Страна](29-country.md) | ⬜ | `CountryDef`, create выбирает страну, климат-пресет от сида | +| [30. Слои и палитра](30-apparel-defs.md) | ⬜ | Поля `ThingDef`, `ColorDef`, ванильная одежда и ноша | +| [31. Генератор одевает](31-dress-generator.md) | ⬜ | Гардероб на человеке, ноша-число, `people.json` | + +29 и 30 независимы; 31 стоит на обеих. + +**Этап B — климат.** Улица, нужда тепла, техник, раздевалки. + +| Фаза | Статус | Зачем | +| --- | --- | --- | +| [32. Погода и тепло](32-weather-warmth.md) | ⬜ | Пресет крутит улицу, кадр часов, нужда, две черты | +| [33. Техник и шкафчики](33-lockers-technician.md) | ⬜ | Комната техника, две раздевалки, раздача шкафчиков | + +32 стоит на 29; 33 — на 31 и 32. + +**Этап C — жизнь одежды.** Износ, переодевание, экран. + +| Фаза | Статус | Зачем | +| --- | --- | --- | +| [34. Износ и дом](34-wear-home.md) | ⬜ | Прочность, утренняя замена, из ветхого не выходят | +| [35. Уместность](35-appropriateness.md) | ⬜ | Правила, переодевание действием, физкультура | +| [36. Вкладки карточки](36-person-card-tabs.md) | ⬜ | Одежда, ноша, сейчас + история за сегодня | +| [37. Правила школы](37-school-rules.md) | ⬜ | Вкладка в «Управлении», четыре комбобокса, с завтра | + +34 стоит на 31; 35 — на 32–34; 36 — на 31 и может идти параллельно с 35 в части вкладок одежды, +история — после 35; 37 стоит на 35 и 36. diff --git a/docs/phases/reviewed.md b/docs/phases/reviewed.md index 0781953..9df1452 100644 --- a/docs/phases/reviewed.md +++ b/docs/phases/reviewed.md @@ -142,14 +142,16 @@ ## Срез 2. Люди - **Фазы:** 5–9 -- **Проверен на:** `5a00ad7`, 2026-08-19 (первичная проверка — `5cd5a6d`) +- **Проверен на:** `bdb1253`, 2026-08-20 (первичная проверка — `5cd5a6d`; предыдущая перепроверка — `5a00ad7`) - **Пути:** `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/PeopleApiTests.cs`, + `tests/HSchool.AppHost.Tests/SchoolSeedTests.cs` - **Итог:** исправлен один тест, который падал на чистом клоне, укреплён второй, дописан один; расхождение с дизайном одно и то же, что в срезе 1 (раскладка оболочки менеджера) @@ -234,16 +236,57 @@ - Исправления среза 2 (карточка семьи, сортировка по фамилии, состав после набора и перезапуска) проходят на новом коде. +### Перепроверка на `bdb1253`, 2026-08-20 + +Дрейф после `5a00ad7` — свой сид школы (фаза 26), золотые ростеры (25), тесты панели (28) и +данные поведения. Обещания фаз 5–9 целы. + +Что подтверждено повторно: + +- **Фаза 5.** Все пункты списка в `PeopleDefTests` (10/10): разбор, наследование, патч черты, + неизвестная ссылка, склонение и таблица, мод со своим набором. +- **Фаза 6.** Все шесть тестов генератора на месте, плюс `NewSchool_HasNoStaff` (фаза 11 не + отменяет детерминизм и заполнение мест). `Assembly_DoesNotReferenceArchAspNetOrSockets` и + `Sources_DoNotUseWallClock` зелёные. Неполные семьи 5–10% — `ReviewFixTests`. +- **Фаза 7.** Одиннадцать кабинетов, сущности в `World`, `PersistPeople` только при изменении + состава. Штат при создании пуст — это фаза 11, тест обновлён (`LeaveJobsEmpty`). +- **Фаза 8.** Список с опубликованного снимка (`FindPeople`), карточка через `TaskCompletionSource`. + Пять API-тестов плюс клиентские фильтры/пейджер (`peoplePanel.test.ts` 3/3). Вкладка, не нижний ряд. +- **Фаза 9.** Выпуск старшей имеющейся, вместимость, семьи через поколения, reload после набора + (`ReloadAfterTheYearlyIntake_RestoresTheNewComposition`). +- **Сид.** Больше не `school.Id`. Новая школа берёт `_createSeed`, сейв — `loaded.Seed`; запасной + `school.Id` только если `people.json` нет. `Card_IncludesFamilyAndLiveNeeds` передаёт `seed: 1`. + `SchoolSeedTests`: тот же сид — те же люди, два без сида — разные, reload хранит выпавший. + +Исправлено: + +- В `design/people.md` скорость нужд всё ещё была «ноль» (нарратив, совет и таблица), хотя срез 5 + это отменил. Помечено как отменённое со ссылкой на `ai.md`, по тому же шаблону, что расписание. + Код не трогался. + +Закрыто из прошлого прохода: + +- **Раскладка оболочки.** «Открыто» выше больше не действует: `design/people.md` и фаза 8 уже + записаны под вкладку рядом с картой. Решение принято в том же ходе, что отказ от панели событий. + +Замечено рядом: + +- «Заведомо не сейчас» в `people.md` всё ещё говорит «должности закрываются генератором» — + отменено срезом 3. Не переписывал исторический список стопов. + +People 34, PeopleDef 10, PeopleInSchool 7, PeopleApi+SchoolSeed 13, клиентская панель 3/3. + ## Срез 3. Штат - **Фазы:** 10–13 -- **Проверен на:** `5a00ad7`, 2026-08-19 (первичная проверка — `21d79cb`) +- **Проверен на:** `bdb1253`, 2026-08-20 (первичная проверка — `21d79cb`; предыдущая перепроверка — `5a00ad7`) - **Пути:** `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` + `tests/HSchool.AppHost.Tests/StaffingApiTests.cs`, + `src/HSchool.Client/src/ui/managementPanel.test.ts` - **Итог:** дописано 3 теста (один из них — прямо требуемый фазой 12 и отсутствовавший), дополнен `docs/protocol.md`; расхождений кода с дизайном не найдено @@ -335,16 +378,48 @@ как штатный исход. Это правильно: с пулом в 32 человека нанять всех и остаться в пределах суммы уже нельзя. +### Перепроверка на `bdb1253`, 2026-08-20 + +Дрейф после `5a00ad7` — `teachersShort` (фаза 25), свой сид, удостоверение пака. Обещания фаз 10–13 +целы; новых тестов не дописывал, расхождений с `design/staffing.md` нет. + +Что подтверждено повторно: + +- **Фаза 10.** `PETeacher` нет, кабинеты без учительской должности, места через `defaultSeats`, + редактор показывает у homeroom одно поле «мест». `Subject_UnknownSkill_FailsTheCatalog`, + стартовая дата 31 марта 2012. +- **Фаза 11.** Все шесть тестов пула на месте (`SameSeedAndWeek`, частичное обновление, 50 недель, + сильный просит больше, пустой штат, соискатель вне ростера). Пул 32, reload хранит ставки. +- **Фаза 12.** `Hire_TwoPeopleAtThreeAndSevenThousand_RejectsTheThird` на фикстуре 10 000. + `AssigningPastTheCap_IsRejectedWithTheNumbers` на API. `GET /staffing` читает снимок, + наём/назначение — `TaskCompletionSource`. `MaxWeeklyHours` не запрещает назначение + (`SubjectNeedingMoreHoursThanOnePersonCarries_StaysUncovered`). Ветка `if/else` вокруг предела + убрана. +- **Фаза 13.** Деньги, непокрытые, пул, штат, назначение с карточки, всё через `t(...)`. + Отказ по пределу текстом — `managementPanel.test.ts`. +- **Покрытие.** `PrimarySchool` на ванили требует трёх учителей (`TeachersShort == 3` в API и в + `PrimarySchool_OnVanillaMap_NeedsThreeTeachers_AndASecondDoesNotCloseIt`). Это фаза 25, не + ломает фазу 12: список непокрытых был и раньше, появилось число. + +Замечено рядом: + +- `people.md` «Заведомо не сейчас» всё ещё говорит, что должности закрывает генератор — то же, что + в срезе 2. Исторический стоп среза 2, не трогал. + +Staffing+ApplicantPool 18, Content 3, StaffingApi 9. + ## Срез 4. Расписание - **Фазы:** 14–17 -- **Проверен на:** `5a00ad7`, 2026-08-19 (первичная проверка — `21d79cb`) +- **Проверен на:** `bdb1253`, 2026-08-20 (первичная проверка — `21d79cb`; предыдущая перепроверка — `5a00ad7`) - **Пути:** `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.AppHost.Tests/TimetableApiTests.cs` + `tests/HSchool.Content.Tests/LunchBreakTests.cs`, + `tests/HSchool.AppHost.Tests/TimetableApiTests.cs`, + `src/HSchool.Client/src/ui/timetableGrid.test.ts` - **Итог:** дописан один тест на пять непокрытых кодов отказа, поправлены две устаревшие строки дизайна; расхождений кода с принятыми решениями нет @@ -413,6 +488,34 @@ Планировщик, каркас дня и API расписания не менялись; дописанный в прошлый проход тест на коды отказа пина проходит. +### Перепроверка на `bdb1253`, 2026-08-20 + +Дрейф после `5a00ad7` — обеденные смены в каркасе дня (`SchoolDay` / `CalendarDefs`) и сид/дамп в +воркере. Планировщик и API расписания не менялись. Обещания фаз 14–17 целы; тестов не дописывал. + +Что подтверждено повторно: + +- **Фаза 14.** Суббота, каникулы и ночь — «вне учебного дня»; 10:20 вторника — третий урок; + шестидневка делает субботу рабочей; зима через 1 января. Физкультура в спортзале. + Обеденные смены уже стоят в `design/schedule.md`: младшие после 3-го, старшие после 4-го, + обе перемены длинные (`LunchBreakTests`). +- **Фаза 15.** Все шесть тестов планировщика на месте, плюс самопроверка сборки без Arch/ASP.NET + и `DateTime.Now`. +- **Фаза 16.** Перестроение только при `PeopleChanged` + `TimetableDirty`, не по тику. + `GET /timetable` читает снимок, пин — `TaskCompletionSource`. `Reload_RestoresLockedLessons`, + `PinAndUnpinRefusals_CarryTheDocumentedCodes`. +- **Фаза 17.** Сетка, закрепление, тексты отказа — `timetableGrid.test.ts` (pin-rejected / + no-teacher / unknown-lesson). Всё через `t(...)`. + +Замечено рядом: + +- `GET /api/catalog` по-прежнему отдаёт каркас без `lunchBreaks` (только `longBreakAfter`). + Сетке расписания смены не нужны — они для решений. Молча расширять HTTP ревью не стало. +- `OpeningASchoolDuringAMathLesson_...` с этой перепроверки падает и в одиночку — см. срез 1; + это явка, не раскладка часов. + +Calendar+обеды 20, планировщик 9, TimetableApi 6. + ## Срез 5. Жизнь школы - **Фазы:** 18–21 diff --git a/tests/HSchool.Simulation.Tests/PresenceTests.cs b/tests/HSchool.Simulation.Tests/PresenceTests.cs index a9e4f75..2f98e81 100644 --- a/tests/HSchool.Simulation.Tests/PresenceTests.cs +++ b/tests/HSchool.Simulation.Tests/PresenceTests.cs @@ -221,6 +221,40 @@ public class PresenceTests Assert.Equal(Fingerprint(skipped.CapturePresence()), Fingerprint(lived.CapturePresence())); } + [Fact] + public void HireAndAssignMath_DuringLessonThree_PutsSomeoneOnCampus() + { + var start = new DateTime(2012, 4, 3, 10, 20, 0, DateTimeKind.Utc); + using var school = OpenEmpty(start); + school.Tick(0.2d, 5d); + Assert.True(school.IsCampusEmpty()); + + var catalog = school.Catalog!; + var map = school.Map!; + var hired = Staffing.Hire( + catalog, + map, + school.Roster!, + school.Applicants!, + school.Applicants!.Applicants[0].Person.Id, + Staffing.TeacherPosition, + Cap); + Assert.Equal(StaffingError.None, hired.Error); + school.ApplyStaffing(hired.Roster, hired.Pool); + + var assigned = Staffing.AssignSubject(catalog, hired.Roster, hired.Pool, hired.Roster.People.Single(person => person.IsStaff).Id, "Mathematics", Cap); + Assert.Equal(StaffingError.None, assigned.Error); + school.ApplyStaffing(assigned.Roster, assigned.Pool); + school.SetTimetable(SchoolTimetables.Build(catalog, map, assigned.Roster, null, weekDays: 5)); + + for (var i = 0; i < 20; i++) + { + school.Tick(0.2d, 5d); + } + + Assert.Contains(school.CapturePresence(), row => row.NodeId is not null); + } + [Fact] public void Simulation_DoesNotReferenceSockets() {