Merge main into phase/39-school-owners.
Keep school owners and phase 41 opinions/portraits together; ResetAsync wipes all saves so shared AppHost tests stay isolated.
This commit is contained in:
@@ -128,6 +128,7 @@ Welcome не трогаем, версию не бампим. Глобальны
|
||||
|
||||
- пауза, скорость, пропуск пустого времени — кадр сокета игнорируется (не закрывает соединение);
|
||||
- удалить, нанять, назначить, сменить правила, закрепить урок — HTTP `403` `not-owner`;
|
||||
- генерировать портреты — **можно**: пресеты лежат на школе, модель одна и та же;
|
||||
- видеть вкладку «Управление» — клиент её не монтирует. Карта и люди — да, карточка — да.
|
||||
|
||||
Хозяин, который смотрит свою, ничего не теряет. Несколько гостей на одну школу — несколько
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
`others` — чужие и бесхозные (`owner` имя или `null`). Карточка школы несёт `mine`
|
||||
- [ ] Welcome.`MaxSchools` — слоты игрока (байт тот же, смысл новый)
|
||||
- [ ] Мутации чужой — HTTP `403` `not-owner`. Бесхозную может удалить любой залогиненный.
|
||||
`OpenSchool` — всем с сессией. Пауза / скорость / пропуск от гостя до работника не доходят
|
||||
`OpenSchool` — всем с сессией. Пауза / скорость / пропуск от гостя до работника не доходят.
|
||||
**Исключение:** `POST .../portrait` — гость генерирует теми же пресетами школы; это не управление
|
||||
- [ ] Меню: блок «Мои» и блок «Чужие». У бесхозной в чужих — удалить, у чужой с хозяином — нет
|
||||
- [ ] Внутри чужой школы нет вкладки «Управление» и нет кнопок часов (пауза, скорость, пропуск).
|
||||
Карта и люди остаются
|
||||
|
||||
+13
-13
@@ -12,25 +12,25 @@
|
||||
|
||||
## Задачи
|
||||
|
||||
- [ ] Мнение A→B: −100…100, несимметрично, ноль не хранить
|
||||
- [ ] Пороги и подписи в `BehaviorDef`; клиент рисует присланную подпись, не считает сам
|
||||
- [ ] Генератор ставит семейные старты (родители, сиблинги, партнёры семьи) из данных
|
||||
- [ ] Каждое рабочее утро (и skip) ненулевые мнения слегка дрейфуют к семейному базису или к нулю;
|
||||
- [x] Мнение A→B: −100…100, несимметрично, ноль не хранить
|
||||
- [x] Пороги и подписи в `BehaviorDef`; клиент рисует присланную подпись, не считает сам
|
||||
- [x] Генератор ставит семейные старты (родители, сиблинги, партнёры семьи) из данных
|
||||
- [x] Каждое рабочее утро (и skip) ненулевые мнения слегка дрейфуют к семейному базису или к нулю;
|
||||
дошли до нуля — запись выкинуть
|
||||
- [ ] `people.json` пишет мнение при сдвиге; старый сейв без поля — все нули кроме семьи
|
||||
- [x] `people.json` пишет мнение при сдвиге; старый сейв без поля — все нули кроме семьи
|
||||
после первой загрузки-переписи
|
||||
- [ ] Вкладка карточки **Связи**: семья с числами, топ друзей/врагов, поиск по ненулевым
|
||||
- [ ] Игрок с вкладки ничего не меняет
|
||||
- [ ] `docs/protocol.md` — HTTP карточки
|
||||
- [x] Вкладка карточки **Связи**: семья с числами, топ друзей/врагов, поиск по ненулевым
|
||||
- [x] Игрок с вкладки ничего не меняет
|
||||
- [x] `docs/protocol.md` — HTTP карточки
|
||||
|
||||
## Тесты, без которых фаза не закрыта
|
||||
|
||||
- [ ] Родитель и ребёнок после генерации имеют плюс в обе стороны; чужие одноклассники — нет записи
|
||||
- [ ] A→B и B→A могут различаться
|
||||
- [ ] После нескольких утренних дрейфов без разговоров мнение одноклассников (если появилось)
|
||||
- [x] Родитель и ребёнок после генерации имеют плюс в обе стороны; чужие одноклассники — нет записи
|
||||
- [x] A→B и B→A могут различаться
|
||||
- [x] После нескольких утренних дрейфов без разговоров мнение одноклассников (если появилось)
|
||||
ближе к нулю, семейное — к старту, не к нулю
|
||||
- [ ] Карточка без отдельного запроса «всех пар школы»: только этот человек
|
||||
- [ ] Клиентский тест: вкладка «Связи» не монтирует лог
|
||||
- [x] Карточка без отдельного запроса «всех пар школы»: только этот человек
|
||||
- [x] Клиентский тест: вкладка «Связи» не монтирует лог
|
||||
|
||||
## Критерий готовности
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
|
||||
| Фаза | Статус | Зачем |
|
||||
| --- | --- | --- |
|
||||
| [39. Хозяин школы](39-school-owners.md) | ⬜ | `owner`, 2 слота на игрока, мои / чужие, гость без управления |
|
||||
| [39. Хозяин школы](39-school-owners.md) | 🔄 | `owner`, 2 слота на игрока, мои / чужие, гость без управления |
|
||||
|
||||
39 стоит на 38.
|
||||
|
||||
@@ -194,13 +194,13 @@
|
||||
|
||||
| Фаза | Статус | Зачем |
|
||||
| --- | --- | --- |
|
||||
| [41. Мнения и «Связи»](41-opinions.md) | 🔄 | −100…100, семья-старт, вкладка карточки |
|
||||
| [41. Мнения и «Связи»](41-opinions.md) | ✅ | −100…100, семья-старт, вкладка карточки |
|
||||
|
||||
**Этап B — разговоры.** Кружок, язык, шёпот.
|
||||
|
||||
| Фаза | Статус | Зачем |
|
||||
| --- | --- | --- |
|
||||
| [42. Темы и кружки](42-talk-circles.md) | ⬜ | `TopicDef`, 2–4 человека, учительская, телефон, вместе |
|
||||
| [42. Темы и кружки](42-talk-circles.md) | 🔄 | `TopicDef`, 2–4 человека, учительская, телефон, вместе |
|
||||
| [43. Шёпот и учитель](43-whisper.md) | ⬜ | Шёпот на уроке, шанс оборвать, разговор после звонка |
|
||||
|
||||
42 стоит на 41; 43 — на 42.
|
||||
|
||||
+45
-7
@@ -174,7 +174,8 @@ Body:
|
||||
"map": null,
|
||||
"countryId": "Russia",
|
||||
"nativeLanguage": null,
|
||||
"seed": null
|
||||
"seed": null,
|
||||
"portraitSettings": null
|
||||
}
|
||||
```
|
||||
|
||||
@@ -188,6 +189,10 @@ the same catalog. Omit `map` (or send `null`) to use that pack set's default lay
|
||||
one from the school seed. An id that is not in the country returns `400` `unknown-native-language`.
|
||||
`seed` is an optional integer. Send it to reproduce a known school; omit it (or send `null`) and
|
||||
the server rolls one. Existing saves keep the seed already stored in the people file.
|
||||
`portraitSettings` is the SwarmUI preset file for **this** school (same shape as
|
||||
`GET /api/settings/swarmui`). Omit it (or send `null`) to copy the server template at create.
|
||||
Invalid presets return `400` `invalid-portrait-settings`. Generation later uses this copy, not
|
||||
the global file, so a guest watching the school draws with the same model.
|
||||
|
||||
| Status | Meaning |
|
||||
| --- | --- |
|
||||
@@ -201,6 +206,7 @@ the server rolls one. Existing saves keep the seed already stored in the people
|
||||
| `400` `invalid-catalog` | The selected packs could not be loaded. |
|
||||
| `400` `unknown-country` | `countryId` is not a placeable `CountryDef` in those packs. |
|
||||
| `400` `unknown-native-language` | `nativeLanguage` is not in that country's `nativeLanguages`. |
|
||||
| `400` `invalid-portrait-settings` | `portraitSettings` failed validation (empty presets, bad age rule, out of range). |
|
||||
| `409` `school-limit-reached` | This player already owns `maxSchools` schools. |
|
||||
| `409` `server-full` | The process already runs `maxSchoolsTotal` schools. |
|
||||
|
||||
@@ -320,6 +326,10 @@ person. `customPortraitPrompt` is the last saved user prompt for the custom vari
|
||||
They are filled on the HTTP thread after the worker returns the card; generation does
|
||||
not happen on this request.
|
||||
|
||||
`connections` is the **Связи** tab: family links with `opinion` / `opinionLabel` from catalog bands,
|
||||
plus `friends`, `enemies` and `others` (every non-family non-zero pair for this person only).
|
||||
The client does not compute thresholds. There is no school-wide opinions endpoint.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "f0.c0",
|
||||
@@ -372,7 +382,34 @@ not happen on this request.
|
||||
"hasAvatar": false,
|
||||
"hasCustom": false,
|
||||
"hasFullBody": false,
|
||||
"customPortraitPrompt": null
|
||||
"customPortraitPrompt": null,
|
||||
"connections": {
|
||||
"family": {
|
||||
"parents": [
|
||||
{
|
||||
"id": "f0.p1",
|
||||
"fullName": "Иванова Ольга Михайловна",
|
||||
"female": true,
|
||||
"opinion": 75,
|
||||
"opinionLabel": "близкие друзья"
|
||||
}
|
||||
],
|
||||
"children": [],
|
||||
"siblings": [
|
||||
{
|
||||
"id": "f0.c1",
|
||||
"fullName": "Иванов Кирилл Петрович",
|
||||
"female": false,
|
||||
"opinion": 45,
|
||||
"opinionLabel": "друзья"
|
||||
}
|
||||
],
|
||||
"partners": []
|
||||
},
|
||||
"friends": [],
|
||||
"enemies": [],
|
||||
"others": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -451,14 +488,15 @@ so the client can disable generate buttons and show reachability without trying
|
||||
|
||||
### `GET /api/settings/swarmui`
|
||||
|
||||
Returns the editable SwarmUI preset file (`swarmui.json`): named presets (model, steps, sampler,
|
||||
LoRA lists, per-kind sizes/prompts), `activePresetId` and `ageRules` mapping age bands to presets.
|
||||
Portrait generation resolves the preset from the person's age before building the prompt.
|
||||
Returns the **default** SwarmUI preset template (`swarmui.json`): named presets (model, steps,
|
||||
sampler, LoRA lists, per-kind sizes/prompts), `activePresetId` and `ageRules`. A new school copies
|
||||
this into its save as `portraitSettings`. Living schools generate from that copy, not from this
|
||||
file.
|
||||
|
||||
### `PUT /api/settings/swarmui`
|
||||
|
||||
Replaces the preset file after validation. Invalid preset ids, age rules or numeric ranges return
|
||||
`400` `invalid-body`. Changes apply immediately to new portrait generations.
|
||||
Replaces the default template after validation. Invalid preset ids, age rules or numeric ranges
|
||||
return `400` `invalid-body`. Already-created schools keep the copy they were created with.
|
||||
|
||||
### `GET /api/settings/swarmui/discovery`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user