Getting Started
Prerequisites
- Docker (for Postgres, web apps, docs and the game server)
- .NET 10 SDK (for local development of the web apps, which target
net10.0) - Godot 4.7 .NET edition (for running the client)
- Python 3 (for the contract guard, token minting and the divergence probe)
The game server targets net8.0 and its image builds the C# project and exports
the Godot binary itself, so a host running only Docker needs neither Godot nor
.NET installed. Running the exported binary directly instead needs the .NET 8
runtime.
Run the stack
cp .env.example .env
# edit secrets/passwords
docker compose up -d --build
- Player portal:
http://localhost:8082 - Admin panel:
http://localhost:8081 - Documentation:
http://localhost:8083 - Uptime monitoring:
http://localhost:3001 - PostgreSQL:
localhost:5432
The default admin account is created from:
Seed__AdminEmail=admin@moondog.local
Seed__AdminPassword=ChangeMe!12345
Change these in production.
Run the game server
The game server is behind the game profile so it can be run independently of
the web apps:
docker compose --profile game build game-server
docker compose --profile game up -d game-server
docker logs -f moondog-game-server-1
It needs JWT_SECRET to match the portal's, since the portal signs tokens and
the game server verifies them. If they diverge, an unset JWT_SECRET produces
tokens that are all rejected with no useful error at the point of failure.
See Deployment for environment variables and hosting.
Run the example client
Host and port come from environment variables:
export MOONDOG_HOST=127.0.0.1
export MOONDOG_PORT=7777
godot --path godot_client
A JWT is needed to authenticate — issue one from the portal, or mint one directly:
python3 scripts/mint_token.py --creds /tmp/moondog-creds.txt --out-dir /tmp/moondog-tokens
godot --path godot_client -- --token-file=/tmp/moondog-tokens/nyx.token
The client accepts --token= / --token-file= and --probe= /
--probe-seconds= as command-line arguments. Host and port do not have CLI
flags — they are environment variables only.
See Example Client.
Database behavior
On startup, both web apps run Database.MigrateAsync() and seed:
- a
mainserver status row AdminandPlayerroles- the configured admin user
Local development shortcuts
cd Moondog.Portal && dotnet run
cd Moondog.Admin && dotnet run
Migrations are generated in Moondog.Core/Data/Migrations. To add one:
cd /root/Moondog
dotnet ef migrations add MigrationName \
--project Moondog.Core --startup-project Moondog.Portal
Working on the docs
This site is Docusaurus, in website/, served by the docs compose service:
cd website
npm install
npm run start # local dev server with hot reload
npm run build # production build
Documentation lives in website/docs/. The sidebar is autogenerated from that
directory, so a new page appears without touching sidebars.ts — but it should
carry sidebar_position in its front matter to land in the right place.
Verifying the RPC contract
The RPC surface and shared movement constants are guarded. Run this after any change to a method signature or a simulation constant:
python3 scripts/check_rpc.py --root .
A movement constant changed on one side only does not fail any RPC — it produces rubber-banding or unreachable speeds, which is much harder to notice. That is what the guard exists for.