From c16c34a83c010ad83701bd79bd43757d6ae127b6 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Wed, 19 Aug 2026 15:57:12 +0300 Subject: [PATCH] Refactor project structure and enhance AI and simulation components - Updated project roles to include a new `HSchool.Ai` component, detailing its responsibilities in routing and decision-making. - Revised the `HSchool.Simulation` component to integrate with the new AI functionalities, improving the interaction between simulation and AI. - Expanded documentation to reflect the changes in project roles and the introduction of AI features. - Added tests for the new AI functionalities, ensuring robust pathfinding and decision-making capabilities. - Updated phase documentation to outline the new stages of school life, focusing on presence and behavior. --- docs/design/ai.md | 321 +++++++++++++++++++++++++++++ docs/design/projects.md | 7 +- docs/phases/18-presence-walking.md | 79 +++++++ docs/phases/19-presence-screen.md | 55 +++++ docs/phases/20-needs-actions.md | 44 ++++ docs/phases/21-decisions.md | 51 +++++ docs/phases/README.md | 23 +++ 7 files changed, 578 insertions(+), 2 deletions(-) create mode 100644 docs/design/ai.md create mode 100644 docs/phases/18-presence-walking.md create mode 100644 docs/phases/19-presence-screen.md create mode 100644 docs/phases/20-needs-actions.md create mode 100644 docs/phases/21-decisions.md diff --git a/docs/design/ai.md b/docs/design/ai.md new file mode 100644 index 0000000..1133b66 --- /dev/null +++ b/docs/design/ai.md @@ -0,0 +1,321 @@ +# Жизнь школы + +Договорённость на срез после расписания, не текущий код. +Расписание: [`schedule.md`](schedule.md). Штат: [`staffing.md`](staffing.md). +Люди: [`people.md`](people.md). Типы и карта: [`defs.md`](defs.md). Экран: [`near-term.md`](near-term.md). + +Люди начинают **ходить и решать**. До сих пор человек находился там, где его застало расписание; +теперь он приходит утром, идёт коридором в кабинет, может уйти с урока в туалет и вернуться — +и всё это видно на карте. + +Это самый дорогой срез базы, поэтому он разбит **на два этапа**. Первый ставит присутствие и +ходьбу и уже сам по себе оживляет школу. Второй добавляет нужды и выбор. Между ними можно +остановиться, и школа останется целостной. + +## Что уже готово + +Три вещи не надо изобретать — они лежат с прошлых срезов: + +- **Граф ходьбы.** `MapLayout.Links` — рёбра между двором и комнатами, валидатор требует + связности. Коридоры, лестницы, вестибюль и двор уже узлы этого графа, а не декорация. +- **Комнаты для нужд.** В `core` есть столовая, два санузла, учительская, библиотека и двор. + Карту под этот срез дорабатывать не нужно. +- **Нужды.** Сон, голод, туалет и общение существуют с нулевым `decayPerHour` — потому что + восполнить их было нечем. + +## Присутствие — состояние, а не функция от времени + +Ключевая смена модели. Сейчас «кто в кабинете» вычисляется из расписания и часов +(`TimetableClock`); занятость нигде не хранится, и это было правильно, пока никто не ходил. + +Теперь у человека есть **место**: узел графа, в котором он находится. Расписание перестаёт быть +источником истины о присутствии и становится тем, чем является, — **обязанностью**: где человеку +следует быть. Совпадение обязанности и места — уже не аксиома, а результат работы ИИ, и именно +в зазоре между ними живёт весь геймплей: опоздания, прогулы, пустой кабинет у слабого завуча. + +## Ходьба + +**Человек всегда находится в узле.** Переход — не полёт между комнатами, а последовательное +занятие узлов пути: полторы минуты в коридоре, потом полминуты на вход в кабинет. Рёбер как +состояний нет. + +Из этого сразу следует то, ради чего всё затевалось: на перемене коридор действительно полон +людей, и это видно в дереве. + +Цена перехода — **свойство дефа локации**: `travelMinutes` на `RoomDef` и `TerritoryDef`, столько +игровых минут занимает пересечение узла. В `core`: кабинет 0.5, коридор 1.5, лестница 1.5, +вестибюль 1, двор 3. Мод, где двор — это парк на гектар, ставит своё число и получает школу, по +которой опаздывают. + +Маршрут — кратчайший по сумме `travelMinutes`, посчитанный **один раз при загрузке школы** +матрицей следующих шагов. Узлов десятки, не тысячи; искать путь на каждом шаге незачем. + +Расстояния сразу дают карте смысл, которого у неё не было: спортзал через двор — это шесть минут +от кабинета 101 при перемене в десять. Кто ставит физкультуру пятым уроком, тот получает класс, +который переодевается в коридоре. + +## Приход и уход + +**Нет расписания — не приходит.** Ученик, у чьего класса сегодня нет уроков, не появляется в +школе вовсе; учитель без уроков сегодня — тоже. Каникулы и воскресенье школа проводит пустой, +и это не отдельное правило, а следствие. + +- **Ученик** приходит к своему первому уроку и уходит после последнего. +- **Учитель** — так же, по своим урокам. +- **Прочий штат** (директор, секретарь, повар, медсестра, библиотекарь) работает по дню школы: + приходит к первому уроку дня и уходит после последнего. +- **Родители** не приходят. Их вызов в школу — отдельный сюжет, не в этом срезе. + +Запас времени на дорогу человек берёт себе сам: путь от двора до кабинета плюс несколько минут +из своего потока случайности. Аккуратный придёт раньше, ленивый — в притык, и если путь длиннее +запаса, он опоздает. Это первое место, где черта характера делает что-то видимое. + +Вне школы человек **не исчезает**: у него состояние «вне школы», он есть в списке людей, и его +карточка это показывает. Дом мы не моделируем. + +## Пропуск пустого времени + +Ночью, в выходной, в праздник и на каникулах школа пуста и не делает ничего. Игроку при этом +остаётся смотреть на часы и мотать ×4 — летние каникулы в таком темпе это несколько минут +пустого экрана, а каждая ночь — полминуты. Поэтому: кнопка **«пропустить»**, переносящая время +к шести утра ближайшего рабочего дня. + +Два условия, и оба проверяет сервер: + +1. в школе никого нет; +2. сейчас **вне рабочего окна дня** — либо нерабочий день целиком, либо рабочий, но до шести + утра или после последнего звонка. + +Первое выглядит как вежливость к игроку, но дело не в ней: **оно и делает прыжок законным.** +Вне школы нужды не тратятся, а сон восстанавливается за кадром — значит для пустой школы +промотанное время и прожитое дают ровно одно состояние. Останься в здании хоть один человек, +прыжок пришлось бы либо честно просимулировать, либо соврать. + +Что прыжок обязан отработать по-настоящему — это события календаря, через которые он +перескакивает: первое сентября (переход, выпуск, набор) и недельное обновление пула соискателей. +Оба уже написаны как «что случилось между двумя датами», а не «что случилось на этом тике», так +что прыжок их не ломает. Но тест обязателен: летние каникулы перескакивают ровно через первое +сентября, и это самый вероятный способ потерять целый набор. + +Разрешение на кнопку приходит **вместе с часами**. Клиент не вычисляет его сам — это была бы +игровая логика на клиенте, а её там нет. + +Шесть утра — то же время, с которого начинается новая школа: до первого звонка есть чем заняться. +В три часа ночи вторника прыжок попадает в утро того же вторника, в десять вечера — в утро +среды, в субботу днём — в утро понедельника. Правило одно: ближайшее шесть утра рабочего дня, +которое ещё впереди. + +Границы рабочего окна берутся из **каркаса дня**, а не из фактического расписания. Разница +важна: у школы без единого нанятого учителя уроков нет вообще, и по фактическому расписанию +кнопка позволила бы перепрыгивать целые учебные дни. По звонкам — не позволит. + +## Действия — дефами + +`ActionDef` существует, но пуст. Он получает поля: + +```jsonc +{ + "defName": "UseToilet", + "room": "Restroom", // где это делают + "minutes": 4, // сколько длится + "need": "Toilet", // что восполняет + "needGain": 1, // насколько, за всё действие + "roles": ["student", "staff"], + "weight": 0, // 0 — ради удовольствия не выбирают +} +``` + +Действие может требовать **вещь** (`thing`), и тогда занимает её на время: за столом в столовой +не сидят вдвоём. Это связывает мебель на карте с поведением, а не только с вместимостью класса. + +Так мод добавляет поведение, не написав ни строчки кода, — ровно как обещано в +[`defs.md`](defs.md). + +**Обязанности из каталога действий не выбирают.** Урок приходит из расписания, работа по +должности — из `RoomDef.works`, который уже проставлен в `core` (`TeachLesson` у кабинета, +`ServeLunch` у столовой, `MedicalDuty` у медкабинета). `ActionDef` описывает то, что человек +делает **сам**: нужды и досуг. + +Числа поведения — отдельный деф правил, как `StaffingDef` у штата: пороги нужд, скорость +обучения на уроке, разброс запаса на дорогу, порог переключения решения. Это игровой баланс, а +не настройка движка, и место ему в контенте. + +## Как выбирается действие + +Гибрид: обязанность — не приказ, а **сильная цель среди прочих**. + +В момент решения человек собирает цели и сравнивает их вес: + +| Цель | Вес | +| --- | --- | +| Обязанность (урок, работа по должности) | постоянный, высокий | +| Нужда ниже порога | тем больше, чем ближе нужда к нулю | +| Досуг | низкий; берётся, когда обязанности нет | + +Побеждает наибольший. Отсюда бесплатно получается то, ради чего гибрид и нужен: ученик с +критическим «туалетом» уходит с урока, потому что нужда **обогнала** обязанность, а не потому +что где-то записано «можно выйти». + +Дальше — планирование. Цель выбирает действие, которое её закрывает, а планировщик достраивает +недостающее: «дойти до узла с такой комнатой» → «сделать». Предусловия у нас всего два — быть в +подходящей комнате и иметь свободную вещь, — поэтому цепочка выходит длиной в два-три шага. + +Честно про GOAP: **это его вырожденный случай**. Полноценный поиск по пространству состояний в +школе не окупается — он стоит заметно дороже и не порождает ни одной новой истории сверх «дойти +и сделать». Если однажды появятся действия, которые открывают другие действия (взять ключ → +открыть кабинет → принести журнал), планировщик углубится, а форма целей и предусловий останется +той же. Место для роста заложено, цена — нет. + +Одно правило против дёрганья: **начатое доводят до конца.** Решение переключается, только если +новая цель весит заметно больше текущей. Иначе человек с двумя почти равными нуждами будет +метаться между столовой и туалетом, ничего не успевая. + +Решение принимается **не каждый тик**, а по событиям: звонок, конец действия, приход в +назначенный узел, пересечение нуждой порога. Двадцать раз в секунду думать не о чем. + +## Нужды + +Декей включается. Голод — столовая, туалет — санузел, общение — перемена рядом с +одноклассниками. Сон **за кадром**: человек вне школы отдыхает, и утром приходит выспавшимся. +Ночной школы у нас нет, а изображать её ради одной полоски — лишняя работа. + +Прогресс-бары нужд в карточке уже нарисованы. В этом срезе они впервые начнут шевелиться. + +## Последствия — минимум + +Осознанно мало: + +- **Навык растёт на уроке** — от предмета, с поправкой на черты и на то, в каком человек + состоянии. Голодный учится хуже. +- **Опоздание и прогул видны.** В кабинете 5Б на математике не двадцать три человека, а + двадцать один, и карточка каждого говорит, где он. + +Оценок, успеваемости, конфликтов и настроения тут нет. Это следующий срез, и он будет +опираться на числа, которые появятся здесь. + +## Что видит игрок + +Игрок пока **наблюдает**. Приказы — «вызвать к директору», «отправить домой» — отдельный +разговор; сначала школа должна жить без вмешательства. + +На экране: + +- **дерево** — сколько человек в каждом узле, живым числом; +- **панель локации** — кто здесь и чем занят; +- **карточка человека** — где он сейчас и что делает, рядом с уже существующим расписанием. + +## Поток на клиент + +Присутствие меняется постоянно, поэтому у него **своё сообщение** — примерно два раза в секунду, +только по открытой школе. Полсекунды задержки на счётчик в дереве незаметны, а двадцать кадров в +секунду ради этого не нужны. + +Побочный выигрыш: **снимок карты становится статическим**. Сейчас он несёт и людей, и текущий +урок, и поэтому пересылается на каждой смене слота. Всё живое уезжает в присутствие, структура +уходит один раз при открытии — и одна из самых неприятных зависимостей в протоколе исчезает. + +Имена в поток не кладём: клиент один раз забирает по HTTP короткий справочник «id → имя» и держит +его открытым; неизвестный id — повод перечитать справочник, а не строка в каждом кадре. + +Присутствие приходит **по всей школе**, а выбранный узел клиент фильтрует сам — как дерево +локаций сейчас. Если школа на тысячу человек однажды сделает это дорогим, серверный +`OpenLocation` — тот самый шов, который для этого оставлен. + +## Отдельная библиотека + +`HSchool.Ai` встаёт рядом с `People` и `Schedule`: зависит на `Content`, `People` и `Schedule`, +не знает ни про Arch, ни про ASP.NET, ни про часы реального времени. + +Граница проходит там, где мало трафика. В библиотеку уезжает всё, что считается функцией от +входов: + +- **маршруты** — граф из `MapLayout.Links` и `travelMinutes` в матрицу следующих шагов и + стоимость пути; +- **план дня** — во сколько человеку выходить, чтобы успеть, и когда он уходит; +- **решение** — цели, веса, выбор действия и цепочка «дойти → сделать»; +- **продвижение плана** — прошло столько-то минут, значит человек здесь и делает вот это. + +В `HSchool.Simulation` остаётся то, что без мира не имеет смысла: компоненты, порядок обхода, +тик и адаптер, который собирает значения из компонентов, зовёт библиотеку и записывает ответ +обратно. + +Адаптер — настоящая цена разделения: на каждое решение надо сложить нужды и роль человека в +структуру и разобрать ответ. Цена приемлема ровно потому, что **решения редки, а движение +часто**: горячий путь — уменьшить остаток минут и перейти в следующий узел — границу не +пересекает вовсе, а думает человек несколько раз за урок. Провести черту где-нибудь ещё +означало бы гонять копирование двадцать раз в секунду на каждого. + +Взамен — то же, что уже дали `People` и `Schedule`: самая сложная логика базы проверяется +таблицей входов и выходов, без ECS, без сокета и без хоста. Для среза, где половина вопросов +будет вида «почему он пошёл туда», это и есть главный аргумент. + +## Детерминизм + +Тот же сид и те же действия игрока дают ту же школу. Это стоит дороже, чем кажется, и держится +на трёх вещах: + +1. **Стабильный порядок обхода.** Порядок сущностей в Arch меняется при создании и удалении — + опираться на него нельзя. Обход идёт по собственному упорядоченному списку людей. +2. **Свой поток случайности у каждого.** Как в генерации: `Seed.Mix` от сида школы, номера + человека, соли и номера дня. Никакого общего `Random` на мир. +3. **Решения по событиям, а не по бюджету времени.** Ограничение «столько решений за тик» — + очередь, а не отсечка: отложенное решение принимается следующим тиком, а не пропадает. + +## Сохранение + +Место человека, его путь и текущее действие едут в файл часов — там же, где уже лежат карта и +моды, с той же редкой периодичностью и обязательным сбросом при остановке. Кого в файле нет +(новичок после набора), тот расставляется по обязанности. + +Отдельного формата под ИИ не заводим: тридцать секунд неточности после аварийной остановки +игрок не заметит, а лишний файл на школу — заметим мы. + +## Масштаб + +Считаем на нынешние сотни людей, а не сразу на тысячу. Дорогих мест два, и оба уже закрыты +формой решения: маршруты считаются один раз при загрузке, решения принимаются по событиям. +Движение — это уменьшение счётчика у тех, кто идёт. + +Потолок решений за тик — настройка движка, ему место в `SimulationOptions`. + +## Что зафиксировано + +| Тема | Решение | +| --- | --- | +| Этапы | Два: присутствие и ходьба, затем нужды и решения | +| Присутствие | Состояние человека, а не функция от расписания | +| Ходьба | Всегда в узле; переход — занятие узлов пути | +| Цена перехода | `travelMinutes` в дефе локации | +| Маршруты | Матрица следующих шагов, один раз при загрузке | +| Приход | Только если сегодня есть уроки; запас на дорогу свой у каждого | +| Прочий штат | По дню школы, а не по своим урокам | +| Родители | Не приходят | +| Вне школы | Состояние, не исчезновение; дом не моделируем | +| Пропуск пустого времени | Кнопка к шести утра ближайшего рабочего дня: ночь, выходной, каникулы | +| Условие кнопки | Пустая школа **и** время вне рабочего окна дня; решает сервер, шлёт с часами | +| Границы окна | По каркасу дня, а не по фактическому расписанию — иначе прыгали бы через дни | +| Сон | Восстанавливается за кадром | +| Действия | Дефами: комната, вещь, длительность, нужда, роли, вес | +| Обязанности | Из расписания и `RoomDef.works`, не из каталога действий | +| Выбор | Обязанность — сильная цель, а не приказ; побеждает наибольший вес | +| Планирование | Вырожденный GOAP: «дойти → сделать», два предусловия | +| Переключение | Начатое доводится до конца, если новая цель не сильно тяжелее | +| Частота решений | По событиям: звонок, конец действия, приход, порог нужды | +| Нужды | Декей включается; сон вне школы | +| Последствия | Навык на уроке, видимые опоздание и прогул. Оценок нет | +| Игрок | Наблюдает; приказов нет | +| Поток присутствия | Своё сообщение, ~2 Гц, по всей школе, фильтр на клиенте | +| Снимок карты | Становится статическим: всё живое уходит в присутствие | +| Имена | Справочник по HTTP, не в каждом кадре | +| Библиотека | `HSchool.Ai` рядом с People и Schedule; в Simulation — только адаптер | +| Детерминизм | Держим: свой порядок обхода, поток на человека, очередь решений | +| Сейв | В файле часов, вместе с картой и модами | + +## Заведомо не сейчас + +- Оценки, успеваемость, настроение, конфликты и дружба. +- Приказы игрока и вызов родителей в школу. +- Ночная школа, сон как событие, продлёнка. +- Пиксельные координаты внутри комнаты и рисованные человечки. +- Полноценный GOAP с поиском по пространству состояний. +- Серверный `OpenLocation` — пока фильтрует клиент. diff --git a/docs/design/projects.md b/docs/design/projects.md index fd44a59..9cff5fa 100644 --- a/docs/design/projects.md +++ b/docs/design/projects.md @@ -10,7 +10,8 @@ Protocol — ни на кого из игровых проектов ( Content — ни на Protocol, ни на Simulation, ни на ASP.NET People → Content (defs, карта, склонения; без Arch и хоста) Schedule → Content (раскладка уроков; без Arch и хоста) -Simulation → People, Content (ростер в World; без HTTP) +Ai → Content, People, Schedule (маршруты и решения; без Arch и хоста) +Simulation → Ai, People, Content (ростер в World; без HTTP) Server → Protocol, Simulation, People, Content Client — своя сторона Protocol (TS) + HTTP ``` @@ -24,7 +25,8 @@ Client — своя сторона Protocol (TS) + HTTP | **HSchool.Content** | Типы def, JSONC-загрузчик, наследование, патчи, слияние локалей, граф карты, валидация связности | Arch, часы, HTTP, потоки, пути `mods/` с диска хоста | | **HSchool.People** | Генерация ростера: семьи, классы, тело/навыки/черты из каталога и сида | Arch, ASP.NET, сокеты, `DateTime.Now`, файлы сейва | | **HSchool.Schedule** | Раскладка учебного плана в таблицу уроков вокруг закреплённых правок | Arch, ASP.NET, сокеты, `DateTime.Now`, файлы сейва | -| **HSchool.Simulation** | `School`, `GameClock`, Arch `World`, тик, применение раскладки к миру | Kestrel, сокеты, `Directory.Enumerate`, сейв-файлы | +| **HSchool.Ai** | Маршруты по графу карты, план дня человека, выбор цели и действия, продвижение плана во времени | Arch, ASP.NET, сокеты, `DateTime.Now`, файлы сейва | +| **HSchool.Simulation** | `School`, `GameClock`, Arch `World`, тик, применение раскладки к миру, адаптер к `Ai` | Kestrel, сокеты, `Directory.Enumerate`, сейв-файлы | | **HSchool.Server** | Хост, супервизор, работник-поток, `mods/` и `saves/` из конфига, HTTP/WS, DTO запросов | Правила «комната должна быть связана», схема Chair | | **HSchool.Protocol** | Кадры сокета, в том числе снимок карты и locale в Hello | Имена комнат, JSONC | | **HSchool.Client** | DOM, `t()` для хрома, рисует снимок | Файлы модов, симуляция | @@ -52,6 +54,7 @@ Arch и фиксированный тик — про живую школу. Па - `tests/HSchool.Content.Tests` — фикстуры JSONC, коллизии имён, дырявый граф, двор обязателен. - `tests/HSchool.People.Tests` — тот же сид даёт тот же ростер; места и должности заполнены. - `tests/HSchool.Schedule.Tests` — тот же штат и карта дают ту же таблицу; четыре запрета. +- `tests/HSchool.Ai.Tests` — путь между комнатами и его цена; те же входы дают то же решение. - `HSchool.Simulation.Tests` — часы, тик, «школа с таким каталогом живёт». - `HSchool.AppHost.Tests` — create с картой, рестарт, сейв на диске. diff --git a/docs/phases/18-presence-walking.md b/docs/phases/18-presence-walking.md new file mode 100644 index 0000000..5175212 --- /dev/null +++ b/docs/phases/18-presence-walking.md @@ -0,0 +1,79 @@ +# Фаза 18. Присутствие и ходьба + +## Зависимости + +- [Фаза 16](16-timetable-in-school.md) + +## Зачем + +Человек перестаёт быть записью в расписании и получает **место**. Он приходит утром, идёт +коридором в кабинет и уходит после последнего урока. Экрана ещё нет — всё проверяется тестами +и логом. + +## Задачи + +- [ ] Новый проект `HSchool.Ai` рядом с `People` и `Schedule`: зависит на `Content`, `People` и + `Schedule`, без Arch и ASP.NET. Рядом — `tests/HSchool.Ai.Tests` +- [ ] `travelMinutes` на `RoomDef` и `TerritoryDef`; в `core` проставлены кабинет 0.5, коридор + 1.5, лестница 1.5, вестибюль 1, двор 3 +- [ ] Маршрутизация по `MapLayout.Links` — в библиотеке: матрица следующих шагов считается + **один раз** при загрузке школы, не на каждом шаге +- [ ] План дня человека — тоже в библиотеке: во сколько выходить, чтобы успеть, и когда уходить. + Часы, компоненты и мир туда не проникают +- [ ] Место человека — компонент в `World`: узел, остаток пути, минуты до выхода из узла. + Человек всегда в узле; рёбер как состояний нет +- [ ] Состояние «вне школы» — не удаление сущности: человек остаётся в списке и в карточке +- [ ] Приход и уход: у кого сегодня нет уроков, тот не приходит вовсе. Ученик и учитель — по + своим урокам, прочий штат — по дню школы +- [ ] Запас на дорогу свой у каждого: путь от двора плюс несколько минут из своего потока + случайности, с поправкой на черты +- [ ] Обязанность как место назначения: урок — кабинет из расписания, должность — комната из + `RoomDef.works`. Выбора действий пока нет +- [ ] Движение по тику: остаток минут в узле уменьшается, при нуле человек переходит в следующий + узел пути +- [ ] Стабильный порядок обхода людей — собственный список, а не порядок сущностей в Arch +- [ ] Адаптер в `HSchool.Simulation`: собирает значения из компонентов, зовёт библиотеку, + записывает ответ обратно. Горячий путь — движение — границу не пересекает +- [ ] Ориентир в `AGENTS.md`, `docs/architecture.md` и `docs/design/projects.md` знает про новый + проект +- [ ] Место, путь и назначение едут в файл часов; кого в файле нет, тот расставляется по + обязанности +- [ ] Потолок решений за тик — в `SimulationOptions`, как очередь, а не как отсечка +- [ ] Предикат «школа пуста»: в здании и во дворе нет никого +- [ ] Рабочее окно дня — от шести утра до последнего звонка по **каркасу дня**, а не по + фактическому расписанию +- [ ] «Ближайшее шесть утра рабочего дня впереди»: ночь ведёт в то же утро, вечер — в следующее, + выходные, праздники и каникулы пропускаются. Поиск ограничен разумным числом дней и + честно отвечает «нет такого» +- [ ] Прыжок часов туда одним шагом: разрешён только при пустой школе и вне рабочего окна, + отрабатывает первое сентября и обновление пула за весь пропуск, после себя пишет сейв + +## Тесты, без которых фаза не закрыта + +- [ ] Путь из кабинета 201 в санузел первого этажа идёт через лестницу и коридор, а не напрямую +- [ ] Сумма `travelMinutes` совпадает с временем, за которое человек реально доходит +- [ ] В воскресенье и в каникулы школа пуста: все вне школы +- [ ] Класс без уроков сегодня не приходит, а его учитель с уроками у другого класса — приходит +- [ ] К началу первого урока ученик находится в своём кабинете +- [ ] Физкультура после урока в главном корпусе: класс доходит до спортзала, и видно, что + перемены на это едва хватает +- [ ] Тот же сид и та же карта дают то же расположение людей на тот же момент +- [ ] Сохранение и загрузка посреди перемены не телепортируют людей +- [ ] Прыжок с субботы попадает в понедельник, 6:00; с трёх ночи вторника — в утро того же + вторника; с десяти вечера — в утро среды +- [ ] Прыжок в учебное время отклоняется. В семь утра рабочего дня, когда школа ещё пуста, — + тоже: это уже рабочее окно +- [ ] У школы без единого учителя рабочий день не пропускается +- [ ] Прыжок через летние каникулы проводит первое сентября: классы перешли, выпуск состоялся, + набор пришёл +- [ ] Промотанная неделя и прожитая неделя дают одно состояние школы + +## Критерий готовности + +- `dotnet test` проходит +- Маршруты и план дня проверены без мира: в `HSchool.Ai.Tests` нет Arch +- В `HSchool.Ai` нет упоминаний Arch, ASP.NET и `DateTime.Now`; в `HSchool.Simulation` — сокетов + +## Стоп + +Не трогать протокол и клиент. Не заводить нужды, действия и выбор — это фазы 20 и 21. diff --git a/docs/phases/19-presence-screen.md b/docs/phases/19-presence-screen.md new file mode 100644 index 0000000..eaea2dd --- /dev/null +++ b/docs/phases/19-presence-screen.md @@ -0,0 +1,55 @@ +# Фаза 19. Присутствие на экране + +## Зависимости + +- [Фаза 18](18-presence-walking.md) +- [Фаза 17](17-timetable-screen.md) + +## Зачем + +Школа впервые видна живой: в дереве идут числа, на перемене коридор наполняется, к звонку +кабинеты сходятся обратно. + +## Задачи + +- [ ] Новое сообщение присутствия, ~2 раза в секунду, только по открытой школе: узлы со + счётчиками и люди с их узлом и состоянием +- [ ] Снимок карты становится **статическим**: люди и текущий урок уезжают в присутствие, + структура уходит один раз при открытии. Пересылка на смене слота убирается +- [ ] `ProtocolCodec.cs`, `protocol.ts` и `docs/protocol.md` правятся тем же коммитом, версия + бумпится до 7 +- [ ] Справочник «id → имя» по HTTP: клиент забирает один раз при открытии школы и перечитывает, + увидев незнакомый id. Имена в кадр присутствия не кладутся +- [ ] Дерево: число людей рядом с узлом, живое. Пустой узел остаётся без числа +- [ ] Панель локации: кто здесь сейчас, списком с именами; урок в комнате берётся из присутствия, + а не из снимка +- [ ] Карточка человека: где он сейчас — узел, «в пути» или «вне школы» +- [ ] Присутствие приходит по всей школе, выбранный узел фильтрует клиент +- [ ] Кадр часов несёт разрешение на пропуск; клиент это разрешение **не вычисляет** +- [ ] Намерение «пропустить» — своё сообщение, как пауза и скорость. Сервер проверяет условия + заново: кадр из браузера не доверенный +- [ ] Кнопка рядом с паузой и скоростями: появляется только когда сервер разрешил, подписана + датой, к которой перенесёт +- [ ] Строки через `t(...)`, обе локали + +## Тесты, без которых фаза не закрыта + +- [ ] Байтовая раскладка присутствия сходится с обеих сторон +- [ ] Круговой прогон сообщения: сервер → клиент → те же значения +- [ ] Снимок карты больше не содержит людей и урока +- [ ] Кадр часов с разрешением на пропуск читается обеими сторонами одинаково +- [ ] Пропуск, присланный в учебное время, сервер отбрасывает, а не исполняет + +## Критерий готовности + +- Открыть школу в 8:25 и увидеть, как двор и вестибюль пустеют, а кабинеты наполняются +- На перемене коридор — самый людный узел дерева +- В каникулы дерево без чисел, а карточки говорят «вне школы» +- В пятницу после уроков дождаться, пока школа опустеет, нажать «пропустить» и оказаться в + понедельник в 6:00; вечером буднего дня та же кнопка ведёт в завтрашнее утро +- `dotnet test`, `npm test` и `npm run build` проходят + +## Стоп + +Не рисовать перемещение анимацией. Не заводить серверный `OpenLocation`. Не добавлять нужды и +действия. diff --git a/docs/phases/20-needs-actions.md b/docs/phases/20-needs-actions.md new file mode 100644 index 0000000..4d30f95 --- /dev/null +++ b/docs/phases/20-needs-actions.md @@ -0,0 +1,44 @@ +# Фаза 20. Нужды и действия + +## Зависимости + +- [Фаза 19](19-presence-screen.md) + +## Зачем + +Нужды перестают быть четырьмя неподвижными полосками, а действия — пустым дефом. Появляется то, +из чего фаза 21 будет собирать решения. + +## Задачи + +- [ ] `ActionDef` получает поля: комната, вещь, длительность, восполняемая нужда и величина, + роли, вес досуга +- [ ] Действия в `core`: поесть в столовой, санузел, отдохнуть на перемене, пообщаться, + прогуляться по коридору и по двору +- [ ] Деф правил поведения — как `StaffingDef` у штата: пороги нужд, скорость обучения на уроке, + разброс запаса на дорогу, порог переключения решения +- [ ] Декей нужд включается в `core`. Сон восстанавливается вне школы, а не действием +- [ ] Действие занимает вещь на время: за один стол не садятся вдвоём +- [ ] Выполнение действия: начало, отсчёт минут, восполнение нужды по завершении. Правила — в + `HSchool.Ai`, компоненты и мир — в `HSchool.Simulation` +- [ ] Валидатор дефов ловит действие без комнаты, с неизвестной нуждой и с неизвестной вещью +- [ ] Карточка человека показывает, что он делает; полоски нужд начинают шевелиться + +## Тесты, без которых фаза не закрыта + +- [ ] Голод падает за игровой час ровно на `decayPerHour` +- [ ] Обед в столовой поднимает голод на заявленную величину и занимает заявленное время +- [ ] Столов меньше, чем желающих: лишние не начинают действие, а не садятся сквозь друг друга +- [ ] Ночь вне школы возвращает сон к максимуму +- [ ] Действие с неизвестной нуждой роняет загрузку каталога с внятной ошибкой +- [ ] Те же входы дают те же значения нужд на тот же момент + +## Критерий готовности + +- `dotnet test` и `npm test` проходят +- В карточке за игровой день видно, как нужды опускаются, а после столовой — поднимаются + +## Стоп + +Не подключать выбор действия — люди пока ходят только по обязанности. Не трогать оценки и +успеваемость. diff --git a/docs/phases/21-decisions.md b/docs/phases/21-decisions.md new file mode 100644 index 0000000..29213b9 --- /dev/null +++ b/docs/phases/21-decisions.md @@ -0,0 +1,51 @@ +# Фаза 21. Выбор действия + +## Зависимости + +- [Фаза 20](20-needs-actions.md) + +## Зачем + +Последний кусок среза: обязанность перестаёт быть приказом. Ученик с критической нуждой уходит с +урока и возвращается, и это видно в кабинете и в его карточке. + +## Задачи + +- [ ] Цели, веса и планирование — целиком в `HSchool.Ai`: на вход значения, на выход решение. + В `HSchool.Simulation` — только сбор входов и запись ответа +- [ ] Цели и их вес: обязанность — постоянный высокий, нужда — тем больше, чем ближе к нулю, + досуг — низкий и только при отсутствии обязанности +- [ ] Побеждает наибольший вес. Уход с урока получается из сравнения, а не из отдельного правила +- [ ] Планирование «дойти → сделать»: цель выбирает действие, планировщик достраивает дорогу до + подходящей комнаты +- [ ] Начатое доводится до конца: переключение только при заметном перевесе новой цели +- [ ] Решения по событиям — звонок, конец действия, приход в назначенный узел, пересечение + нуждой порога, — а не каждый тик +- [ ] Очередь решений с потолком за тик: отложенное принимается следующим тиком, а не пропадает +- [ ] Возвращение к обязанности после закрытия нужды, если урок ещё идёт +- [ ] Навык растёт на уроке: от предмета, с поправкой на черты и на состояние нужд +- [ ] Опоздание и отсутствие видны: в панели локации на уроке не весь класс, в карточке написано, + где человек +- [ ] Строки через `t(...)`, обе локали + +## Тесты, без которых фаза не закрыта + +- [ ] Ученик с нулевым «туалетом» уходит с урока, доходит до санузла и возвращается в кабинет +- [ ] Нужда чуть ниже порога урок не срывает +- [ ] Две почти равные нужды не заставляют метаться: действие доводится до конца +- [ ] На перемене выбирается досуг, на уроке — нет +- [ ] Навык после учебного дня вырос, у голодного — меньше +- [ ] Потолок решений за тик не теряет решения, а сдвигает их +- [ ] Тот же сид и те же действия игрока дают ту же школу через игровую неделю +- [ ] Разбор решения таблицей входов и выходов, без мира и без хоста + +## Критерий готовности + +- Прогнать школу на ×4 через учебный день и увидеть осмысленную картину: уроки, перемены с + коридором и столовой, редкие отлучки посреди урока +- `dotnet test`, `npm test` и `npm run build` проходят + +## Стоп + +Не добавлять оценки, настроение, конфликты и дружбу. Не давать игроку приказы. Не углублять +планировщик до полного GOAP. diff --git a/docs/phases/README.md b/docs/phases/README.md index 0d2cb8f..fbcd084 100644 --- a/docs/phases/README.md +++ b/docs/phases/README.md @@ -68,3 +68,26 @@ | [15. Планировщик](15-timetable-planner.md) | ✅ | Раскладка часов по слотам, четыре запрета | | [16. Расписание в школе](16-timetable-in-school.md) | ✅ | Сейв, снимок, «кто где сейчас» | | [17. Расписание на экране](17-timetable-screen.md) | ✅ | Скобки в дереве, сетка класса, расписание учителя | + +## Срез 5. Жизнь школы + +Дизайн: [`../design/ai.md`](../design/ai.md). + +Люди начинают ходить и решать. Присутствие перестаёт вычисляться из расписания и становится +состоянием: человек приходит утром, идёт коридором, может уйти с урока в туалет и вернуться. + +Срез идёт **двумя этапами**, и между ними можно остановиться — школа останется целостной. + +**Этап A — присутствие.** Люди ходят по обязанности; выбора у них ещё нет. + +| Фаза | Статус | Зачем | +| --- | --- | --- | +| [18. Присутствие и ходьба](18-presence-walking.md) | ⬜ | `HSchool.Ai`, место человека, маршруты, приход и уход | +| [19. Присутствие на экране](19-presence-screen.md) | ⬜ | Своё сообщение ~2 Гц, числа в дереве, статический снимок карты, пропуск пустого времени | + +**Этап B — поведение.** Появляются нужды, действия и выбор между ними и обязанностью. + +| Фаза | Статус | Зачем | +| --- | --- | --- | +| [20. Нужды и действия](20-needs-actions.md) | ⬜ | `ActionDef` с полями, декей, восполнение | +| [21. Выбор действия](21-decisions.md) | ⬜ | Цели и веса, «дойти → сделать», уход с урока, рост навыка |