Dedicated server: launch, ENet and export
Headless startup, server role, UDP port, complete C# example and choosing Strip Visuals, Keep and Remove.
What server mode changes
A dedicated server runs game logic and networking without rendering. These are separate choices:
| Action | Effect |
|---|---|
--headless | Selects the headless display driver and Dummy audio. It does not select server game logic by itself. |
-- --server | A user argument interpreted by your code. The example below starts an ENet server. |
An export with the dedicated_server tag | The engine automatically selects headless and Dummy; the example enables server logic without --server. |
Export as dedicated server | Adds the tag and allows unnecessary visual resources to be replaced with placeholders. |
Adding only the dedicated_server tag manually does not strip textures or meshes from the pack. Headless also does not mean zero processing cost: physics, scripts, imports and network updates still perform their work.
First run from a project
You need a C# project, a configured main scene and a working project build. Put both files below alongside your other scripts. Attach DedicatedServerHost.cs to one Entity in the main scene. This example owns the tree MultiplayerPeer; do not attach a second host to the same Multiplayer API.
Replace the paths in this command:
& 'C:\Tools\Reckfell\Reckfell Engine.console.exe' --headless --path 'D:\Games\MyGame' -- --server --port=7777 --bind=127.0.0.1-- separates engine arguments from game arguments. Put --headless before it and --server, --port=... and --bind=... after it. OS.GetCmdlineUserArgs() receives only the latter part.
Expected output: SERVER_READY bind=127.0.0.1 port=7777. The client connects to 127.0.0.1, port 7777. ENet uses UDP. This message confirms local server creation; it does not prove internet reachability or player authentication.
The example listens only on the current computer by default. For a network host, explicitly pass --bind=0.0.0.0 or --bind=*; clients use the actual server address, not 0.0.0.0. The --bind option accepts an IP address or *, not a hostname. Allow the selected UDP port in the firewall. External clients behind NAT may also require router configuration.
Without --server or the dedicated_server tag, the code does not create an ENet server, even under --headless. The same project can therefore run headless checks without unintentionally opening a network port.
Complete startup code
ServerOptions.cs selects the role and validates options. It accepts one --port=<1..65535> and one --bind=...; arguments are case-sensitive. This example deliberately rejects port zero so that the server cannot receive a random port instead of the requested one.
using System;
using System.Globalization;
using System.Linq;
using System.Net;
public sealed record ServerOptions(bool Enabled, int Port, string BindAddress)
{
public static bool TryRead(string[] args, bool dedicatedBuild,
out ServerOptions options, out string error)
{
options = new(dedicatedBuild || args.Contains("--server"), 7777, "127.0.0.1");
error = "";
if (!options.Enabled) return true;
bool portSeen = false, bindSeen = false;
foreach (string arg in args)
{
if (arg.StartsWith("--port=", StringComparison.Ordinal))
{
if (portSeen || !int.TryParse(arg[7..], NumberStyles.None,
CultureInfo.InvariantCulture, out int port) || port is < 1 or > 65535)
{ error = "Use one --port=<1..65535>."; return false; }
portSeen = true;
options = options with { Port = port };
}
else if (arg.StartsWith("--bind=", StringComparison.Ordinal))
{
string address = arg[7..];
if (bindSeen || (address != "*" && !IPAddress.TryParse(address, out _)))
{ error = "Use one --bind=<IP address or *>; hostnames are not accepted."; return false; }
bindSeen = true;
options = options with { BindAddress = address };
}
}
return true;
}
}DedicatedServerHost.cs opens the port, assigns the transport and releases it when the Entity exits the tree. Repeated StopServer() calls are safe. The example sets a 32-client limit; that is an ENet configuration value, not a measured game capacity.
using Reckfell;
public partial class DedicatedServerHost : Entity
{
private ENetMultiplayerPeer? _peer;
public bool Listening { get; private set; }
public override void _Ready()
{
if (!ServerOptions.TryRead(OS.GetCmdlineUserArgs(), OS.HasFeature("dedicated_server"),
out var options, out string error))
{ ReckfellApi.PrintErr(error); GetTree().Quit(2); return; }
if (!options.Enabled) return;
_peer = new ENetMultiplayerPeer();
_peer.SetBindIP(options.BindAddress);
Error result = _peer.CreateServer(options.Port, maxClients: 32);
if (result != Error.Ok)
{
ReckfellApi.PrintErr($"SERVER_FAILED {result} bind={options.BindAddress} port={options.Port}");
_peer.Dispose(); _peer = null;
GetTree().Quit(3); return;
}
Multiplayer.MultiplayerPeer = _peer;
Listening = true;
ReckfellApi.Print($"SERVER_READY bind={options.BindAddress} port={options.Port}");
}
public void StopServer()
{
if (_peer is null) return;
Multiplayer.MultiplayerPeer = null!;
_peer.Close(); _peer.Dispose(); _peer = null;
Listening = false;
}
public override void _ExitTree() => StopServer();
}This starts the transport. Entity replication, RPC, authority checks and game rules are separate: see networked gameplay. An established connection does not make client commands trustworthy.
Export without unnecessary resources
- Ensure the C# project has both
.csprojand.sln. A successful.csprojbuild alone does not prove export readiness: C# export checks the solution file. - Install matching engine-version export templates with C# support for the target platform and architecture. Run the exported executable for deployment; use the editor for import and export.
- Create a separate export preset, such as
Windows Server. In its Resources tab, selectExport as dedicated server. - Set the project root to
Strip Visuals, then override individual files and folders using the table below. Folder settings are inherited until a child has its own mode. - Export the project and copy the whole output directory, including required C# files. Do not arbitrarily copy only the
.exeor a single PCK. - Run the export and check for
SERVER_READY. The tag selects headless drivers and the example server branch automatically;--portand--bindstill belong after--.
| Mode | Result | Use it when |
|---|---|---|
Strip Visuals | Replaces supported resources with placeholders. For example, Texture2D retains dimensions, Mesh retains its AABB, and a material becomes a placeholder. Original pixels and geometry are not retained as usable visual data. | The server does not need that resource data. |
Keep | Exports the resource with its original data. | Height maps, pixels used by game rules, geometry for calculations, or other data actually read by the server. |
Remove | Excludes the file from the pack. References are not repaired automatically. | The server branch never loads the file and required scenes/resources do not reference it. |
Strip Visuals does not universally remove all graphical content: replacement depends on a resource type supporting create_placeholder. A mesh placeholder with an AABB does not retain triangles for your calculations. If collision shapes are stored separately, retain the required shapes; if you build them from a mesh at startup, retain the original mesh with Keep too. Similarly, image analysis requires Keep, not just retained width and height.
Do not make the server branch unconditionally load a client menu, camera or file marked Remove. Use a server main scene or create client Entities only in the client branch. Reckfell text resources use .rdata; levels use .rscene and prefabs use .rprefab.
See feature tags, command line and project export.
Running the export template
A PCK is not an executable. For a manual pack check, use a matching C# export template, not the editor: running the editor does not verify loading exported C# assemblies.
The tested Windows 1.0.0 debug template disables path overrides: --path and --main-pack abort startup. Do not carry these source-project launch arguments into the exported-game command.
For a separate pack, names must match: Server.exe and Server.pck sit beside each other; the console wrapper is Server.console.exe. With a normal export, preserve its generated names and complete output structure. An embedded pack does not require a separate PCK.
Open a terminal in the exported directory and run:
& '.\Server.console.exe' -- --port=7777 --bind=127.0.0.1Neither --headless nor --server is required here when the exported project has dedicated_server. After SERVER_READY, connect a separate client. The server pack itself does not provide a client menu.
If the server does not work
| Symptom | Check |
|---|---|
No window, but no SERVER_READY either | --headless alone does not select the role. Pass -- --server or check the exported project tag. |
Use one --port..., exit code 2 | Use one integer port from 1 to 65535, without whitespace or a duplicate option. |
Use one --bind..., code 2 | Supply one IP address or *, not a hostname. |
SERVER_FAILED, code 3 | Read the error name, address and port on that line. Check UDP port availability and whether the address belongs to a local interface. |
| Local clients connect, external clients do not | The default bind address is 127.0.0.1. Check --bind, the client address, the UDP firewall rule and NAT. |
| Image or mesh data is missing after export | Check inherited Strip Visuals; select Keep for data the server actually reads. |
| Resource loading fails | Check Remove, required references and completeness of the exported directory. |
| C# export reports a missing solution | Restore the correct .sln beside the project; a successful standalone .csproj build does not fix this. |
The example was checked on Windows with installed SDK 1.0.0: headless launch, an ENet loopback connection and a debug export template loading a separate PCK. Texture, mesh and material placeholders, Keep and Remove were checked in the pack. This is not Linux deployment, release-template, external-network or load acceptance.