When to use this prompt
Your server is not built on a supported engine library. It speaks its own TCP or UDP protocol and you are happy that players connect straight to its address and port.
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: Prepare this server (it has its own TCP or UDP protocol) for the NetCraftGames Custom native lane. This lane is configuration and deployment only: players connect straight to the server address and port. There is no gateway, no TLS from us and no origin check.
1. Build for Linux x86-64 (Go: GOOS=linux GOARCH=amd64 CGO_ENABLED=0; Rust: the x86_64-unknown-linux-gnu target; C++, C# and Java: build on ubuntu:24.04 or older so glibc is 2.39 or older).
2. For a TCP game, follow R1 to R7 below. For a UDP-only game, keep the UDP game port on 7777 and instead add a small HTTP side listener on TCP 8080 that answers /health with "ok" and /join-probe with a body containing "joinable", and declare both ports.
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 47777.
R2 Health probe: the port accepts a TCP connection within 3 seconds. An open port alone proves nothing about your game, so R3 matters more.
R3 Join probe: as soon as a client connects, the server speaks first and sends a fixed hello line that ends with a newline, for example "MYGAME-READY v1", within 3 seconds and within the first 4096 bytes. The host looks for the expected text anywhere in what it receives. Use a hello line that only your game sends.
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.
3. Write ncg.json. TCP game with a hello line:
----- ncg.json (start) -----
{
"ncg": 1,
"game": {
"name": "My Game"
},
"version": "0.1.0",
"build": {
"dir": "./build"
},
"manifest": {
"engine": "custom-native",
"transport": "tcp",
"platforms": [
"windows"
],
"mode": "dedicated",
"networkingImplemented": true,
"os": "linux",
"arch": "x64",
"command": [
"/game/server"
],
"ports": [
{
"port": 47777,
"protocol": "tcp"
}
],
"profile": "standard",
"savePath": "/game/saves",
"health": {
"type": "tcp",
"port": 47777
},
"playerProbe": {
"type": "http",
"port": 9090,
"path": "/players"
},
"joinProbe": {
"type": "tcp",
"port": 47777,
"expected": "MYGAME-READY"
},
"shutdown": {
"signal": "SIGTERM",
"graceSeconds": 30
},
"idleSupported": true
}
}
----- ncg.json (end) -----
UDP game with the HTTP side listener:
----- ncg.json (start) -----
{
"ncg": 1,
"game": {
"name": "My Game"
},
"version": "0.1.0",
"build": {
"dir": "./build"
},
"manifest": {
"engine": "custom-native",
"transport": "udp",
"platforms": [
"windows"
],
"mode": "dedicated",
"networkingImplemented": true,
"os": "linux",
"arch": "x64",
"command": [
"/game/server"
],
"ports": [
{
"port": 7777,
"protocol": "udp"
},
{
"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) -----
Put your real hello text in "joinProbe.expected" for the TCP game, and the real program name in "command".
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), "build.dir" (the folder that holds the final Linux server files), "manifest.platforms" (the platforms the game's players really use) and, where a step says so, the program name in "manifest.command".
- Everything inside the build folder becomes /game on the host, so "/game/server" means the file "server" at the top of that folder.
- "ncg pack <folder>" only zips a folder into the upload and checks it. It does not build, bundle or install anything, so every step above that builds something is yours.
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 Read the hello line: bash -c 'exec 3<>/dev/tcp/127.0.0.1/47777; head -n 1 <&3' must print your hello line.
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.