When to use this prompt
Your game server is written in Python with the websockets library or FastAPI, and your browser game connects to it over WebSocket.
If you are not sure this is the right prompt, answer the five questions on the check first: it names the one prompt that fits.
The prompt
Status: Draft. The manifest is checked; the engine steps are not yet proven.
You are preparing an existing multiplayer game so a hosting service (NetCraftGames) can run its dedicated server on a Linux machine.
Rules for you:
- Make ONLY the changes listed below. Do not redesign game logic. Do not add multiplayer to a game that has none. If something below cannot be done without a redesign, stop and tell me instead of improvising.
- Never put a password, key or token in code or in any file. Read secrets from environment variables.
- Run every proof step at the end and show me the real output. If a proof step fails, fix it or tell me exactly why you cannot.
- If a step needs a tool you do not have, say so and stop at that step. Do not invent a substitute and do not pretend the step passed.
Facts about the host (exact):
- Linux x86-64, glibc 2.39 or older for every compiled program in the upload.
- The server starts as an unprivileged user in /game. Writable places: /game/saves and a 256 MB /tmp. Nothing is installed, downloaded or compiled on the host. The start command is a fixed list of arguments, not a shell string.
- The host (Ubuntu 24.04 LTS) provides these programs: sh, bash, dash, python3, node, node24 (Python 3.12, Node.js 24.21.0). There is no Java, .NET, Deno or Bun on the host unless you ship it inside the upload.
- Nothing is installed or downloaded on the host: packages travel inside the upload, built for Linux x64 on the owner's own computer.
- A game server cannot look up other websites by name (DNS is closed to it on purpose), so do not make the server depend on calling an outside address by name.
- The host does not set a PORT variable. The port your server uses by default must be the port written in the manifest.
- The host stops the server with SIGTERM and waits up to the number of seconds in the manifest (between 1 and 300).
- The upload is one .tar.gz of at most 512 MiB with at most 10,000 files, no symlinks and no setuid files.
- The host checks your server from outside with a 3 second timeout per request.
TASK: Make this Python WebSocket game server ready for NetCraftGames. The host runs Python 3.12.
Option A, the websockets library (version 13 or newer):
- Serve with: async with serve(handler, '0.0.0.0', int(os.environ.get('PORT', 8080)), process_request=process_request): await asyncio.Future()
- In process_request(connection, request): for request.path == '/health' return connection.respond(HTTPStatus.OK, 'ok'); for '/join-probe' return a 200 response whose body contains 'joinable' while players can join; return None for the WebSocket request.
Option B, FastAPI on uvicorn: @app.get('/health') and @app.get('/join-probe'), run with uvicorn.run(app, host='0.0.0.0', port=int(os.environ.get('PORT', 8080))).
Accept WebSocket connections at the root path "/" (simplest). If the code must keep another fixed path such as "/ws", that is fine: declare it as "wsPath" in the manifest (the second ncg.json below) and the gateway connects to exactly that path. Do not use a path that changes for each room or each player: the gateway cannot follow it. Players never connect to the path themselves; they use the gateway address from the connection config. With FastAPI that means @app.websocket('/') or one fixed route such as @app.websocket('/ws').
Add R4 (a second tiny server on 127.0.0.1) and R5 (loop.add_signal_handler for SIGTERM and SIGINT).
Packaging: the host has Python 3.12 but none of your libraries, and nothing is installed there.
- List the packages in requirements.txt, one per line, with versions. Use packages that publish Linux wheels; stop and tell me which package has none, because nothing is compiled.
- Do NOT write a start script and do NOT pack anything yourself. The owner runs ncg pack . --python --install which installs the packages as Linux wheels (Python 3.12, manylinux) into a vendor folder inside .ncg/build, puts your code under app/ and writes the start script named server. Then ncg check .ncg/build and fix everything it lists.
Server requirements (all exact):
R1 Listen on 0.0.0.0 (never 127.0.0.1 only, never a random port). Read the port from the PORT environment variable and default to 8080.
R2 Health probe: an HTTP GET /health on TCP port 8080 answers HTTP 200 with the body exactly "ok" within 3 seconds. It must not touch a database or game state. Return 503 if the server loop is stalled or shutting down.
R3 Join probe: an HTTP GET /join-probe on the same port answers 200 with a body that contains "joinable" while a new player could be admitted, otherwise 503 with the body "full" or "stopping". It needs no login and exposes nothing private.
R4 Player count: a second HTTP listener bound to 127.0.0.1 only, port 9090. GET /players answers 200 with JSON {"players": N}, where N is the number of connected players, a whole number from 0 to 10,000, in a body under 4 KB. Do not count a health probe, a join probe or a player who is only in a reconnect grace window.
R5 Shutdown: on SIGTERM (and SIGINT) stop admitting players, tell connected clients, write the save atomically (write a temporary file in the same folder, flush it, then rename it) under /game/saves, close sockets and exit with code 0 within the grace seconds.
R6 Saves and every other file the server writes live under /game/saves only. Read the folder from an environment variable SAVE_DIR and default to /game/saves (use ./saves when running on your own computer).
R7 Log events (join, leave, error), not every tick. The host drops log lines above its rate limit.
Write ncg.json with exactly this content:
----- ncg.json (start) -----
{
"ncg": 1,
"game": {
"name": "My Game"
},
"version": "0.1.0",
"build": {
"dir": ".ncg/build"
},
"manifest": {
"engine": "browser-wss",
"transport": "wss",
"platforms": [
"web"
],
"mode": "dedicated",
"networkingImplemented": true,
"os": "linux",
"arch": "x64",
"command": [
"/game/server"
],
"ports": [
{
"port": 8080,
"protocol": "tcp"
}
],
"profile": "standard",
"savePath": "/game/saves",
"health": {
"type": "http",
"port": 8080,
"path": "/health",
"expected": "ok"
},
"playerProbe": {
"type": "http",
"port": 9090,
"path": "/players"
},
"joinProbe": {
"type": "http",
"port": 8080,
"path": "/join-probe",
"expected": "joinable"
},
"shutdown": {
"signal": "SIGTERM",
"graceSeconds": 30
},
"idleSupported": true
}
}
----- ncg.json (end) -----
If the WebSocket route keeps a path (for example '/ws'), write this ncg.json instead; it differs only in "wsPath":
----- ncg.json (start) -----
{
"ncg": 1,
"game": {
"name": "My Game"
},
"version": "0.1.0",
"build": {
"dir": ".ncg/build"
},
"manifest": {
"engine": "browser-wss",
"transport": "wss",
"platforms": [
"web"
],
"mode": "dedicated",
"networkingImplemented": true,
"os": "linux",
"arch": "x64",
"command": [
"/game/server"
],
"ports": [
{
"port": 8080,
"protocol": "tcp"
}
],
"profile": "standard",
"savePath": "/game/saves",
"health": {
"type": "http",
"port": 8080,
"path": "/health",
"expected": "ok"
},
"playerProbe": {
"type": "http",
"port": 9090,
"path": "/players"
},
"joinProbe": {
"type": "http",
"port": 8080,
"path": "/join-probe",
"expected": "joinable"
},
"shutdown": {
"signal": "SIGTERM",
"graceSeconds": 30
},
"idleSupported": true,
"wsPath": "/ws"
}
}
----- ncg.json (end) -----
About ncg.json (the project file the ncg tool reads; it is never packed into the upload):
- Write it in the project root with exactly the content shown. Change only: "game.name" (the real name of the game, 1 to 100 characters), "manifest.platforms" (the platforms the game's players really use) and, if your server uses another port, the port numbers in "manifest.ports", "manifest.health" and "manifest.joinProbe".
- "build.dir" is ".ncg/build": the folder that "ncg pack . --node --install" (or "--python --install") builds for you from this project: your code under app/, the packages for Linux x64, and a start script named "server" that the manifest starts as "/game/server". Do not write that script yourself.
- Run "ncg pack" again after every change: it rebuilds .ncg/build from scratch and never changes your own files.
Proof (run these and show me the real output):
P1 Start the finished server the way the host will: the exact command from ncg.json, in a Linux shell (WSL or an ubuntu:24.04 container if you are on Windows or Mac), with no environment variables set except PATH, LANG and HOME.
P2 curl -s -w " %{http_code}" http://127.0.0.1:8080/health must print ok and 200.
P3 curl -s http://127.0.0.1:9090/players must print {"players":0}. Connect one real client: it must become {"players":1}.
P4 Send SIGTERM (kill -TERM <pid>). The process must exit with code 0 inside the grace seconds and the save file must exist and parse.
P5 If the ncg tool is installed, run "ncg check" in the project folder and fix everything it lists. If it is not installed, say so; do not write a substitute.After your AI finishes
- Read what it changed. It was told to change only what the prompt lists and to stop and ask when something needs a redesign.
- Run the game with two real players. A prompt cannot prove your game plays well.
- Come back to the check and answer again, or run the local check the prompt ends with when you have the ncg tool.