This is a one-project Godot 4.6 spike proving a small online-world topology with:
- One client role.
- One master server role that stays online and owns world lifecycle.
- One world server role, started on demand once per active world key.
- Master-owned SQLite persistence (MVP): guests, name-only login, and saved world/position. See VirtuCade Database MVP.
- Chat hosted by the master server on the same
MasterNetsocket, showing display names for guests and logged-in players. - Local-first networking constants for client-advertised host/ports.
- Per-launch world registration tokens generated by master.
- A shared build/version gate so mismatched clients and world servers are rejected before login, route loading, or world travel.
- Native Godot high-level multiplayer over
WebSocketMultiplayerPeer. - Separate client multiplayer contexts for master/control/chat and the active world.
- A persistent master/chat connection while the active world connection is replaced.
- Server-authority player spawn/despawn with client-authority movement.
- Four visibly distinct worlds:
hubleft_worldright_worldtop_world
Portal topology:
hub -> left_worldhub -> right_worldhub -> top_worldleft_world -> hubleft_world -> right_worldleft_world -> top_worldright_world -> hubright_world -> left_worldright_world -> top_worldtop_world -> hubtop_world -> left_worldtop_world -> right_world
For the full walkthrough, read Godot Multi-Server Architecture Guide.
addons/godot-sqlite/: vendored2shady4u/godot-sqliteGDExtension (master-only SQLite).shared/main/: feature-tag bootstrap scene.client/: playable client root and UI, includingclient/login/(bottom-right login widget).server/master/: master server scene, script, and world process manager. HostsMasterNet.server/master/db/: master-ownedDatabaseServiceand repositories (the only SQLite access boundary).server/world/: world server scene and script.server/worlds/: playable world source folders. These are included in server exports and can be packed separately for client download.addons/pack_rat/: PackRat runtime DLC/PCK loader subtree used by clients before world travel.shared/net/: endpoint scripts and minimal derived network config.shared/world/: reusable world base scene and portal logic.shared/player/: replicated player scene and script.tools/: export and smoke-test scripts.docs/: architecture, research, and audit notes.editor/run_instance_grid.gd: editor-only autoload that tiles visible Run Instances windows for manual debugging. Export scripts remove this autoload during export so/editor/*never ships in runtime builds.
Main documentation:
- Godot Multi-Server Architecture Guide: canonical current architecture.
- VirtuCade Database MVP: the implemented SQLite persistence, guest/login system, and player save/resume.
- VirtuCade Experience Architecture Research: Roblox-like world/experience architecture research for the custom Godot path.
- VirtuCade Replication, Lifecycle, And Session Roadmap: future roadmap for replication scaling, explicit world lifecycle states, and authenticated reconnect/session design.
- VirtuCade Database Handling Spike: master-owned persistence, SQLite, auth transport, and world save/load research.
- VirtuCade Custom Godot, SQLite, And PocketBase Decision Challenge: custom infrastructure decision spike.
- Godot Tiny MMO Comparison Research: Tiny MMO comparison and lessons.
- Godot Resource Database Wrapper Spike: Resource-file persistence challenge spike.
Historical research, including the Nakama branch notes, remains under docs/ but is not the canonical current architecture.
PackRat is tracked as a Git subtree at addons/pack_rat/. The subtree source is
the PackRat repository's addon branch, not main. That branch contains only
the contents that should live directly under res://addons/pack_rat/; pulling
from PackRat main would nest the full project inside this addon folder.
Update PackRat with:
git subtree pull --prefix=addons/pack_rat https://github.com/Shilo/pack-rat.git addon --squash -m "chore: update PackRat subtree"Do not edit addons/pack_rat/ directly in this project unless the change is an
urgent local integration fix. Prefer changing Shilo/pack-rat, let its workflow
sync the addon branch, then pull the subtree here. The current subtree was
imported from PackRat addon commit 505a656, which was synced from PackRat
main commit 37e0f54 and tag v1.0.2.
The exported client uses the default client main scene. The exported server uses one server feature tag and command-line user args:
serverordedicated_serverwith no user args: startsres://server/master/master.tscnserverordedicated_serverwith a world key after--: startsres://server/world/world.tscn- no server feature tag: starts
res://client/client.tscn
The main scene is:
res://shared/main/main.tscn
For smoke tests and CI with the editor binary, launch the master and client scenes directly with Godot's built-in --scene option. The master starts world scenes itself. Exported smoke runs one standalone server executable; that server creates additional instances of itself for worlds.
The runtime compatibility version is the Godot project setting:
application/config/version="0.1"
Client, master, and world server processes all read that same setting with
ProjectSettings.get_setting("application/config/version"). The login panel
shows the value as v<version> so testers can see which client build is
running.
On startup:
- Client connects to the master.
- Client calls
request_routes(<application/config/version>). - Master rejects the route request if the version differs.
- Web clients show a "Game Updated" prompt with a reload button that appends
?v=<server_version>for cache busting. - World servers also include
application/config/versionwhen registering with the master, so a stale world process cannot join a newer cluster.
Web exports are patched after Godot finishes exporting so index.html,
index.js, index.wasm, and index.pck are requested with the same build
query string. That keeps the GitHub Pages/browser cache behavior explicit
without adding service workers or custom CDN headers.
This is a compatibility gate, not real authentication. Public VirtuCade builds still need authenticated sessions before account/resume security is production ready.
World selection still uses a bare positional key after Godot's --. Master-owned launches append a private launch token after that key so the world can register with master.
Examples:
& $godot --headless --path . --scene res://server/world/world.tscn -- hub
& $godot --headless --path . --scene res://server/world/world.tscn -- left_world
& $godot --headless --path . --scene res://server/world/world.tscn -- right_world
& $godot --headless --path . --scene res://server/world/world.tscn -- top_worldThese direct commands are useful for isolated world-scene debugging, but they do not register with master because they do not have a master-issued launch token. A world key is required. If the key is missing or unknown, startup fails clearly.
Use the local Godot binary:
$godot = "C:\Programming_Files\Godot\Godot_v4.6.3-stable_win64.exe\Godot_v4.6.3-stable_win64.exe"Start the master in one terminal:
& $godot --headless --path . --scene res://server/master/master.tscnLaunch a manual client:
& $godot --path . --scene res://client/client.tscnManual client mode requires only the master at startup. The master starts the initial world when the client asks for routes, then starts transfer targets on demand.
In editor/manual runs, PackRat uses local editor-exported packs automatically.
This keeps local testing fresh and fast while still exercising the same
cache/mount path used by exported clients. The client prints
WORLD_PACK_EDITOR_EXPORT, WORLD_PACK_PROGRESS, and WORLD_PACK_READY while
it builds or reuses those editor packs.
To force the HTTP/static download path locally instead, build and serve world packs, start the master from a shell with the matching pack URL/path, then pass the client arg:
powershell -ExecutionPolicy Bypass -File tools\export_world_packs.ps1
powershell -ExecutionPolicy Bypass -File tools\start_world_pack_server.ps1
$env:MULTI_SERVER_WORLD_PACK_BASE_URL="http://127.0.0.1:19100/world_packs"
$env:MULTI_SERVER_WORLD_PACK_DIR=(Resolve-Path builds\world_packs)
& $godot --path . --scene res://client/client.tscn -- force_packrat_world_packsThat forced path should print WORLD_PACK_START, WORLD_PACK_PROGRESS, and
WORLD_PACK_READY. If the local static server is not running, the client is
waiting on a dead http://127.0.0.1:19100/world_packs/... URL and will print
WORLD_PACK_FAILED when the PackRat request times out/fails.
Editor mode uses the World Pack - <world_key> export preset for each world.
Those presets are the universal pack presets: they enable both desktop
(s3tc/bptc) and mobile (etc2/astc) texture variants so the same exported
PCK can be used by Windows, Web, macOS, Linux, iOS, and Android clients.
PackRat then copies the generated pack through its normal cache/mount path and
simulates one second of local load progress for uncached packs.
Use Godot's editor launcher when you want visible local clients and visible local servers.
Open:
Debug > Customize Run Instances...
Recommended setup for two visible clients:
- Main editor run: visible client with no launch arguments.
- Extra instance 1: visible client with no launch arguments.
- Extra instance 2: master using the
serverfeature tag and no user args.
Stop the previous run before starting another one so old processes do not keep ports 19080 through 19084 bound. World instances are spawned by the master and inherit the master's display mode: visible masters spawn visible worlds, while headless masters spawn headless worlds. Empty worlds should shut down automatically after they are empty.
Editor/headless smoke:
powershell -ExecutionPolicy Bypass -File tools\run_smoke.ps1Two simultaneous editor/headless clients:
powershell -ExecutionPolicy Bypass -File tools\run_smoke.ps1 -ClientCount 2PackRat world-pack smoke:
powershell -ExecutionPolicy Bypass -File tools\run_smoke.ps1 -UsePackRatWorldPacks -TimeoutSeconds 90This exports one temporary local PCK per server/worlds/<world_key>/ folder using the
matching World Pack - <world_key> export preset into
.logs/smoke/pack_server/world_packs/, serves those packs over
http://127.0.0.1:19100/, and forces the editor client to download and mount them through PackRat before
loading each world scene. It is the current automated proof that world travel
works without relying on scenes already bundled into the client.
After running tools\export_all.ps1, exported PackRat smoke can run the same
download path through the exported client and server executables:
powershell -ExecutionPolicy Bypass -File tools\run_smoke.ps1 -UseExported -UsePackRatWorldPacks -TimeoutSeconds 120SQLite persistence smoke can also run through the exported server/client. This is the proof that the server export still has the SQLite GDExtension packaged while the client export remains free of server-only SQLite sidecars:
powershell -ExecutionPolicy Bypass -File tools\run_db_test.ps1 -UseExported -TimeoutSeconds 180PackRat editor-export smoke:
powershell -ExecutionPolicy Bypass -File tools\run_smoke.ps1 -UsePackRatEditorExports -TimeoutSeconds 120This does not start the local HTTP pack server. Instead, the client sets
PackRatOptions.editor_pack_export_preset to World Pack - <world_key> and
PackRatOptions.editor_simulated_local_load_seconds to 1.0, so PackRat builds
fresh editor packs only when the watched world resources or export presets
change. This is the normal editor behavior; the smoke requires
WORLD_PACK_EDITOR_EXPORT and WORLD_PACK_READY.
Version gate smoke:
powershell -ExecutionPolicy Bypass -File tools\run_version_gate_smoke.ps1This temporarily launches the master with one project version and the client
with another. It passes only if the client is rejected before any world process
starts, then restores project.godot.
Project version unit smoke:
powershell -ExecutionPolicy Bypass -File tools\run_project_version_test.ps1This validates strict MAJOR.MINOR parsing, minor rollover, and the local
PowerShell compatibility wrapper. It backs up and restores project.godot.
PackRat version/cache smoke:
powershell -ExecutionPolicy Bypass -File tools\run_packrat_version_cache_smoke.ps1This exports universal world packs once, downloads them at one app version,
bumps only application/config/version, then confirms the next run keeps using
PackRat cache hits for the unchanged pack metadata.
Successful full editor smoke logs include:
MASTER_READYMASTER_WORLD_STARTED key=hubMASTER_WORLD_REGISTERED key=hubMASTER_WORLD_STOP_REQUESTED key=hub reason=idleMASTER_WORLD_STOPPED key=hubMASTER_WORLD_STARTED key=left_worldMASTER_WORLD_REGISTERED key=left_worldMASTER_WORLD_STOP_REQUESTED key=left_world reason=idleMASTER_WORLD_STOPPED key=left_worldMASTER_WORLD_STARTED key=right_worldMASTER_WORLD_REGISTERED key=right_worldMASTER_WORLD_STOP_REQUESTED key=right_world reason=idleMASTER_WORLD_STOPPED key=right_worldMASTER_WORLD_STARTED key=top_worldMASTER_WORLD_REGISTERED key=top_worldMASTER_WORLD_STOP_REQUESTED key=top_world reason=idleMASTER_WORLD_STOPPED key=top_worldSMOKE_PROCESS_GONE hub_after_master_killSMOKE_PASS
PackRat smoke also includes client-side WORLD_PACK_READY lines. Editor-export
PackRat smoke also includes WORLD_PACK_EDITOR_EXPORT.
Logs are written under .logs/ and ignored by git.
Web export smoke:
powershell -ExecutionPolicy Bypass -File tools\run_web_smoke.ps1This exports the Windows client/server, exports the Web client, builds world
PCKs, verifies the exported PCK file tables, starts a local static host for
builds/web/, starts the exported master server, and drives the exported Web
client through Edge/Playwright. It proves the browser client downloads
world_packs/*.pck before joining worlds, then reuses PackRat cache on repeated
world visits.
If artifacts are already exported and verified, run only the Web smoke:
powershell -ExecutionPolicy Bypass -File tools\run_web_smoke.ps1 -SkipExportInstall Godot export templates for 4.6.3.stable first if needed.
Export the client and standalone server artifacts:
powershell -ExecutionPolicy Bypass -File tools\export_all.ps1Debug exports are the default because the smoke tests are diagnostic. To build release-shaped artifacts:
powershell -ExecutionPolicy Bypass -File tools\export_all.ps1 -ReleaseInspect or update the project version:
python tools/project_version.py --print
python tools/project_version.py --set 1.4
python tools/project_version.py --bump-minorThe version format is MAJOR.MINOR; minor rolls from 9 to the next major
(0.9 -> 1.0). Local exports do not bump the version. They use the committed
application/config/version.
Local PowerShell export outputs:
builds/client/client.exebuilds/client/client.pckbuilds/server/server.exebuilds/web/index.htmlbuilds/web/index.jsbuilds/web/index.pckbuilds/web/index.wasmbuilds/world_packs/*.pckas the universal source-of-truth world packsbuilds/web/world_packs/*.pckas byte-for-byte mirrored copies for Web/static hosting
After exporting, verify that client artifacts do not bundle server/world scenes and that each platform world pack contains the expected isolated world scene:
python tools\verify_export_artifacts.pyThe GitHub release workflow verifies Linux/Web release artifacts with:
python tools/verify_export_artifacts.py --server-binary server/server.x86_64The deployed Web site also includes a generated manifest for release auditing:
https://shilo.github.io/multi-server-test/deployment_manifest.json
That manifest lists deployed Web files and world PCK files with file sizes, SHA-256 fingerprints, the project version, the release commit, the workflow trigger commit, and the workflow run id. GitHub Pages does not provide an FTP-style file browser for Actions deployments, so this manifest is the lightweight way to check exactly what was published.
World packs exported through Godot's --export-pack are not literal raw copies
of only server/worlds/<world_key>/. They include the world scene remap, the
converted .godot/exported/... scene, and small Godot-generated metadata such
as project.binary, UID cache, global script class cache, and icon.svg.
The verification tools treat only those generated entries as allowed and fail if
a world pack contains editor files, client files, another world folder, or any
other unexpected source directory.
The server executable contains the master server, world server, and all discovered world scenes. Starting it with no user args runs the master. The master starts one additional process per active world key by creating another instance of the same executable and passing the world key plus a private launch token. The builds/world_packs/*.pck files are the universal client-downloadable DLC artifacts. builds/web/world_packs/*.pck is only a hosted mirror of those same files so the Web export can serve them beside index.html.
For CDN/static hosting, deploy the contents of builds/web/ as the Web site.
Keep builds/web/world_packs/*.pck beside index.html under the
world_packs/ path, or upload that folder to a dedicated static file host. The
master/VPS should advertise that same public pack base:
MULTI_SERVER_WORLD_PACK_BASE_URL=https://<owner>.github.io/<repo>/world_packs
MULTI_SERVER_WORLD_PACK_DIR=<server filesystem path to the exact hosted Web world_packs mirror>
The master advertises stable pack URLs without a version query. PackRat appends
?v=<application/config/version> to outbound HTTP requests when the URL does
not already include v. PackRat still decides whether to reuse or redownload a
world pack from the server-provided pack_modified_time and pack_size, so
app version bumps do not redownload unchanged packs.
Current GitHub Pages deployment is handled by the manual GitHub Actions workflow.
The repository Pages setting should be Build and deployment -> Source:
GitHub Actions. The workflow exports the Web client, exports one universal
world-pack set, mirrors those PCKs into builds/web/world_packs/, verifies
that runtime builds do not include the wrong folders, uploads builds/web/ as
a Pages artifact, and deploys that artifact with actions/deploy-pages. The
deployed page is:
https://shilo.github.io/multi-server-test/
Do not use Deploy from a branch or a generated gh-pages branch for this
project. Branch deployment is useful for simple static files, but this project
must atomically build and publish the Web client, Web PCK files, Linux server
artifact, version bump, and release tag from one manual release workflow.
Generated Web/PCK binaries should stay out of git history.
GitHub Pages does not provide an FTP-style file browser for Actions deployments. Use these release records instead:
- Git history and tags for source/version history, for example
v0.6. - The workflow run artifacts for generated release outputs.
- The live deployment manifest for published Web/PCK files:
https://shilo.github.io/multi-server-test/deployment_manifest.json
For this GitHub Pages test, gameplay still runs locally. Start the master after exporting/deploying so it reads metadata from the same Web PCK files that GitHub Pages serves:
$env:MULTI_SERVER_WORLD_PACK_BASE_URL="https://shilo.github.io/multi-server-test/world_packs"
$env:MULTI_SERVER_WORLD_PACK_DIR=(Resolve-Path builds\web\world_packs)
& $godot --headless --path . --scene res://server/master/master.tscnFor this test, the Web client still points at 127.0.0.1 / ws by default, so
the GitHub Pages browser client connects to the local gameplay server while
downloading Web client/PCK files from GitHub Pages. Public server testing can
override that without rebuilding by appending
?master_url=wss://<host>/&world_url_template=wss://<host>/{world_key} to the
page URL.
For setting up a fresh DigitalOcean/Ubuntu VPS for GitHub Actions deploys, see DigitalOcean VPS Setup.
GitHub Actions uses manual workflow dispatch only. One run sets an exact
MAJOR.MINOR version or bumps the minor version once, creates a local release
commit for the visible project.godot change, exports Linux server and Web
artifacts from that release commit, verifies them, runs the exported Web smoke,
pushes the release commit, uploads the Linux server artifact plus world packs,
deploys the Web client and all Web world packs to GitHub Pages, verifies the
live hosted bytes against the deployment manifest, stages the new Linux server
and world-pack mirror on the configured VPS, stops the VPS server, swaps in the
new files, starts the VPS server, then tags the release commit. If a release tag
already exists, it must point at the current commit or the workflow fails before
publishing. Releases are intentionally restricted to the main branch.
VPS deploy uses these GitHub Actions repository secrets:
VIRTUCADE_HOST: VPS public host or IP.VIRTUCADE_USER: restricted deploy user, currentlygithub-deploy.VIRTUCADE_SSH_KEY: private SSH key for that deploy user.VIRTUCADE_KNOWN_HOSTS: pinned SSH host key line for the VPS.
Optional reverse-proxy deploy uses these GitHub Actions variables or secrets:
VIRTUCADE_GAME_HOST: public gameplay DNS name, for examplegame.example.com.VIRTUCADE_ACME_EMAIL: optional ACME contact email for Caddy.
The VPS service is virtucade.service. The workflow uploads the full
builds/server/ Linux export folder and builds/world_packs/*.pck to staging
folders first, preserving PCK modified times. Only after staging succeeds does
it stop the service, swap the staged files into /opt/virtucade/server/ and
/opt/virtucade/world_packs/, start the service, and check that it is active.
The github-deploy user should only have write access to /opt/virtucade and
limited passwordless sudo for virtucade.service, caddy.service, Caddy
validation, and installing /etc/caddy/Caddyfile. When VIRTUCADE_GAME_HOST is
set, the workflow also renders a static Caddyfile, validates it on the VPS,
installs it to /etc/caddy/Caddyfile when changed, and reloads caddy.service
after the Godot backend is healthy. The workflow writes
/opt/virtucade/virtucade.env on every deploy so stale public URL settings
cannot survive between releases.
The workflow title shows Release v<version> when an exact version input is
provided. Auto-bump runs are titled Release auto-bump because GitHub computes
workflow run names before steps can read and bump project.godot; the computed
version is still written to logs, the release commit, the tag, and the artifact
name.
Release deploys intentionally publish the Web client and every world pack together. That keeps the client, PackRat PCK metadata, and server artifact on one version without partial-deploy ambiguity. The release tag is created after the hosted Pages verification step, so the tag means the release commit was built, smoked, uploaded as a server artifact, deployed to Pages, and checked against the hosted manifest.
If a release run fails after the workflow has committed the version but before
the final release tag is created, rerun the workflow with the exact failed
version in the version input. Do not use the empty auto-bump path for that
retry, or it will intentionally create the next release version.
The release workflow patches the generated Web shell with
tools/patch_web_cache_bust.py so the project version is applied to Godot's
generated index.js, index.wasm, and index.pck requests. The local
PowerShell export script still performs the same patch for Windows/editor
diagnostics.
For local Web smoke, the script sets the base URL to
http://127.0.0.1:19200/world_packs, matching the temporary static server.
The master reads pack size and modified time from MULTI_SERVER_WORLD_PACK_DIR;
the static host must serve matching bytes. The release workflow also downloads
every live GitHub Pages file listed in deployment_manifest.json after deploy
and verifies its size/SHA-256 value.
GitHub Pages may rewrite Last-Modified headers to the deployment time. The
release workflow treats those mismatches as warnings, then syncs the VPS
/opt/virtucade/world_packs/ mirror mtimes to the actual hosted
Last-Modified headers before restarting the server. That way the master sends
PackRat metadata that matches what clients will see from GitHub Pages.
The server/ and client/ folders are export-bundle ownership labels. The concern ends at what gets bundled into each executable/artifact; runtime clients can still mount a downloaded pack at a res://server/worlds/... path because that path is not user-facing.
The export script uses three executable/Web presets:
Windows Client: no role feature tag.Windows Server: dedicated server export with theserverfeature tag.Web Client: browser client export. It must not includeserver/worlds/*; worlds are downloaded as separate PackRat PCKs.
PackRat editor-export testing uses four additional Windows Desktop PCK presets:
World Pack - hubWorld Pack - left_worldWorld Pack - right_worldWorld Pack - top_world
These same World Pack - <world_key> presets are also the production universal
pack presets. Each one has both texture_format/s3tc_bptc=true and
texture_format/etc2_astc=true, while the project keeps
rendering/textures/vram_compression/import_s3tc_bptc=true and
rendering/textures/vram_compression/import_etc2_astc=true enabled so imported
textures actually generate both variants. Smoke/CI launches master and client
scenes directly when testing from the editor binary. Editor runs use PackRat
editor exports by default. -UsePackRatWorldPacks exercises HTTP downloads
from a temporary local static server under .logs/smoke/pack_server/, while
-UsePackRatEditorExports explicitly asserts the default PackRat editor export
flow. Production and smoke world packs are exported through Godot's
--export-pack CLI once, then mirrored into builds/web/world_packs/ for Web
hosting.
tools\export_world_packs.ps1 fails early when a discovered world folder is
missing the matching export preset for the selected prefix. This keeps new
VirtuCade-style mini-games from silently missing their downloadable pack.
This MVP keeps local bind ports and default advertised client URLs in shared/net/net_config.gd. Playable worlds live at server/worlds/<world_key>/<world_key>.tscn, discovered by folder convention. Ports are derived from the sorted world keys. The master and world server processes communicate on 127.0.0.1; clients dial the advertised public host/scheme resolved from deployment overrides first, then the local defaults.
World registration uses per-launch tokens generated by server/master/world_process_manager.gd, not a shared secret stored in client-exported scripts. Client travel uses two credentials: a refreshable TravelLease while the client prepares assets, then a short-lived one-use join ticket after the lease is redeemed. The master can advertise world routes and pack metadata without starting the world process; the world process starts only after PackRat is finished and the client redeems the TravelLease. Source-world transfer locks are keyed to the exact lease and clear only when that lease fails or the target world confirms the join ticket was consumed.
When a world scene is not bundled, the master attaches flat PackRat metadata to the route endpoint:
pack_url
pack_modified_time
pack_size
The client derives the expected scene path from the world key, calls
PackRat.load_resource_pack(pack_url, PackRatOptions.from_expected_metadata(...)),
then loads res://server/worlds/<world_key>/<world_key>.tscn after the pack is
mounted.
Travel order:
1. Master validates route, portal, or login-resume travel.
2. Master sends a TravelLease and PackRat metadata.
3. Client refreshes the TravelLease while downloading or mounting the PCK.
4. Client redeems the TravelLease after `WORLD_PACK_READY`.
5. Master starts the target world if needed and issues the one-use join ticket.
6. Client connects to the world with that join ticket.
7. Target world confirms admission back to master; master commits active world state and clears the source transfer lock.
TravelLeases are refreshed every few seconds during asset preparation. They have a short soft expiry that refreshes while the client is active and a one-hour hard expiry so abandoned or malicious transfers cannot live forever. If the hard expiry is too close to redeem safely, the client cancels the PackRat request and fails the travel attempt clearly.
Local defaults:
default client host=127.0.0.1 in shared/net/net_config.gd
default client scheme=ws in shared/net/net_config.gd
default world pack base URL=https://shilo.github.io/multi-server-test/world_packs
PackRat request URLs append ?v=<application/config/version> when missing
editor default MULTI_SERVER_WORLD_PACK_DIR=builds/world_packs
exported server default=<server executable directory>/../world_packs
Production/server testing with Caddy should set full public URLs and bind Godot to localhost, then set the world-pack base URL/path to the CDN/static-host location and matching local metadata mirror where the current PCK files live. Web builds can override the same public URLs without rebuilding by using URL query parameters:
?master_url=wss://<public gameplay host>/&world_url_template=wss://<public gameplay host>/{world_key}&world_pack_base_url=<public pack base url>
The query override only changes the client's initial connection targets. The
master still advertises world URLs from its own environment, so the VPS must
also set MULTI_SERVER_PUBLIC_MASTER_URL /
MULTI_SERVER_PUBLIC_WORLD_URL_TEMPLATE correctly or world joins will still
point at the wrong address.
For Caddy reverse-proxy mode, use full public URLs instead of host/port composition:
MULTI_SERVER_BIND_HOST=127.0.0.1
MULTI_SERVER_PUBLIC_MASTER_URL=wss://game.example.com/
MULTI_SERVER_PUBLIC_WORLD_URL_TEMPLATE=wss://game.example.com/{world_key}
This keeps Caddy as the only public 443 listener while master and temporary
world servers remain on deterministic localhost ports. The same override can be
tested from the hosted Web client with:
?master_url=wss://game.example.com/&world_url_template=wss://game.example.com/{world_key}
If the gameplay server should speak wss:// directly, also set:
MULTI_SERVER_TLS_CERT=<absolute path to fullchain .crt>
MULTI_SERVER_TLS_KEY=<absolute path to private .key>
Godot's WebSocketMultiplayerPeer.create_server() supports TLS directly, so a
Hetzner VPS can run the exported master/world servers on their normal ports with
real certificates instead of requiring a protocol-converting proxy.
This prototype assumes master/world server processes run together on one host and use local ports internally.
- Persistence is the MVP described in VirtuCade Database MVP: master-owned SQLite, guests, name-only login, and saved world/position.
- No real auth: login is name-only with no password (anyone can log in as any existing name).
- Name-only sessions are still unauthenticated, but world travel already uses master-owned TravelLease and join-ticket checks.
- No authenticated client identity.
- No inventory/currency/per-experience persistence yet (schema is ready to grow).
- No standalone gateway.
- No standalone chat process.
- PackRat is wired for per-world PCK downloads, with automated HTTP, editor-export, exported Windows, and exported Web smoke coverage.
- No external production supervisor configuration yet.
- No server-side movement validation.
Minimum guardrails already present: master-owned process launch, per-launch world registration tokens, ACK-gated registration, refreshable TravelLeases for asset preparation, short-lived world join tickets, server-side portal/travel validation, start-timeout reaping, heartbeat expiry, player-count plus pending-join idle shutdown, target-world startup after lease redemption, chat length/rate caps, and master-only database access with server-validated position saves. Missing before public testing: authenticated sessions (login is name-only today), replacing command-line launch tokens on shared hosts, systemd/service hardening for the master, final VPS service wiring, and stronger movement validation.