This website contains technical documentation of Digital Extinction (GitHub).

Also see Rust documentation.

Camera Movement

Move your mouse close to a screen edge to move the camera along the map surface (north, south, west, east) or use the arrow keys.

Use mouse wheel to zoom closer or further from the terrain.

Press and hold shift or the mouse wheel and then move your mouse to tilt and/or rotate the camera around its focus point on the terrain.

Minimap

Left Click

  • Moves the camera to the target.

Right Click

  • Sends selected units to the click position.
  • Sets manufacturing delivery location to the click position.

Hotkeys

  • Escape — cancel current action or display menu.

Building and Unit Selection

Left click on any of your units or buildings to select it. Press and hold Ctrl before left clicking on buildings and units to (de)select more entities.

Drag mouse to select all entities inside a rectangle. Press and hold Ctrl extend current selections instead of replacing it.

Double click on a unit to select all visible units of that type. Holding Ctrl adds to the existing selection.

Press Ctrl+A to select all entities.

Press Ctrl+Shift+A to select all visible entities.

Building Construction

You have to select a building to construct by pressing a key, place it on an empty place on the terrain by moving your mouse and then confirm the construction by left clicking the mouse.

Building Keys

  • B — Base
  • P — Power Hub

Unit Manufacturing

  • Select a single building with a factory (for example a base).
  • Clicking "manufacture unit" buttons in the bottom middle panel enqueues the units for manufacturing.

Delivery Location

Right clicking on the terrain sets manufacturing delivery location to the click position.

Commanding Units and Buildings

Right click on the terrain sends selected units to that location. Right click on an enemy building or a unit commands selected units and buildings to attack that entity.

Game Design

This documents summarizes high level design of the game. It does not capture current state but the desired state attainable in reasonable amount of time.

The document is based on conclusions from our discussions on ideas. If you have an idea, consult the discussions first.

Energy

There is only one currency / resource in the game and that is energy.

  • Many kinds of activity (for example unit movement, manufacturing & construction, combat) require energy. These are shut down by priority if there is not enough locally available energy.

  • Energy can be generated with several different kinds of power plants: solar, nuclear, and wind.

  • Energy can be stored in integrated batteries (inside buildings and units) or in dedicated battery farms with higher capacity.

  • Energy is not a global resource but has to be distributed via an electricity grid. The grid consists of Power Hubs transmitting the energy via lasers. The grid might be attacked by an opponent. It has to be constructed and defended.

  • Energy is transmitted from a source by an energy laser to a target by an energy panel. A beam from a laser to a panel is formed along a clear line of sight.

  • Buildings and units might be equipped with 0 or more energy lasers and energy panels.

  • The energy grid operates autonomously: it constructs a semi-optimal transmission network based on priorities, remaining battery capacities, and other criteria.

Buildings

Base

  • Is capable of producing all unit.
  • Has a battery of a very high capacity.
  • Is equipped with several laser cannons for defense.
  • Produces small amount of energy via integrated solar panels.
  • Has several energy lasers.
  • Has several energy panels.

Factory

  • Is capable of producing all unit.
  • Has a battery of medium capacity.

Solar Power Plant

  • Has a single energy laser.

Battery Farm

  • Stores large amount of energy.
  • Has a single energy panel.

Power Hub

  • Has several energy lasers.
  • Has several energy panels.
  • Has no batteries.

Units

Attacker Drone

  • Has an integrated battery of medium capacity.
  • Has a single energy panel.
  • Is equipped with a laser cannon.

Construction Drone

  • Has an integrated battery of small capacity.
  • Has a single energy panel.
  • Can construct any kind of building.

Battery Drone

This drone can be used to deliver energy to areas of scarcity.

  • Has a battery of large capacity.
  • Has a single energy panel.
  • Has a single energy laser.

Map Objects

Every object which can be placed on the game map has an associated object info file. It is an ordinary JSON file containing various information about the object. The file name has suffix .obj.json.

JSON Schema

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/product.schema.json",
  "title": "Object Info",
  "type": "object",
  "description": "Description of a game object.",
  "properties": {
    "footprint": {
      "type": "object",
      "description": "A 2D footprint of the object used for path planning and collision avoidance.",
      "properties": {
        "convex_hull": {
          "type": "array",
          "description": "A convex polygon fully containing the object.",
          "items": {
            "type": "array",
            "description": "2D coordinates in the object space.",
            "items": {
              "type": "number"
            },
            "minItems": 2,
            "maxItems": 2
          },
          "minItems": 3
        }
      },
      "required": [
        "convex_hull"
      ]
    },
    "shape": {
      "type": "object",
      "description": "A tri-mesh collider of the object. The object 3D model is fully contained inside the buffer. Due to performance reasons, the number of triangles should be very small.",
      "properties": {
        "vertices": {
          "type": "array",
          "description": "Array of unique vertices of the tri-mesh.",
          "items": {
            "type": "array",
            "description": "A 3D point in object space.",
            "items": {
              "type": "number"
            },
            "minItems": 3,
            "maxItems": 3
          },
          "minItems": 4,
          "uniqueItems": true
        },
        "indices": {
          "type": "array",
          "description": "Triangle vertex triplets. Each triplets is counter clockwise.",
          "items": {
            "type": "array",
            "description": "An index of `vertices` array.",
            "items": {
              "type": "integer"
            },
            "minItems": 3,
            "maxItems": 3,
            "minimum": 0
          },
          "minItems": 4
        }
      },
      "required": [
        "vertices",
        "indices"
      ]
    },
    "cannon": {
      "type": "object",
      "description": "Properties of a laser cannon.",
      "properties": {
        "muzzle": {
          "type": "array",
          "description": "3D coordinates of the cannon muzzle in object space.",
          "items": {
            "type": "number"
          },
          "minItems": 3,
          "maxItems": 3
        },
        "range": {
          "type": "number",
          "description": "Maximum firing distance of the cannon.",
          "exclusiveMinimum": 0
        },
        "damage": {
          "type": "number",
          "description": "Enemy damage when hit by the gun.",
          "exclusiveMinimum": 0
        },
        "charge_time_sec": {
          "type": "number",
          "description": "How long it takes to fully charge the gun.",
          "exclusiveMinimum": 0
        },
        "discharge_time_sec": {
          "type": "number",
          "description": "How long it takes until the gun is fully discharged if not actively charged.",
          "exclusiveMinimum": 0
        }
      },
      "required": [
        "muzzle",
        "range",
        "damage",
        "charge_time_sec",
        "discharge_time_sec"
      ]
    },
    "flight": {
      "type": "object",
      "description": "Configuration of object flight capabilities. This property is not defined for object with no flight capability.",
      "properties": {
        "min_height": {
          "type": "number",
          "description": "Minimum flight height (vertical distance from the terrain).",
          "exclusiveMinimum": 0
        },
        "max_height": {
          "type": "number",
          "description": "Maximum flight height (vertical distance from the terrain).",
          "exclusiveMinimum": 0
        }
      },
      "required": [
        "min_height",
        "max_height"
      ]
    },
    "factory": {
      "type": "object",
      "description": "Configuration of the manufacturing capability of the object.",
      "properties": {
        "products": {
          "type": "array",
          "description": "List of products which can be manufactured.",
          "items": {
            "type": "string",
            "enum": [
              "Attacker"
            ]
          },
          "minItems": 1
        },
        "position": {
          "type": "array",
          "description": "Position of the assembly line, i.e. where the products are manufactured and spawned. A 2D position in the object space.",
          "items": {
            "type": "number"
          },
          "minItems": 2,
          "maxItems": 2
        }
      },
      "required": [
        "products",
        "position"
      ]
    }
  },
  "required": [
    "footprint",
    "shape"
  ]
}

Multiplayer

The multiplayer functionality comprises a game lobby and in-game communication.

Before a game starts, players are required to log into DE Lobby Server using their accounts. They can browse through existing games and join one, or alternatively, create a new game. This functionality and other functionality is facilitated through a REST API.

When initiating a multiplayer game, all players are interconnected via DE Connector game server. All communication is routed through this server, utilizing a custom real-time networking protocol that operates over UDP.

DE Lobby Server

Multiplayer games are managed and initiated via DE Lobby Server. The lobby server implements a simple HTTP API for user and game management.

The HTTP API is documented with Open API Specification: openapi.yaml.

Configuration

The server is configured via environment variables:

  • DE_DB_URL (required) – SQLite database URL passed to SqliteConnectOptions of sqlx Rust library.

  • DE_JWT_SECRET (required) – A Base64 encoded secret used for signing and validation of JSON Web Tokens (JWT). The secret must have between 12 and 86 characters.

    Make sure to invalidate all JWT by changing the secret after any changes or purges of the database.

  • DE_HTTP_PORT (optional) – HTTP server port number. Defaults to 8080.

  • RUST_LOG (optional) – logging configuration, see env_logger documentation.

DE Connector

DE Connector interconnects players so they can exchange messages in real-time.

Communication between clients and the server relies on a low-level communication protocol, providing reliable and non-reliable means of binary data exchange.

Binary encoded messages are sent via the low-level protocol as packages to the server. Based on the package target (peers), the server either receives and interprets the data, or transmits it to other clients connected to the same game.

Game participants exchange messages through a game server. For efficiency, multiple messages may be grouped into a single package, and the messages are buffered and flushed at the end of each game update (frame). The buffering is independent for unreliable, unordered, and semi-ordered packages, with their buffers being flushed in the respective order.

The main server listens on port 8082, which is designated solely for server control messages such as game initiation requests. Upon the creation of a new game, a unique sub-server, listening on a different port, is started. It is within these sub-servers that clients exchange data among themselves.

Principles

Game networking is designed in such a way that complete game determinism is not required for certain aspects, such as entity movement. It is assumed, however, that the divergence among individual clients is negligible in the short term (on the order of several seconds). This approach simplifies movement simulation but comes at the cost of the need for regular synchronization and higher network bandwidth requirements.

All clients simulate all game activities while receiving updates to correct any possible simulation divergence. Each action has its authoritative owner, corresponding to the client that is locally controlling the entity responsible for the action, whether through a human player or AI. The owner sends a synchronization message, which is respected by the other clients. For instance, an entity damage action (a decrease in health) is owned by the client simulating the entity that causes the damage (for example, by firing a laser cannon).

Whenever possible, messages are sent in unreliable mode. This method of data exchange consumes fewer resources but does not guarantee delivery or ordering. For instance, entity movement synchronization occurs unreliably. This approach is both possible and preferable because the position of each entity, out of possibly thousands, is regularly updated, and messages that reference non-existent entities are disregarded.

Network Protocol

The protocol is based on UDP datagrams and provides means of reliable and non-reliable binary data delivery.

There are two types of datagrams:

  • package – a datagram with a header and user data payload,
  • protocol control – a datagram with a header and system data.

All integers are big-endian encoded.

The structure of each datagram is this:

  1. flags – 1 byte
  2. ID – 3 bytes
  3. payload – 504 bytes or less

Protocol control is signaled by the highest bit of the flags byte (represented by the mask 0b1000_0000).

Package

Each user package has an ID, encoded within the last three bytes of the datagram header. These IDs increment until they reach the maximum value that can be encoded within three bytes, after which the counter resets to 0. The ID sequence for reliable and unreliable packages are independent. Each connection / client has independent reliable package numbering.

Packages can be transmitted in several reliability modes, as listed below. The second and third bits (represented by the mask 0b0110_0000) of the flags byte control the reliability.

  • Unreliable (bits 00) – these packages may be lost, delivered multiple times, or delivered out of order with respect to other packages.
  • Unordered (bits 01) – non-duplicate delivery of these packages is guaranteed. However, there are no guarantees regarding the ordering of these packages with respect to other packages.
  • Semi-ordered (bits 10) – non-duplicate delivery of these packages is guaranteed. Additionally, the packages are guaranteed to be delivered after all previously reliably sent packages, that is, all unordered and semi-ordered packages sent by the same client before a semi-ordered package are always delivered before the semi-ordered package.

Packages can be targeted to the server, as opposed to all other game participants. This is indicated by the fourth highest bit of the flags byte (represented by the mask 0b0001_0000).

Package payload comprises the user data intended for delivery.

Examples

The datagram carrying a semi-reliable package with ID 107907, targeted to the server and containing the payload bytes 0x12 0x34 0x56, would look like this: 0x50 0x01 0xA5 0x83 0x12 0x34 0x56.

  1. 0x50 (0b0101_0000) – flags: semi-reliable, server targeted
  2. 0x01 0xA5 0x83 – package ID: 107,907
  3. 0x12 0x34 0x56 – package payload

Protocol Control

Currently, the only type of control datagram is the delivery confirmation datagram. All bits in the header of these datagrams, except for the first one, are set to 0. The payload consists of IDs for all user data datagrams that have been sent reliably and delivered successfully. Each ID is encoded using 3 bytes.

Examples

A datagram confirming three packages with IDs 1238, 17, and 2443, respectively, would look like this: 0x80 0x00 0x00 0x00 0x00 0x04 0xD6 0x00 0x00 0x11 0x00 0x09 0x8B.

  1. 0x80 (0b1000_0000) – protocol control flag
  2. 0x00 0x00 0x00 – always zeros
  3. 0x00 0x04 0xD6 – first confirmed package ID equal to 1238
  4. 0x00 0x00 0x11 – second confirmed package ID equal to 17
  5. 0x00 0x09 0x8B – third confirmed package ID equal to 2443

Message Encoding

Individual messages, implemented as Rust enums, are encoded via Bincode version 2. Usually, multiple messages are sent in a single package. Big endian with variable bit encoding is used.

See individual message documentation.

Configuration

Game configuration is stored into and loaded from a YAML file located at {user_conf_dir}/DigitalExtinction/conf.yaml. {user_conf_dir} is obtained with dirs::config_dir() and conforms to the following table:

PlatformValueExample
Linux$XDG_CONFIG_HOME or $HOME/.config/home/alice/.config
macOS$HOME/Library/Application Support/Users/Alice/Library/Application Support
Windows{FOLDERID_RoamingAppData}C:\Users\Alice\AppData\Roaming

Configuration YAML

All properties in the YAML tree are optional, default values are used instead. Missing configuration YAML file is treated equally to an empty YAML file, id est as if all properties are missing.

  • multiplayer (object) – multiplayer and network configuration.
    • lobby (string; default: http://lobby.de-game.org) – lobby server base URL.
    • connector (string; default: 127.0.0.1:8082) – DE Connector main server socket address. It must be valid IPv4 or IPv6 address.
  • camera (object) – in-game camera configuration.
    • move_margin (f32; default: 40.0) – horizontal camera movement is initiated if mouse is withing this distance in logical pixels to a window edge. It must be a finite positive number.
    • min_distance (f32; default: 20.0) – minimum camera distance from the terrain. It must be a finite number larger or equal to 10.0.
    • max_distance (f32; default: 80.0) – maximum camera distance from the terrain. It must be a finite number larger or equal to min_distance and smaller or equal to 300.0.
    • wheel_zoom_sensitivity (f32; default: 1.1) – camera to terrain distance scale factor used during mouse wheel zooming. The distance is changed to current_distance * wheel_zoom_sensitivity (zoom out) or current_distance / wheel_zoom_sensitivity (zoom in) with each mouse wheel tick. It must be a finite number larger than 1.0.
    • touchpad_zoom_sensitivity (f32; default: 1.01) – camera to terrain distance scale factor used during touchpad zooming. The distance is changed to current_distance * touchpad_zoom_sensitivity (zoom out) or current_distance / touchpad_zoom_sensitivity (zoom in) with each pixel movement. It must be a finite number larger than 1.0.
    • rotation_sensitivity (f32; default: 0.008) – multiplicative factor used during camera tilting and rotation. Mouse drag by delta logical pixels leads to the change of elevation and azimuth by delta * rotation_sensitivity radians. It must be a positive finite number.
    • scroll_inverted (bool; default: false) – if true, mouse wheel and touchpad scrolling is inverted.
  • audio (object) – audio configuration.
    • master_volume (f32; default: 1.0) – sets the master volume of all audio. It must be a finite number between 0.0 and 1.0. If set to 0 no audio will play.
    • sound_volume (f32; default: 1.0) – sets the SFX volume. It must be a finite number between 0.0 and 1.0. If set to 0 sound effects will not play.
    • music_volume (f32; default: 1.0) – sets the music volume. It must be a finite number between 0.0 and 1.0. If set to 0 music will not play.

Example Configuration

multiplayer:
  lobby: 'http://lobby.de_game.org/'
  connector: '127.0.0.1:8082'
camera:
  move_margin: 40.0
  min_distance: 20.0
  max_distance: 80.0
  wheel_zoom_sensitivity: 1.1
  touchpad_zoom_sensitivity: 1.1
  rotation_sensitivity: 0.01
  scroll_inverted: false
audio:
  master_volume: 1.0
  sound_volume: 1.0
  music_volume: 1.0

Logging

Game logs are both sent to standard output and persistently stored in text files located at {user_cache_dir}/DigitalExtinction/logs/. After each game startup, a new log file named according to the pattern %Y-%m-%d_%H-%M-%S.jsonl is created. The log files consist of JSON lines.

One might use a variation of the following command for pretty printing the logs cat 2023-05-31_17-44-25.jsonl | jq -cr '.timestamp + ": " + .fields.message'.

{user_cache_dir} is obtained with dirs::cache_dir() and conforms to the following table:

PlatformValueExample
Linux$XDG_CACHE_HOME or $HOME/.cache/home/alice/.cache
macOS$HOME/Library/Caches/Users/Alice/Library/Caches
Windows{FOLDERID_LocalAppData}C:\Users\Alice\AppData\Local