A small Godot 4 project with an authoritative server tick, a client with prediction/reconciliation, and a built-in tile map editor.
client/: Godot client scenes and scriptsserver/: headless/host server scripts and run scriptsshared/: deterministic shared simulation + network packet typestools/: one-off utility scriptsassets/tilesets/: runtime tileset packages (image + defs + manifest)rulesets/: ship/weapon tuning (base.json+classic/+extreme/)modes/: game-mode config profiles, layered overserver.cfg(see Game Modes)maps/: map JSON, includingmaps/imported/converted from original.lvlfilesdata/: runtime text data (help ticker pages)tests/: smoke and contract suitesdocs/formats/: normative schema docs for the versioned JSON contractsoriginal_content/: not distributed — a local-only scratch directory holding original SubSpace reference material (server.cfg, TEMPLATE.SSS, main.c). Supply your own copy to work on parity; seeplans/subspace-parity.md
Project version is stored in VERSION as:
MAJOR.MINOR.REVISION
Rules:
- REVISION: small changes/fixes
- MINOR: large feature additions
- MAJOR: huge milestones
Bump helper:
powershell -NoProfile -ExecutionPolicy Bypass -File tools/bump_version.ps1 -Part revisionpowershell -NoProfile -ExecutionPolicy Bypass -File tools/bump_version.ps1 -Part minorpowershell -NoProfile -ExecutionPolicy Bypass -File tools/bump_version.ps1 -Part major
Tile behavior is defined in tiles_def.json.
Preferred location (tileset packages):
assets/tilesets/<tileset_name>/tiles_def.json
Legacy location (still supported):
client/graphics/tilesets/<tileset_name>/tiles_def.json
Key properties:
solid: whether the tile should be collidable.layer(tileset packages) /render_layer(legacy): where the tile should draw (bg/mid/fgvsbg/solid/fg).safe_zone: marker used for gameplay rules (non-colliding by default).door: marker for door animation frames.
On load, map tiles are routed into the correct render layer based on render_layer, and collision is derived from the map's solid candidates filtered by solid from tiles_def.json.
Format docs (recommended for learning):
docs/map_format_v1.md(overview)docs/formats/map.schema.md(full schema + checksum rules)docs/formats/tilemap.schema.md(what[x,y,ax,ay]means)docs/formats/tiles_def.schema.md(tile behavior:solid, doors, render layers)
There is a separate runtime tool scene for editing tileset metadata:
tools/tilemap_editor/TilemapEditor.tscn
It can import a PNG, display a zoomable grid, edit per-tile metadata, and save/load tileset packages under assets/tilesets/<tileset_name>/.
Server reads boot configuration from user://server_config.json first; if missing, it falls back to res://server_config.json.
This file is a strict, versioned JSON contract (no silent defaults).
Example:
{
"format": "driftline.server_config",
"schema_version": 2,
"default_map": "res://maps/default.json",
"ruleset": "res://rulesets/base.json"
}See schema docs:
docs/formats/server_config.schema.mddocs/formats/ruleset.schema.md
From VS Code you can run the provided task:
run server (ctrl+c to stop)
Or directly:
server/run_server.ps1server/run_server.cmd
Open the project in the Godot editor and run.
Client settings (audio/UI/keybinds) are persisted to user://settings.json.
This is a strict, versioned JSON contract (no silent fallback for unknown formats/versions).
If you have an older user://settings.json from before this was versioned, it will be rejected; delete it to reset.
See schema docs:
docs/formats/client_settings.schema.md
In-game:
Esc: toggle the in-game menu (non-blocking)- In the menu: "Back to Menu" returns to the connection screen
- In the menu: "Save Bug Report" writes a client-only artifact bundle containing the last ~30s replay buffer and metadata.
- Output:
res://.ci_artifacts/bugreports/...when writable (CI/workspace), otherwiseuser://.ci_artifacts/bugreports/... - A
.zipis created best-effort; the folder is always written. - Maintainer option:
bugreport_cleanup_after_zip=truedeletes the folder after a successful zip (best-effort; on failure the folder is kept).
- Output:
Options menu:
- Audio sliders apply immediately.
- UI toggles include minimap visibility and help ticker enable/disable.
- Keybinds can be rebound from the Options menu and persist via
controls.bindings.
Help / training ticker:
Esc+F6: toggle the help tickerF1: cycle help pages
Defaults follow the original SubSpace 1.34 layout (original_content/controls.txt).
Everything here is rebindable from the Options menu.
Flying and weapons:
| Key | Action |
|---|---|
| Arrow keys | Rotate / thrust forward / thrust reverse |
Shift + thrust |
Afterburner (drains energy) |
Ctrl |
Fire guns |
Tab |
Fire bomb |
Shift+Tab |
Lay mine |
Del |
Toggle multifire (requires the MultiFire prize) |
Abilities. Each must be won as a prize unless the ship's *Status in server.cfg
is 2 — only the Spider starts with stealth, and only the Spider and Shark can
ever cloak:
| Key | Ability |
|---|---|
Home |
Stealth (hides you from radar) |
Shift+Home |
Cloak (hides you on screen; XRadar counters it) |
End |
XRadar |
Shift+End |
AntiWarp |
Items (limited charges, shown on the left HUD edge):
| Key | Item |
|---|---|
Shift+Ctrl |
Repel |
Shift+Del |
Burst |
Ins |
Warp |
Shift+Ins |
Portal (drop a 60 s return beacon; press again to warp back) |
F3 |
Rocket |
F4 |
Brick |
F5 |
Decoy |
F6 |
Thor's Hammer |
Screen and session:
| Key | Action |
|---|---|
F2 |
Cycle the player stat box (off, names, points, sorted, by team, W/L, freq stats) |
F11 |
Toggle spectator mode |
F12 |
Cycle ship type (loses powerups) |
Enter or T |
Open chat |
Ctrl+M |
Skip music track |
Esc opens the SubSpace-style menu. Options on the left, ship selection on the right,
any other key resumes.
| Key | Action | Key | Action | |
|---|---|---|---|---|
Q |
Quit to the connect screen | 1-8 |
Change ship | |
F1 |
Help ticker page | S |
Spectator mode | |
F2 |
Stat box mode | |||
F3 |
Name tags (name+bounty / name / off) | |||
F4 |
Radar on/off | |||
F5 |
Messages on/off | |||
F6 |
Help ticker on/off | |||
F8 |
Engine sounds on/off | |||
C |
Options | |||
R |
Save bug report | |||
PgUp/PgDn |
Stat box size |
A (arena list), B (set banner) and I (ignore macros) are shown dimmed: they exist
in the original menu but the features are not implemented yet.
Type in chat. Unknown ? commands are sent as normal chat, as in SubSpace.
Reference: original_content/old assets/.../HELP/sshelpge41.html.
=NNNN— change frequency (team)?help— list commands?ping,?packetloss,?status— connection and ship diagnostics?flags,?team— who holds flags, who is on your freq?kill— toggle kill messages?lines=N— number of chat lines shown?ignore <name>— toggle ignoring a player/t <msg>team,/priv <name> <msg>private,/arena <msg>arena
Messages expand % macros as they send: %coord %area %selfname %freq %bounty %flags %energy %shield %super %killer %killed %red %redname %redflags %redbounty. Use %% for a literal %.
Open the in-project map editor with F10 from the connection screen. F10 again
inside either editor switches between the map editor and the tileset editor.
Controls (editor):
F1: full hotkey overlay (the status bar stays compact)- Mouse: move cursor (tile under mouse)
Ctrl+Z/Ctrl+Y: undo / redo. A rect or flood fill is a single undo step;Ctrl+Shift+Zalso redoes. New/load/paste replace the document and clear history.B/L/G: rect, line and bucket-fill toolsI: eyedropper (adopt the tile under the cursor)LMBdrag: apply the active toolShift+LMBdrag: rectangle outlineSpace: place tile at cursorRMBorBackspace: erase tile at cursorWASD: move camera (cursor remains mouse-controlled)- Mouse wheel or
+/-: zoom 1/2/3: zoom presetsMMBdrag orSpace+LMBdrag: panTab: cycle layer (bg/solid/fg)Q: open tile palette (click a tile to select;Esccloses)Shift+Q/Shift+E: cycle favorite tilesCtrl+N: new map (choose size presets or custom size)Ctrl+S: save map JSON touser://mapsand copy the JSON to clipboard (holdShiftto also print it)Ctrl+O: load map (shows a picker if there are multiple)Ctrl+Shift+O: load newest map directlyCtrl+T: cycle tileset packages underassets/tilesets/
Tile metadata (editor):
- The right-side Tile Properties panel edits per-atlas tile metadata in the tileset package
tiles_def.jsonwhen available (fallback:client/graphics/tilesets/<tileset>/tiles_meta.json). - Changes apply immediately (collision cache, overlays, and test puck).
T: toggle test puck mode. In test mode: click to shoot; right click resets.
Map sizes in the editor UI are in pixels (multiples of 16). Internally the map is stored in tiles.
server.cfg [Game] Mode selects a profile from res://modes/<mode>.cfg, layered
between res://server.cfg (defaults) and user://server.cfg (local overrides). Each
profile holds only the keys that differ, generated from the original per-arena
SERVER.CFG files that shipped with the SubSpace subgame server.
Available: war chaos king rabbit soccer speed jackpot alpha.
Leave Mode="" for the plain defaults.
Two behaviours are map-driven rather than mode-driven, so client and server agree without extra replication:
- Powerball exists only on maps that contain
goal(orbase) entities. - Flags come from
flagentities; team0means a claim-by-touch turf flag.
King of the Hill is configured by [King] in server.cfg and is off unless a
profile enables it (modes/king.cfg does). Every player starts a round crowned with
ExpireTime on the clock; the clock only runs while alive, killing an uncrowned
player worth at least NonCrownMinimumBounty adds NonCrownAdjustTime, losing more
than DeathCount deaths strips your crown, and CrownRecoverKills crown kills win it
back. Last crown standing takes RewardFactor points via the FlagReward formula.
- Server runs an authoritative fixed tick.
- Client sends input commands; server broadcasts snapshots.
- Core simulation logic lives in
shared/so both sides agree on movement/collisions. - Team/friendliness rendering is derived from replicated
ship.freq. - Client-side color mapping is centralized in
client/team_colors.gd(includes a reserved priority color for objective carrier and radar/minimap helper mapping APIs). - Server spawn locations can come from map
entitieswithtype="spawn". - Wall-bounce sound is triggered from shared collision events (client-side audio).
- Bullet vs wall collision is swept/continuous per tick (segment cast) to prevent tunneling at high speeds.
- Bullet velocity is computed as:
ship_velocity_at_fire_time + (dir * bullet_speed)(applied once at spawn; deterministic). - Sustained high-speed travel is intentionally a tactical commitment (speed-scaled afterburner drain + high-speed handling penalties).
Driftline includes a headless bot client script:
res://client/bot_client.gd
Bots connect to the server as normal clients and generate input locally. They do not bypass authoritative rules:
- Safe zones are enforced by shared action validation. Bots may still press fire in safe zones to trigger the existing “brake” behavior, but offensive output is still rejected.
Bot AI is intentionally not perfect information:
- Targeting uses a perception layer with periodic updates, limited FOV/range, short memory, LOS uncertainty, and brief pursuit of last-known positions.
Bots have deterministic per-bot “personality” constants derived from bot_id / ship_id:
- Reaction timing, aim error baseline, braking style, aggression/chase persistence, and disengage thresholds.
This makes each bot’s style consistent across runs/replays for a given ship_id, while keeping the authoritative simulation deterministic and unchanged.
Driftline includes server-authoritative "greens" (prizes): spawn/despawn/pickup are deterministic on the server and replicated to clients via snapshots.
Config lives in server.cfg:
[Prize]: spawn timing and limits (seconds; converted to ticks on load)[PrizeWeight]: relative probability weights per prize kind
Config precedence is layered, lowest to highest: res://server.cfg, then the
res://modes/<mode>.cfg profile selected by [Game] Mode, then user://server.cfg.
Negative prizes mirror the original: Glue is the Engine Shutdown prize
([Prize] EngineShutdownTime, with a ~40 s severe variant), and a negative
QuickCharge empties the energy bar.
On pickup, the client plays res://client/audio/prize.wav.
No license specified yet. Add one if/when you want to open-source the project.
Driftline uses two testing layers:
- Required dependency-free headless contract tests for all versioned JSON formats.
- Optional future unit/integration tests (GUT/GdUnit4) only if gameplay complexity demands it.
Run contract tests:
godot --headless --quit --path . --script res://tests/run_contract_tests.gdRun smoke (sanity) tests:
godot --headless --quit --path . --script res://tests/run_smoke_tests.gdCI runs both contract tests and smoke tests on push and pull requests.