The URL keeps the open school and current view; sessionStorage keeps list filters and searches so F5 and re-entry do not dump the player on an empty map. Co-authored-by: Cursor <cursoragent@cursor.com>
178 lines
13 KiB
Markdown
178 lines
13 KiB
Markdown
# Вне очереди
|
||
|
||
Задачи, которых нет в нумерованных срезах. Сюда не дописывают срез 9 и не заводят срез 10:
|
||
склейка уже живых систем и мелкий DX. После любой фазы школа целая.
|
||
|
||
Люди: [`people.md`](people.md). Штат: [`staffing.md`](staffing.md). Расписание:
|
||
[`schedule.md`](schedule.md). Присутствие: [`ai.md`](ai.md). Одежда и климат:
|
||
[`inventory.md`](inventory.md).
|
||
|
||
## Связность урока
|
||
|
||
Урок уже растёт навык, если ученик стоит в кабинете и не голоден. Рядом лежат числа, которые
|
||
игрок видит и которые на этот рост не влияют: учитель в туалете, навык учителя, тепло,
|
||
учебник в сумке, снег на дороге.
|
||
|
||
Этот кусок отвечает на вопрос «почему нанимать сильного, чинить тепло и класть учебник в
|
||
сумку». Оценок, посещаемости как журнала и приказов нет.
|
||
|
||
### Что меняется в уже принятом
|
||
|
||
| Было | Стало | Почему |
|
||
| --- | --- | --- |
|
||
| Ученик в кабинете качает навык, учитель может быть где угодно | Рост только если назначенный учитель стоит в том же узле и не идёт | Зазор «обязанность / место» уже есть; урок его не смотрел |
|
||
| Зарплата зависит от навыков, урок — нет | Навык учителя по предмету масштабирует `Gain` | Иначе «математика 78» в пуле — только цена |
|
||
| Голод режет урок, тепло — нет | Та же `NeedFactor` на тепло | Полоска уже живая; [`inventory.md`](inventory.md) откладывал штраф |
|
||
| Учебники сегодняшних предметов едут в сумке | Нет в сумке на этом уроке — половинный рост | Переноска и шкафчик иначе остаются двумя числами |
|
||
| Погода одевает, на приход не влияет | Дождь и снег добавляют минуты к запасу на дорогу | Часы уже показывают осадки; ленивый уже опаздывает от черты |
|
||
|
||
### Формула
|
||
|
||
`HSchool.Ai.LessonLearning.Gain` по-прежнему чистое число. Множители:
|
||
|
||
- **нужды** — `NeedFactor(голод) × NeedFactor(тепло)`; нет нужды в каталоге — множитель 1;
|
||
- **черты ученика** — как сейчас;
|
||
- **учитель** — `NeedFactor(навык / 100)`, навык — среднее по `SubjectDef.skills` у учителя,
|
||
нет ключа — `Range.Min` этого навыка; учителя нет в комнате — множитель 0, рост не пишется;
|
||
- **учебник** — 1, если в сумке есть экземпляр с `subject` этого урока; иначе
|
||
`BehaviorDef.lessonNoTextbookFactor` (ваниль 0.5).
|
||
|
||
Учитель «стоит в комнате»: `NodeId` = кабинет урока, путь пуст, остаток минут 0. Действие
|
||
учителя не отменяет урок (дежурство — не `ActionDef`). Нет человека с этим id — учителя нет.
|
||
|
||
Дорога: к нынешнему `SlackMinutes` (черты + бросок) прибавляются минуты из
|
||
`BehaviorDef.commuteRainMinutes` / `commuteSnowMinutes`, если в момент сборки плана дня на
|
||
улице дождь или снег. `HSchool.Ai` осадки не знает — симуляция передаёт уже посчитанные минуты.
|
||
|
||
### Что видит игрок
|
||
|
||
Числа на карточке. Плюс строка в сегодняшней истории, **один раз на ключ в сутки**, не каждый
|
||
тик:
|
||
|
||
| Тип | Когда | `thingDef` |
|
||
| --- | --- | --- |
|
||
| `lesson-no-teacher` | Урок шёл, учителя не было в кабинете | предмет |
|
||
| `lesson-cold` | На уроке, который иначе учил, тепло ниже `needThreshold` | предмет |
|
||
| `lesson-no-textbook` | На уроке нет учебника этого предмета в сумке | предмет |
|
||
|
||
Опоздание из-за снега в лог не пишем: весь состав получил бы одну и ту же строку, а место
|
||
человека и так видно.
|
||
|
||
Клиент по-прежнему рисует `label` с сервера. Сокет и сейв не меняются. Версию протокола не
|
||
бампить.
|
||
|
||
### Где живёт код
|
||
|
||
Нового проекта нет. Формула и план дня — `HSchool.Ai`. Присутствие учителя, тепло, сумка,
|
||
погода на входе в план — `HSchool.Simulation`. Подписи лога — каталог. Список типов —
|
||
`docs/protocol.md` (HTTP лога).
|
||
|
||
### Заведомо не сейчас
|
||
|
||
- Оценки, журнал посещаемости, выговор за прогул.
|
||
- Перегруз замедляет ходьбу.
|
||
- Насмешки над формой и мнения — срез 9.
|
||
- Вызов родителей, деньги за одежду, болезнь от промокания.
|
||
- Клиент сам считает «учится хуже».
|
||
|
||
### Зафиксировано этим разговором
|
||
|
||
| Тема | Решение |
|
||
| --- | --- |
|
||
| Нарезка | Пять фаз во «Вне очереди», не срез 10 |
|
||
| Стоп | После каждой фазы школа целая |
|
||
| Протокол | Не бампить; новые `type` только в HTTP лога |
|
||
| Сейв | Не трогать |
|
||
| Учитель на уроке | Тот же узел, не идёт; нет id — нет роста |
|
||
| Навык учителя | Среднее по скиллам предмета, та же кривая что у нужды |
|
||
| Тепло | Рядом с голодом, произведение двух `NeedFactor` |
|
||
| Учебник | Только сумка, не шкафчик и не дом; ваниль ×0.5 |
|
||
| Погода на дорогу | Минуты в `BehaviorDef`; лога нет |
|
||
|
||
## Пропуск сборки при запуске
|
||
|
||
### Зачем
|
||
|
||
`run-aspire.cmd` каждый раз вызывает `dotnet run`, а тот — MSBuild графа Aspire, даже когда
|
||
исходники не менялись. Повторный запуск после Ctrl+C стоит десятки секунд впустую: SDK
|
||
пересчитывает хост, хотя dll на месте.
|
||
|
||
### Было / Стало / Почему
|
||
|
||
**Было.** Скрипт всегда `dotnet run --project src/HSchool.AppHost`. Аргументы проходили насквозь.
|
||
|
||
**Стало.** Если `HSchool.AppHost.dll` и `HSchool.Server.dll` новее входов (C#, csproj, props,
|
||
`global.json`), скрипт делает `dotnet run --no-build`. Иначе — `dotnet build`, потом `--no-build`.
|
||
`--rebuild` собирает всегда. Конфигурация (`-c` / `--configuration`) выбирает, какие dll сравнивать;
|
||
по умолчанию Debug, как у `dotnet run`.
|
||
|
||
**Почему.** Инкрементальный MSBuild всё равно оценивает Aspire SDK. `--no-build` это пропускает.
|
||
Клиентский TypeScript и JSONC модов не входы: Vite и каталог читают диск (`ContentRoot`), а не
|
||
`bin`. Тестовые проекты AppHost не ссылается — их правки запуск не пересобирают.
|
||
|
||
Правила «устарел ли билд» живут в `tools/apphost-uptodate.ps1` (то, что вызывает `run-aspire.ps1`) и в
|
||
`LaunchBuildStamp` (то, что проверяют тесты). Менять надо оба.
|
||
|
||
### Что не входит
|
||
|
||
Голый `dotnet run --project src/HSchool.AppHost` по-прежнему собирает — так удобнее агентам с
|
||
грязным деревом. Linux-обёртки нет: точка входа Windows — `.cmd`. Протокол, HTTP и сейв не
|
||
трогаем.
|
||
|
||
## Что нового после входа
|
||
|
||
### Зачем
|
||
|
||
Альфа обновляется часто. Игрок не смотрит git: если с прошлого захода в этот браузер накатили
|
||
другую сборку Server, один раз показать, что вошло в неё.
|
||
|
||
### Было / Стало / Почему
|
||
|
||
**Было.** Нет версии сборки на экране. Сессионная кука HttpOnly и про вход.
|
||
|
||
**Стало.** Сборка Server вшивает `HEAD` и `git log --first-parent`. `GET /api/changelog?since=`
|
||
отдаёт текущий SHA и коммиты после якоря. Клиент держит `hschool.seen-rev` (год, `Path=/`,
|
||
`SameSite=Lax`, не HttpOnly). Первый заход без куки окно не показывает — только запоминает
|
||
текущий SHA. Окно — после входа, не при возврате из школы.
|
||
|
||
**Почему.** «Накатанный» — то, что в dll, а не грязный HEAD при `--no-build`. First-parent после
|
||
`--no-ff` — это слитые фазы, не внутренняя нарезка ветки. `Mark phase N` оставляем в цепочке
|
||
якорем, в список не рисуем: иначе кука на таком HEAD потеряла бы следующее слияние.
|
||
|
||
### Что не входит
|
||
|
||
Ручной `CHANGELOG.md`. Запись на пользователе в `users.json`. Вызов git из бегущего процесса.
|
||
Бамп версии сокета. Окно внутри открытой школы.
|
||
|
||
## Состояние экрана
|
||
|
||
### Зачем
|
||
|
||
Менеджер — списки с фильтрами. Сейчас они живут только в RAM: F5 всегда меню, повторный вход
|
||
сбрасывает вкладки и запросы. Игрок сравнивает людей и соискателей; потерять «ученики 5А» на
|
||
обновлении — дырка в уже живых экранах, не новая фича.
|
||
|
||
### Было / Стало / Почему
|
||
|
||
**Было.** SPA без адреса. `GameScreen.show` и `PeoplePanel.show` обнуляют вкладки, фильтры и
|
||
выбор. Язык уже в `localStorage`; игровые часы — на сервере.
|
||
|
||
**Стало.** Query URL держит *где я*: школа, обзор/управление, карта/люди, выбранные комната и
|
||
человек. `sessionStorage` по `schoolId` держит фильтры, поиски, сорт, страницу, вкладку карточки
|
||
и класс сетки — и последний маршрут, чтобы вход с меню поднял то же место. F5 с `?school=`
|
||
шлёт тот же `OpenSchool`, что карточка. Вход/выход — `pushState`; смена вкладки и фильтра —
|
||
`replaceState`, чтобы «назад» не листал каждый клик.
|
||
|
||
**Почему.** Сервер не знает про вкладки; класть их в сейв — лишний HTTP. `localStorage` как у
|
||
языка держал бы «5А» через месяц. URL без фильтров остаётся коротким и копируемым.
|
||
|
||
### Что видит игрок
|
||
|
||
F5 внутри школы — та же школа, те же вкладки и фильтры. Назад — меню. Снова открыть ту же
|
||
школу — не сброс на карту. Гость не входит в управление по ссылке. Скорость часов, пауза,
|
||
открытый пул соискателей и мастер создания не вспоминаются.
|
||
|
||
### Что не входит
|
||
|
||
Поиск по имени в списках, где его нет. Роутер-библиотека. Состояние на сервере. Бамп протокола.
|