2026-08-20 16:17:17 +03:00
2026-08-18 07:25:57 +00:00

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 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 (global.json pins the 10.0.1xx band)
  • Node.js 22.12 or newer
  • Optional: the Aspire CLI (dotnet tool install -g aspire.cli) if you prefer aspire run

Run everything

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 Vites 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

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.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.
npm --prefix src/HSchool.Client test

npm test runs tsc -b then Vitest (codec, calendar formatting, RU/EN dictionaries). A duplicate key in strings.ts fails typecheck without npm run build. test:watch is Vitest only.

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 MonFri; 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.

S
Description
No description provided
Readme MIT
2.7 MiB
Languages
C# 80.4%
TypeScript 17.9%
CSS 1.1%
PowerShell 0.3%
Shell 0.2%