Files
h-school/docs/design/inventory.md
T
Leonid PershinandCursor ce71a91a67 Add rain and snow minutes to the morning commute.
Day plans take a precomputed extra; Ai does not know precipitation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 09:40:24 +03:00

433 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Одежда, вещи и климат
Договорённость на следующий срез, не текущий код.
Люди: [`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` не принимается.
## Климат и погода
Погода — не картинка и не текст. На школе живёт состояние: **уличная температура** и **осадки**
(нет / дождь / снег). Его видно у часов, и от него одеваются. Дождь и снег ещё добавляют минуты
к запасу на дорогу (`BehaviorDef.commuteRainMinutes` / `commuteSnowMinutes`): план дня получает
уже число, симуляция считает его с улицы. Ходьбу по коридору это не режет.
Числа живут в `ClimatePresetDef`, не в коде. Страна только ссылается на id. Мод меняет зиму,
не трогая имена.
Пресет описывает:
- ход температуры по году (месячные нормы);
- как температура гуляет внутри дня и от нормы (разброс);
- когда и какие осадки — с поправкой на температуру: ниже нуля тот же бросок даёт снег, выше —
дождь.
Погода пересчитывается не каждый тик, а когда это меняет одежду или надпись у часов: сдвиг на
несколько десятых градуса, смена осадков. Детерминизм: пресет + сид школы + календарная дата и
час. Тот же сид, тот же день — та же улица.
**В здании теплее, чем на улице**, даже без сотрудника: стены держат. Но комфорт — работа
техника. Без него зимой в кабинетах прохладно, летом душно; с ним держится жилая полоса.
Двор, крыльцо и переход между корпусами — улица.
Пропуск пустого времени погоду не «перепрыгивает молча»: к утру рабочего дня состояние считается
из даты, как годовой набор уже считается через каникулы.
## Тепло как нужда
Новая нужда — **тепло** (`Warmth`). Шкала как у остальных: 0–1, полоска на карточке.
Она падает, когда одежда не соответствует **температуре места**, где человек сейчас: на морозе
без куртки, в жару в шубе. В комфортной полосе почти не падает. Дома (вне школы) восстанавливается
за кадром, как сон.
Две черты, несовместимые друг с другом:
- **теплолюбивый** — комфортная полоса сдвинута вверх, на жару реагирует спокойнее, на холод —
раньше;
- **холодолюбивый** — наоборот.
Сдвиг — поле на `TraitDef` (смещение комфортной температуры), не список фамилий в коде. Мод
добавляет «мерзлявый» тем же полем.
Голодный учится хуже уже сейчас. Замёрзший — тоже: та же `NeedFactor`, что у голода. Нужда
видна, двигает переодевание и режет рост навыка на уроке. Оценок как журнала нет.
## Один 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` | трусы |
| `UpperUnderwear` | бюстгальтер (поверх трусов, под верх) |
| `Socks` | носки |
| `Bottom` | юбка, штаны, джинсы, брюки |
| `Top` | футболка, рубашка |
| `OverTop` | свитер, жилет |
| `Outer` | куртка, пальто, шуба |
| `Shoes` | обувь |
| `Head` | шапка, кепка |
| `Accessory` | ремень, шарф, значок |
Платье — **один предмет на несколько слоёв**: `layers: ["Bottom", "Top"]`. Пиджак, если его можно
снять отдельно от брюк, — отдельная вещь на `OverTop` или `Outer`, не «костюм-монолит».
На одном слое — не больше одной вещи. Надеть свитер поверх рубашки можно: это разные слои.
Надеть две футболки нельзя.
### Ванильный набор
Не витрина моды, а достаточный гардероб, чтобы генератор и правила было чем кормить:
- бельё: абстрактный `Underwear`, от него трусы / боксеры / трусики и бюстгальтеры (`UpperUnderwear`);
- носки нескольких цветов;
- низы: юбка (женская), штаны, джинсы, брюки — длины обычная и короткая;
- верх: футболка, рубашка;
- поверх: свитер;
- верхняя: куртка, пальто, шуба;
- обувь: повседневная и зимняя;
- голова: шапка;
- аксессуары: ремень, шарф;
- платье (два слоя);
- форма физкультуры (верх + низ, `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 вещи. Экземпляр + палитра.
- Не ставить домашний узел на карту. Тридцать тысяч «комнат» семей не окупить.
- Не раздавать шкафчик каждому на ванильной карте. Нехватка — правило, не баг.
- Не вешать перегруз на ходьбу, пока не измерен. Число на карточке сначала.
- Не возвращать общую ленту событий школы. История — у человека, за сегодня.
## Заведомо не сейчас
- Магазины, деньги за одежду, стирка, кража, выдача со склада игроком.
- Игрок одевает человека с карточки.
- Болезни от промокания, прогул «не в форме» как оценка. Штраф к уроку от холода — фаза 51.
- Смена страны или климатического пресета у живой школы.
- Почасовая анимация погоды, ветер, грязь, зонты как система.
- Шкафчики сотрудников, раздевалки в каждом корпусе сверх двух ванильных.
- История старше суток и школьная лента на весь экран.
- Портреты и спрайты одежды. Слои — текст и цвет, не картинка.
## Зафиксировано этим разговором
| Тема | Решение |
| --- | --- |
| Вид def | Один `ThingDef`; одежда и ноша — поля |
| Цвет | Поле экземпляра; `ColorDef` с тегами уместности |
| Костюм / платье | Один предмет на несколько слоёв; снимаемый пиджак — отдельная вещь |
| Слои | Бельё, носки, низ, верх, поверх, верхняя, обувь, голова, аксессуар |
| Уместность | Пол, возраст, сезон/погода, урок (физкультура), формальность, дресс-код, цвет |
| Запрет vs оценка | Оценка; система сама не наденет неуместное |
| Короткая форма | Длина юбки и летний рукав; малышам неуместно — ходят в обычной |
| Цвет vs строгость | Два независимых правила; своё у учеников и у сотрудников |
| Ноша | Навык `Hauling` + сила + выносливость; на карточке число, ходьбу не трогает |
| Учебники | Один def, экземпляр на предмет параллели; в сумке — сегодняшние |
| Прочее в сумке | Портфель, телефон, бутылка |
| Генератор | Одевает всех, включая родителей и соискателей |
| Переодевание дома | Мгновенно, событием |
| Переодевание в школе | Действие в своей раздевалке |
| Дом | Не узел карты; текущее дело «дома» |
| Шкафчик | Слот карты, один ученик; старшие раньше; не хватило — не досталось |
| Раздевалки | Два узла, м/ж; шкафчики добавляются числом в редакторе |
| Износ | Падает на теле в школе; полоска + подписи; из дома в ветхом не выходят |
| Замена одежды | Утром дома событием |
| Страна | `CountryDef`: вложенные имена + ссылки на пресеты климата |
| Create | Пикер страны вместо набора имён; язык — если у страны их несколько |
| Климат | Пресет выбирается сидом из списка страны, на живой школе не меняется |
| Погода | Температура и осадки; у часов; одежда; минуты на дорогу |
| В здании | Теплее улицы всегда; комфорт — если нанят техник |
| Техник | Комната + должность; холод летом и тепло зимой |
| Нужда | `Warmth`; черты теплолюбивый / холодолюбивый |
| Правила | Вкладка в «Управлении»; четыре комбобокса; с завтра |
| Карточка | Вкладки обзор / одежда / ноша / сейчас+история; чистый просмотр |
| История | Только этот человек, только сегодня, HTTP, поиск и пейджинг |
| Инвентарь в сейве | На человеке в `people.json`, писать при содержательной смене |
| Игрок надевает | Нет |
| Погода влияет на ИИ | Да: переодеться. Ходьбу не штрафует. Урок — через нужду тепла |