155 lines
6.9 KiB
Markdown
155 lines
6.9 KiB
Markdown
# h-school
|
||
|
||
Base for a school-management game: an authoritative .NET server that owns the saves and their
|
||
in-game calendars, a TypeScript client that renders the menus, and .NET Aspire tying them together
|
||
for local runs and integration tests.
|
||
|
||
What exists today is the shell around a game: a main menu of schools, a creation form, and a
|
||
school screen with a running game clock. People already walk, dress, take lessons and talk.
|
||
|
||
## Stack
|
||
|
||
| Layer | Choice |
|
||
| --- | --- |
|
||
| Server | .NET 10, ASP.NET Core |
|
||
| Simulation | [Arch](https://github.com/genaray/Arch) ECS, fixed 20 Hz tick |
|
||
| Transport | REST for the menu, raw WebSocket + binary protocol for the clock |
|
||
| Client | TypeScript, Vite, plain DOM |
|
||
| Orchestration | .NET Aspire 13 |
|
||
| Tests | xUnit v3, Vitest, `Aspire.Hosting.Testing` |
|
||
|
||
## Prerequisites
|
||
|
||
- [.NET SDK 10](https://dotnet.microsoft.com/download) (`global.json` pins the 10.0.1xx band)
|
||
- [Node.js](https://nodejs.org/) 22.12 or newer
|
||
- Optional: the Aspire CLI (`dotnet tool install -g aspire.cli`) if you prefer `aspire run`
|
||
|
||
## Run everything
|
||
|
||
```bash
|
||
dotnet run --project src/HSchool.AppHost
|
||
```
|
||
|
||
On Windows `run-aspire.ps1` does the same (`run-aspire.cmd` forwards to it and can be
|
||
double-clicked). On Ubuntu `./run-aspire.sh`. Both check that the .NET SDK and Node are on
|
||
PATH first and pass any arguments through (`--launch-profile http`). A second launch with no
|
||
C# changes skips MSBuild; `--rebuild` forces a build.
|
||
|
||
The Aspire dashboard opens with two resources: `server` (ASP.NET Core) and `client` (Vite dev
|
||
server on port **5173**).
|
||
|
||
## Share with friends on Tailscale
|
||
|
||
For a dev session over your tailnet, expose only the Vite client — not the game server (5180),
|
||
Aspire dashboard (15180), or SwarmUI.
|
||
|
||
`run-aspire.ps1` / `run-aspire.sh` run `tailscale serve --bg 5173` before the AppHost when
|
||
`tailscale` is on PATH and connected. If Tailscale is missing, offline, or `serve` fails, the script prints a
|
||
warning and continues with local access only (`http://localhost:5173`). When Serve is up, it
|
||
prints the HTTPS client URL next to the local one; the Aspire dashboard stays on localhost.
|
||
Pass `--no-tailscale` to skip Serve entirely. You can also run the command yourself once per
|
||
boot if you start with `dotnet run --project src/HSchool.AppHost` instead.
|
||
|
||
Friends on the same tailnet open `https://<your-machine>.<tailnet>.ts.net`. The page, REST API
|
||
and game WebSocket all go through Vite’s proxy on one origin; Kestrel stays on localhost.
|
||
|
||
Change `HSchool:AlphaPassword` from the repo default before sharing the link. Optional: set
|
||
`HSCHOOL_TAILSCALE_SERVE=1` in the client process environment if you want Vite HMR through Serve
|
||
(`wss` on port 443); gameplay works without it.
|
||
|
||
Do **not** add port 5180 or the Aspire dashboard to `tailscale serve`.
|
||
|
||
## What you can do
|
||
|
||
- **Main menu** — every school as a card with its name and its current in-game date and time,
|
||
ticking live. Deleting one asks for confirmation first. The footer switches the UI between
|
||
Russian and English; the choice is remembered in the browser.
|
||
- **Create a school** — type a name or roll a random one, pick a start date (3 April 2012, 06:00
|
||
by default). When the player has filled their school slots the create button is disabled and
|
||
says why.
|
||
- **Inside a school** — the date, time and weekday of the game calendar, play/pause and the
|
||
×½ ×1 ×2 ×5 ×10 speed buttons, plus a way back to the menu.
|
||
|
||
Time runs at 1 game minute per real second at ×1. **Every school runs on its own**, whether or
|
||
not you are inside it — the menu cards keep counting. Only the pause button stops a school, and it
|
||
stays paused (the card says so) until you press play again.
|
||
|
||
Schools are written under `saves/` on the server. Restarting the process brings them back.
|
||
|
||
## Run the pieces separately
|
||
|
||
```bash
|
||
dotnet run --project src/HSchool.Server
|
||
```
|
||
|
||
```bash
|
||
npm --prefix src/HSchool.Client run dev
|
||
```
|
||
|
||
Without Aspire the client falls back to `http://localhost:5180` for its `/api` and `/ws` proxy,
|
||
which matches the server's launch profile.
|
||
|
||
## Tests
|
||
|
||
```bash
|
||
dotnet test
|
||
```
|
||
|
||
- `tests/HSchool.Architecture.Tests` — layer bans (Arch, ASP.NET, sockets, wall-clock) for
|
||
Protocol through Simulation. No host.
|
||
- `tests/HSchool.Protocol.Tests` — wire-format round-trips and byte layouts.
|
||
- `tests/HSchool.Content.Tests` — JSONC catalog, inheritance, patches, map connectivity. No host.
|
||
- `tests/HSchool.Simulation.Tests` — the game clock, school load/create, and the school registry, no host involved.
|
||
- `tests/HSchool.AppHost.Tests` — boots the real Aspire graph, drives the menu API and the
|
||
WebSocket clock. Runs headless (`--HSchool:Headless=true`), so no Node install is needed.
|
||
|
||
```bash
|
||
npm --prefix src/HSchool.Client test
|
||
```
|
||
|
||
Vitest covers the client codec, the calendar formatting and the RU/EN dictionaries.
|
||
|
||
## Layout
|
||
|
||
```
|
||
src/
|
||
HSchool.Protocol/ binary wire format (shared contract with the client)
|
||
HSchool.Content/ JSONC defs, patches, map validation
|
||
HSchool.Simulation/ schools, the game clock, the Arch ECS world
|
||
HSchool.Server/ ASP.NET Core host, menu API, WebSocket, workers
|
||
HSchool.Server/mods/ pack folders; `core` is always on
|
||
HSchool.ServiceDefaults/ Aspire telemetry, health checks, resilience
|
||
HSchool.AppHost/ Aspire orchestration
|
||
HSchool.Client/ Vite + TypeScript UI
|
||
tests/
|
||
docs/
|
||
architecture.md how the pieces fit together
|
||
protocol.md the HTTP API and the wire format, byte by byte
|
||
AGENTS.md working agreements for humans and coding agents
|
||
```
|
||
|
||
## Configuration
|
||
|
||
Simulation tunables live under the `Simulation` section of
|
||
`src/HSchool.Server/appsettings.json`:
|
||
|
||
| Key | Default | Meaning |
|
||
| --- | --- | --- |
|
||
| `TickRate` | 20 | fixed simulation steps per second |
|
||
| `MaxSchools` | 2 | how many schools each player may own |
|
||
| `GameMinutesPerRealSecond` | 1 | game minutes per real second at ×1 |
|
||
| `DefaultStartDate` | `2012-03-31T06:00:00` | prefilled start of a new school |
|
||
| `SavesDirectory` | `saves` | per-school JSON files |
|
||
| `ModsDirectory` | `mods` | pack folders; `core` is required |
|
||
| `SaveIntervalSeconds` | 30 | rare clock snapshot; not every tick |
|
||
| `MinSaveIntervalMilliseconds` | 1000 | shortest gap between saves caused by pause or speed |
|
||
| `MonthlyPayrollCap` | 100000 | monthly payroll the player may commit; hires and assignments that would exceed it are rejected |
|
||
| `SchoolWeekDays` | 5 | working days from Monday (5 is Mon–Fri; 6 adds Saturday) |
|
||
| `MaxDecisionsPerTick` | 64 | presence decisions processed per tick; overflow waits |
|
||
|
||
## What is deliberately missing
|
||
|
||
No authentication, no action execution, and the create dialog does not yet pick mods or edit the
|
||
map — every new school gets the vanilla `core` layout. Each of these has a seam described in
|
||
[`docs/architecture.md`](docs/architecture.md).
|