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. The school itself is still empty.
Stack
| Layer | Choice |
|---|---|
| Server | .NET 10, ASP.NET Core |
| Simulation | Arch ECS, fixed 20 Hz tick |
| Transport | REST for the menu, raw WebSocket + binary protocol for the clock |
| Client | TypeScript, Vite, plain DOM (PixiJS 8 is installed for the game view that comes next) |
| Orchestration | .NET Aspire 13 |
| Tests | xUnit v3, Vitest, Aspire.Hosting.Testing |
Prerequisites
- .NET SDK 10 (
global.jsonpins the 10.0.1xx band) - Node.js 22.12 or newer
- Optional: the Aspire CLI (
dotnet tool install -g aspire.cli) if you preferaspire run
Run everything
dotnet run --project src/HSchool.AppHost
On Windows run-aspire.cmd does the same and can be double-clicked; it checks that the .NET SDK
and Node are on PATH first and passes any arguments through
(run-aspire.cmd --launch-profile http).
The Aspire dashboard opens with two resources: server (ASP.NET Core) and client (Vite dev
server). Aspire assigns the client a random port on every run, so take its URL from the dashboard
rather than guessing.
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.
- Create a school — type a name or roll a random one, pick a start date (3 April 2012, 06:00 by default). At six schools 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 ×3 ×4 speed buttons, plus a way back to the menu.
Time runs at 5 game minutes 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 live in server memory: restarting the server clears them.
Run the pieces separately
dotnet run --project src/HSchool.Server
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
dotnet test
tests/HSchool.Protocol.Tests— wire-format round-trips and byte layouts.tests/HSchool.Simulation.Tests— the game clock 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.
npm --prefix src/HSchool.Client test
Vitest covers the client codec and the calendar formatting.
Layout
src/
HSchool.Protocol/ binary wire format (shared contract with the client)
HSchool.Simulation/ schools, the game clock, the Arch ECS world
HSchool.Server/ ASP.NET Core host, menu API, WebSocket endpoint, game loop
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 |
6 | how many schools may exist at once |
GameMinutesPerRealSecond |
5 | game minutes per real second at ×1 |
DefaultStartDate |
2012-04-03T06:00:00 |
prefilled start of a new school |
What is deliberately missing
No persistence, no authentication, and nothing inside a school yet — every school owns an empty
ECS world waiting for its first entities. Each of these has a seam described in
docs/architecture.md.