Add HSchool.Content project for JSONC definitions, catalog, and map validation. Update solution structure to include new content and tests projects. Enhance school management to support mod packs and map instances, ensuring proper loading and validation. Revise documentation to reflect these changes and update tests for new functionality.
ci / server (push) Failing after 3m36s
ci / client (push) Successful in 13s

This commit is contained in:
Leonid Pershin
2026-08-18 14:35:09 +03:00
parent 37c39a3beb
commit 1bc75244e8
55 changed files with 2289 additions and 59 deletions
+14 -9
View File
@@ -14,8 +14,11 @@ there is no UI on the server.
│ │ ├── SchoolWorker ×N │ └──────────────────┘ │
│ │ │ └── School │ │
│ │ │ ├─ Clock│ │
│ │ │ ├─ Catalog (frozen Content) │
│ │ │ ├─ Map │
│ │ │ └─ World│ (Arch ECS, empty for now) │
│ │ ├── SchoolStore │ saves/{id}.json │
│ │ ├── ModContent │ mods/<id>/ │
│ │ └── ClientRegistry │ │
│ └────────────────────────┘ │
│ │ OTLP logs / traces / metrics │
@@ -29,14 +32,15 @@ there is no UI on the server.
| Project | Role |
| --- | --- |
| `src/HSchool.Protocol` | Binary wire format. No dependencies, referenced by everything that talks to the socket. |
| `src/HSchool.Simulation` | Schools, the game clock, the Arch ECS world. No ASP.NET, no sockets — this is what unit tests exercise. |
| `src/HSchool.Server` | ASP.NET Core host: the menu API, the WebSocket endpoint, per-school workers, disk saves. |
| `src/HSchool.Content` | JSONC defs, inheritance, patches, locales, map instance and connectivity. No Arch, no ASP.NET. |
| `src/HSchool.Simulation` | Schools, the game clock, the Arch ECS world. Holds a frozen catalog and map; no HTTP. |
| `src/HSchool.Server` | ASP.NET Core host: the menu API, the WebSocket endpoint, per-school workers, `mods/` and `saves/`. |
| `src/HSchool.ServiceDefaults` | Shared Aspire wiring: OpenTelemetry, health checks, service discovery, resilience. |
| `src/HSchool.AppHost` | Aspire orchestration: which resources run and how they find each other. |
| `src/HSchool.Client` | Vite + TypeScript UI: main menu, creation form, the school screen. |
Dependency direction is one-way: `Protocol ← Server → Simulation`. Nothing in `Simulation` knows
about HTTP, and nothing in `Protocol` knows about schools.
Dependency direction is one-way: `Protocol ← Server → Simulation → Content`. Nothing in
`Simulation` or `Content` knows about HTTP, and nothing in `Protocol` knows about schools.
## Two channels, on purpose
@@ -94,8 +98,9 @@ cannot land between a mouse-down and a click.
Each school is a JSON file under `Simulation:SavesDirectory` (`saves/{id}.json` plus `index.json`
for the next id). The worker writes on create, pause, speed change, shutdown, and on a rare clock
snapshot (`SaveIntervalSeconds`, 30 by default) — never on every tick. The supervisor reloads the
directory at process start. A file that cannot be read is left in place and logged.
snapshot (`SaveIntervalSeconds`, 30 by default) — never on every tick. The file also stores the
mod pack ids and the map layout; the catalog is loaded again from `mods/` on start. A missing
mod folder or a map that no longer validates leaves the file in place and that school unstarted.
## Connection lifetime
@@ -115,6 +120,6 @@ that cannot keep up loses intermediate clock frames instead of stalling a worker
`School.Tick`, and unit-test them against `School` directly — no server needed.
- **More state on the cards**: extend `SchoolState` and the JSON response; the menu reloads from
the server after every change, so nothing else has to know.
- **Defs, map, mods**: the save record is intentionally small so later slices can add layout and
pack ids without a second persistence mechanism. That work lives in
[`phases/03-defs-map.md`](phases/03-defs-map.md).
- **Create editor and the map snapshot**: the catalog and vanilla map exist; the player still
cannot pick mods or see the tree from the server. That work lives in
[`phases/04-create-editor.md`](phases/04-create-editor.md).
+2 -3
View File
@@ -1,8 +1,7 @@
# Нарезка проектов (целевая)
Сейчас в коде: `Protocol ← Server → Simulation`. Этого мало, когда появятся defs, карта и моды.
Класть каталог в Simulation (рядом с Arch) или в Server (рядом с Kestrel) — оба варианта смешают
слои. Ниже — куда что идёт в этом срезе. `architecture.md` правится, когда код так и станет.
Сейчас в коде: `Protocol ← Server → Simulation → Content`. Каталог не в Simulation (рядом с Arch)
и не в Server (рядом с Kestrel) — парсер JSONC и проверка графа не знают, что такое tick.
## Зависимости
+13 -13
View File
@@ -10,19 +10,19 @@
## Задачи
- [ ] Проект `HSchool.Content` (+ тесты): JSONC, defs, локали, граф карты, валидация. Не Arch, не ASP.NET
- [ ] Загрузчик пачки документов (сервер потом подставит папки `mods/`)
- [ ] `core` всегда первый; дальше моды по списку; повтор `defName` / ключа локали — последний победил, warning в лог
- [ ] Резолв ссылок `ThingDef.actions`, слотов RoomDef после чтения всего набора
- [ ] Карта-инстанс: двор (TerritoryDef) всегда есть; здания (BuildingDef), этажи (FloorDef + id), помещения, двусторонние рёбра
- [ ] Валидация: неизвестный def, ребро в никуда, несвязный граф, изолированный узел
- [ ] Пустая комната допустима; должности для панели — из def узла, если они там есть
- [ ] Каталог замораживается на работнике при create/load; сейв хранит id модов и раскладку, не развёрнутый каталог
- [ ] Нет папки мода из сейва — школу не стартовать, файл не удалять, в лог
- [ ] Обновить ориентир в `AGENTS.md` (куда класть defs)
- [ ] Наследование: `parent`, `abstract`, замена полей, запрет цикла и чужого вида, abstract нельзя на карту
- [ ] Патчи из `patches/`: add / replace / remove по JSON Pointer; неизвестный op или нет target — ошибка каталога
- [ ] Тесты Content: фикстуры JSONC, last-wins, parent/abstract, патч add в actions, связность, пустая комната, одна комната без двора — отказ
- [x] Проект `HSchool.Content` (+ тесты): JSONC, defs, локали, граф карты, валидация. Не Arch, не ASP.NET
- [x] Загрузчик пачки документов (сервер потом подставит папки `mods/`)
- [x] `core` всегда первый; дальше моды по списку; повтор `defName` / ключа локали — последний победил, warning в лог
- [x] Резолв ссылок `ThingDef.actions`, слотов RoomDef после чтения всего набора
- [x] Карта-инстанс: двор (TerritoryDef) всегда есть; здания (BuildingDef), этажи (FloorDef + id), помещения, двусторонние рёбра
- [x] Валидация: неизвестный def, ребро в никуда, несвязный граф, изолированный узел
- [x] Пустая комната допустима; должности для панели — из def узла, если они там есть
- [x] Каталог замораживается на работнике при create/load; сейв хранит id модов и раскладку, не развёрнутый каталог
- [x] Нет папки мода из сейва — школу не стартовать, файл не удалять, в лог
- [x] Обновить ориентир в `AGENTS.md` (куда класть defs)
- [x] Наследование: `parent`, `abstract`, замена полей, запрет цикла и чужого вида, abstract нельзя на карту
- [x] Патчи из `patches/`: add / replace / remove по JSON Pointer; неизвестный op или нет target — ошибка каталога
- [x] Тесты Content: фикстуры JSONC, last-wins, parent/abstract, патч add в actions, связность, пустая комната, одна комната без двора — отказ
## Критерий готовности
+1 -1
View File
@@ -14,5 +14,5 @@
| [0. Убрать PixiJS](00-drop-pixi.md) | ✅ | Сцена не планируется |
| [1. Оболочка менеджера](01-manager-shell.md) | ✅ | Панели с секциями среза, пока без данных |
| [2. Работник школы и диск](02-school-worker.md) | ✅ | Поток + World + сейв — основа |
| [3. Каталог def и карта](03-defs-map.md) | | JSONC, core, валидация раскладки |
| [3. Каталог def и карта](03-defs-map.md) | | JSONC, core, валидация раскладки |
| [4. Моды и редактор в create](04-create-editor.md) | ⬜ | Выбор модов, карта в POST, снимок при открытии |