<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://www.fightorder.net/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Qrow</id>
	<title>Fightorder - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://www.fightorder.net/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Qrow"/>
	<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Special:Contributions/Qrow"/>
	<updated>2026-04-21T01:00:04Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.44.2</generator>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:Building_Recoil&amp;diff=2720</id>
		<title>Recoil:Building Recoil</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:Building_Recoil&amp;diff=2720"/>
		<updated>2026-03-12T02:17:23Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;  Engine Docker Build v2&lt;br /&gt;
&lt;br /&gt;
This directory contains the Docker engine build scripts.&lt;br /&gt;
&lt;br /&gt;
The container images bundle all required toolchains, dependencies, and configuration. This allows reliably compiling the engine in CI for releases and by developers locally resolving issues caused by different environment setups.&lt;br /&gt;
&lt;br /&gt;
The rest of this document will focus on [how to use the scripts locally]( usage), with an [implementation overview]( implementation-overview) at the end.&lt;br /&gt;
&lt;br /&gt;
Requirements&lt;br /&gt;
&lt;br /&gt;
You need to know the basics of how to use [command line](https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Environment_setup/Command_line), git, and have the engine source checked out locally.&lt;br /&gt;
&lt;br /&gt;
Windows&lt;br /&gt;
&lt;br /&gt;
You can use the scripts natively from Windows or use [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/).&lt;br /&gt;
&lt;br /&gt;
Native Setup&lt;br /&gt;
&lt;br /&gt;
1. Install [Docker Desktop](https://docs.docker.com/desktop/) following [official instructions](https://docs.docker.com/desktop/setup/install/windows-install/).&lt;br /&gt;
2. Install a Bash shell, you have two options:&lt;br /&gt;
   - **[Recommended]** Install [Git for Windows](https://git-scm.com/install/windows) which comes with Bash.&lt;br /&gt;
   - Use [Cygwin](https://cygwin.com)&lt;br /&gt;
&lt;br /&gt;
When executing commands in the Usage section, make sure that Docker is running, and execute all commands from the installed Bash command line.&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!WARNING]&lt;br /&gt;
&amp;gt; This setup is very slow in comparison to other ones. When a compiler running inside a container accesses source files it crosses the boundary between the virtual machine and Windows host system which has a huge performance penalty (Some parts of compilation, e.g. configuration, can be even 100x slower).&lt;br /&gt;
&lt;br /&gt;
WSL Setup&lt;br /&gt;
&lt;br /&gt;
1. Set up WSL following [official documentation tutorial](https://learn.microsoft.com/en-us/windows/wsl/setup/environment). WSL *2* is required.&lt;br /&gt;
2. You can install [Docker Desktop](https://docs.docker.com/desktop/) on Windows following [official instructions](https://docs.docker.com/desktop/setup/install/windows-install/), or set up the container runtime inside of the WSL following [Linux Setup instructions]( linux-setup).&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!IMPORTANT]&lt;br /&gt;
&amp;gt; Make sure you have the engine source checked out *inside* of the WSL file system, not directly on the Windows disk, before proceeding to the [Usage]( usage) section. There is a large overhead when accessing files outside of WSL especially for workflows like compilation. If source code is checked out outside of WSL, the overhead will be similar to the &amp;quot;Native Setup&amp;quot; described in the previous section.&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!TIP]&lt;br /&gt;
&amp;gt; If you&#039;re using VSCode you can use [Remote Development Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) to conveniently work on files inside of the WSL drive.&lt;br /&gt;
&lt;br /&gt;
Linux Setup&lt;br /&gt;
&lt;br /&gt;
Ensure you have some container runtime installed. The scripts support any of the following:&lt;br /&gt;
&lt;br /&gt;
- **[Podman](https://podman.io/)**: will be available directly in your distribution repository, daemonless, and by default more secure.&lt;br /&gt;
&lt;br /&gt;
- **Docker Engine**: install following [official instructions](https://docs.docker.com/engine/install/) and don&#039;t skip the [post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/). The scripts also support the more secure [Rootless mode](https://docs.docker.com/engine/security/rootless/) of operation you can set up instead.&lt;br /&gt;
&lt;br /&gt;
- **Docker Desktop**: install following [official instructions](https://docs.docker.com/desktop/setup/install/linux/). Note that Docker Desktop on Linux runs a virtual machine which has a performance overhead in comparison to Podman and Docker Engine which run natively in containers on the host.&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE]&lt;br /&gt;
&amp;gt; When both Podman and Docker are installed, by default the script chooses to use Docker. It can be overridden via the `CONTAINER_RUNTIME` environment variable.&lt;br /&gt;
&lt;br /&gt;
Usage&lt;br /&gt;
&lt;br /&gt;
After installing requirements, you can execute the build locally using the `build.sh` script from a shell:&lt;br /&gt;
&lt;br /&gt;
```console&lt;br /&gt;
$ docker-build-v2/build.sh --help&lt;br /&gt;
Usage: docker-build-v2/build.sh [--help] [--configure|--compile] [-j|--jobs {number_of_jobs}] {windows|linux} [cmake_flag...]&lt;br /&gt;
Options:&lt;br /&gt;
  -h, --help   print this help message&lt;br /&gt;
  --configure  only configure, don&#039;t compile&lt;br /&gt;
  --compile    only compile, don&#039;t configure&lt;br /&gt;
  -j, --jobs   number of concurrent processes to use when building&lt;br /&gt;
&lt;br /&gt;
Some behaviors can be changed by setting environment variables. Consult the script source for those more advanced use cases.&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
For example&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker-build-v2/build.sh windows&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
will:&lt;br /&gt;
&lt;br /&gt;
1. Automatically fetch the correct Docker image with the engine build environment from [GitHub packages](https://github.com/orgs/beyond-all-reason/packages?repo_name=RecoilEngine)&lt;br /&gt;
2. Configure the release configuration of the engine build&lt;br /&gt;
3. Compile and install the engine using the following paths in the repository root:&lt;br /&gt;
   - `.cache`: compilation cache&lt;br /&gt;
   - `build-windows`: compilation output&lt;br /&gt;
   - `build-windows/install`: ready to use installation&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!CAUTION]&lt;br /&gt;
&amp;gt; The build output like in archives fetched from the [releases page](https://github.com/beyond-all-reason/RecoilEngine/releases) is inside of `build-windows/install` directory, **not** `build-windows`. The improvement is tracked in [ 2742](https://github.com/beyond-all-reason/RecoilEngine/issues/2747).&lt;br /&gt;
&lt;br /&gt;
&amp;gt; **TODO:** Link to documentation article about how to start engine, load game in it etc once it exists. Some current references of not best quality specifically for BAR:&lt;br /&gt;
&amp;gt;   - https://github.com/beyond-all-reason/Beyond-All-Reason/wiki/Testing-New-Engine-Releases-in-BAR&lt;br /&gt;
&amp;gt;   - https://discord.com/channels/549281623154229250/724924957074915358/1411338224114204693&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!TIP]&lt;br /&gt;
&amp;gt; Don&#039;t forget that symlinks exist and can be used to link compilation output `build-windows/install` to the game installation folder. It can be helpful if your Recoil game/lobby supports starting arbitrary engine versions. For example, [skylobby](https://github.com/skynet-gh/skylobby) for generic lobby software, and for BAR, there is the [Debug Launcher](https://github.com/beyond-all-reason/bar_debug_launcher).&lt;br /&gt;
&amp;gt; - Windows: [mklink](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/mklink) or third-party [Link Shell Extension (LSE)](https://schinagl.priv.at/nt/hardlinkshellext/linkshellextension.html).&lt;br /&gt;
&amp;gt; - Linux: [`ln -s`](https://linuxize.com/post/how-to-create-symbolic-links-in-linux-using-the-ln-command/)&lt;br /&gt;
&lt;br /&gt;
Custom build config&lt;br /&gt;
&lt;br /&gt;
The script accepts CMake arguments, so the compilation can be easily customized. For example to compile Linux release with Tracy support and skip building headless:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker-build-v2/build.sh linux -DBUILD_spring-headless=OFF -DTRACY_ENABLE=ON&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
To list all cmake options and their values run:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker-build-v2/build.sh --configure linux -LH&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
When the configuration phase is not selected the arguments are passed to&lt;br /&gt;
the compilation phase, so it can run custom targets (with options).&lt;br /&gt;
&lt;br /&gt;
For example, to compile the unit tests with verbose output run:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker-build-v2/build.sh --compile linux -t tests --verbose&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
In the case both configuration and compilation phase are selected, the&lt;br /&gt;
configuration phase takes precedent over the arguments.&lt;br /&gt;
&lt;br /&gt;
Custom Docker image&lt;br /&gt;
&lt;br /&gt;
The official images are built as part of a CI workflow (see [Implementation Overview]( implementation-overview)), but if you want to adjust the Docker build image or test local changes, you can build it locally:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker build -t recoil-build-amd64-windows docker-build-v2/amd64-windows&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
and then pass the custom image to the build script via the environment variable `CONTAINER_IMAGE`:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
CONTAINER_IMAGE=recoil-build-amd64-windows docker-build-v2/build.sh windows&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
and `build.sh` will use it.&lt;br /&gt;
&lt;br /&gt;
For details on private testing, check the wiki [here](https://github.com/beyond-all-reason/RecoilEngine/wiki/Pre-release-testing-Checklist,-and-release-engine-checklist private-testing)&lt;br /&gt;
&lt;br /&gt;
Implementation Overview&lt;br /&gt;
&lt;br /&gt;
There are two separate build images, one for Windows, one for Linux. The Docker images are built as part of a [GitHub Actions workflow](../../.github/workflows/docker-images-build.yml) and stored in the GitHub Package repository. The images are relatively small (~300-400MiB compressed) and building them takes 2-3 minutes.&lt;br /&gt;
&lt;br /&gt;
Each of the images contains a complete required build environment with all dependencies installed (including [mingwlibs](https://github.com/beyond-all-reason/mingwlibs64) etc.), configured for proper resolution from engine CMake configuration, and caching with [ccache](https://ccache.dev/).&lt;br /&gt;
&lt;br /&gt;
The build step is then a platform agnostic invocation of CMake with generic release build configuration.&lt;br /&gt;
&lt;br /&gt;
To sum up, there is a separation:&lt;br /&gt;
&lt;br /&gt;
- Build environment: dependencies, toolchain, etc. are part of the Docker image.&lt;br /&gt;
- Build options: e.g. optimization level, are in the platform agnostic build script `docker-build-v2/scripts/configure.sh` stored in the repository.&lt;br /&gt;
- Post build scripts: platform agnostic scripts:&lt;br /&gt;
  - `docker-build-v2/scripts/split-debug-info.sh`: Splits debug information from binaries&lt;br /&gt;
  - `docker-build-v2/scripts/package.sh`: Creates 2 archives, one with engine and one with debug symbols.&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:Building_Recoil&amp;diff=2719</id>
		<title>Recoil:Building Recoil</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:Building_Recoil&amp;diff=2719"/>
		<updated>2026-03-12T02:14:27Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;# Engine Docker Build v2  This directory contains the Docker engine build scripts.  The container images bundle all required toolchains, dependencies, and configuration. This allows reliably compiling the engine in CI for releases and by developers locally resolving issues caused by different environment setups.  The rest of this document will focus on [how to use the scripts locally](#usage), with an [implementation overview](#implementation-overview) at the end.  ## Re...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;# Engine Docker Build v2&lt;br /&gt;
&lt;br /&gt;
This directory contains the Docker engine build scripts.&lt;br /&gt;
&lt;br /&gt;
The container images bundle all required toolchains, dependencies, and configuration. This allows reliably compiling the engine in CI for releases and by developers locally resolving issues caused by different environment setups.&lt;br /&gt;
&lt;br /&gt;
The rest of this document will focus on [how to use the scripts locally](#usage), with an [implementation overview](#implementation-overview) at the end.&lt;br /&gt;
&lt;br /&gt;
## Requirements&lt;br /&gt;
&lt;br /&gt;
You need to know the basics of how to use [command line](https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Environment_setup/Command_line), git, and have the engine source checked out locally.&lt;br /&gt;
&lt;br /&gt;
### Windows&lt;br /&gt;
&lt;br /&gt;
You can use the scripts natively from Windows or use [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/).&lt;br /&gt;
&lt;br /&gt;
#### Native Setup&lt;br /&gt;
&lt;br /&gt;
1. Install [Docker Desktop](https://docs.docker.com/desktop/) following [official instructions](https://docs.docker.com/desktop/setup/install/windows-install/).&lt;br /&gt;
2. Install a Bash shell, you have two options:&lt;br /&gt;
   - **[Recommended]** Install [Git for Windows](https://git-scm.com/install/windows) which comes with Bash.&lt;br /&gt;
   - Use [Cygwin](https://cygwin.com)&lt;br /&gt;
&lt;br /&gt;
When executing commands in the Usage section, make sure that Docker is running, and execute all commands from the installed Bash command line.&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!WARNING]&lt;br /&gt;
&amp;gt; This setup is very slow in comparison to other ones. When a compiler running inside a container accesses source files it crosses the boundary between the virtual machine and Windows host system which has a huge performance penalty (Some parts of compilation, e.g. configuration, can be even 100x slower).&lt;br /&gt;
&lt;br /&gt;
#### WSL Setup&lt;br /&gt;
&lt;br /&gt;
1. Set up WSL following [official documentation tutorial](https://learn.microsoft.com/en-us/windows/wsl/setup/environment). WSL *2* is required.&lt;br /&gt;
2. You can install [Docker Desktop](https://docs.docker.com/desktop/) on Windows following [official instructions](https://docs.docker.com/desktop/setup/install/windows-install/), or set up the container runtime inside of the WSL following [Linux Setup instructions](#linux-setup).&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!IMPORTANT]&lt;br /&gt;
&amp;gt; Make sure you have the engine source checked out *inside* of the WSL file system, not directly on the Windows disk, before proceeding to the [Usage](#usage) section. There is a large overhead when accessing files outside of WSL especially for workflows like compilation. If source code is checked out outside of WSL, the overhead will be similar to the &amp;quot;Native Setup&amp;quot; described in the previous section.&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!TIP]&lt;br /&gt;
&amp;gt; If you&#039;re using VSCode you can use [Remote Development Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) to conveniently work on files inside of the WSL drive.&lt;br /&gt;
&lt;br /&gt;
### Linux Setup&lt;br /&gt;
&lt;br /&gt;
Ensure you have some container runtime installed. The scripts support any of the following:&lt;br /&gt;
&lt;br /&gt;
- **[Podman](https://podman.io/)**: will be available directly in your distribution repository, daemonless, and by default more secure.&lt;br /&gt;
&lt;br /&gt;
- **Docker Engine**: install following [official instructions](https://docs.docker.com/engine/install/) and don&#039;t skip the [post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/). The scripts also support the more secure [Rootless mode](https://docs.docker.com/engine/security/rootless/) of operation you can set up instead.&lt;br /&gt;
&lt;br /&gt;
- **Docker Desktop**: install following [official instructions](https://docs.docker.com/desktop/setup/install/linux/). Note that Docker Desktop on Linux runs a virtual machine which has a performance overhead in comparison to Podman and Docker Engine which run natively in containers on the host.&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE]&lt;br /&gt;
&amp;gt; When both Podman and Docker are installed, by default the script chooses to use Docker. It can be overridden via the `CONTAINER_RUNTIME` environment variable.&lt;br /&gt;
&lt;br /&gt;
## Usage&lt;br /&gt;
&lt;br /&gt;
After installing requirements, you can execute the build locally using the `build.sh` script from a shell:&lt;br /&gt;
&lt;br /&gt;
```console&lt;br /&gt;
$ docker-build-v2/build.sh --help&lt;br /&gt;
Usage: docker-build-v2/build.sh [--help] [--configure|--compile] [-j|--jobs {number_of_jobs}] {windows|linux} [cmake_flag...]&lt;br /&gt;
Options:&lt;br /&gt;
  -h, --help   print this help message&lt;br /&gt;
  --configure  only configure, don&#039;t compile&lt;br /&gt;
  --compile    only compile, don&#039;t configure&lt;br /&gt;
  -j, --jobs   number of concurrent processes to use when building&lt;br /&gt;
&lt;br /&gt;
Some behaviors can be changed by setting environment variables. Consult the script source for those more advanced use cases.&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
For example&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker-build-v2/build.sh windows&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
will:&lt;br /&gt;
&lt;br /&gt;
1. Automatically fetch the correct Docker image with the engine build environment from [GitHub packages](https://github.com/orgs/beyond-all-reason/packages?repo_name=RecoilEngine)&lt;br /&gt;
2. Configure the release configuration of the engine build&lt;br /&gt;
3. Compile and install the engine using the following paths in the repository root:&lt;br /&gt;
   - `.cache`: compilation cache&lt;br /&gt;
   - `build-windows`: compilation output&lt;br /&gt;
   - `build-windows/install`: ready to use installation&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!CAUTION]&lt;br /&gt;
&amp;gt; The build output like in archives fetched from the [releases page](https://github.com/beyond-all-reason/RecoilEngine/releases) is inside of `build-windows/install` directory, **not** `build-windows`. The improvement is tracked in [#2742](https://github.com/beyond-all-reason/RecoilEngine/issues/2747).&lt;br /&gt;
&lt;br /&gt;
&amp;gt; **TODO:** Link to documentation article about how to start engine, load game in it etc once it exists. Some current references of not best quality specifically for BAR:&lt;br /&gt;
&amp;gt;   - https://github.com/beyond-all-reason/Beyond-All-Reason/wiki/Testing-New-Engine-Releases-in-BAR&lt;br /&gt;
&amp;gt;   - https://discord.com/channels/549281623154229250/724924957074915358/1411338224114204693&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!TIP]&lt;br /&gt;
&amp;gt; Don&#039;t forget that symlinks exist and can be used to link compilation output `build-windows/install` to the game installation folder. It can be helpful if your Recoil game/lobby supports starting arbitrary engine versions. For example, [skylobby](https://github.com/skynet-gh/skylobby) for generic lobby software, and for BAR, there is the [Debug Launcher](https://github.com/beyond-all-reason/bar_debug_launcher).&lt;br /&gt;
&amp;gt; - Windows: [mklink](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/mklink) or third-party [Link Shell Extension (LSE)](https://schinagl.priv.at/nt/hardlinkshellext/linkshellextension.html).&lt;br /&gt;
&amp;gt; - Linux: [`ln -s`](https://linuxize.com/post/how-to-create-symbolic-links-in-linux-using-the-ln-command/)&lt;br /&gt;
&lt;br /&gt;
### Custom build config&lt;br /&gt;
&lt;br /&gt;
The script accepts CMake arguments, so the compilation can be easily customized. For example to compile Linux release with Tracy support and skip building headless:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker-build-v2/build.sh linux -DBUILD_spring-headless=OFF -DTRACY_ENABLE=ON&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
To list all cmake options and their values run:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker-build-v2/build.sh --configure linux -LH&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
When the configuration phase is not selected the arguments are passed to&lt;br /&gt;
the compilation phase, so it can run custom targets (with options).&lt;br /&gt;
&lt;br /&gt;
For example, to compile the unit tests with verbose output run:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker-build-v2/build.sh --compile linux -t tests --verbose&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
In the case both configuration and compilation phase are selected, the&lt;br /&gt;
configuration phase takes precedent over the arguments.&lt;br /&gt;
&lt;br /&gt;
### Custom Docker image&lt;br /&gt;
&lt;br /&gt;
The official images are built as part of a CI workflow (see [Implementation Overview](#implementation-overview)), but if you want to adjust the Docker build image or test local changes, you can build it locally:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
docker build -t recoil-build-amd64-windows docker-build-v2/amd64-windows&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
and then pass the custom image to the build script via the environment variable `CONTAINER_IMAGE`:&lt;br /&gt;
&lt;br /&gt;
```shell&lt;br /&gt;
CONTAINER_IMAGE=recoil-build-amd64-windows docker-build-v2/build.sh windows&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
and `build.sh` will use it.&lt;br /&gt;
&lt;br /&gt;
For details on private testing, check the wiki [here](https://github.com/beyond-all-reason/RecoilEngine/wiki/Pre-release-testing-Checklist,-and-release-engine-checklist#private-testing)&lt;br /&gt;
&lt;br /&gt;
## Implementation Overview&lt;br /&gt;
&lt;br /&gt;
There are two separate build images, one for Windows, one for Linux. The Docker images are built as part of a [GitHub Actions workflow](../../.github/workflows/docker-images-build.yml) and stored in the GitHub Package repository. The images are relatively small (~300-400MiB compressed) and building them takes 2-3 minutes.&lt;br /&gt;
&lt;br /&gt;
Each of the images contains a complete required build environment with all dependencies installed (including [mingwlibs](https://github.com/beyond-all-reason/mingwlibs64) etc.), configured for proper resolution from engine CMake configuration, and caching with [ccache](https://ccache.dev/).&lt;br /&gt;
&lt;br /&gt;
The build step is then a platform agnostic invocation of CMake with generic release build configuration.&lt;br /&gt;
&lt;br /&gt;
To sum up, there is a separation:&lt;br /&gt;
&lt;br /&gt;
- Build environment: dependencies, toolchain, etc. are part of the Docker image.&lt;br /&gt;
- Build options: e.g. optimization level, are in the platform agnostic build script `docker-build-v2/scripts/configure.sh` stored in the repository.&lt;br /&gt;
- Post build scripts: platform agnostic scripts:&lt;br /&gt;
  - `docker-build-v2/scripts/split-debug-info.sh`: Splits debug information from binaries&lt;br /&gt;
  - `docker-build-v2/scripts/package.sh`: Creates 2 archives, one with engine and one with debug symbols.&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil&amp;diff=2718</id>
		<title>Recoil</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil&amp;diff=2718"/>
		<updated>2026-03-12T02:10:35Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;h2 data-notoc=&amp;quot;&amp;quot;&amp;gt;Recoil is a battle tested open-source RTS engine that, allied with a flexible Lua API, allows you to implement the perfect UI and mechanics for your game with the ability to support thousands of complex units simultaneously.&amp;lt;/h2&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=Recoil is a recent hard fork of [Spring](https://github.com/spring/spring) from the [105 tree](https://github.com/spring/spring/releases/tag/105.0.1), many references to it might and will be present. Overall most documented Spring API and tutorials are compatible with Recoil since they are based on the 105 tree.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
--&amp;gt;{|border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;4&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
| width=&amp;quot;50%&amp;quot; valign=&amp;quot;top&amp;quot;  |&lt;br /&gt;
== For Players ==&lt;br /&gt;
&lt;br /&gt;
=== Content ===&lt;br /&gt;
&#039;&#039;&#039;[[Recoil Games]]&#039;&#039;&#039; Games that use the Recoil engine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Maps]]&#039;&#039;&#039; All about maps for the Spring engine and where to get them.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Getting Recoil Content]]&#039;&#039;&#039; Sites from which you can download spring Games, Maps, and more.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[AI:Skirmish|AIs]]&#039;&#039;&#039; Skirmish AIs for Singleplayer.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lua Widgets]]&#039;&#039;&#039; All about installing and using LuaUI widgets.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Recoil:Replays|Replays]]&#039;&#039;&#039; Watching recordings of previously played matches.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Community:Recoil| Community]]&#039;&#039;&#039; Documents that shape our online community.&lt;br /&gt;
&lt;br /&gt;
=== Help ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[FAQ]]&#039;&#039;&#039; Has it been asked before?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Hosting Recoil]]&#039;&#039;&#039; Autohosting and related topics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Spring_on_MacOSX |Mac OS]]&#039;&#039;&#039; Mac OSX information.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[SetupGuide |Linux]]&#039;&#039;&#039; NetBSD / Solaris / Linux / Cross-platform information.&lt;br /&gt;
&lt;br /&gt;
| width=&amp;quot;50%&amp;quot; valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
== For Developers ==&lt;br /&gt;
&lt;br /&gt;
=== Content Development ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Recoil:Building Recoil|Building Recoil]]&#039;&#039;&#039; Installing Recoil and Configuration&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Gamedev:Main|Game Development]]&#039;&#039;&#039; Lots of links to valuable Articles and forum threads regarding Game/Unit Development.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Mapdev:Main|Map Development]]&#039;&#039;&#039; Tutorials and other useful information for creating maps.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lua Scripting]]&#039;&#039;&#039; Information regarding the use of the supplementary Lua scripting language for Widgets and beyond.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Chili|Chili UI framework]]&#039;&#039;&#039; Framework for creating GUIs in Lua.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Map, Mod, And Unit Development Programs|Content Development Programs]]&#039;&#039;&#039; Links to all kind of useful programs.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[General Resources]]&#039;&#039;&#039; Usable textures, sounds and code snippets by the community, for the community.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Associated Development Groups]]&#039;&#039;&#039; Teams, Forums and Groups associated with Spring content and code development.&lt;br /&gt;
&lt;br /&gt;
=== Engine and Native Development ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Development:Getting_Started|Engine Development]]&#039;&#039;&#039; Starting point for engine development.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Building spring|Compiling Engine]]&#039;&#039;&#039; How to build the Spring engine from source.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lobby_Development|Lobby Development (Clients and Servers)]]&#039;&#039;&#039; Information regarding contributing to the source code of the different lobby clients and servers available for Spring.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[AI:Development|AI Development]]&#039;&#039;&#039; Lots of information about developing Skirmish, and Unit AIs for Spring.&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{||&lt;br /&gt;
|&lt;br /&gt;
=This wiki needs your help!=&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&amp;quot;A wiki is a web application that allows users to add content, as on an Internet forum, but also allows anyone to edit the content.&amp;quot;&#039;&#039;&lt;br /&gt;
- [http://en.wikipedia.org/wiki/Wiki Wikipedia.org]&lt;br /&gt;
&lt;br /&gt;
The Spring project is very dependent on its community of users to help new players and developers. [[FightOrderEditing|Learn more here.]]&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| |&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
 __NOTOC__&lt;br /&gt;
__NOEDITSECTION__&lt;br /&gt;
[[Category:Spring]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil&amp;diff=2717</id>
		<title>Recoil</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil&amp;diff=2717"/>
		<updated>2026-03-12T02:09:55Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;h2 data-notoc=&amp;quot;&amp;quot;&amp;gt;Recoil is a battle tested open-source RTS engine that, allied with a flexible Lua API, allows you to implement the perfect UI and mechanics for your game with the ability to support thousands of complex units simultaneously.&amp;lt;/h2&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=Recoil is a recent hard fork of [Spring](https://github.com/spring/spring) from the [105 tree](https://github.com/spring/spring/releases/tag/105.0.1), many references to it might and will be present. Overall most documented Spring API and tutorials are compatible with Recoil since they are based on the 105 tree.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
--&amp;gt;{|border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;4&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
| width=&amp;quot;50%&amp;quot; valign=&amp;quot;top&amp;quot;  |&lt;br /&gt;
== For Players ==&lt;br /&gt;
&lt;br /&gt;
=== Content ===&lt;br /&gt;
&#039;&#039;&#039;[[Recoil Games]]&#039;&#039;&#039; Games that use the Recoil engine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Maps]]&#039;&#039;&#039; All about maps for the Spring engine and where to get them.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Getting Recoil Content]]&#039;&#039;&#039; Sites from which you can download spring Games, Maps, and more.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[AI:Skirmish|AIs]]&#039;&#039;&#039; Skirmish AIs for Singleplayer.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lua Widgets]]&#039;&#039;&#039; All about installing and using LuaUI widgets.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Recoil:Replays|Replays]]&#039;&#039;&#039; Watching recordings of previously played matches.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Community:Recoil| Community]]&#039;&#039;&#039; Documents that shape our online community.&lt;br /&gt;
&lt;br /&gt;
=== Help ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[FAQ]]&#039;&#039;&#039; Has it been asked before?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Hosting Recoil]]&#039;&#039;&#039; Autohosting and related topics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Spring_on_MacOSX |Mac OS]]&#039;&#039;&#039; Mac OSX information.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[SetupGuide |Linux]]&#039;&#039;&#039; NetBSD / Solaris / Linux / Cross-platform information.&lt;br /&gt;
&lt;br /&gt;
| width=&amp;quot;50%&amp;quot; valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
== For Developers ==&lt;br /&gt;
&lt;br /&gt;
=== Content Development ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Gamedev:Main|Building Recoil]]&#039;&#039;&#039; Installing Recoil and Configuration&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Gamedev:Main|Game Development]]&#039;&#039;&#039; Lots of links to valuable Articles and forum threads regarding Game/Unit Development.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Mapdev:Main|Map Development]]&#039;&#039;&#039; Tutorials and other useful information for creating maps.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lua Scripting]]&#039;&#039;&#039; Information regarding the use of the supplementary Lua scripting language for Widgets and beyond.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Chili|Chili UI framework]]&#039;&#039;&#039; Framework for creating GUIs in Lua.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Map, Mod, And Unit Development Programs|Content Development Programs]]&#039;&#039;&#039; Links to all kind of useful programs.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[General Resources]]&#039;&#039;&#039; Usable textures, sounds and code snippets by the community, for the community.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Associated Development Groups]]&#039;&#039;&#039; Teams, Forums and Groups associated with Spring content and code development.&lt;br /&gt;
&lt;br /&gt;
=== Engine and Native Development ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Development:Getting_Started|Engine Development]]&#039;&#039;&#039; Starting point for engine development.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Building spring|Compiling Engine]]&#039;&#039;&#039; How to build the Spring engine from source.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lobby_Development|Lobby Development (Clients and Servers)]]&#039;&#039;&#039; Information regarding contributing to the source code of the different lobby clients and servers available for Spring.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[AI:Development|AI Development]]&#039;&#039;&#039; Lots of information about developing Skirmish, and Unit AIs for Spring.&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{||&lt;br /&gt;
|&lt;br /&gt;
=This wiki needs your help!=&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&amp;quot;A wiki is a web application that allows users to add content, as on an Internet forum, but also allows anyone to edit the content.&amp;quot;&#039;&#039;&lt;br /&gt;
- [http://en.wikipedia.org/wiki/Wiki Wikipedia.org]&lt;br /&gt;
&lt;br /&gt;
The Spring project is very dependent on its community of users to help new players and developers. [[FightOrderEditing|Learn more here.]]&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| |&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
 __NOTOC__&lt;br /&gt;
__NOEDITSECTION__&lt;br /&gt;
[[Category:Spring]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Lua_sync_to_unsync&amp;diff=2716</id>
		<title>Lua sync to unsync</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Lua_sync_to_unsync&amp;diff=2716"/>
		<updated>2026-03-12T01:20:29Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Synced -&amp;gt; Unsynced Communication =&lt;br /&gt;
&lt;br /&gt;
==Using SendToUnsynced==&lt;br /&gt;
Currently, the recommended way is to use the SendToUnsynced call-in to send an event to the unsynced portion of a gadget. It&#039;s also possible to send an event from a gadget to a Widget. First, you send an &#039;internal event&#039; from synced to unsynced space, within the gadget, then you can create an event which may be subscribed to by any widget. Follows an image showcasing that flow and working/tested code:&lt;br /&gt;
&lt;br /&gt;
[[File:Sync unsync comm.png|670px]]&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
https://pastebin.com/b1eVWn1d&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br /&amp;gt;Bear in mind that only one widget can hook to one LuaUI event at once, trying to listen to it in multiple widgets will lead to erratic behavior. Workaround here would be firing multiple LuaUI events, one for each consuming widget.&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;The first parameter in the event handler (in the example, it&#039;s named &#039;cmd&#039; in HandleCommCountEvent) is rarely used. It holds the name of the synced event, so you know what synced event fired the function, as in the following example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
local function func(str)&lt;br /&gt;
    if str == &amp;quot;abcd&amp;quot; then&lt;br /&gt;
        -- do something&lt;br /&gt;
    elseif str == &amp;quot;efgh&amp;quot;&lt;br /&gt;
        -- do another thing&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
gadgetHandler:AddSyncAction(&amp;quot;abcd&amp;quot;, func)&lt;br /&gt;
gadgetHandler:AddSyncAction(&amp;quot;efgh&amp;quot;, func)&lt;br /&gt;
&lt;br /&gt;
SendToUnsynced(&amp;quot;abcd&amp;quot;)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Futher reading in the following forum thread: &lt;br /&gt;
https://springrts.com/phpbb/viewtopic.php?t&amp;amp;t=11408&lt;br /&gt;
&lt;br /&gt;
==Using globals (96.0 and previous) ==&lt;br /&gt;
When making a gadget you will sometimes want to combine synced and unsynced code.&lt;br /&gt;
&lt;br /&gt;
For example if you do some things with units, that code will need to be synced.&lt;br /&gt;
&lt;br /&gt;
The UnitDestroyed, UnitFinished etc. callins can only be called in synced code. Using them inside unsynced code is not going to work.&lt;br /&gt;
&lt;br /&gt;
If you want to draw on the screen (for example to mark units) then the drawing will have to happen in unsynced code.&lt;br /&gt;
&lt;br /&gt;
The following example demonstrates how to change the value inside the synced block of code and read the value in the unsynced block of code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
if (gadgetHandler:IsSyncedCode()) then&lt;br /&gt;
   --SYNCED CODE&lt;br /&gt;
   local number = 10               -- declaring local variable &#039;number&#039;&lt;br /&gt;
   function gadget:SomeSyncedCallin(...)&lt;br /&gt;
      number=number+1              -- increment variable&#039;s value by one&lt;br /&gt;
      _G.number=number             -- save the new value into a global variable&lt;br /&gt;
   end&lt;br /&gt;
else&lt;br /&gt;
   --UNSYNCED CODE&lt;br /&gt;
   function gadget:SomeUnsyncedCallin(...)&lt;br /&gt;
      Spring.Echo(SYNCED.number)   -- print the global variable&#039;s value on screen&lt;br /&gt;
   end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 97.0+ ==&lt;br /&gt;
As of 97.0 unsynced gets most of the [[Lua:Callins|callins]] available to synced (excepting those which control synced actions e.g. AllowUnitCreation etc) which means that forwarding data from synced to unsynced is now not always required. Additionally in 97.0 there were changes to the behaviour of the &amp;lt;code&amp;gt;SYNCED&amp;lt;/code&amp;gt; table described above.&lt;br /&gt;
&lt;br /&gt;
Change 1: &amp;lt;code&amp;gt;SYNCED&amp;lt;/code&amp;gt; does a copy on access now and so is slow:&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Bad:    &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;for i=1,100 do ... SYNCED.foo[i] ... end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Good:   &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local foo = SYNCED.foo; for i=1,100 do ... foo[i] ... end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Change 2: &amp;lt;code&amp;gt;SYNCED.&amp;lt;/code&amp;gt; can&#039;t be localized in global scope anymore:&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Bad:    &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local foo = SYNCED.foo;    function gadget:DrawWorld() ... foo ... end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Good:   &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function gadget:DrawWorld() local foo = SYNCED.foo; ... foo ... end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Here is a (older) description by quantum:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;[LCC]quantum[0K] every time there  is a new target, i send &amp;quot;addTarget&amp;quot; and its number to unsynced&lt;br /&gt;
[LCC]quantum[0K] when a target is removed from the list, i send &amp;quot;removeTarget&amp;quot; and its number&lt;br /&gt;
[LCC]quantum[0K] this wierd stuff:&lt;br /&gt;
[LCC]quantum[0K]   _G.laserEventArgs = eventArgs&lt;br /&gt;
[LCC]quantum[0K]   SendToUnsynced(&amp;quot;LaserEvent&amp;quot;)&lt;br /&gt;
[LCC]quantum[0K]   _G.laserEventArgs = nil&lt;br /&gt;
[LCC]quantum[0K] puts the eventArgs table somewhere were unsynced can find it&lt;br /&gt;
[LCC]quantum[0K] sends a signal to unsynced called &amp;quot;LaserEvent&amp;quot;&lt;br /&gt;
[LCC]quantum[0K] and removes the table&lt;br /&gt;
[LCC]quantum[0K] on the unsynced side, _G.laserEventArgs becomes SYNCED.laserEventArgs&lt;br /&gt;
[LCC]quantum[0K] this copies it to another table, ready for reuse:   for k, v in spairs(SYNCED.laserEventArgs) do&lt;br /&gt;
[LCC]quantum[0K]     eventArgs[k] = v&lt;br /&gt;
[LCC]quantum[0K]   end&lt;br /&gt;
[LCC]quantum[0K] also, this: gadgetHandler:AddSyncAction(&amp;quot;LaserEvent&amp;quot;, HandleLaserEvent)&lt;br /&gt;
[LCC]quantum[0K] means that i want the HandleLaserEvent function to be called every time i do SendToUnsynced(&amp;quot;LaserEvent&amp;quot;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
http://pastebin.com/hMHv3Lrf&lt;br /&gt;
&lt;br /&gt;
[[Category: Lua]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Lua_sync_to_unsync&amp;diff=2715</id>
		<title>Lua sync to unsync</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Lua_sync_to_unsync&amp;diff=2715"/>
		<updated>2026-03-12T01:19:48Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Synced -&amp;gt; Unsynced Communication =&lt;br /&gt;
&lt;br /&gt;
==Using SendToUnsynced==&lt;br /&gt;
Currently, the recommended way is to use the SendToUnsynced call-in to send an event to the unsynced portion of a gadget. It&#039;s also possible to send an event from a gadget to a Widget. First, you send an &#039;internal event&#039; from synced to unsynced space, within the gadget, then you can create an event which may be subscribed to by any widget. Follows an image showcasing that flow and working/tested code:&lt;br /&gt;
&lt;br /&gt;
[[File:Sync unsync comm.png|670px]]&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
https://pastebin.com/b1eVWn1d&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br /&amp;gt;Bear in mind that only one widget can hook to one LuaUI event at once, trying to listen to it in multiple widgets will lead to erratic behavior. Workaround here would be firing multiple LuaUI events, one for each consuming widget.&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;The first parameter in the event handler (in the example, it&#039;s named &#039;cmd&#039; in HandleCommCountEvent) is rarely used. It holds the name of the synced event, so you know what synced event fired the function, as in the following example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local function func(str)&lt;br /&gt;
    if str == &amp;quot;abcd&amp;quot; then&lt;br /&gt;
        -- do something&lt;br /&gt;
    elseif str == &amp;quot;efgh&amp;quot;&lt;br /&gt;
        -- do another thing&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
gadgetHandler:AddSyncAction(&amp;quot;abcd&amp;quot;, func)&lt;br /&gt;
gadgetHandler:AddSyncAction(&amp;quot;efgh&amp;quot;, func)&lt;br /&gt;
&lt;br /&gt;
SendToUnsynced(&amp;quot;abcd&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Futher reading in the following forum thread: &lt;br /&gt;
https://springrts.com/phpbb/viewtopic.php?t&amp;amp;t=11408&lt;br /&gt;
&lt;br /&gt;
==Using globals (96.0 and previous) ==&lt;br /&gt;
When making a gadget you will sometimes want to combine synced and unsynced code.&lt;br /&gt;
&lt;br /&gt;
For example if you do some things with units, that code will need to be synced.&lt;br /&gt;
&lt;br /&gt;
The UnitDestroyed, UnitFinished etc. callins can only be called in synced code. Using them inside unsynced code is not going to work.&lt;br /&gt;
&lt;br /&gt;
If you want to draw on the screen (for example to mark units) then the drawing will have to happen in unsynced code.&lt;br /&gt;
&lt;br /&gt;
The following example demonstrates how to change the value inside the synced block of code and read the value in the unsynced block of code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
if (gadgetHandler:IsSyncedCode()) then&lt;br /&gt;
   --SYNCED CODE&lt;br /&gt;
   local number = 10               -- declaring local variable &#039;number&#039;&lt;br /&gt;
   function gadget:SomeSyncedCallin(...)&lt;br /&gt;
      number=number+1              -- increment variable&#039;s value by one&lt;br /&gt;
      _G.number=number             -- save the new value into a global variable&lt;br /&gt;
   end&lt;br /&gt;
else&lt;br /&gt;
   --UNSYNCED CODE&lt;br /&gt;
   function gadget:SomeUnsyncedCallin(...)&lt;br /&gt;
      Spring.Echo(SYNCED.number)   -- print the global variable&#039;s value on screen&lt;br /&gt;
   end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 97.0+ ==&lt;br /&gt;
As of 97.0 unsynced gets most of the [[Lua:Callins|callins]] available to synced (excepting those which control synced actions e.g. AllowUnitCreation etc) which means that forwarding data from synced to unsynced is now not always required. Additionally in 97.0 there were changes to the behaviour of the &amp;lt;code&amp;gt;SYNCED&amp;lt;/code&amp;gt; table described above.&lt;br /&gt;
&lt;br /&gt;
Change 1: &amp;lt;code&amp;gt;SYNCED&amp;lt;/code&amp;gt; does a copy on access now and so is slow:&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Bad:    &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;for i=1,100 do ... SYNCED.foo[i] ... end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Good:   &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local foo = SYNCED.foo; for i=1,100 do ... foo[i] ... end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Change 2: &amp;lt;code&amp;gt;SYNCED.&amp;lt;/code&amp;gt; can&#039;t be localized in global scope anymore:&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Bad:    &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;local foo = SYNCED.foo;    function gadget:DrawWorld() ... foo ... end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Good:   &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;function gadget:DrawWorld() local foo = SYNCED.foo; ... foo ... end&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
Here is a (older) description by quantum:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;[LCC]quantum[0K] every time there  is a new target, i send &amp;quot;addTarget&amp;quot; and its number to unsynced&lt;br /&gt;
[LCC]quantum[0K] when a target is removed from the list, i send &amp;quot;removeTarget&amp;quot; and its number&lt;br /&gt;
[LCC]quantum[0K] this wierd stuff:&lt;br /&gt;
[LCC]quantum[0K]   _G.laserEventArgs = eventArgs&lt;br /&gt;
[LCC]quantum[0K]   SendToUnsynced(&amp;quot;LaserEvent&amp;quot;)&lt;br /&gt;
[LCC]quantum[0K]   _G.laserEventArgs = nil&lt;br /&gt;
[LCC]quantum[0K] puts the eventArgs table somewhere were unsynced can find it&lt;br /&gt;
[LCC]quantum[0K] sends a signal to unsynced called &amp;quot;LaserEvent&amp;quot;&lt;br /&gt;
[LCC]quantum[0K] and removes the table&lt;br /&gt;
[LCC]quantum[0K] on the unsynced side, _G.laserEventArgs becomes SYNCED.laserEventArgs&lt;br /&gt;
[LCC]quantum[0K] this copies it to another table, ready for reuse:   for k, v in spairs(SYNCED.laserEventArgs) do&lt;br /&gt;
[LCC]quantum[0K]     eventArgs[k] = v&lt;br /&gt;
[LCC]quantum[0K]   end&lt;br /&gt;
[LCC]quantum[0K] also, this: gadgetHandler:AddSyncAction(&amp;quot;LaserEvent&amp;quot;, HandleLaserEvent)&lt;br /&gt;
[LCC]quantum[0K] means that i want the HandleLaserEvent function to be called every time i do SendToUnsynced(&amp;quot;LaserEvent&amp;quot;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
http://pastebin.com/hMHv3Lrf&lt;br /&gt;
&lt;br /&gt;
[[Category: Lua]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Defense_buildings&amp;diff=2714</id>
		<title>Defense buildings</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Defense_buildings&amp;diff=2714"/>
		<updated>2026-03-09T05:41:57Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;== Defensive Buildings == &amp;lt;p&amp;gt;This details the various defensive buildings in XTA.&amp;lt;/p&amp;gt;  === Land-Based === &amp;lt;p&amp;gt;The larger group (as opposed to sea-based, and (maybe;)) eventually air-based). This group includes everything, from lasers to missiles and plasma cannon. &amp;lt;br&amp;gt;  * Ground Defense ** Lasers *** LLT (Light Laser Tower) *** HLT (Heavy Laser Tower) *** Energy Weapon ** Plasma *** Pop-Up Cannon *** Plasma Battery *** LRPC (Long Range Plasma Cannon - aka...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Defensive Buildings ==&lt;br /&gt;
&amp;lt;p&amp;gt;This details the various defensive buildings in [[XTA]].&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Land-Based ===&lt;br /&gt;
&amp;lt;p&amp;gt;The larger group (as opposed to sea-based, and (maybe;)) eventually air-based). This group includes everything, from lasers to missiles and plasma cannon.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Ground Defense&lt;br /&gt;
** Lasers&lt;br /&gt;
*** [[LLT]] (Light Laser Tower)&lt;br /&gt;
*** [[HLT]] (Heavy Laser Tower)&lt;br /&gt;
*** [[Energy Weapon]]&lt;br /&gt;
** Plasma&lt;br /&gt;
*** Pop-Up Cannon&lt;br /&gt;
*** Plasma Battery&lt;br /&gt;
*** LRPC (Long Range Plasma Cannon - aka VLRPC)&lt;br /&gt;
*** RFLRPC (Rapid-fire Long Range Plasma Cannon)&lt;br /&gt;
** Wall&lt;br /&gt;
*** Dragon&#039;s Teeth&lt;br /&gt;
*** Fortification Wall&lt;br /&gt;
* Air Defense&lt;br /&gt;
** Missiles&lt;br /&gt;
*** [[Missile Tower]]&lt;br /&gt;
** Flak&lt;br /&gt;
*** [[Flak Cannon]]&lt;br /&gt;
* Intelligence&lt;br /&gt;
** Radar&lt;br /&gt;
** Advanced Radar&lt;br /&gt;
** Jammer&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sea-Based ===&lt;br /&gt;
&amp;lt;p&amp;gt;This category include every definsive structure built on or under the sea.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Sea Defense&lt;br /&gt;
** Lasers&lt;br /&gt;
*** Floating HLT&lt;br /&gt;
** Torpedo&lt;br /&gt;
*** Depth Charge Launcher&lt;br /&gt;
*** Advanced Torpedo Launcher&lt;br /&gt;
** Anti-Air&lt;br /&gt;
*** Floating Missile Tower&lt;br /&gt;
&lt;br /&gt;
[[Category:Xta]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:Migrating_From_Spring&amp;diff=2712</id>
		<title>Recoil:Migrating From Spring</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:Migrating_From_Spring&amp;diff=2712"/>
		<updated>2026-03-06T06:28:02Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Recoil: Migrating From Spring =&lt;br /&gt;
&lt;br /&gt;
Recoil is a fork and continuation of the Spring RTS engine (version 105.0), maintained by the [https://github.com/beyond-all-reason Beyond All Reason] team. While Recoil is mostly compatible with Spring 105, divergences have naturally developed over time. This page lists the most relevant breaking changes and how to fix them when migrating a game or widget from Spring to Recoil.&lt;br /&gt;
&lt;br /&gt;
For a quick overview of why you might choose Recoil over the original Spring, see [[Recoil:Why Recoil]].&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Versioning ==&lt;br /&gt;
&lt;br /&gt;
Recoil switched to a date-based versioning scheme beginning with version 2025.01. Version numbers jump from 105-xxxx to 2025.xx. Use &amp;lt;code&amp;gt;Engine.versionMajor&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Engine.versionMinor&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Engine.commitsNumber&amp;lt;/code&amp;gt; instead of the old commit-count scheme where possible.&lt;br /&gt;
&lt;br /&gt;
For feature detection without version comparisons, use the &amp;lt;code&amp;gt;Engine.FeatureSupport&amp;lt;/code&amp;gt; table (see [[#Feature Support Table|Feature Support Table]] below).&lt;br /&gt;
&lt;br /&gt;
== Lua API Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Font Rendering ===&lt;br /&gt;
&lt;br /&gt;
Rendering fonts now obeys the GL color state. Text may appear in a different color than expected. To restore previous behavior, add a &amp;lt;code&amp;gt;gl.Color&amp;lt;/code&amp;gt; call before &amp;lt;code&amp;gt;gl.Text&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
gl.Color(1, 1, 1, 1)  -- set white before drawing text&lt;br /&gt;
gl.Text(&amp;quot;Hello world&amp;quot;, x, y, size, options)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Alternatively, use inline colour codes: &amp;lt;code&amp;gt;\255\rrr\ggg\bbb&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.Marker Functions ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.MarkerAddPoint&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.MarkerAddLine&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Spring.MarkerErasePosition&amp;lt;/code&amp;gt; no longer accept numerical &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;onlyLocal&amp;lt;/code&amp;gt; parameter. Lua semantics now apply (0 is truthy). Change &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
Spring.MarkerAddPoint(x, y, z, text, 0)&lt;br /&gt;
Spring.MarkerAddLine(x1, y1, z1, x2, y2, z2, 0, playerId)&lt;br /&gt;
Spring.MarkerErasePosition(x, y, z, noop, 0, playerId)&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
Spring.MarkerAddPoint(x, y, z, text, false)&lt;br /&gt;
Spring.MarkerAddLine(x1, y1, z1, x2, y2, z2, false, playerId)&lt;br /&gt;
Spring.MarkerErasePosition(x, y, z, noop, false, playerId)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetConfig Functions ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.GetConfigInt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.GetConfigFloat&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Spring.GetConfigString&amp;lt;/code&amp;gt; now accept &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; as the second argument (the default value to return if the key is not set). Previously, &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; was treated as &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; (Int/Float) or &amp;lt;code&amp;gt;&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (String).&lt;br /&gt;
&lt;br /&gt;
To restore previous behavior:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local originalGetConfigInt = Spring.GetConfigInt&lt;br /&gt;
Spring.GetConfigInt = function(key, def)&lt;br /&gt;
    return originalGetConfigInt(key, def) or 0&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetSelectedUnitsSorted and Spring.GetSelectedUnitsCounts ===&lt;br /&gt;
&lt;br /&gt;
The tables returned by &amp;lt;code&amp;gt;Spring.GetSelectedUnitsSorted&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.GetSelectedUnitsCounts&amp;lt;/code&amp;gt; no longer contain an &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; key with the number of unitDefs. Use the second return value instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
local counts = Spring.GetSelectedUnitsCounts()&lt;br /&gt;
local unitDefsCount = counts.n&lt;br /&gt;
counts.n = nil&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
local counts, unitDefsCount = Spring.GetSelectedUnitsCounts()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.UnitIconSetDraw Renamed ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.UnitIconSetDraw&amp;lt;/code&amp;gt; has been renamed to &amp;lt;code&amp;gt;Spring.SetUnitIconDraw&amp;lt;/code&amp;gt;. The old spelling still works but is deprecated and will be removed in a future version.&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetUnitCommands / Spring.GetCommandQueue ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.GetUnitCommands(unitID, 0)&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.GetCommandQueue&amp;lt;/code&amp;gt; are deprecated for counting queue size. Use the new dedicated function instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
local count = Spring.GetUnitCommands(unitID, 0)&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
local count = Spring.GetUnitCommandCount(unitID)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Similarly, &amp;lt;code&amp;gt;Spring.GetFactoryCommands(unitID, 0)&amp;lt;/code&amp;gt; is deprecated in favor of &amp;lt;code&amp;gt;Spring.GetFactoryCommandCount(unitID)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Iterating Synced Proxy Tables ===&lt;br /&gt;
&lt;br /&gt;
The functions &amp;lt;code&amp;gt;snext&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;spairs&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;sipairs&amp;lt;/code&amp;gt; for iterating synced proxy tables have been &#039;&#039;&#039;removed&#039;&#039;&#039;. They have been equivalent to the standard Lua &amp;lt;code&amp;gt;next&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pairs&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ipairs&amp;lt;/code&amp;gt; for years. Replace them:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
for key, value in spairs(SYNCED.foo) do ... end&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
for key, value in  pairs(SYNCED.foo) do ... end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VFS Mapping API ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.MapArchive&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.UnmapArchive&amp;lt;/code&amp;gt; (and equivalently &amp;lt;code&amp;gt;VFS.MapArchive&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;VFS.UnmapArchive&amp;lt;/code&amp;gt;) have been temporarily removed due to sync-unsafety. Use &amp;lt;code&amp;gt;Spring.UseArchive&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;VFS.UseArchive&amp;lt;/code&amp;gt; in the meantime. The original functions may return at a future date.&lt;br /&gt;
&lt;br /&gt;
=== UnitUnitCollision Return Value ===&lt;br /&gt;
&lt;br /&gt;
The return value from the &amp;lt;code&amp;gt;UnitUnitCollision&amp;lt;/code&amp;gt; callin is now ignored, and only one event fires per collision pair (instead of two). There is currently no replacement for the old two-event behavior.&lt;br /&gt;
&lt;br /&gt;
=== gl.GetMatrix Removed ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;gl.GetMatrix&amp;lt;/code&amp;gt; and the interface accessible from its returned matrix object have been removed. These were unused; no direct replacement is available.&lt;br /&gt;
&lt;br /&gt;
=== Removed Platform Constants ===&lt;br /&gt;
&lt;br /&gt;
The following depth buffer support constants have been removed:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport16bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport24bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport32bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Replace with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
Platform.glSupportDepthBufferBitDepth &amp;gt;= 16  -- or 24, or 32&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Game.allowTeamColors Removed ===&lt;br /&gt;
&lt;br /&gt;
The deprecated &amp;lt;code&amp;gt;Game.allowTeamColors&amp;lt;/code&amp;gt; entry (which was always &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;) has been removed. If you used it as a boolean condition, note that its removal inverts its effect — previously &amp;lt;code&amp;gt;if Game.allowTeamColors then&amp;lt;/code&amp;gt; was always true; now the variable is &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; (falsy).&lt;br /&gt;
&lt;br /&gt;
=== Removed UnitDefs Keys ===&lt;br /&gt;
&lt;br /&gt;
The following deprecated &amp;lt;code&amp;gt;UnitDefs&amp;lt;/code&amp;gt; keys have been removed (they previously returned zero with a warning):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Removed Key&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;techLevel&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;harvestStorage&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;extractSquare&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;canHover&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;drag&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;isAirBase&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;cloakTimeout&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;minx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;miny&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;minz&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;maxx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;maxy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;maxz&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;midx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;midy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;midz&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== tdfID Removed from WeaponDefs ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;tdfID&amp;lt;/code&amp;gt; field has been removed from weapon defs and the &amp;lt;code&amp;gt;WeaponDefs&amp;lt;/code&amp;gt; table. Any remaining interfaces receive &amp;lt;code&amp;gt;weaponDefID&amp;lt;/code&amp;gt; instead.&lt;br /&gt;
&lt;br /&gt;
=== loadstring Bytecode ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;loadstring&amp;lt;/code&amp;gt; in Lua no longer accepts bytecode by default, for safety and maintainability reasons. It still works in devmode.&lt;br /&gt;
&lt;br /&gt;
== Unit Defs ==&lt;br /&gt;
&lt;br /&gt;
=== Hovercraft and Ships Upright Behavior ===&lt;br /&gt;
&lt;br /&gt;
Hovercraft and ships brought out of water are no longer forced to be upright. To restore previous behavior, add &amp;lt;code&amp;gt;upright = true&amp;lt;/code&amp;gt; to all unit defs whose movedef is of the hovercraft or ship type.&lt;br /&gt;
&lt;br /&gt;
=== useFootPrintCollisionVolume ===&lt;br /&gt;
&lt;br /&gt;
Units with &amp;lt;code&amp;gt;useFootPrintCollisionVolume = true&amp;lt;/code&amp;gt; but no &amp;lt;code&amp;gt;collisionVolumeScales&amp;lt;/code&amp;gt; set will now actually use the footprint volume (previously they mistakenly used the model&#039;s bounding sphere).&lt;br /&gt;
&lt;br /&gt;
To keep the old hitvolume behavior, set &amp;lt;code&amp;gt;useFootPrintCollisionVolume = false&amp;lt;/code&amp;gt; for units with no &amp;lt;code&amp;gt;collisionVolumeScales&amp;lt;/code&amp;gt;. This can be done in unit def post-processing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
for unitDefID, unitDef in pairs(UnitDefs) do&lt;br /&gt;
    if not unitDef.collisionvolumescales then&lt;br /&gt;
        unitDef.usefootprintcollisionvolume = nil&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tree Feature Defs ===&lt;br /&gt;
&lt;br /&gt;
The feature defs &amp;lt;code&amp;gt;treetype0&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;treetype16&amp;lt;/code&amp;gt; are now provided by the basecontent archive instead of the engine itself. Games that shipped their own basecontent will know what to do; most games are unaffected.&lt;br /&gt;
&lt;br /&gt;
=== Firestarter Weapon Tag ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;firestarter&amp;lt;/code&amp;gt; weapon tag is no longer capped at 10000 in defs (which was 100 in Lua &amp;lt;code&amp;gt;WeaponDefs&amp;lt;/code&amp;gt; after rescaling). It is now uncapped.&lt;br /&gt;
&lt;br /&gt;
To restore the previous cap:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
for weaponDefID, weaponDef in pairs(WeaponDefs) do&lt;br /&gt;
    if weaponDef.firestarter then&lt;br /&gt;
        weaponDef.firestarter = math.min(weaponDef.firestarter, 10000)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== acceleration and brakeRate (Deprecation Warning) ===&lt;br /&gt;
&lt;br /&gt;
The unit def tags &amp;lt;code&amp;gt;acceleration&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;brakeRate&amp;lt;/code&amp;gt; are &#039;&#039;&#039;scheduled&#039;&#039;&#039; for a unit change from elmo/frame to elmo/second. They currently still work as before, but it is strongly recommended to migrate to &amp;lt;code&amp;gt;maxAcc&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;maxDec&amp;lt;/code&amp;gt; respectively, which will remain in elmo/frame:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- In unit def files or post-processing:&lt;br /&gt;
-- acceleration  →  maxAcc   (elmo/frame, unchanged)&lt;br /&gt;
-- brakeRate     →  maxDec   (elmo/frame, unchanged)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Def Validity Checks ===&lt;br /&gt;
&lt;br /&gt;
Some invalid or missing def values are now handled more strictly:&lt;br /&gt;
&lt;br /&gt;
* Negative values for health, speed, reverse speed, metal/energy/buildtime cause the unit def to be &#039;&#039;&#039;rejected&#039;&#039;&#039; (previously clamped to 0.1).&lt;br /&gt;
* Negative values for acceleration and brake rate cause rejection (previously, absolute value was used).&lt;br /&gt;
* Metal cost can now be 0 (undefined metal cost defaults to 0 instead of 1).&lt;br /&gt;
* Weapon &amp;lt;code&amp;gt;edgeEffectiveness&amp;lt;/code&amp;gt; can now be exactly 1 (previously capped at 0.999).&lt;br /&gt;
* Weapon damage and unit armor multiplier can now be 0.&lt;br /&gt;
&lt;br /&gt;
=== trackStretch Reciprocal Change ===&lt;br /&gt;
&lt;br /&gt;
The unit def &amp;lt;code&amp;gt;trackStretch&amp;lt;/code&amp;gt; values are now treated reciprocally. A stretch factor of 2 now means the track is stretched 2× longer (previously it was squeezed 0.5× shorter). Adjust your values accordingly.&lt;br /&gt;
&lt;br /&gt;
=== GroundDecals Springsetting ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;GroundDecals&amp;lt;/code&amp;gt; springsetting is now a boolean. The previous behavior of using it as a decal duration multiplier is gone. Use the new &amp;lt;code&amp;gt;scarTTL&amp;lt;/code&amp;gt; weapon def tag to set scar durations instead. Note: if you had explicitly set &amp;lt;code&amp;gt;scarTTL&amp;lt;/code&amp;gt; values, those are now used directly as the TTL (previously they were multiplied by the ground decal level, which defaulted to 3).&lt;br /&gt;
&lt;br /&gt;
=== Live Unit Footprint Size ===&lt;br /&gt;
&lt;br /&gt;
Live mobile units&#039; footprint is now the size of their movedef (previously the unit def footprint). This affects bugger-off behavior, construction blocking, and the size of the built-in selection square. Unit defs are unaffected; individual units adjust if their movedef changes at runtime.&lt;br /&gt;
&lt;br /&gt;
== Gameplay Behavior ==&lt;br /&gt;
&lt;br /&gt;
=== Stop Command on Manual Share ===&lt;br /&gt;
&lt;br /&gt;
Manually shared units no longer receive the Stop command automatically. To replicate the old behavior, use the &amp;lt;code&amp;gt;UnitGiven&amp;lt;/code&amp;gt; callin in a gadget or widget:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function wupget:UnitGiven(unitID, unitDefID, newTeam)&lt;br /&gt;
    if newTeam == Spring.GetMyTeamID() then  -- if in unsynced context&lt;br /&gt;
        Spring.GiveOrderToUnit(unitID, CMD.STOP, 0, 0)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Resurrecting Units ===&lt;br /&gt;
&lt;br /&gt;
Resurrecting units no longer overrides their health to 5%. To restore the old behavior, add a gadget with the following callin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function gadget:UnitCreated(unitID, unitDefID, teamID, builderID)&lt;br /&gt;
    if builderID and Spring.GetUnitWorkerTask(builderID) == CMD.RESURRECT then&lt;br /&gt;
        Spring.SetUnitHealth(unitID, Spring.GetUnitHealth(unitID) * 0.05)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weapons groundBounce ===&lt;br /&gt;
&lt;br /&gt;
Weapons with &amp;lt;code&amp;gt;groundBounce = true&amp;lt;/code&amp;gt; will now bounce even if &amp;lt;code&amp;gt;numBounces&amp;lt;/code&amp;gt; is undefined. The default value of &amp;lt;code&amp;gt;numBounces = -1&amp;lt;/code&amp;gt; now correctly results in infinite bounces instead of 0. Set an explicit positive value of &amp;lt;code&amp;gt;numBounces&amp;lt;/code&amp;gt; if you want a finite number of bounces.&lt;br /&gt;
&lt;br /&gt;
=== Default Targeting Priority Random Component Removed ===&lt;br /&gt;
&lt;br /&gt;
The default targeting priority for typed units no longer includes a ±30% random component. Use &amp;lt;code&amp;gt;gadget:AllowWeaponTarget&amp;lt;/code&amp;gt; to restore the previous randomized behavior.&lt;br /&gt;
&lt;br /&gt;
=== Handicap No Longer Applies to Reclaim ===&lt;br /&gt;
&lt;br /&gt;
Handicap (resource income multiplier) no longer applies to reclaim operations.&lt;br /&gt;
&lt;br /&gt;
== Camera ==&lt;br /&gt;
&lt;br /&gt;
=== Camera Modifiers ===&lt;br /&gt;
&lt;br /&gt;
The following keyboard modifiers were unhardcoded from the engine:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Spring Camera:&#039;&#039;&#039; rotating on the x (pitch) or y (yaw) axis with ALT + middle mouse button while moving the cursor.&lt;br /&gt;
* &#039;&#039;&#039;Resetting camera:&#039;&#039;&#039; ALT + mousewheel scroll down.&lt;br /&gt;
* &#039;&#039;&#039;Pitch rotation:&#039;&#039;&#039; CTRL + mousewheel.&lt;br /&gt;
&lt;br /&gt;
If games and players do not change engine defaults, no action is needed. To explicitly enable these modifiers as before, add the following keybindings:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
bind Any+ctrl movetilt    -- rotates camera over x axis on mousewheel&lt;br /&gt;
bind Any+alt  movereset   -- resets camera state on mousewheel move&lt;br /&gt;
bind Any+alt  moverotate  -- rotates camera in x/y on MMB move (Spring cam)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Rendering ==&lt;br /&gt;
&lt;br /&gt;
=== LOD Rendering Removed ===&lt;br /&gt;
&lt;br /&gt;
LOD rendering (2D unit billboards when zoomed far out) has been removed, along with the &amp;lt;code&amp;gt;/distdraw&amp;lt;/code&amp;gt; command and the &amp;lt;code&amp;gt;UnitLodDist&amp;lt;/code&amp;gt; springsetting entry.&lt;br /&gt;
&lt;br /&gt;
=== Dynamic Sky Removed ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AdvSky&amp;lt;/code&amp;gt; springsetting and the &amp;lt;code&amp;gt;/dynamicsky&amp;lt;/code&amp;gt; command (which made clouds move across the sky) have been removed. There is no direct replacement, though similar behavior can be achieved via custom Lua rendering.&lt;br /&gt;
&lt;br /&gt;
=== Screenshot Format Changed ===&lt;br /&gt;
&lt;br /&gt;
The default &amp;lt;code&amp;gt;/screenshot&amp;lt;/code&amp;gt; format is now PNG (was previously BMP/other). Review any automated processing pipelines that parse screenshot files. Screenshot and replay demo file names also now use a UTC timestamp format instead of a sequential number.&lt;br /&gt;
&lt;br /&gt;
=== Paletted Image Files ===&lt;br /&gt;
&lt;br /&gt;
Paletted image files are no longer accepted. Convert any paletted images to non-paletted format (e.g. standard RGBA PNG).&lt;br /&gt;
&lt;br /&gt;
=== Shader Changes for Unit Rendering ===&lt;br /&gt;
&lt;br /&gt;
Unit vertex shaders now receive TRS (Translation/Rotation/Scale) transforms instead of matrices. Code that calls &amp;lt;code&amp;gt;vbo:ModelsVBO()&amp;lt;/code&amp;gt; must update shader code. The skinning/bones interface at uniform location 5 was extended in version 2025.04 from &amp;lt;code&amp;gt;uvec2&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;uvec3&amp;lt;/code&amp;gt;. Update shaders from:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;glsl&amp;quot;&amp;gt;&lt;br /&gt;
// Old (pre-1775)&lt;br /&gt;
layout (location = 5) in uint pieceIndex;&lt;br /&gt;
&lt;br /&gt;
// After 1775 (bones/skinning era)&lt;br /&gt;
layout (location = 5) in uvec2 bonesInfo; // boneIDs, boneWeights&lt;br /&gt;
#define pieceIndex (bonesInfo.x &amp;amp; 0x000000FFu)&lt;br /&gt;
&lt;br /&gt;
// After 2025.04 (extended bone info)&lt;br /&gt;
layout (location = 5) in uvec3 bonesInfo; // boneID low byte, boneWeight, boneID high byte&lt;br /&gt;
#define pieceIndex (bonesInfo.x &amp;amp; 0x000000FFu)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Feature Support Table ==&lt;br /&gt;
&lt;br /&gt;
Recoil provides &amp;lt;code&amp;gt;Engine.FeatureSupport&amp;lt;/code&amp;gt;, a table of feature availability flags for forward/backward compatibility. Use it instead of &amp;lt;code&amp;gt;Script.IsEngineMinVersion&amp;lt;/code&amp;gt; where applicable, as it is self-documenting and does not assume linear commit numbering.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
if Engine.FeatureSupport.rmlUiApiVersion then&lt;br /&gt;
    -- use rmlUI&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Key entries include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Key !! Value !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;rmlUiApiVersion&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; || RmlUI GUI framework available&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;hasExitOnlyYardmaps&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || &#039;e&#039; yardmap tile available&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;NegativeGetUnitCurrentCommand&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || Negative indices work in GetUnitCurrentCommand&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;noAutoShowMetal&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; → future &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || Automatic metal map toggle deprecation tracking&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;maxPiecesPerModel&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;65534&amp;lt;/code&amp;gt; || Maximum pieces per model (was 254)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Death Event Changes ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;wupget:UnitDestroyed&amp;lt;/code&amp;gt; callin received several changes:&lt;br /&gt;
&lt;br /&gt;
* Now receives a 7th argument: &amp;lt;code&amp;gt;weaponDefID&amp;lt;/code&amp;gt;, the cause of death. This is never &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; — all causes of death are attributable.&lt;br /&gt;
* The builder is passed as the killer if a unit is reclaimed.&lt;br /&gt;
&lt;br /&gt;
New &amp;lt;code&amp;gt;Game.envDamageTypes&amp;lt;/code&amp;gt; constants identify the cause of death:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Constant !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AircraftCrashed&amp;lt;/code&amp;gt; || Aircraft hitting the ground&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Kamikaze&amp;lt;/code&amp;gt; || Unit exploding via kamikaze ability&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SelfD&amp;lt;/code&amp;gt; || Self-destruct command and countdown&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ConstructionDecay&amp;lt;/code&amp;gt; || Abandoned nanoframe disappearing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Reclaimed&amp;lt;/code&amp;gt; || Killed via reclaim&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TurnedIntoFeature&amp;lt;/code&amp;gt; || Died due to &amp;lt;code&amp;gt;isFeature&amp;lt;/code&amp;gt; tag on completion&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TransportKilled&amp;lt;/code&amp;gt; || Was in a transport with no &amp;lt;code&amp;gt;releaseHeld&amp;lt;/code&amp;gt; when transport died&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FactoryKilled&amp;lt;/code&amp;gt; || Was being built in a factory that died&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FactoryCancel&amp;lt;/code&amp;gt; || Build order was cancelled in factory&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UnitScript&amp;lt;/code&amp;gt; || COB script ordered the unit&#039;s death&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SetNegativeHealth&amp;lt;/code&amp;gt; || Unit had negative health for non-damage reasons (e.g. set via Lua)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;OutOfBounds&amp;lt;/code&amp;gt; || Unit was thrown far out of map bounds&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;KilledByCheat&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;/remove&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;/destroy&amp;lt;/code&amp;gt; command used&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;KilledByLua&amp;lt;/code&amp;gt; || Default cause when using &amp;lt;code&amp;gt;Spring.DestroyUnit&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;KilledByLua&amp;lt;/code&amp;gt; is guaranteed to be the last value. You can define custom damage type constants above it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
Game.envDamageTypes.MyCustomDeath = Game.envDamageTypes.KilledByLua - 1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Other Notable Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Python AI Bindings Removed ===&lt;br /&gt;
&lt;br /&gt;
Python bindings for AI have been removed. Only native C++ AIs are built by default via CMake.&lt;br /&gt;
&lt;br /&gt;
=== 32-bit Builds ===&lt;br /&gt;
&lt;br /&gt;
32-bit builds are no longer supported.&lt;br /&gt;
&lt;br /&gt;
=== Mandatory OpenGL Feature Support ===&lt;br /&gt;
&lt;br /&gt;
Support for non-power-of-2 textures, float textures, and framebuffer objects (FBO) is now mandatory. Systems that do not support these (all of which were common 15+ years ago) are no longer supported.&lt;br /&gt;
&lt;br /&gt;
=== /ally Command ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/ally&amp;lt;/code&amp;gt; command no longer announces the alliance to unrelated players via a console message. The affected players still see the message. Use the &amp;lt;code&amp;gt;TeamChanged&amp;lt;/code&amp;gt; callin if you want to make alliance announcements public.&lt;br /&gt;
&lt;br /&gt;
=== Group Command Change ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;/group add N&amp;lt;/code&amp;gt; no longer selects the entire group after adding units to it.&lt;br /&gt;
&lt;br /&gt;
=== Automatic Metal View Toggle (Deprecated) ===&lt;br /&gt;
&lt;br /&gt;
The metal view automatically toggling when a player begins building a metal extractor is deprecated. Disable it via &amp;lt;code&amp;gt;Spring.SetAutoShowMetal(false)&amp;lt;/code&amp;gt; and re-implement manually. An example widget is available at &amp;lt;code&amp;gt;(basecontent)/examples/Widgets/gui_autoshowmetal.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== DrawUnit and DrawFeature Widget Callins (Deprecated) ===&lt;br /&gt;
&lt;br /&gt;
Widget drawcalls such as &amp;lt;code&amp;gt;wupget:DrawUnit&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;wupget:DrawFeature&amp;lt;/code&amp;gt;, etc. are deprecated. Migrate to alternative rendering approaches.&lt;br /&gt;
&lt;br /&gt;
=== Removed Springsettings ===&lt;br /&gt;
&lt;br /&gt;
The following springsettings have been removed:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Springsetting !! Replacement / Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AdvSky&amp;lt;/code&amp;gt; || No direct replacement; use custom Lua rendering&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UnitLodDist&amp;lt;/code&amp;gt; || LOD rendering removed entirely&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UpdateWeaponVectorsMT&amp;lt;/code&amp;gt; || Removed (MT is now always on)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UpdateBoundingVolumeMT&amp;lt;/code&amp;gt; || Removed (MT is now always on)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AnimationMT&amp;lt;/code&amp;gt; || Removed after proving stable; animations always MT&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UsePBO&amp;lt;/code&amp;gt; || Already did nothing; removed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Removed Commands ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Command !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/distdraw&amp;lt;/code&amp;gt; || LOD rendering removed&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/dynamicsky&amp;lt;/code&amp;gt; || Dynamic sky removed&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/AdvModelShading&amp;lt;/code&amp;gt; || Removed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Recoil:Choose Recoil]] — Why switch from Spring to Recoil&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine/tree/master/doc/site/content/changelogs Full Recoil Changelog]&lt;br /&gt;
* [https://beyond-all-reason.github.io/spring/ldoc/ Recoil Lua API Reference]&lt;br /&gt;
* [https://discord.gg/GUpRg6Wz3e Recoil Discord] — for help and support&lt;br /&gt;
&lt;br /&gt;
[[Category:Recoil]]&lt;br /&gt;
[[Category:Engine]]&lt;br /&gt;
[[Category:Migration]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:Migrating_From_Spring&amp;diff=2711</id>
		<title>Recoil:Migrating From Spring</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:Migrating_From_Spring&amp;diff=2711"/>
		<updated>2026-03-06T06:26:10Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Recoil: Migrating From Spring =&lt;br /&gt;
&lt;br /&gt;
Recoil is a fork and continuation of the Spring RTS engine (version 105.0), maintained by the [https://github.com/beyond-all-reason Beyond All Reason] team. While Recoil is mostly compatible with Spring 105, divergences have naturally developed over time. This page lists the most relevant breaking changes and how to fix them when migrating a game or widget from Spring to Recoil.&lt;br /&gt;
&lt;br /&gt;
For a quick overview of why you might choose Recoil over the original Spring, see [[Recoil:Why Recoil]].&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Versioning ==&lt;br /&gt;
&lt;br /&gt;
Recoil switched to a date-based versioning scheme beginning with version 2025.01. Version numbers jump from 105-xxxx to 2025.xx. Use &amp;lt;code&amp;gt;Engine.versionMajor&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Engine.versionMinor&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Engine.commitsNumber&amp;lt;/code&amp;gt; instead of the old commit-count scheme where possible.&lt;br /&gt;
&lt;br /&gt;
For feature detection without version comparisons, use the &amp;lt;code&amp;gt;Engine.FeatureSupport&amp;lt;/code&amp;gt; table (see [[#Feature Support Table|Feature Support Table]] below).&lt;br /&gt;
&lt;br /&gt;
== Lua API Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Font Rendering ===&lt;br /&gt;
&lt;br /&gt;
Rendering fonts now obeys the GL color state. Text may appear in a different color than expected. To restore previous behavior, add a &amp;lt;code&amp;gt;gl.Color&amp;lt;/code&amp;gt; call before &amp;lt;code&amp;gt;gl.Text&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
gl.Color(1, 1, 1, 1)  -- set white before drawing text&lt;br /&gt;
gl.Text(&amp;quot;Hello world&amp;quot;, x, y, size, options)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Alternatively, use inline colour codes: &amp;lt;code&amp;gt;\255\rrr\ggg\bbb&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.Marker Functions ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.MarkerAddPoint&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.MarkerAddLine&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Spring.MarkerErasePosition&amp;lt;/code&amp;gt; no longer accept numerical &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;onlyLocal&amp;lt;/code&amp;gt; parameter. Lua semantics now apply (0 is truthy). Change &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
Spring.MarkerAddPoint(x, y, z, text, 0)&lt;br /&gt;
Spring.MarkerAddLine(x1, y1, z1, x2, y2, z2, 0, playerId)&lt;br /&gt;
Spring.MarkerErasePosition(x, y, z, noop, 0, playerId)&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
Spring.MarkerAddPoint(x, y, z, text, false)&lt;br /&gt;
Spring.MarkerAddLine(x1, y1, z1, x2, y2, z2, false, playerId)&lt;br /&gt;
Spring.MarkerErasePosition(x, y, z, noop, false, playerId)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetConfig Functions ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.GetConfigInt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.GetConfigFloat&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Spring.GetConfigString&amp;lt;/code&amp;gt; now accept &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; as the second argument (the default value to return if the key is not set). Previously, &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; was treated as &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; (Int/Float) or &amp;lt;code&amp;gt;&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (String).&lt;br /&gt;
&lt;br /&gt;
To restore previous behavior:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local originalGetConfigInt = Spring.GetConfigInt&lt;br /&gt;
Spring.GetConfigInt = function(key, def)&lt;br /&gt;
    return originalGetConfigInt(key, def) or 0&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetSelectedUnitsSorted and Spring.GetSelectedUnitsCounts ===&lt;br /&gt;
&lt;br /&gt;
The tables returned by &amp;lt;code&amp;gt;Spring.GetSelectedUnitsSorted&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.GetSelectedUnitsCounts&amp;lt;/code&amp;gt; no longer contain an &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; key with the number of unitDefs. Use the second return value instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
local counts = Spring.GetSelectedUnitsCounts()&lt;br /&gt;
local unitDefsCount = counts.n&lt;br /&gt;
counts.n = nil&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
local counts, unitDefsCount = Spring.GetSelectedUnitsCounts()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.UnitIconSetDraw Renamed ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.UnitIconSetDraw&amp;lt;/code&amp;gt; has been renamed to &amp;lt;code&amp;gt;Spring.SetUnitIconDraw&amp;lt;/code&amp;gt;. The old spelling still works but is deprecated and will be removed in a future version.&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetUnitCommands / Spring.GetCommandQueue ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.GetUnitCommands(unitID, 0)&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.GetCommandQueue&amp;lt;/code&amp;gt; are deprecated for counting queue size. Use the new dedicated function instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
local count = Spring.GetUnitCommands(unitID, 0)&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
local count = Spring.GetUnitCommandCount(unitID)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Similarly, &amp;lt;code&amp;gt;Spring.GetFactoryCommands(unitID, 0)&amp;lt;/code&amp;gt; is deprecated in favor of &amp;lt;code&amp;gt;Spring.GetFactoryCommandCount(unitID)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Iterating Synced Proxy Tables ===&lt;br /&gt;
&lt;br /&gt;
The functions &amp;lt;code&amp;gt;snext&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;spairs&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;sipairs&amp;lt;/code&amp;gt; for iterating synced proxy tables have been &#039;&#039;&#039;removed&#039;&#039;&#039;. They have been equivalent to the standard Lua &amp;lt;code&amp;gt;next&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pairs&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ipairs&amp;lt;/code&amp;gt; for years. Replace them:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
for key, value in spairs(SYNCED.foo) do ... end&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
for key, value in  pairs(SYNCED.foo) do ... end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VFS Mapping API ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.MapArchive&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.UnmapArchive&amp;lt;/code&amp;gt; (and equivalently &amp;lt;code&amp;gt;VFS.MapArchive&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;VFS.UnmapArchive&amp;lt;/code&amp;gt;) have been temporarily removed due to sync-unsafety. Use &amp;lt;code&amp;gt;Spring.UseArchive&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;VFS.UseArchive&amp;lt;/code&amp;gt; in the meantime. The original functions may return at a future date.&lt;br /&gt;
&lt;br /&gt;
=== UnitUnitCollision Return Value ===&lt;br /&gt;
&lt;br /&gt;
The return value from the &amp;lt;code&amp;gt;UnitUnitCollision&amp;lt;/code&amp;gt; callin is now ignored, and only one event fires per collision pair (instead of two). There is currently no replacement for the old two-event behavior.&lt;br /&gt;
&lt;br /&gt;
=== gl.GetMatrix Removed ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;gl.GetMatrix&amp;lt;/code&amp;gt; and the interface accessible from its returned matrix object have been removed. These were unused; no direct replacement is available.&lt;br /&gt;
&lt;br /&gt;
=== Removed Platform Constants ===&lt;br /&gt;
&lt;br /&gt;
The following depth buffer support constants have been removed:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport16bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport24bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport32bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Replace with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
Platform.glSupportDepthBufferBitDepth &amp;gt;= 16  -- or 24, or 32&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Game.allowTeamColors Removed ===&lt;br /&gt;
&lt;br /&gt;
The deprecated &amp;lt;code&amp;gt;Game.allowTeamColors&amp;lt;/code&amp;gt; entry (which was always &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;) has been removed. If you used it as a boolean condition, note that its removal inverts its effect — previously &amp;lt;code&amp;gt;if Game.allowTeamColors then&amp;lt;/code&amp;gt; was always true; now the variable is &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; (falsy).&lt;br /&gt;
&lt;br /&gt;
=== Removed UnitDefs Keys ===&lt;br /&gt;
&lt;br /&gt;
The following deprecated &amp;lt;code&amp;gt;UnitDefs&amp;lt;/code&amp;gt; keys have been removed (they previously returned zero with a warning):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Removed Key&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;techLevel&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;harvestStorage&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;extractSquare&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;canHover&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;drag&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;isAirBase&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;cloakTimeout&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;minx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;miny&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;minz&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;maxx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;maxy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;maxz&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;midx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;midy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;midz&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== tdfID Removed from WeaponDefs ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;tdfID&amp;lt;/code&amp;gt; field has been removed from weapon defs and the &amp;lt;code&amp;gt;WeaponDefs&amp;lt;/code&amp;gt; table. Any remaining interfaces receive &amp;lt;code&amp;gt;weaponDefID&amp;lt;/code&amp;gt; instead.&lt;br /&gt;
&lt;br /&gt;
=== loadstring Bytecode ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;loadstring&amp;lt;/code&amp;gt; in Lua no longer accepts bytecode by default, for safety and maintainability reasons. It still works in devmode.&lt;br /&gt;
&lt;br /&gt;
== Unit Defs ==&lt;br /&gt;
&lt;br /&gt;
=== Hovercraft and Ships Upright Behavior ===&lt;br /&gt;
&lt;br /&gt;
Hovercraft and ships brought out of water are no longer forced to be upright. To restore previous behavior, add &amp;lt;code&amp;gt;upright = true&amp;lt;/code&amp;gt; to all unit defs whose movedef is of the hovercraft or ship type.&lt;br /&gt;
&lt;br /&gt;
=== useFootPrintCollisionVolume ===&lt;br /&gt;
&lt;br /&gt;
Units with &amp;lt;code&amp;gt;useFootPrintCollisionVolume = true&amp;lt;/code&amp;gt; but no &amp;lt;code&amp;gt;collisionVolumeScales&amp;lt;/code&amp;gt; set will now actually use the footprint volume (previously they mistakenly used the model&#039;s bounding sphere).&lt;br /&gt;
&lt;br /&gt;
To keep the old hitvolume behavior, set &amp;lt;code&amp;gt;useFootPrintCollisionVolume = false&amp;lt;/code&amp;gt; for units with no &amp;lt;code&amp;gt;collisionVolumeScales&amp;lt;/code&amp;gt;. This can be done in unit def post-processing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
for unitDefID, unitDef in pairs(UnitDefs) do&lt;br /&gt;
    if not unitDef.collisionvolumescales then&lt;br /&gt;
        unitDef.usefootprintcollisionvolume = nil&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tree Feature Defs ===&lt;br /&gt;
&lt;br /&gt;
The feature defs &amp;lt;code&amp;gt;treetype0&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;treetype16&amp;lt;/code&amp;gt; are now provided by the basecontent archive instead of the engine itself. Games that shipped their own basecontent will know what to do; most games are unaffected.&lt;br /&gt;
&lt;br /&gt;
=== Firestarter Weapon Tag ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;firestarter&amp;lt;/code&amp;gt; weapon tag is no longer capped at 10000 in defs (which was 100 in Lua &amp;lt;code&amp;gt;WeaponDefs&amp;lt;/code&amp;gt; after rescaling). It is now uncapped.&lt;br /&gt;
&lt;br /&gt;
To restore the previous cap:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
for weaponDefID, weaponDef in pairs(WeaponDefs) do&lt;br /&gt;
    if weaponDef.firestarter then&lt;br /&gt;
        weaponDef.firestarter = math.min(weaponDef.firestarter, 10000)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== acceleration and brakeRate (Deprecation Warning) ===&lt;br /&gt;
&lt;br /&gt;
The unit def tags &amp;lt;code&amp;gt;acceleration&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;brakeRate&amp;lt;/code&amp;gt; are &#039;&#039;&#039;scheduled&#039;&#039;&#039; for a unit change from elmo/frame to elmo/second. They currently still work as before, but it is strongly recommended to migrate to &amp;lt;code&amp;gt;maxAcc&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;maxDec&amp;lt;/code&amp;gt; respectively, which will remain in elmo/frame:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- In unit def files or post-processing:&lt;br /&gt;
-- acceleration  →  maxAcc   (elmo/frame, unchanged)&lt;br /&gt;
-- brakeRate     →  maxDec   (elmo/frame, unchanged)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Def Validity Checks ===&lt;br /&gt;
&lt;br /&gt;
Some invalid or missing def values are now handled more strictly:&lt;br /&gt;
&lt;br /&gt;
* Negative values for health, speed, reverse speed, metal/energy/buildtime cause the unit def to be &#039;&#039;&#039;rejected&#039;&#039;&#039; (previously clamped to 0.1).&lt;br /&gt;
* Negative values for acceleration and brake rate cause rejection (previously, absolute value was used).&lt;br /&gt;
* Metal cost can now be 0 (undefined metal cost defaults to 0 instead of 1).&lt;br /&gt;
* Weapon &amp;lt;code&amp;gt;edgeEffectiveness&amp;lt;/code&amp;gt; can now be exactly 1 (previously capped at 0.999).&lt;br /&gt;
* Weapon damage and unit armor multiplier can now be 0.&lt;br /&gt;
&lt;br /&gt;
=== trackStretch Reciprocal Change ===&lt;br /&gt;
&lt;br /&gt;
The unit def &amp;lt;code&amp;gt;trackStretch&amp;lt;/code&amp;gt; values are now treated reciprocally. A stretch factor of 2 now means the track is stretched 2× longer (previously it was squeezed 0.5× shorter). Adjust your values accordingly.&lt;br /&gt;
&lt;br /&gt;
=== GroundDecals Springsetting ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;GroundDecals&amp;lt;/code&amp;gt; springsetting is now a boolean. The previous behavior of using it as a decal duration multiplier is gone. Use the new &amp;lt;code&amp;gt;scarTTL&amp;lt;/code&amp;gt; weapon def tag to set scar durations instead. Note: if you had explicitly set &amp;lt;code&amp;gt;scarTTL&amp;lt;/code&amp;gt; values, those are now used directly as the TTL (previously they were multiplied by the ground decal level, which defaulted to 3).&lt;br /&gt;
&lt;br /&gt;
=== Live Unit Footprint Size ===&lt;br /&gt;
&lt;br /&gt;
Live mobile units&#039; footprint is now the size of their movedef (previously the unit def footprint). This affects bugger-off behavior, construction blocking, and the size of the built-in selection square. Unit defs are unaffected; individual units adjust if their movedef changes at runtime.&lt;br /&gt;
&lt;br /&gt;
== Gameplay Behavior ==&lt;br /&gt;
&lt;br /&gt;
=== Stop Command on Manual Share ===&lt;br /&gt;
&lt;br /&gt;
Manually shared units no longer receive the Stop command automatically. To replicate the old behavior, use the &amp;lt;code&amp;gt;UnitGiven&amp;lt;/code&amp;gt; callin in a gadget or widget:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function wupget:UnitGiven(unitID, unitDefID, newTeam)&lt;br /&gt;
    if newTeam == Spring.GetMyTeamID() then  -- if in unsynced context&lt;br /&gt;
        Spring.GiveOrderToUnit(unitID, CMD.STOP, 0, 0)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Resurrecting Units ===&lt;br /&gt;
&lt;br /&gt;
Resurrecting units no longer overrides their health to 5%. To restore the old behavior, add a gadget with the following callin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function gadget:UnitCreated(unitID, unitDefID, teamID, builderID)&lt;br /&gt;
    if builderID and Spring.GetUnitWorkerTask(builderID) == CMD.RESURRECT then&lt;br /&gt;
        Spring.SetUnitHealth(unitID, Spring.GetUnitHealth(unitID) * 0.05)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weapons groundBounce ===&lt;br /&gt;
&lt;br /&gt;
Weapons with &amp;lt;code&amp;gt;groundBounce = true&amp;lt;/code&amp;gt; will now bounce even if &amp;lt;code&amp;gt;numBounces&amp;lt;/code&amp;gt; is undefined. The default value of &amp;lt;code&amp;gt;numBounces = -1&amp;lt;/code&amp;gt; now correctly results in infinite bounces instead of 0. Set an explicit positive value of &amp;lt;code&amp;gt;numBounces&amp;lt;/code&amp;gt; if you want a finite number of bounces.&lt;br /&gt;
&lt;br /&gt;
=== Default Targeting Priority Random Component Removed ===&lt;br /&gt;
&lt;br /&gt;
The default targeting priority for typed units no longer includes a ±30% random component. Use &amp;lt;code&amp;gt;gadget:AllowWeaponTarget&amp;lt;/code&amp;gt; to restore the previous randomized behavior.&lt;br /&gt;
&lt;br /&gt;
=== Handicap No Longer Applies to Reclaim ===&lt;br /&gt;
&lt;br /&gt;
Handicap (resource income multiplier) no longer applies to reclaim operations.&lt;br /&gt;
&lt;br /&gt;
== Camera ==&lt;br /&gt;
&lt;br /&gt;
=== Camera Modifiers ===&lt;br /&gt;
&lt;br /&gt;
The following keyboard modifiers were unhardcoded from the engine:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Spring Camera:&#039;&#039;&#039; rotating on the x (pitch) or y (yaw) axis with ALT + middle mouse button while moving the cursor.&lt;br /&gt;
* &#039;&#039;&#039;Resetting camera:&#039;&#039;&#039; ALT + mousewheel scroll down.&lt;br /&gt;
* &#039;&#039;&#039;Pitch rotation:&#039;&#039;&#039; CTRL + mousewheel.&lt;br /&gt;
&lt;br /&gt;
If games and players do not change engine defaults, no action is needed. To explicitly enable these modifiers as before, add the following keybindings:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
bind Any+ctrl movetilt    -- rotates camera over x axis on mousewheel&lt;br /&gt;
bind Any+alt  movereset   -- resets camera state on mousewheel move&lt;br /&gt;
bind Any+alt  moverotate  -- rotates camera in x/y on MMB move (Spring cam)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Rendering ==&lt;br /&gt;
&lt;br /&gt;
=== LOD Rendering Removed ===&lt;br /&gt;
&lt;br /&gt;
LOD rendering (2D unit billboards when zoomed far out) has been removed, along with the &amp;lt;code&amp;gt;/distdraw&amp;lt;/code&amp;gt; command and the &amp;lt;code&amp;gt;UnitLodDist&amp;lt;/code&amp;gt; springsetting entry.&lt;br /&gt;
&lt;br /&gt;
=== Dynamic Sky Removed ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AdvSky&amp;lt;/code&amp;gt; springsetting and the &amp;lt;code&amp;gt;/dynamicsky&amp;lt;/code&amp;gt; command (which made clouds move across the sky) have been removed. There is no direct replacement, though similar behavior can be achieved via custom Lua rendering.&lt;br /&gt;
&lt;br /&gt;
=== Screenshot Format Changed ===&lt;br /&gt;
&lt;br /&gt;
The default &amp;lt;code&amp;gt;/screenshot&amp;lt;/code&amp;gt; format is now PNG (was previously BMP/other). Review any automated processing pipelines that parse screenshot files. Screenshot and replay demo file names also now use a UTC timestamp format instead of a sequential number.&lt;br /&gt;
&lt;br /&gt;
=== Paletted Image Files ===&lt;br /&gt;
&lt;br /&gt;
Paletted image files are no longer accepted. Convert any paletted images to non-paletted format (e.g. standard RGBA PNG).&lt;br /&gt;
&lt;br /&gt;
=== Shader Changes for Unit Rendering ===&lt;br /&gt;
&lt;br /&gt;
Unit vertex shaders now receive TRS (Translation/Rotation/Scale) transforms instead of matrices. Code that calls &amp;lt;code&amp;gt;vbo:ModelsVBO()&amp;lt;/code&amp;gt; must update shader code. The skinning/bones interface at uniform location 5 was extended in version 2025.04 from &amp;lt;code&amp;gt;uvec2&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;uvec3&amp;lt;/code&amp;gt;. Update shaders from:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;glsl&amp;quot;&amp;gt;&lt;br /&gt;
// Old (pre-1775)&lt;br /&gt;
layout (location = 5) in uint pieceIndex;&lt;br /&gt;
&lt;br /&gt;
// After 1775 (bones/skinning era)&lt;br /&gt;
layout (location = 5) in uvec2 bonesInfo; // boneIDs, boneWeights&lt;br /&gt;
#define pieceIndex (bonesInfo.x &amp;amp; 0x000000FFu)&lt;br /&gt;
&lt;br /&gt;
// After 2025.04 (extended bone info)&lt;br /&gt;
layout (location = 5) in uvec3 bonesInfo; // boneID low byte, boneWeight, boneID high byte&lt;br /&gt;
#define pieceIndex (bonesInfo.x &amp;amp; 0x000000FFu)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Feature Support Table ==&lt;br /&gt;
&lt;br /&gt;
Recoil provides &amp;lt;code&amp;gt;Engine.FeatureSupport&amp;lt;/code&amp;gt;, a table of feature availability flags for forward/backward compatibility. Use it instead of &amp;lt;code&amp;gt;Script.IsEngineMinVersion&amp;lt;/code&amp;gt; where applicable, as it is self-documenting and does not assume linear commit numbering.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
if Engine.FeatureSupport.rmlUiApiVersion then&lt;br /&gt;
    -- use rmlUI&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Key entries include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Key !! Value !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;rmlUiApiVersion&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; || RmlUI GUI framework available&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;hasExitOnlyYardmaps&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || &#039;e&#039; yardmap tile available&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;NegativeGetUnitCurrentCommand&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || Negative indices work in GetUnitCurrentCommand&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;noAutoShowMetal&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; → future &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || Automatic metal map toggle deprecation tracking&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;maxPiecesPerModel&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;65534&amp;lt;/code&amp;gt; || Maximum pieces per model (was 254)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Death Event Changes ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;wupget:UnitDestroyed&amp;lt;/code&amp;gt; callin received several changes:&lt;br /&gt;
&lt;br /&gt;
* Now receives a 7th argument: &amp;lt;code&amp;gt;weaponDefID&amp;lt;/code&amp;gt;, the cause of death. This is never &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; — all causes of death are attributable.&lt;br /&gt;
* The builder is passed as the killer if a unit is reclaimed.&lt;br /&gt;
&lt;br /&gt;
New &amp;lt;code&amp;gt;Game.envDamageTypes&amp;lt;/code&amp;gt; constants identify the cause of death:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Constant !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AircraftCrashed&amp;lt;/code&amp;gt; || Aircraft hitting the ground&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Kamikaze&amp;lt;/code&amp;gt; || Unit exploding via kamikaze ability&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SelfD&amp;lt;/code&amp;gt; || Self-destruct command and countdown&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ConstructionDecay&amp;lt;/code&amp;gt; || Abandoned nanoframe disappearing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Reclaimed&amp;lt;/code&amp;gt; || Killed via reclaim&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TurnedIntoFeature&amp;lt;/code&amp;gt; || Died due to &amp;lt;code&amp;gt;isFeature&amp;lt;/code&amp;gt; tag on completion&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TransportKilled&amp;lt;/code&amp;gt; || Was in a transport with no &amp;lt;code&amp;gt;releaseHeld&amp;lt;/code&amp;gt; when transport died&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FactoryKilled&amp;lt;/code&amp;gt; || Was being built in a factory that died&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FactoryCancel&amp;lt;/code&amp;gt; || Build order was cancelled in factory&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UnitScript&amp;lt;/code&amp;gt; || COB script ordered the unit&#039;s death&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SetNegativeHealth&amp;lt;/code&amp;gt; || Unit had negative health for non-damage reasons (e.g. set via Lua)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;OutOfBounds&amp;lt;/code&amp;gt; || Unit was thrown far out of map bounds&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;KilledByCheat&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;/remove&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;/destroy&amp;lt;/code&amp;gt; command used&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;KilledByLua&amp;lt;/code&amp;gt; || Default cause when using &amp;lt;code&amp;gt;Spring.DestroyUnit&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;KilledByLua&amp;lt;/code&amp;gt; is guaranteed to be the last value. You can define custom damage type constants above it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
Game.envDamageTypes.MyCustomDeath = Game.envDamageTypes.KilledByLua - 1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Other Notable Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Python AI Bindings Removed ===&lt;br /&gt;
&lt;br /&gt;
Python bindings for AI have been removed. Only native C++ AIs are built by default via CMake.&lt;br /&gt;
&lt;br /&gt;
=== 32-bit Builds ===&lt;br /&gt;
&lt;br /&gt;
32-bit builds are no longer supported.&lt;br /&gt;
&lt;br /&gt;
=== Mandatory OpenGL Feature Support ===&lt;br /&gt;
&lt;br /&gt;
Support for non-power-of-2 textures, float textures, and framebuffer objects (FBO) is now mandatory. Systems that do not support these (all of which were common 15+ years ago) are no longer supported.&lt;br /&gt;
&lt;br /&gt;
=== /ally Command ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/ally&amp;lt;/code&amp;gt; command no longer announces the alliance to unrelated players via a console message. The affected players still see the message. Use the &amp;lt;code&amp;gt;TeamChanged&amp;lt;/code&amp;gt; callin if you want to make alliance announcements public.&lt;br /&gt;
&lt;br /&gt;
=== Group Command Change ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;/group add N&amp;lt;/code&amp;gt; no longer selects the entire group after adding units to it.&lt;br /&gt;
&lt;br /&gt;
=== Automatic Metal View Toggle (Deprecated) ===&lt;br /&gt;
&lt;br /&gt;
The metal view automatically toggling when a player begins building a metal extractor is deprecated. Disable it via &amp;lt;code&amp;gt;Spring.SetAutoShowMetal(false)&amp;lt;/code&amp;gt; and re-implement manually. An example widget is available at &amp;lt;code&amp;gt;(basecontent)/examples/Widgets/gui_autoshowmetal.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== DrawUnit and DrawFeature Widget Callins (Deprecated) ===&lt;br /&gt;
&lt;br /&gt;
Widget drawcalls such as &amp;lt;code&amp;gt;wupget:DrawUnit&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;wupget:DrawFeature&amp;lt;/code&amp;gt;, etc. are deprecated. Migrate to alternative rendering approaches.&lt;br /&gt;
&lt;br /&gt;
=== Removed Springsettings ===&lt;br /&gt;
&lt;br /&gt;
The following springsettings have been removed:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Springsetting !! Replacement / Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AdvSky&amp;lt;/code&amp;gt; || No direct replacement; use custom Lua rendering&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UnitLodDist&amp;lt;/code&amp;gt; || LOD rendering removed entirely&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UpdateWeaponVectorsMT&amp;lt;/code&amp;gt; || Removed (MT is now always on)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UpdateBoundingVolumeMT&amp;lt;/code&amp;gt; || Removed (MT is now always on)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AnimationMT&amp;lt;/code&amp;gt; || Removed after proving stable; animations always MT&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UsePBO&amp;lt;/code&amp;gt; || Already did nothing; removed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Removed Commands ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Command !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/distdraw&amp;lt;/code&amp;gt; || LOD rendering removed&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/dynamicsky&amp;lt;/code&amp;gt; || Dynamic sky removed&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/AdvModelShading&amp;lt;/code&amp;gt; || Removed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Recoil:Choose Recoil]] — Why switch from Spring to Recoil&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine/tree/master/doc/site/content/changelogs Full Recoil Changelog]&lt;br /&gt;
* [https://beyond-all-reason.github.io/spring/ldoc/ Recoil Lua API Reference]&lt;br /&gt;
* [https://discord.gg/GUpRg6Wz3e Recoil Discord] — for help and support&lt;br /&gt;
&lt;br /&gt;
[[Category:Recoil]]&lt;br /&gt;
[[Category:Engine]]&lt;br /&gt;
[[Category:Migration]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:Migrating_From_Spring&amp;diff=2710</id>
		<title>Recoil:Migrating From Spring</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:Migrating_From_Spring&amp;diff=2710"/>
		<updated>2026-03-06T06:23:44Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;= Recoil: Migrating From Spring =  Recoil is a fork and continuation of the Spring RTS engine (version 105.0), maintained by the [https://github.com/beyond-all-reason Beyond All Reason] team. While Recoil is mostly compatible with Spring 105, divergences have naturally developed over time. This page lists the most relevant breaking changes and how to fix them when migrating a game or widget from Spring to Recoil.  For a quick overview of why you might choose Recoil over...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Recoil: Migrating From Spring =&lt;br /&gt;
&lt;br /&gt;
Recoil is a fork and continuation of the Spring RTS engine (version 105.0), maintained by the [https://github.com/beyond-all-reason Beyond All Reason] team. While Recoil is mostly compatible with Spring 105, divergences have naturally developed over time. This page lists the most relevant breaking changes and how to fix them when migrating a game or widget from Spring to Recoil.&lt;br /&gt;
&lt;br /&gt;
For a quick overview of why you might choose Recoil over the original Spring, see [[Recoil:Choose Recoil]].&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== Versioning ==&lt;br /&gt;
&lt;br /&gt;
Recoil switched to a date-based versioning scheme beginning with version 2025.01. Version numbers jump from 105-xxxx to 2025.xx. Use &amp;lt;code&amp;gt;Engine.versionMajor&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Engine.versionMinor&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Engine.commitsNumber&amp;lt;/code&amp;gt; instead of the old commit-count scheme where possible.&lt;br /&gt;
&lt;br /&gt;
For feature detection without version comparisons, use the &amp;lt;code&amp;gt;Engine.FeatureSupport&amp;lt;/code&amp;gt; table (see [[#Feature Support Table|Feature Support Table]] below).&lt;br /&gt;
&lt;br /&gt;
== Lua API Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Font Rendering ===&lt;br /&gt;
&lt;br /&gt;
Rendering fonts now obeys the GL color state. Text may appear in a different color than expected. To restore previous behavior, add a &amp;lt;code&amp;gt;gl.Color&amp;lt;/code&amp;gt; call before &amp;lt;code&amp;gt;gl.Text&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
gl.Color(1, 1, 1, 1)  -- set white before drawing text&lt;br /&gt;
gl.Text(&amp;quot;Hello world&amp;quot;, x, y, size, options)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Alternatively, use inline colour codes: &amp;lt;code&amp;gt;\255\rrr\ggg\bbb&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.Marker Functions ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.MarkerAddPoint&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.MarkerAddLine&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Spring.MarkerErasePosition&amp;lt;/code&amp;gt; no longer accept numerical &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;onlyLocal&amp;lt;/code&amp;gt; parameter. Lua semantics now apply (0 is truthy). Change &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
Spring.MarkerAddPoint(x, y, z, text, 0)&lt;br /&gt;
Spring.MarkerAddLine(x1, y1, z1, x2, y2, z2, 0, playerId)&lt;br /&gt;
Spring.MarkerErasePosition(x, y, z, noop, 0, playerId)&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
Spring.MarkerAddPoint(x, y, z, text, false)&lt;br /&gt;
Spring.MarkerAddLine(x1, y1, z1, x2, y2, z2, false, playerId)&lt;br /&gt;
Spring.MarkerErasePosition(x, y, z, noop, false, playerId)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetConfig Functions ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.GetConfigInt&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.GetConfigFloat&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Spring.GetConfigString&amp;lt;/code&amp;gt; now accept &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; as the second argument (the default value to return if the key is not set). Previously, &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; was treated as &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; (Int/Float) or &amp;lt;code&amp;gt;&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; (String).&lt;br /&gt;
&lt;br /&gt;
To restore previous behavior:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local originalGetConfigInt = Spring.GetConfigInt&lt;br /&gt;
Spring.GetConfigInt = function(key, def)&lt;br /&gt;
    return originalGetConfigInt(key, def) or 0&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetSelectedUnitsSorted and Spring.GetSelectedUnitsCounts ===&lt;br /&gt;
&lt;br /&gt;
The tables returned by &amp;lt;code&amp;gt;Spring.GetSelectedUnitsSorted&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.GetSelectedUnitsCounts&amp;lt;/code&amp;gt; no longer contain an &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; key with the number of unitDefs. Use the second return value instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
local counts = Spring.GetSelectedUnitsCounts()&lt;br /&gt;
local unitDefsCount = counts.n&lt;br /&gt;
counts.n = nil&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
local counts, unitDefsCount = Spring.GetSelectedUnitsCounts()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Spring.UnitIconSetDraw Renamed ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.UnitIconSetDraw&amp;lt;/code&amp;gt; has been renamed to &amp;lt;code&amp;gt;Spring.SetUnitIconDraw&amp;lt;/code&amp;gt;. The old spelling still works but is deprecated and will be removed in a future version.&lt;br /&gt;
&lt;br /&gt;
=== Spring.GetUnitCommands / Spring.GetCommandQueue ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.GetUnitCommands(unitID, 0)&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.GetCommandQueue&amp;lt;/code&amp;gt; are deprecated for counting queue size. Use the new dedicated function instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
local count = Spring.GetUnitCommands(unitID, 0)&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
local count = Spring.GetUnitCommandCount(unitID)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Similarly, &amp;lt;code&amp;gt;Spring.GetFactoryCommands(unitID, 0)&amp;lt;/code&amp;gt; is deprecated in favor of &amp;lt;code&amp;gt;Spring.GetFactoryCommandCount(unitID)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Iterating Synced Proxy Tables ===&lt;br /&gt;
&lt;br /&gt;
The functions &amp;lt;code&amp;gt;snext&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;spairs&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;sipairs&amp;lt;/code&amp;gt; for iterating synced proxy tables have been &#039;&#039;&#039;removed&#039;&#039;&#039;. They have been equivalent to the standard Lua &amp;lt;code&amp;gt;next&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pairs&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ipairs&amp;lt;/code&amp;gt; for years. Replace them:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Before&lt;br /&gt;
for key, value in spairs(SYNCED.foo) do ... end&lt;br /&gt;
&lt;br /&gt;
-- After&lt;br /&gt;
for key, value in  pairs(SYNCED.foo) do ... end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VFS Mapping API ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Spring.MapArchive&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.UnmapArchive&amp;lt;/code&amp;gt; (and equivalently &amp;lt;code&amp;gt;VFS.MapArchive&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;VFS.UnmapArchive&amp;lt;/code&amp;gt;) have been temporarily removed due to sync-unsafety. Use &amp;lt;code&amp;gt;Spring.UseArchive&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;VFS.UseArchive&amp;lt;/code&amp;gt; in the meantime. The original functions may return at a future date.&lt;br /&gt;
&lt;br /&gt;
=== UnitUnitCollision Return Value ===&lt;br /&gt;
&lt;br /&gt;
The return value from the &amp;lt;code&amp;gt;UnitUnitCollision&amp;lt;/code&amp;gt; callin is now ignored, and only one event fires per collision pair (instead of two). There is currently no replacement for the old two-event behavior.&lt;br /&gt;
&lt;br /&gt;
=== gl.GetMatrix Removed ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;gl.GetMatrix&amp;lt;/code&amp;gt; and the interface accessible from its returned matrix object have been removed. These were unused; no direct replacement is available.&lt;br /&gt;
&lt;br /&gt;
=== Removed Platform Constants ===&lt;br /&gt;
&lt;br /&gt;
The following depth buffer support constants have been removed:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport16bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport24bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Platform.glSupport32bitDepthBuffer&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Replace with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
Platform.glSupportDepthBufferBitDepth &amp;gt;= 16  -- or 24, or 32&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Game.allowTeamColors Removed ===&lt;br /&gt;
&lt;br /&gt;
The deprecated &amp;lt;code&amp;gt;Game.allowTeamColors&amp;lt;/code&amp;gt; entry (which was always &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;) has been removed. If you used it as a boolean condition, note that its removal inverts its effect — previously &amp;lt;code&amp;gt;if Game.allowTeamColors then&amp;lt;/code&amp;gt; was always true; now the variable is &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; (falsy).&lt;br /&gt;
&lt;br /&gt;
=== Removed UnitDefs Keys ===&lt;br /&gt;
&lt;br /&gt;
The following deprecated &amp;lt;code&amp;gt;UnitDefs&amp;lt;/code&amp;gt; keys have been removed (they previously returned zero with a warning):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Removed Key&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;techLevel&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;harvestStorage&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;extractSquare&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;canHover&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;drag&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;isAirBase&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;cloakTimeout&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;minx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;miny&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;minz&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;maxx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;maxy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;maxz&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;midx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;midy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;midz&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== tdfID Removed from WeaponDefs ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;tdfID&amp;lt;/code&amp;gt; field has been removed from weapon defs and the &amp;lt;code&amp;gt;WeaponDefs&amp;lt;/code&amp;gt; table. Any remaining interfaces receive &amp;lt;code&amp;gt;weaponDefID&amp;lt;/code&amp;gt; instead.&lt;br /&gt;
&lt;br /&gt;
=== loadstring Bytecode ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;loadstring&amp;lt;/code&amp;gt; in Lua no longer accepts bytecode by default, for safety and maintainability reasons. It still works in devmode.&lt;br /&gt;
&lt;br /&gt;
== Unit Defs ==&lt;br /&gt;
&lt;br /&gt;
=== Hovercraft and Ships Upright Behavior ===&lt;br /&gt;
&lt;br /&gt;
Hovercraft and ships brought out of water are no longer forced to be upright. To restore previous behavior, add &amp;lt;code&amp;gt;upright = true&amp;lt;/code&amp;gt; to all unit defs whose movedef is of the hovercraft or ship type.&lt;br /&gt;
&lt;br /&gt;
=== useFootPrintCollisionVolume ===&lt;br /&gt;
&lt;br /&gt;
Units with &amp;lt;code&amp;gt;useFootPrintCollisionVolume = true&amp;lt;/code&amp;gt; but no &amp;lt;code&amp;gt;collisionVolumeScales&amp;lt;/code&amp;gt; set will now actually use the footprint volume (previously they mistakenly used the model&#039;s bounding sphere).&lt;br /&gt;
&lt;br /&gt;
To keep the old hitvolume behavior, set &amp;lt;code&amp;gt;useFootPrintCollisionVolume = false&amp;lt;/code&amp;gt; for units with no &amp;lt;code&amp;gt;collisionVolumeScales&amp;lt;/code&amp;gt;. This can be done in unit def post-processing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
for unitDefID, unitDef in pairs(UnitDefs) do&lt;br /&gt;
    if not unitDef.collisionvolumescales then&lt;br /&gt;
        unitDef.usefootprintcollisionvolume = nil&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tree Feature Defs ===&lt;br /&gt;
&lt;br /&gt;
The feature defs &amp;lt;code&amp;gt;treetype0&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;treetype16&amp;lt;/code&amp;gt; are now provided by the basecontent archive instead of the engine itself. Games that shipped their own basecontent will know what to do; most games are unaffected.&lt;br /&gt;
&lt;br /&gt;
=== Firestarter Weapon Tag ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;firestarter&amp;lt;/code&amp;gt; weapon tag is no longer capped at 10000 in defs (which was 100 in Lua &amp;lt;code&amp;gt;WeaponDefs&amp;lt;/code&amp;gt; after rescaling). It is now uncapped.&lt;br /&gt;
&lt;br /&gt;
To restore the previous cap:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
for weaponDefID, weaponDef in pairs(WeaponDefs) do&lt;br /&gt;
    if weaponDef.firestarter then&lt;br /&gt;
        weaponDef.firestarter = math.min(weaponDef.firestarter, 10000)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== acceleration and brakeRate (Deprecation Warning) ===&lt;br /&gt;
&lt;br /&gt;
The unit def tags &amp;lt;code&amp;gt;acceleration&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;brakeRate&amp;lt;/code&amp;gt; are &#039;&#039;&#039;scheduled&#039;&#039;&#039; for a unit change from elmo/frame to elmo/second. They currently still work as before, but it is strongly recommended to migrate to &amp;lt;code&amp;gt;maxAcc&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;maxDec&amp;lt;/code&amp;gt; respectively, which will remain in elmo/frame:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- In unit def files or post-processing:&lt;br /&gt;
-- acceleration  →  maxAcc   (elmo/frame, unchanged)&lt;br /&gt;
-- brakeRate     →  maxDec   (elmo/frame, unchanged)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Def Validity Checks ===&lt;br /&gt;
&lt;br /&gt;
Some invalid or missing def values are now handled more strictly:&lt;br /&gt;
&lt;br /&gt;
* Negative values for health, speed, reverse speed, metal/energy/buildtime cause the unit def to be &#039;&#039;&#039;rejected&#039;&#039;&#039; (previously clamped to 0.1).&lt;br /&gt;
* Negative values for acceleration and brake rate cause rejection (previously, absolute value was used).&lt;br /&gt;
* Metal cost can now be 0 (undefined metal cost defaults to 0 instead of 1).&lt;br /&gt;
* Weapon &amp;lt;code&amp;gt;edgeEffectiveness&amp;lt;/code&amp;gt; can now be exactly 1 (previously capped at 0.999).&lt;br /&gt;
* Weapon damage and unit armor multiplier can now be 0.&lt;br /&gt;
&lt;br /&gt;
=== trackStretch Reciprocal Change ===&lt;br /&gt;
&lt;br /&gt;
The unit def &amp;lt;code&amp;gt;trackStretch&amp;lt;/code&amp;gt; values are now treated reciprocally. A stretch factor of 2 now means the track is stretched 2× longer (previously it was squeezed 0.5× shorter). Adjust your values accordingly.&lt;br /&gt;
&lt;br /&gt;
=== GroundDecals Springsetting ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;GroundDecals&amp;lt;/code&amp;gt; springsetting is now a boolean. The previous behavior of using it as a decal duration multiplier is gone. Use the new &amp;lt;code&amp;gt;scarTTL&amp;lt;/code&amp;gt; weapon def tag to set scar durations instead. Note: if you had explicitly set &amp;lt;code&amp;gt;scarTTL&amp;lt;/code&amp;gt; values, those are now used directly as the TTL (previously they were multiplied by the ground decal level, which defaulted to 3).&lt;br /&gt;
&lt;br /&gt;
=== Live Unit Footprint Size ===&lt;br /&gt;
&lt;br /&gt;
Live mobile units&#039; footprint is now the size of their movedef (previously the unit def footprint). This affects bugger-off behavior, construction blocking, and the size of the built-in selection square. Unit defs are unaffected; individual units adjust if their movedef changes at runtime.&lt;br /&gt;
&lt;br /&gt;
== Gameplay Behavior ==&lt;br /&gt;
&lt;br /&gt;
=== Stop Command on Manual Share ===&lt;br /&gt;
&lt;br /&gt;
Manually shared units no longer receive the Stop command automatically. To replicate the old behavior, use the &amp;lt;code&amp;gt;UnitGiven&amp;lt;/code&amp;gt; callin in a gadget or widget:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function wupget:UnitGiven(unitID, unitDefID, newTeam)&lt;br /&gt;
    if newTeam == Spring.GetMyTeamID() then  -- if in unsynced context&lt;br /&gt;
        Spring.GiveOrderToUnit(unitID, CMD.STOP, 0, 0)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Resurrecting Units ===&lt;br /&gt;
&lt;br /&gt;
Resurrecting units no longer overrides their health to 5%. To restore the old behavior, add a gadget with the following callin:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function gadget:UnitCreated(unitID, unitDefID, teamID, builderID)&lt;br /&gt;
    if builderID and Spring.GetUnitWorkerTask(builderID) == CMD.RESURRECT then&lt;br /&gt;
        Spring.SetUnitHealth(unitID, Spring.GetUnitHealth(unitID) * 0.05)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== weapons groundBounce ===&lt;br /&gt;
&lt;br /&gt;
Weapons with &amp;lt;code&amp;gt;groundBounce = true&amp;lt;/code&amp;gt; will now bounce even if &amp;lt;code&amp;gt;numBounces&amp;lt;/code&amp;gt; is undefined. The default value of &amp;lt;code&amp;gt;numBounces = -1&amp;lt;/code&amp;gt; now correctly results in infinite bounces instead of 0. Set an explicit positive value of &amp;lt;code&amp;gt;numBounces&amp;lt;/code&amp;gt; if you want a finite number of bounces.&lt;br /&gt;
&lt;br /&gt;
=== Default Targeting Priority Random Component Removed ===&lt;br /&gt;
&lt;br /&gt;
The default targeting priority for typed units no longer includes a ±30% random component. Use &amp;lt;code&amp;gt;gadget:AllowWeaponTarget&amp;lt;/code&amp;gt; to restore the previous randomized behavior.&lt;br /&gt;
&lt;br /&gt;
=== Handicap No Longer Applies to Reclaim ===&lt;br /&gt;
&lt;br /&gt;
Handicap (resource income multiplier) no longer applies to reclaim operations.&lt;br /&gt;
&lt;br /&gt;
== Camera ==&lt;br /&gt;
&lt;br /&gt;
=== Camera Modifiers ===&lt;br /&gt;
&lt;br /&gt;
The following keyboard modifiers were unhardcoded from the engine:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Spring Camera:&#039;&#039;&#039; rotating on the x (pitch) or y (yaw) axis with ALT + middle mouse button while moving the cursor.&lt;br /&gt;
* &#039;&#039;&#039;Resetting camera:&#039;&#039;&#039; ALT + mousewheel scroll down.&lt;br /&gt;
* &#039;&#039;&#039;Pitch rotation:&#039;&#039;&#039; CTRL + mousewheel.&lt;br /&gt;
&lt;br /&gt;
If games and players do not change engine defaults, no action is needed. To explicitly enable these modifiers as before, add the following keybindings:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
bind Any+ctrl movetilt    -- rotates camera over x axis on mousewheel&lt;br /&gt;
bind Any+alt  movereset   -- resets camera state on mousewheel move&lt;br /&gt;
bind Any+alt  moverotate  -- rotates camera in x/y on MMB move (Spring cam)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Rendering ==&lt;br /&gt;
&lt;br /&gt;
=== LOD Rendering Removed ===&lt;br /&gt;
&lt;br /&gt;
LOD rendering (2D unit billboards when zoomed far out) has been removed, along with the &amp;lt;code&amp;gt;/distdraw&amp;lt;/code&amp;gt; command and the &amp;lt;code&amp;gt;UnitLodDist&amp;lt;/code&amp;gt; springsetting entry.&lt;br /&gt;
&lt;br /&gt;
=== Dynamic Sky Removed ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AdvSky&amp;lt;/code&amp;gt; springsetting and the &amp;lt;code&amp;gt;/dynamicsky&amp;lt;/code&amp;gt; command (which made clouds move across the sky) have been removed. There is no direct replacement, though similar behavior can be achieved via custom Lua rendering.&lt;br /&gt;
&lt;br /&gt;
=== Screenshot Format Changed ===&lt;br /&gt;
&lt;br /&gt;
The default &amp;lt;code&amp;gt;/screenshot&amp;lt;/code&amp;gt; format is now PNG (was previously BMP/other). Review any automated processing pipelines that parse screenshot files. Screenshot and replay demo file names also now use a UTC timestamp format instead of a sequential number.&lt;br /&gt;
&lt;br /&gt;
=== Paletted Image Files ===&lt;br /&gt;
&lt;br /&gt;
Paletted image files are no longer accepted. Convert any paletted images to non-paletted format (e.g. standard RGBA PNG).&lt;br /&gt;
&lt;br /&gt;
=== Shader Changes for Unit Rendering ===&lt;br /&gt;
&lt;br /&gt;
Unit vertex shaders now receive TRS (Translation/Rotation/Scale) transforms instead of matrices. Code that calls &amp;lt;code&amp;gt;vbo:ModelsVBO()&amp;lt;/code&amp;gt; must update shader code. The skinning/bones interface at uniform location 5 was extended in version 2025.04 from &amp;lt;code&amp;gt;uvec2&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;uvec3&amp;lt;/code&amp;gt;. Update shaders from:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;glsl&amp;quot;&amp;gt;&lt;br /&gt;
// Old (pre-1775)&lt;br /&gt;
layout (location = 5) in uint pieceIndex;&lt;br /&gt;
&lt;br /&gt;
// After 1775 (bones/skinning era)&lt;br /&gt;
layout (location = 5) in uvec2 bonesInfo; // boneIDs, boneWeights&lt;br /&gt;
#define pieceIndex (bonesInfo.x &amp;amp; 0x000000FFu)&lt;br /&gt;
&lt;br /&gt;
// After 2025.04 (extended bone info)&lt;br /&gt;
layout (location = 5) in uvec3 bonesInfo; // boneID low byte, boneWeight, boneID high byte&lt;br /&gt;
#define pieceIndex (bonesInfo.x &amp;amp; 0x000000FFu)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Feature Support Table ==&lt;br /&gt;
&lt;br /&gt;
Recoil provides &amp;lt;code&amp;gt;Engine.FeatureSupport&amp;lt;/code&amp;gt;, a table of feature availability flags for forward/backward compatibility. Use it instead of &amp;lt;code&amp;gt;Script.IsEngineMinVersion&amp;lt;/code&amp;gt; where applicable, as it is self-documenting and does not assume linear commit numbering.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
if Engine.FeatureSupport.rmlUiApiVersion then&lt;br /&gt;
    -- use rmlUI&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Key entries include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Key !! Value !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;rmlUiApiVersion&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; || RmlUI GUI framework available&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;hasExitOnlyYardmaps&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || &#039;e&#039; yardmap tile available&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;NegativeGetUnitCurrentCommand&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || Negative indices work in GetUnitCurrentCommand&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;noAutoShowMetal&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; → future &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; || Automatic metal map toggle deprecation tracking&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;maxPiecesPerModel&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;65534&amp;lt;/code&amp;gt; || Maximum pieces per model (was 254)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Death Event Changes ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;wupget:UnitDestroyed&amp;lt;/code&amp;gt; callin received several changes:&lt;br /&gt;
&lt;br /&gt;
* Now receives a 7th argument: &amp;lt;code&amp;gt;weaponDefID&amp;lt;/code&amp;gt;, the cause of death. This is never &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; — all causes of death are attributable.&lt;br /&gt;
* The builder is passed as the killer if a unit is reclaimed.&lt;br /&gt;
&lt;br /&gt;
New &amp;lt;code&amp;gt;Game.envDamageTypes&amp;lt;/code&amp;gt; constants identify the cause of death:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Constant !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AircraftCrashed&amp;lt;/code&amp;gt; || Aircraft hitting the ground&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Kamikaze&amp;lt;/code&amp;gt; || Unit exploding via kamikaze ability&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SelfD&amp;lt;/code&amp;gt; || Self-destruct command and countdown&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ConstructionDecay&amp;lt;/code&amp;gt; || Abandoned nanoframe disappearing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Reclaimed&amp;lt;/code&amp;gt; || Killed via reclaim&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TurnedIntoFeature&amp;lt;/code&amp;gt; || Died due to &amp;lt;code&amp;gt;isFeature&amp;lt;/code&amp;gt; tag on completion&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TransportKilled&amp;lt;/code&amp;gt; || Was in a transport with no &amp;lt;code&amp;gt;releaseHeld&amp;lt;/code&amp;gt; when transport died&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FactoryKilled&amp;lt;/code&amp;gt; || Was being built in a factory that died&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;FactoryCancel&amp;lt;/code&amp;gt; || Build order was cancelled in factory&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UnitScript&amp;lt;/code&amp;gt; || COB script ordered the unit&#039;s death&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SetNegativeHealth&amp;lt;/code&amp;gt; || Unit had negative health for non-damage reasons (e.g. set via Lua)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;OutOfBounds&amp;lt;/code&amp;gt; || Unit was thrown far out of map bounds&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;KilledByCheat&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;/remove&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;/destroy&amp;lt;/code&amp;gt; command used&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;KilledByLua&amp;lt;/code&amp;gt; || Default cause when using &amp;lt;code&amp;gt;Spring.DestroyUnit&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;KilledByLua&amp;lt;/code&amp;gt; is guaranteed to be the last value. You can define custom damage type constants above it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
Game.envDamageTypes.MyCustomDeath = Game.envDamageTypes.KilledByLua - 1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Other Notable Changes ==&lt;br /&gt;
&lt;br /&gt;
=== Python AI Bindings Removed ===&lt;br /&gt;
&lt;br /&gt;
Python bindings for AI have been removed. Only native C++ AIs are built by default via CMake.&lt;br /&gt;
&lt;br /&gt;
=== 32-bit Builds ===&lt;br /&gt;
&lt;br /&gt;
32-bit builds are no longer supported.&lt;br /&gt;
&lt;br /&gt;
=== Mandatory OpenGL Feature Support ===&lt;br /&gt;
&lt;br /&gt;
Support for non-power-of-2 textures, float textures, and framebuffer objects (FBO) is now mandatory. Systems that do not support these (all of which were common 15+ years ago) are no longer supported.&lt;br /&gt;
&lt;br /&gt;
=== /ally Command ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/ally&amp;lt;/code&amp;gt; command no longer announces the alliance to unrelated players via a console message. The affected players still see the message. Use the &amp;lt;code&amp;gt;TeamChanged&amp;lt;/code&amp;gt; callin if you want to make alliance announcements public.&lt;br /&gt;
&lt;br /&gt;
=== Group Command Change ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;/group add N&amp;lt;/code&amp;gt; no longer selects the entire group after adding units to it.&lt;br /&gt;
&lt;br /&gt;
=== Automatic Metal View Toggle (Deprecated) ===&lt;br /&gt;
&lt;br /&gt;
The metal view automatically toggling when a player begins building a metal extractor is deprecated. Disable it via &amp;lt;code&amp;gt;Spring.SetAutoShowMetal(false)&amp;lt;/code&amp;gt; and re-implement manually. An example widget is available at &amp;lt;code&amp;gt;(basecontent)/examples/Widgets/gui_autoshowmetal.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== DrawUnit and DrawFeature Widget Callins (Deprecated) ===&lt;br /&gt;
&lt;br /&gt;
Widget drawcalls such as &amp;lt;code&amp;gt;wupget:DrawUnit&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;wupget:DrawFeature&amp;lt;/code&amp;gt;, etc. are deprecated. Migrate to alternative rendering approaches.&lt;br /&gt;
&lt;br /&gt;
=== Removed Springsettings ===&lt;br /&gt;
&lt;br /&gt;
The following springsettings have been removed:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Springsetting !! Replacement / Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AdvSky&amp;lt;/code&amp;gt; || No direct replacement; use custom Lua rendering&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UnitLodDist&amp;lt;/code&amp;gt; || LOD rendering removed entirely&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UpdateWeaponVectorsMT&amp;lt;/code&amp;gt; || Removed (MT is now always on)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UpdateBoundingVolumeMT&amp;lt;/code&amp;gt; || Removed (MT is now always on)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;AnimationMT&amp;lt;/code&amp;gt; || Removed after proving stable; animations always MT&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;UsePBO&amp;lt;/code&amp;gt; || Already did nothing; removed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Removed Commands ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Command !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/distdraw&amp;lt;/code&amp;gt; || LOD rendering removed&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/dynamicsky&amp;lt;/code&amp;gt; || Dynamic sky removed&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/AdvModelShading&amp;lt;/code&amp;gt; || Removed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Recoil:Choose Recoil]] — Why switch from Spring to Recoil&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine/tree/master/doc/site/content/changelogs Full Recoil Changelog]&lt;br /&gt;
* [https://beyond-all-reason.github.io/spring/ldoc/ Recoil Lua API Reference]&lt;br /&gt;
* [https://discord.gg/GUpRg6Wz3e Recoil Discord] — for help and support&lt;br /&gt;
&lt;br /&gt;
[[Category:Recoil]]&lt;br /&gt;
[[Category:Engine]]&lt;br /&gt;
[[Category:Migration]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil_Game_Tutorial&amp;diff=2709</id>
		<title>Recoil Game Tutorial</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil_Game_Tutorial&amp;diff=2709"/>
		<updated>2026-03-06T06:20:15Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;{{DISPLAYTITLE:Making A Basic Recoil Game}} __TOC__  = Making A Basic Recoil Game =  This guide walks you through creating a minimal but complete game with the Recoil engine. By the end you will have a working game archive that loads in the engine, with a playable Commander unit, a simple weapon, and a basic Lua gadget.  No prior Spring or Recoil experience is assumed, though basic familiarity with Lua will help when you reach the scripting sections.  {{note|1=Recoil is...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:Making A Basic Recoil Game}}&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
= Making A Basic Recoil Game =&lt;br /&gt;
&lt;br /&gt;
This guide walks you through creating a minimal but complete game with the Recoil engine.&lt;br /&gt;
By the end you will have a working game archive that loads in the engine, with a playable Commander unit, a simple weapon, and a basic Lua gadget.&lt;br /&gt;
&lt;br /&gt;
No prior Spring or Recoil experience is assumed, though basic familiarity with Lua will help when you reach the scripting sections.&lt;br /&gt;
&lt;br /&gt;
{{note|1=Recoil is descended from the Spring engine and is highly compatible with Spring 105 games and mods. If you are migrating an existing Spring game, the bulk of your content will work as-is — see [[Recoil:Migrating From Spring]] for the specific breaking changes.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 1. What is Recoil? ==&lt;br /&gt;
&lt;br /&gt;
Recoil is an open-source, cross-platform real-time strategy game engine descended from [[Spring]] (which itself descended from Total Annihilation).&lt;br /&gt;
The engine handles low-level simulation, networking, and rendering.&lt;br /&gt;
Your game is a Lua + asset package that rides on top of the engine: you control the rules, the units, the UI, and everything else.&lt;br /&gt;
&lt;br /&gt;
Out of the box the engine provides:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Physics simulation&#039;&#039;&#039; — projectile trajectories, terrain collision, unit movement and pathfinding&lt;br /&gt;
* &#039;&#039;&#039;Resource system&#039;&#039;&#039; — metal and energy by default, fully customisable&lt;br /&gt;
* &#039;&#039;&#039;Networking&#039;&#039;&#039; — authoritative server, deterministic lock-step simulation, automatic replay recording&lt;br /&gt;
* &#039;&#039;&#039;Lua scripting&#039;&#039;&#039; — every mechanic is overridable via Lua gadgets and widgets&lt;br /&gt;
* &#039;&#039;&#039;VFS&#039;&#039;&#039; — a virtual file system that merges your game archive, maps, and basecontent into one namespace&lt;br /&gt;
&lt;br /&gt;
=== Key terminology ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Term !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Game&#039;&#039;&#039; || Your content archive (units, Lua scripts, textures, …) loaded by the engine.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Map&#039;&#039;&#039; || A separate terrain archive (.smf height data + textures). Distributed independently of your game.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Gadget&#039;&#039;&#039; || A Lua script that runs game logic (synced or unsynced). Lives in &amp;lt;code&amp;gt;luarules/gadgets/&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Widget&#039;&#039;&#039; || A Lua script that runs UI logic. Lives in &amp;lt;code&amp;gt;luaui/widgets/&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Wupget&#039;&#039;&#039; || Informal collective term for widgets and gadgets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Callin&#039;&#039;&#039; || A function in your Lua code that the engine calls on events (e.g. &amp;lt;code&amp;gt;gadget:UnitCreated&amp;lt;/code&amp;gt;).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Callout&#039;&#039;&#039; || A function the engine exposes to Lua (e.g. &amp;lt;code&amp;gt;Spring.GetUnitPosition&amp;lt;/code&amp;gt;).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;VFS&#039;&#039;&#039; || Virtual File System — unified read-only view of the game archive, map archive, and basecontent.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Basecontent&#039;&#039;&#039; || An archive always loaded by the engine that provides default wupget handlers, water textures, and other bare necessities.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;.sdd&#039;&#039;&#039; || A game folder whose name ends in &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt;. Treated as an uncompressed archive during development.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;.sdz / .sd7&#039;&#039;&#039; || Production game archives (zip and 7-zip respectively). Faster to distribute and load than .sdd.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Elmo&#039;&#039;&#039; || The internal distance unit. Roughly 8 height units of map height. Used in unit defs for speed, range, etc.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 2. Prerequisites ==&lt;br /&gt;
&lt;br /&gt;
Before starting you need:&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;The Recoil engine binary.&#039;&#039;&#039; Download a pre-built release from [https://github.com/beyond-all-reason/RecoilEngine/releases GitHub Releases] or build from source following the [https://github.com/beyond-all-reason/RecoilEngine/blob/master/docker-build-v2/README.md build documentation]. The main executable is &amp;lt;code&amp;gt;spring&amp;lt;/code&amp;gt; on Linux or &amp;lt;code&amp;gt;spring.exe&amp;lt;/code&amp;gt; on Windows.&lt;br /&gt;
# &#039;&#039;&#039;A map archive&#039;&#039;&#039; (&amp;lt;code&amp;gt;.smf&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;.sd7&amp;lt;/code&amp;gt;) placed in the &amp;lt;code&amp;gt;maps/&amp;lt;/code&amp;gt; subfolder of your Recoil data directory. Any map from an existing Recoil game works for early testing.&lt;br /&gt;
# &#039;&#039;&#039;A text editor.&#039;&#039;&#039; Any editor works. For the best experience, configure the [[Recoil:Lua Language Server]] with Recoil type stubs so you get autocomplete on all &amp;lt;code&amp;gt;Spring.*&amp;lt;/code&amp;gt; functions.&lt;br /&gt;
&lt;br /&gt;
Your Recoil data directory typically looks like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
recoil-data/&lt;br /&gt;
├── spring            ← engine binary (Linux) or spring.exe (Windows)&lt;br /&gt;
├── spring_headless   ← headless binary (no rendering; useful for automated testing)&lt;br /&gt;
├── spring_dedicated  ← dedicated server binary&lt;br /&gt;
├── games/            ← your game archives / development folders go here&lt;br /&gt;
└── maps/             ← map archives go here&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 3. Virtual File System ==&lt;br /&gt;
&lt;br /&gt;
The engine loads content from several sources, merged into one Virtual File System (VFS):&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Game archive&#039;&#039;&#039; — your &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; folder or &amp;lt;code&amp;gt;.sdz&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;.sd7&amp;lt;/code&amp;gt; file. Files here have the highest priority.&lt;br /&gt;
* &#039;&#039;&#039;Map archive&#039;&#039;&#039; — the terrain and map-specific data. Maps can override some of your game&#039;s properties via &amp;lt;code&amp;gt;MapOptions.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Basecontent&#039;&#039;&#039; — always loaded by the engine. Provides the default wupget handler framework, stock water textures, and other bare essentials.&lt;br /&gt;
&lt;br /&gt;
Game archives can declare dependencies on other archives in &amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
The engine merges all archives into the VFS, with your game taking priority over its dependencies.&lt;br /&gt;
This lets you build on top of an existing game without copying its entire asset tree.&lt;br /&gt;
&lt;br /&gt;
{{note|1=Files in a dependency are completely overridden if your game provides a file at the same path. There is currently no way to partially merge files.}}&lt;br /&gt;
&lt;br /&gt;
See [[Recoil:VFS Basics]] for the full list of VFS modes available to Lua scripts.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 4. Game Archive Structure ==&lt;br /&gt;
&lt;br /&gt;
During development, use an &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; folder. Create it inside your &amp;lt;code&amp;gt;games/&amp;lt;/code&amp;gt; directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
games/&lt;br /&gt;
└── mygame.sdd/&lt;br /&gt;
    ├── modinfo.lua           ← required: identifies your game to the engine&lt;br /&gt;
    ├── gamedata/             ← def pre/post processing, game options, rules&lt;br /&gt;
    │   ├── modrules.lua&lt;br /&gt;
    │   ├── unitdefs_post.lua&lt;br /&gt;
    │   └── ModOptions.lua&lt;br /&gt;
    ├── units/                ← unit definition files (one per unit by convention)&lt;br /&gt;
    │   └── commander.lua&lt;br /&gt;
    ├── weapons/              ← weapon definition files&lt;br /&gt;
    │   └── laser.lua&lt;br /&gt;
    ├── features/             ← feature (prop/wreck/tree) definitions&lt;br /&gt;
    │   └── trees.lua&lt;br /&gt;
    ├── luarules/&lt;br /&gt;
    │   └── gadgets/          ← synced and unsynced game logic&lt;br /&gt;
    │       └── game_end.lua&lt;br /&gt;
    ├── luaui/&lt;br /&gt;
    │   └── widgets/          ← UI scripts&lt;br /&gt;
    │       └── resources_display.lua&lt;br /&gt;
    ├── objects3d/            ← unit models (.dae / .obj / .s3o)&lt;br /&gt;
    └── unittextures/         ← unit icon textures (unitname.png)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=The &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; extension on the folder name is mandatory. Without it the engine will not recognise it as a game archive.}}&lt;br /&gt;
&lt;br /&gt;
For production, compress the contents into a &amp;lt;code&amp;gt;.sdz&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;.sd7&amp;lt;/code&amp;gt; archive.&lt;br /&gt;
&amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt; must end up at the archive root — not inside a subfolder.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 5. modinfo.lua ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt; is the most important file in your archive. Without it the engine will not recognise it as a game.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- games/mygame.sdd/modinfo.lua&lt;br /&gt;
&lt;br /&gt;
local modinfo = {&lt;br /&gt;
    -- Displayed in lobbies and the engine title bar&lt;br /&gt;
    name        = &amp;quot;My Recoil Game&amp;quot;,&lt;br /&gt;
    -- Short unique identifier; no spaces, keep it stable across versions&lt;br /&gt;
    shortName   = &amp;quot;MRG&amp;quot;,&lt;br /&gt;
    description = &amp;quot;A minimal Recoil game for learning purposes.&amp;quot;,&lt;br /&gt;
    version     = &amp;quot;0.1.0&amp;quot;,&lt;br /&gt;
    -- Leave empty for base games (not mods layered on top of another game)&lt;br /&gt;
    mutator     = &amp;quot;&amp;quot;,&lt;br /&gt;
    -- Other archive names this game depends on; basecontent is always loaded&lt;br /&gt;
    depend      = {},&lt;br /&gt;
    -- 1 = game/mod (visible in lobbies), 0 = hidden&lt;br /&gt;
    modtype     = 1,&lt;br /&gt;
    -- Faction names available in start scripts and team configuration&lt;br /&gt;
    sides       = { &amp;quot;MyFaction&amp;quot; },&lt;br /&gt;
    -- Per-side starting unit def name&lt;br /&gt;
    startscreens = {&lt;br /&gt;
        MyFaction = &amp;quot;commander&amp;quot;,&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return modinfo&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=&amp;lt;code&amp;gt;shortName&amp;lt;/code&amp;gt; must be unique across all games the engine knows about. Changing it later breaks lobby integrations and replay compatibility.}}&lt;br /&gt;
&lt;br /&gt;
=== ModOptions.lua ===&lt;br /&gt;
&lt;br /&gt;
To expose configurable options in lobbies (sliders, checkboxes, dropdowns), add &amp;lt;code&amp;gt;gamedata/ModOptions.lua&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- gamedata/ModOptions.lua&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    {&lt;br /&gt;
        key  = &amp;quot;startmetal&amp;quot;,&lt;br /&gt;
        name = &amp;quot;Starting Metal&amp;quot;,&lt;br /&gt;
        desc = &amp;quot;Metal each player starts with.&amp;quot;,&lt;br /&gt;
        type = &amp;quot;number&amp;quot;,&lt;br /&gt;
        def  = 1000,&lt;br /&gt;
        min  = 0,&lt;br /&gt;
        max  = 10000,&lt;br /&gt;
        step = 100,&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        key  = &amp;quot;commanderdie&amp;quot;,&lt;br /&gt;
        name = &amp;quot;Commander Death Ends Game&amp;quot;,&lt;br /&gt;
        desc = &amp;quot;Lose when your Commander is destroyed.&amp;quot;,&lt;br /&gt;
        type = &amp;quot;bool&amp;quot;,&lt;br /&gt;
        def  = true,&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Read options at runtime in gadgets with &amp;lt;code&amp;gt;Spring.GetModOptions()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 6. Unit Definitions ==&lt;br /&gt;
&lt;br /&gt;
Unit defs are Lua files in &amp;lt;code&amp;gt;units/&amp;lt;/code&amp;gt; that return a table of unit type definitions.&lt;br /&gt;
The convention is one unit per file, where the filename matches the unit def name.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- units/commander.lua&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    commander = {&lt;br /&gt;
        -- ── Display ──────────────────────────────────────────────────&lt;br /&gt;
        name        = &amp;quot;Commander&amp;quot;,&lt;br /&gt;
        description = &amp;quot;The starting unit.&amp;quot;,&lt;br /&gt;
        buildPic    = &amp;quot;commander.png&amp;quot;,   -- icon shown in unit HUD&lt;br /&gt;
&lt;br /&gt;
        -- ── Model ────────────────────────────────────────────────────&lt;br /&gt;
        objectName  = &amp;quot;commander.dae&amp;quot;,   -- path inside objects3d/&lt;br /&gt;
&lt;br /&gt;
        -- ── Health ───────────────────────────────────────────────────&lt;br /&gt;
        health      = 3000,&lt;br /&gt;
        armorType   = &amp;quot;commander&amp;quot;,       -- armour class name&lt;br /&gt;
&lt;br /&gt;
        -- ── Footprint ────────────────────────────────────────────────&lt;br /&gt;
        footprintX  = 2,                 -- width in map squares (8 elmos each)&lt;br /&gt;
        footprintZ  = 2,&lt;br /&gt;
        collisionVolumeScales = &amp;quot;30 50 30&amp;quot;,&lt;br /&gt;
        collisionVolumeType   = &amp;quot;ellipsoid&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- ── Movement ─────────────────────────────────────────────────&lt;br /&gt;
        moveDef = {&lt;br /&gt;
            family   = &amp;quot;tank&amp;quot;,&lt;br /&gt;
            maxSlope = 36,     -- steepest traversable slope in degrees&lt;br /&gt;
            turnRate = 1024,&lt;br /&gt;
        },&lt;br /&gt;
        maxVelocity = 42,      -- elmos/second&lt;br /&gt;
        maxAcc      = 0.5,     -- elmos/second²&lt;br /&gt;
        maxDec      = 1.5,&lt;br /&gt;
&lt;br /&gt;
        -- ── Construction ─────────────────────────────────────────────&lt;br /&gt;
        workerTime      = 200, -- build power provided when assisting&lt;br /&gt;
        buildCostMetal  = 0,   -- Commanders are spawned free by the engine&lt;br /&gt;
        buildCostEnergy = 0,&lt;br /&gt;
        buildTime       = 1,&lt;br /&gt;
&lt;br /&gt;
        -- ── Economy ──────────────────────────────────────────────────&lt;br /&gt;
        metalMake  = 1,        -- passive metal income per second&lt;br /&gt;
        energyMake = 20,       -- passive energy income per second&lt;br /&gt;
&lt;br /&gt;
        -- ── Weapons ──────────────────────────────────────────────────&lt;br /&gt;
        weapons = {&lt;br /&gt;
            { def = &amp;quot;COMMANDER_LASER&amp;quot; },&lt;br /&gt;
        },&lt;br /&gt;
&lt;br /&gt;
        -- ── Misc ─────────────────────────────────────────────────────&lt;br /&gt;
        commander     = true,  -- marks this as the starting Commander unit&lt;br /&gt;
        sightDistance = 660,&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=The &amp;lt;code&amp;gt;UnitDefs&amp;lt;/code&amp;gt; table available inside gadgets and widgets is &#039;&#039;&#039;not&#039;&#039;&#039; the same table as the one in def files. After loading, the engine parses definitions into internal structures and re-exposes them with different key names (e.g. &amp;lt;code&amp;gt;buildCostMetal&amp;lt;/code&amp;gt; becomes &amp;lt;code&amp;gt;metalCost&amp;lt;/code&amp;gt;) and different value scales. Never copy-paste keys between def files and wupget code.}}&lt;br /&gt;
&lt;br /&gt;
=== Notable unit def keys ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Key !! Type !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; || string || Display name shown in the UI. Not the same as the def name (the Lua table key).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;health&amp;lt;/code&amp;gt; || number || Maximum hit points.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;armorType&amp;lt;/code&amp;gt; || string || Armour class name. Controls how much damage weapons of each type deal.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;moveDef&amp;lt;/code&amp;gt; || table || Inline movement class. &amp;lt;code&amp;gt;family&amp;lt;/code&amp;gt; can be &amp;lt;code&amp;gt;tank&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;kbot&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;hover&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ship&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;boat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;amphibious&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;submarine&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;air&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;maxVelocity&amp;lt;/code&amp;gt; || number || Top speed in elmos/second.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;buildCostMetal&amp;lt;/code&amp;gt; || number || Metal cost. Constructor must have enough free income to begin building.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;buildTime&amp;lt;/code&amp;gt; || number || Build time in seconds at 1 build power. Actual time = &amp;lt;code&amp;gt;buildTime / workerTime&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;workerTime&amp;lt;/code&amp;gt; || number || Build power this unit contributes when constructing or assisting.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;weapons&amp;lt;/code&amp;gt; || table || Up to 3 entries; each a table with a &amp;lt;code&amp;gt;def&amp;lt;/code&amp;gt; key naming a WeaponDef.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;commander&amp;lt;/code&amp;gt; || bool || Marks this unit as a Commander for lobby/engine purposes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;extractsMetal&amp;lt;/code&amp;gt; || number || &amp;gt; 0 means this unit extracts metal when placed over a metal spot.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;customParams&amp;lt;/code&amp;gt; || table || Arbitrary string/number key-value pairs preserved by the engine for gadget use.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For the full list of unit def keys and their effects see the [https://beyond-all-reason.github.io/spring/ldoc/ Recoil Lua API documentation].&lt;br /&gt;
&lt;br /&gt;
=== Unit types overview ===&lt;br /&gt;
&lt;br /&gt;
Recoil games traditionally group units into several broad categories.&lt;br /&gt;
Understanding these helps when designing your roster:&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Ground vehicles&#039;&#039;&#039; (family: &amp;lt;code&amp;gt;tank&amp;lt;/code&amp;gt;)&lt;br /&gt;
: Typically heavy armour, strong weapons, poor slope climbing. Sluggish turning, struggles on hills. Best for open terrain and armour-versus-armour engagements.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;K-bots / bipeds&#039;&#039;&#039; (family: &amp;lt;code&amp;gt;kbot&amp;lt;/code&amp;gt;)&lt;br /&gt;
: Lighter and cheaper than vehicles. Excellent slope climbing — can go where tanks cannot. Smaller profile, so they slip through narrow gaps in terrain or walls.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Aircraft&#039;&#039;&#039; (family: &amp;lt;code&amp;gt;air&amp;lt;/code&amp;gt;)&lt;br /&gt;
: Ignores terrain completely. Fastest units. Roles: bombers, gunships, scouts, air superiority fighters, torpedo bombers, transports. Countered by anti-air (AA) units and structures. Expensive and fragile compared to ground units.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Ships&#039;&#039;&#039; (family: &amp;lt;code&amp;gt;ship&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;boat&amp;lt;/code&amp;gt;)&lt;br /&gt;
: Powerful weapons and heavy armour, but confined to water. Long effective range. Very expensive. Vulnerable to submarines and aircraft with torpedoes.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Hovercraft&#039;&#039;&#039; (family: &amp;lt;code&amp;gt;hover&amp;lt;/code&amp;gt;)&lt;br /&gt;
: Amphibious — traverse both land and water. Weaker than dedicated land vehicles and ships, but unique in their flexibility. Poor slope climbing.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Amphibious units&#039;&#039;&#039; (family: &amp;lt;code&amp;gt;amphibious&amp;lt;/code&amp;gt;)&lt;br /&gt;
: Can operate both underwater and on the surface. Slow. Excellent for surprise attacks and on maps with mixed terrain.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Structures / towers&#039;&#039;&#039;&lt;br /&gt;
: Fixed-position buildings. Includes factories, resource extractors, power generators, defence turrets, radar towers, and storage. Cannot move.&lt;br /&gt;
&lt;br /&gt;
=== Def pre- and post-processing ===&lt;br /&gt;
&lt;br /&gt;
The engine reads &amp;lt;code&amp;gt;gamedata/defs.lua&amp;lt;/code&amp;gt; (provided by basecontent) to orchestrate def loading.&lt;br /&gt;
It runs &amp;lt;code&amp;gt;gamedata/unitdefs_pre.lua&amp;lt;/code&amp;gt; first (populates a &amp;lt;code&amp;gt;Shared&amp;lt;/code&amp;gt; table visible to all def files), then loads every &amp;lt;code&amp;gt;units/*.lua&amp;lt;/code&amp;gt;, then runs &amp;lt;code&amp;gt;gamedata/unitdefs_post.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- gamedata/unitdefs_post.lua&lt;br /&gt;
&lt;br /&gt;
-- Normalise key casing so post-processing code is consistent&lt;br /&gt;
for _, unitDef in pairs(UnitDefs) do&lt;br /&gt;
    lowerkeys(unitDef)   -- lowerkeys() is provided by basecontent&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Example: give every unit 10% bonus health&lt;br /&gt;
for _, unitDef in pairs(UnitDefs) do&lt;br /&gt;
    if unitDef.health then&lt;br /&gt;
        unitDef.health = unitDef.health * 1.1&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{note|1=&amp;lt;code&amp;gt;lowerkeys()&amp;lt;/code&amp;gt; converts all keys to lowercase in-place. If your def files are consistent in their casing you don&#039;t need it, but it prevents subtle bugs when multiple contributors write defs in different styles.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 7. Weapon Definitions ==&lt;br /&gt;
&lt;br /&gt;
Weapon defs follow the same pattern as unit defs — Lua files in &amp;lt;code&amp;gt;weapons/&amp;lt;/code&amp;gt; returning tables.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- weapons/laser.lua&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    COMMANDER_LASER = {&lt;br /&gt;
        name        = &amp;quot;Commander Laser&amp;quot;,&lt;br /&gt;
        description = &amp;quot;Short-range direct-fire laser.&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- Weapon physics model&lt;br /&gt;
        -- Options: Cannon, LaserCannon, MissileLauncher, AircraftBomb,&lt;br /&gt;
        --          BeamLaser, LightningCannon, Flame, TorpedoLauncher, ...&lt;br /&gt;
        weaponType  = &amp;quot;LaserCannon&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- Only engage targets in these categories&lt;br /&gt;
        onlyTargetCategory = &amp;quot;SURFACE&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- ── Visuals ──────────────────────────────────────────────────&lt;br /&gt;
        rgbColor        = &amp;quot;1 0.2 0.2&amp;quot;,    -- beam colour (R G B, 0-1 each)&lt;br /&gt;
        rgbColor2       = &amp;quot;1 0.9 0.9&amp;quot;,    -- colour at the far end of the beam&lt;br /&gt;
        beamtime        = 1,              -- visual beam length in elmos&lt;br /&gt;
        thickness       = 3,&lt;br /&gt;
        corethickness   = 0.3,&lt;br /&gt;
        laserflaresize  = 8,&lt;br /&gt;
&lt;br /&gt;
        -- ── Ballistics ───────────────────────────────────────────────&lt;br /&gt;
        projectilespeed = 800,  -- elmos/second&lt;br /&gt;
        range           = 350,  -- elmos&lt;br /&gt;
        reloadtime      = 1.5,  -- seconds between shots&lt;br /&gt;
        burst           = 1,    -- shots per fire command&lt;br /&gt;
        accuracy        = 0,    -- spread in degrees (0 = perfect accuracy)&lt;br /&gt;
&lt;br /&gt;
        -- ── Damage ───────────────────────────────────────────────────&lt;br /&gt;
        damage = {&lt;br /&gt;
            default = 50,       -- applies to armour types not explicitly listed&lt;br /&gt;
        },&lt;br /&gt;
        areaOfEffect = 8,   -- splash radius in elmos (0 = single target)&lt;br /&gt;
        impulse      = 0,   -- knockback force&lt;br /&gt;
        firestarter  = 0,   -- chance to ignite (0 = no fire)&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Like unit defs, you can post-process weapon defs in &amp;lt;code&amp;gt;gamedata/weapondefs_post.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{note|1=&amp;lt;code&amp;gt;WeaponDefs&amp;lt;/code&amp;gt; inside wupgets is &#039;&#039;&#039;0-indexed&#039;&#039;&#039; (unlike &amp;lt;code&amp;gt;UnitDefs&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FeatureDefs&amp;lt;/code&amp;gt; which are 1-indexed). Beware of &amp;lt;code&amp;gt;for i = 1, #WeaponDefs do&amp;lt;/code&amp;gt; — this will skip the first weapon def.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 8. Feature Definitions ==&lt;br /&gt;
&lt;br /&gt;
Features are non-commandable objects on the map: trees, rocks, wreckage, and any other static props.&lt;br /&gt;
They can block pathfinding, provide reclaim value, and leave behind other features when destroyed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- features/trees.lua&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    tree = {&lt;br /&gt;
        description = &amp;quot;A tree.&amp;quot;,&lt;br /&gt;
        object      = &amp;quot;tree.dae&amp;quot;,   -- model in objects3d/&lt;br /&gt;
        damage      = 50,           -- HP before the feature is destroyed&lt;br /&gt;
        metal       = 0,            -- metal gained when reclaimed&lt;br /&gt;
        energy      = 10,           -- energy gained when reclaimed&lt;br /&gt;
        blocking    = true,         -- blocks ground unit pathfinding&lt;br /&gt;
        reclaimable = true,&lt;br /&gt;
        deathFeature = &amp;quot;&amp;quot;,          -- feature name to spawn on death (blank = nothing)&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{tip|1=Trees are provided by basecontent (&amp;lt;code&amp;gt;treetype0&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;treetype16&amp;lt;/code&amp;gt;), so you don&#039;t need to define them unless you want custom trees.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 9. Game Rules (modrules.lua) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;gamedata/modrules.lua&amp;lt;/code&amp;gt; configures engine-level behaviour not driven by unit or weapon defs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- gamedata/modrules.lua&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    system = {&lt;br /&gt;
        -- Starting resources (also overridable via ModOptions or start script)&lt;br /&gt;
        startMetal  = 1000,&lt;br /&gt;
        startEnergy = 1000,&lt;br /&gt;
&lt;br /&gt;
        -- Maximum units per team (0 = unlimited)&lt;br /&gt;
        maxUnits    = 1000,&lt;br /&gt;
&lt;br /&gt;
        -- 0 = game continues on Commander death&lt;br /&gt;
        -- 1 = team loses when Commander dies&lt;br /&gt;
        -- 2 = lineage mode (Commander transferred to surviving ally)&lt;br /&gt;
        gameMode    = 1,&lt;br /&gt;
&lt;br /&gt;
        -- Repair speed cap (% max health per second; 0 = unlimited)&lt;br /&gt;
        repairSpeed = 0,&lt;br /&gt;
&lt;br /&gt;
        -- Whether resources are recovered when resurrecting a unit&lt;br /&gt;
        resurrectHealth = false,&lt;br /&gt;
&lt;br /&gt;
        -- Metal recovered when reclaiming a living unit (1 = full cost)&lt;br /&gt;
        reclaimUnitMethod = 1,&lt;br /&gt;
&lt;br /&gt;
        -- Whether destroyed units leave wreck features&lt;br /&gt;
        featureDeathResurrect = true,&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 10. Lua Scripting ==&lt;br /&gt;
&lt;br /&gt;
Lua is the scripting language for everything above the engine&#039;s C++ layer.&lt;br /&gt;
The engine provides several isolated Lua environments:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Environment !! Entry point !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaRules&#039;&#039;&#039; (synced) || &amp;lt;code&amp;gt;luarules/main.lua&amp;lt;/code&amp;gt; || Authoritative game logic. All clients run the same code to stay in sync.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaRules&#039;&#039;&#039; (unsynced) || &amp;lt;code&amp;gt;luarules/draw.lua&amp;lt;/code&amp;gt; || Effects and UI driven by game logic but not part of the simulation.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaUI&#039;&#039;&#039; || &amp;lt;code&amp;gt;luaui/main.lua&amp;lt;/code&amp;gt; || Player-facing UI: HUD, minimap overlays, build menus, etc.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaGaia&#039;&#039;&#039; || &amp;lt;code&amp;gt;luagaia/main.lua&amp;lt;/code&amp;gt; || Logic for the neutral Gaia team (wrecks, map features, world events).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaIntro&#039;&#039;&#039; || &amp;lt;code&amp;gt;luaintro/main.lua&amp;lt;/code&amp;gt; || Loading screen and pre-game briefings.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaMenu&#039;&#039;&#039; || &amp;lt;code&amp;gt;luamenu/main.lua&amp;lt;/code&amp;gt; || Main menu (pre-game). Requires a menu archive.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Basecontent provides default &amp;lt;code&amp;gt;main.lua&amp;lt;/code&amp;gt; entry points and the wupget handler boilerplate so you can start writing gadgets and widgets without building a handler yourself.&lt;br /&gt;
&lt;br /&gt;
=== Synced vs unsynced ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Synced&#039;&#039;&#039; code participates in the game simulation. All clients run exactly the same instructions in exactly the same order — this is what keeps the game deterministic. Only synced code can affect units, resources, or any other simulation state.&lt;br /&gt;
* &#039;&#039;&#039;Unsynced&#039;&#039;&#039; code runs on each client independently. It can read simulation state for display purposes, but cannot modify it. UI, rendering, and sound belong here.&lt;br /&gt;
&lt;br /&gt;
{{warning|1=Calling unsynced functions from synced code causes a desync error. When writing gadgets, guard appropriately with &amp;lt;code&amp;gt;gadgetHandler:IsSyncedCode()&amp;lt;/code&amp;gt;.}}&lt;br /&gt;
&lt;br /&gt;
=== Your first gadget ===&lt;br /&gt;
&lt;br /&gt;
A gadget is a Lua file loaded by the gadget handler from &amp;lt;code&amp;gt;luarules/gadgets/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- luarules/gadgets/game_end.lua&lt;br /&gt;
-- Ends the game when a team&#039;s Commander is destroyed.&lt;br /&gt;
&lt;br /&gt;
local gadget = gadget ---@type Gadget&lt;br /&gt;
&lt;br /&gt;
function gadget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name    = &amp;quot;Game End Conditions&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Ends the game on Commander death.&amp;quot;,&lt;br /&gt;
        author  = &amp;quot;YourName&amp;quot;,&lt;br /&gt;
        date    = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer   = 0,&lt;br /&gt;
        enabled = true,&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Only run the logic in synced mode&lt;br /&gt;
if not gadgetHandler:IsSyncedCode() then return end&lt;br /&gt;
&lt;br /&gt;
-- Track alive Commanders by team&lt;br /&gt;
local commanderByTeam = {}&lt;br /&gt;
&lt;br /&gt;
function gadget:UnitFinished(unitID, unitDefID, teamID)&lt;br /&gt;
    local def = UnitDefs[unitDefID]&lt;br /&gt;
    if def and def.isCommander then&lt;br /&gt;
        commanderByTeam[teamID] = unitID&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function gadget:UnitDestroyed(unitID, unitDefID, teamID)&lt;br /&gt;
    if commanderByTeam[teamID] == unitID then&lt;br /&gt;
        commanderByTeam[teamID] = nil&lt;br /&gt;
        local allyTeamID = Spring.GetTeamAllyTeamID(teamID)&lt;br /&gt;
        local hasCommander = false&lt;br /&gt;
        for _, ally in ipairs(Spring.GetTeamList(allyTeamID)) do&lt;br /&gt;
            if commanderByTeam[ally] then&lt;br /&gt;
                hasCommander = true&lt;br /&gt;
                break&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
        if not hasCommander then&lt;br /&gt;
            Spring.GameOver({ allyTeamID })&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Your first widget ===&lt;br /&gt;
&lt;br /&gt;
A widget is a Lua file loaded by the widget handler from &amp;lt;code&amp;gt;luaui/widgets/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- luaui/widgets/resources_display.lua&lt;br /&gt;
-- Draws current metal and energy in the corner of the screen.&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name    = &amp;quot;Resources Display&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Shows current resource totals.&amp;quot;,&lt;br /&gt;
        author  = &amp;quot;YourName&amp;quot;,&lt;br /&gt;
        date    = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer   = 0,&lt;br /&gt;
        enabled = true,&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function widget:DrawScreen()&lt;br /&gt;
    local metal, _, metalIncome = Spring.GetTeamResources(Spring.GetMyTeamID(), &amp;quot;metal&amp;quot;)&lt;br /&gt;
    local energy, _, energyIncome = Spring.GetTeamResources(Spring.GetMyTeamID(), &amp;quot;energy&amp;quot;)&lt;br /&gt;
    if not metal then return end&lt;br /&gt;
&lt;br /&gt;
    gl.Color(1, 1, 1, 1)&lt;br /&gt;
    gl.Text(string.format(&amp;quot;Metal: %.0f (+%.1f/s)&amp;quot;, metal, metalIncome), 10, 60, 14, &amp;quot;s&amp;quot;)&lt;br /&gt;
    gl.Text(string.format(&amp;quot;Energy: %.0f (+%.1f/s)&amp;quot;, energy, energyIncome), 10, 40, 14, &amp;quot;s&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Callins and callouts ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Callins&#039;&#039;&#039; are functions in your code the engine invokes at the right moment: &amp;lt;code&amp;gt;UnitCreated&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;UnitDestroyed&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GameOver&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;DrawScreen&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Update&amp;lt;/code&amp;gt;, and many more.&lt;br /&gt;
You can list all available callins at runtime with &amp;lt;code&amp;gt;Script.GetCallInList()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Callouts&#039;&#039;&#039; are &amp;lt;code&amp;gt;Spring.*&amp;lt;/code&amp;gt; functions you call into the engine: &amp;lt;code&amp;gt;Spring.GetTeamResources&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.GiveOrderToUnit&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.Echo&amp;lt;/code&amp;gt;, etc.&lt;br /&gt;
The full reference is at [https://beyond-all-reason.github.io/spring/ldoc/ beyond-all-reason.github.io/spring/ldoc/].&lt;br /&gt;
&lt;br /&gt;
=== Communicating between environments ===&lt;br /&gt;
&lt;br /&gt;
The Lua environments are isolated from each other by design.&lt;br /&gt;
Common bridges:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Direction !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;WG&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;GG&amp;lt;/code&amp;gt; table || Within one environment || Widgets share the global &amp;lt;code&amp;gt;WG&amp;lt;/code&amp;gt; table; gadgets share &amp;lt;code&amp;gt;GG&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SendToUnsynced&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;RecvFromSynced&amp;lt;/code&amp;gt; || Synced → Unsynced || Pass data from game logic to rendering or UI without exposing it as a callout.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Spring.SendLuaRulesMsg&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;gadget:RecvLuaMsg&amp;lt;/code&amp;gt; || LuaUI → LuaRules || UI triggers a game action beyond normal unit orders.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Spring.SendLuaUIMsg&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;widget:RecvLuaMsg&amp;lt;/code&amp;gt; || LuaUI ↔ all LuaUIs || Broadcast UI state to other players&#039; widgets.&lt;br /&gt;
|-&lt;br /&gt;
| Rules params (&amp;lt;code&amp;gt;Spring.SetRulesParam&amp;lt;/code&amp;gt;) || LuaRules → all || Expose synced values as read-only key/value pairs readable by any environment.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
See [[Recoil:Wupget Communication]] for detailed examples and best practices.&lt;br /&gt;
&lt;br /&gt;
=== Creating user interfaces ===&lt;br /&gt;
&lt;br /&gt;
There are two main approaches to UI in Recoil:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;OpenGL drawing&#039;&#039;&#039; — use &amp;lt;code&amp;gt;gl.*&amp;lt;/code&amp;gt; callouts directly (as in the widget example above) or a higher-level framework like [https://github.com/beyond-all-reason/Beyond-All-Reason/tree/master/luaui/Widgets FlowUI] or [https://springrts.com/wiki/Chili Chili].&lt;br /&gt;
* &#039;&#039;&#039;RmlUi&#039;&#039;&#039; — Recoil has built-in support for an HTML/CSS-style reactive UI framework. Recommended for complex interfaces. See [[Recoil:RmlUI Starter Guide]].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 11. Running Your Game ==&lt;br /&gt;
&lt;br /&gt;
The engine is launched with a &#039;&#039;&#039;start script&#039;&#039;&#039; — a plain text file (conventionally &amp;lt;code&amp;gt;.sdf&amp;lt;/code&amp;gt;) describing who is playing, which game, and which map.&lt;br /&gt;
&lt;br /&gt;
=== Minimal start script ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[GAME]&lt;br /&gt;
{&lt;br /&gt;
    GameType = My Recoil Game;   -- must match modinfo.lua name or shortName&lt;br /&gt;
    MapName  = my_map.smf;       -- the .smf file inside the map archive&lt;br /&gt;
&lt;br /&gt;
    IsHost   = 1;&lt;br /&gt;
    MyPlayerName = Dev;&lt;br /&gt;
&lt;br /&gt;
    [PLAYER0]&lt;br /&gt;
    {&lt;br /&gt;
        Name      = Dev;&lt;br /&gt;
        Password  = ;&lt;br /&gt;
        Spectator = 0;&lt;br /&gt;
        Team      = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    [TEAM0]&lt;br /&gt;
    {&lt;br /&gt;
        TeamLeader = 0;&lt;br /&gt;
        AllyTeam   = 0;&lt;br /&gt;
        RgbColor   = 0.1 0.4 1.0;&lt;br /&gt;
        Side       = MyFaction;   -- must match a side declared in modinfo.lua&lt;br /&gt;
        StartPosX  = 4000;&lt;br /&gt;
        StartPosZ  = 4000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    [ALLYTEAM0]&lt;br /&gt;
    {&lt;br /&gt;
        NumAllies = 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Save as &amp;lt;code&amp;gt;game.sdf&amp;lt;/code&amp;gt; in your Recoil data directory and launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./spring game.sdf          # Linux&lt;br /&gt;
spring.exe game.sdf        # Windows&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=If the engine cannot find your game, check that the folder name ends in exactly &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; (case-sensitive on Linux) and that &amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt; is at the root of that folder, not in a subfolder.}}&lt;br /&gt;
&lt;br /&gt;
=== Headless mode ===&lt;br /&gt;
&lt;br /&gt;
For automated testing or AI-vs-AI matches without graphics:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./spring_headless game.sdf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Widgets still run in headless mode, making it useful for data-gathering scripts and CI testing.&lt;br /&gt;
&lt;br /&gt;
=== Hot-reloading Lua scripts ===&lt;br /&gt;
&lt;br /&gt;
While a game is running you can reload Lua environments without restarting:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/luarules reload    ← reload LuaRules gadgets&lt;br /&gt;
/luaui reload      ← reload LuaUI widgets&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This dramatically speeds up iteration. Keep &amp;lt;code&amp;gt;/cheat&amp;lt;/code&amp;gt; toggled on during development so these commands are available.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 12. Useful Debug Commands ==&lt;br /&gt;
&lt;br /&gt;
In-game chat commands prefixed with &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt; control engine and game state.&lt;br /&gt;
Enable cheats first with &amp;lt;code&amp;gt;/cheat&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Command !! Effect&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/cheat&amp;lt;/code&amp;gt; || Toggle cheats on/off. Required for most debug commands.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/give [unitname]&amp;lt;/code&amp;gt; || Spawn a unit at the cursor position.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/destroy&amp;lt;/code&amp;gt; || Destroy all selected units immediately.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/invincible&amp;lt;/code&amp;gt; || Toggle invincibility for selected units.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/godmode&amp;lt;/code&amp;gt; || Toggle god mode — all units obey all players.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/speed [x]&amp;lt;/code&amp;gt; || Set game speed multiplier (e.g. &amp;lt;code&amp;gt;/speed 5&amp;lt;/code&amp;gt;).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/luarules reload&amp;lt;/code&amp;gt; || Reload all LuaRules gadgets.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/luaui reload&amp;lt;/code&amp;gt; || Reload all LuaUI widgets.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/editdefs&amp;lt;/code&amp;gt; || Enter def-editing mode. Lets you change unit defs live via Lua assignment (development only, desyncs in multiplayer).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Spring.Echo(&amp;quot;msg&amp;quot;)&amp;lt;/code&amp;gt; || Print a message to the in-game console from any Lua environment.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{{tip|1=Add &amp;lt;code&amp;gt;Spring.Echo()&amp;lt;/code&amp;gt; calls liberally while developing. They appear in the in-game console and in the engine log file (&amp;lt;code&amp;gt;infolog.txt&amp;lt;/code&amp;gt;).}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 13. Resources and Economy ==&lt;br /&gt;
&lt;br /&gt;
Recoil&#039;s economy follows the TA tradition: resources are a flow rate, not a stockpile of discrete units.&lt;br /&gt;
&lt;br /&gt;
=== Metal ===&lt;br /&gt;
&lt;br /&gt;
Metal is the primary construction resource, gathered by:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Metal extractors&#039;&#039;&#039; (mexes) — units with &amp;lt;code&amp;gt;extractsMetal &amp;gt; 0&amp;lt;/code&amp;gt; placed over metal spots defined in the map.&lt;br /&gt;
* &#039;&#039;&#039;Metal converters / makers&#039;&#039;&#039; — units that convert energy into metal at an exchange rate.&lt;br /&gt;
* &#039;&#039;&#039;Reclaiming&#039;&#039;&#039; — construction units can reclaim wreckage, trees, and living units/structures to recover metal.&lt;br /&gt;
&lt;br /&gt;
Metal spots are defined in the map archive (as an SMF metal intensity map or a Lua table) and read with &amp;lt;code&amp;gt;Spring.GetMetalMapSize()&amp;lt;/code&amp;gt; and related callouts.&lt;br /&gt;
&lt;br /&gt;
=== Energy ===&lt;br /&gt;
&lt;br /&gt;
Energy is produced by:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Solar collectors&#039;&#039;&#039; — cheap, stable, small output.&lt;br /&gt;
* &#039;&#039;&#039;Geothermal plants&#039;&#039;&#039; — placed on geothermal vents for high, free output.&lt;br /&gt;
* &#039;&#039;&#039;Fusion reactors&#039;&#039;&#039; — high output, high cost, large explosion on death.&lt;br /&gt;
* &#039;&#039;&#039;Wind generators&#039;&#039;&#039; — cheap, output depends on map wind setting.&lt;br /&gt;
&lt;br /&gt;
Most level-2 weapons and some movement types consume energy to fire or operate.&lt;br /&gt;
An energy deficit stalls construction and disables energy-hungry weapons.&lt;br /&gt;
&lt;br /&gt;
{{tip|1=Keep metal and energy production roughly balanced. An energy surplus is wasted; an energy deficit stalls your economy. Watching the +/- indicator next to each resource bar is the fastest way to spot a developing shortage.}}&lt;br /&gt;
&lt;br /&gt;
=== Storage ===&lt;br /&gt;
&lt;br /&gt;
By default teams have a small resource cap. Build storage structures to increase it.&lt;br /&gt;
&amp;lt;code&amp;gt;metalStorage&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;energyStorage&amp;lt;/code&amp;gt; are unit def keys you set on storage buildings.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 14. Maps and Map Integration ==&lt;br /&gt;
&lt;br /&gt;
Maps are separate archives placed in &amp;lt;code&amp;gt;maps/&amp;lt;/code&amp;gt;.&lt;br /&gt;
They are not bundled with your game — players download them independently.&lt;br /&gt;
&lt;br /&gt;
The map defines:&lt;br /&gt;
* Height terrain data (the &amp;lt;code&amp;gt;.smf&amp;lt;/code&amp;gt; file)&lt;br /&gt;
* Sky, water, atmosphere settings&lt;br /&gt;
* Metal spot positions&lt;br /&gt;
* Feature placement (trees, rocks)&lt;br /&gt;
* Optional &amp;lt;code&amp;gt;mapinfo.lua&amp;lt;/code&amp;gt; with gravity, max wind, and other overrides&lt;br /&gt;
&lt;br /&gt;
=== Reading map data from Lua ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
-- Inside a gadget (synced):&lt;br /&gt;
local mapX, mapZ = Game.mapSizeX, Game.mapSizeZ&lt;br /&gt;
local metalX, metalZ = Spring.GetMetalMapSize()&lt;br /&gt;
&lt;br /&gt;
-- Place a unit at the start position of team 0:&lt;br /&gt;
local x, y, z = Spring.GetTeamStartPosition(0)&lt;br /&gt;
Spring.CreateUnit(&amp;quot;commander&amp;quot;, x, y, z, &amp;quot;n&amp;quot;, 0)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restricting map compatibility ===&lt;br /&gt;
&lt;br /&gt;
Check map properties at startup and call &amp;lt;code&amp;gt;Spring.GameOver&amp;lt;/code&amp;gt; if requirements are not met:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
function gadget:GamePreload()&lt;br /&gt;
    local mapOptions = Spring.GetMapOptions()&lt;br /&gt;
    if mapOptions.myRequiredKey ~= &amp;quot;expectedValue&amp;quot; then&lt;br /&gt;
        Spring.Echo(&amp;quot;This map is not compatible with MyGame!&amp;quot;)&lt;br /&gt;
        Spring.GameOver({})&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 15. Packaging and Distribution ==&lt;br /&gt;
&lt;br /&gt;
When ready to distribute, compress your &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; folder contents:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Linux — create .sdz (zip)&lt;br /&gt;
cd games/mygame.sdd&lt;br /&gt;
zip -r ../mygame-0.1.0.sdz .&lt;br /&gt;
&lt;br /&gt;
# Linux — create .sd7 (7-zip; better compression)&lt;br /&gt;
cd games/mygame.sdd&lt;br /&gt;
7z a ../mygame-0.1.0.sd7 .&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=Make sure &amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt; ends up at the &#039;&#039;&#039;root&#039;&#039;&#039; of the archive, not inside a subfolder. Some zip tools create an extra folder level automatically — check the archive structure before distributing.}}&lt;br /&gt;
&lt;br /&gt;
=== Rapid ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/beyond-all-reason/rapid Rapid] is the standard package distribution system for Recoil games.&lt;br /&gt;
It allows lobby clients such as [https://github.com/beyond-all-reason/bar-lobby BAR Lobby] or [https://github.com/beyond-all-reason/Chobby Chobby] to download and update games automatically.&lt;br /&gt;
See the [https://github.com/beyond-all-reason/rapid Rapid documentation] for how to host and tag releases.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 16. Next Steps ==&lt;br /&gt;
&lt;br /&gt;
With a working minimal game, the usual next steps are:&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;More unit types&#039;&#039;&#039;&lt;br /&gt;
: Add builders, resource extractors, power generators, turrets, and mobile combat units following the same unit def pattern. Give each its own file in &amp;lt;code&amp;gt;units/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;MoveClasses&#039;&#039;&#039;&lt;br /&gt;
: Define movement types in &amp;lt;code&amp;gt;gamedata/movedefs.lua&amp;lt;/code&amp;gt; to control how units traverse terrain: hover, boat, amphibious, kbot, tank, aircraft. Each family has different slope tolerance and water behaviour.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Armour classes&#039;&#039;&#039;&lt;br /&gt;
: Define armour types in &amp;lt;code&amp;gt;gamedata/armorclasses.lua&amp;lt;/code&amp;gt; and reference them in unit defs. Then use the &amp;lt;code&amp;gt;damage&amp;lt;/code&amp;gt; table in weapon defs to give different damage values against different armour classes.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Resource extractors and converters&#039;&#039;&#039;&lt;br /&gt;
: A unit with &amp;lt;code&amp;gt;extractsMetal &amp;gt; 0&amp;lt;/code&amp;gt; placed over a metal spot extracts metal. Add an energy converter unit to turn metal into energy and vice versa. Add storage buildings with &amp;lt;code&amp;gt;metalStorage&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;energyStorage&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Tech levels and factories&#039;&#039;&#039;&lt;br /&gt;
: A factory unit has a &amp;lt;code&amp;gt;buildOptions&amp;lt;/code&amp;gt; list of unit def names it can produce. Level-2 factories can be unlocked by a prerequisite gadget once a level-2 lab is built.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;AI support&#039;&#039;&#039;&lt;br /&gt;
: The Skirmish AI interface lets players add bot opponents without any gadget work. For custom AI behaviour, write a [[https://springrts.com/wiki/Lua_AI Lua AI]] inside your game&#039;s Lua scripting.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Full UI&#039;&#039;&#039;&lt;br /&gt;
: The basecontent widget handler gives you a functional HUD for free. For a polished interface, use [[Recoil:RmlUI Starter Guide|RmlUI]] for HTML/CSS-style reactive widgets, or raw &amp;lt;code&amp;gt;gl.*&amp;lt;/code&amp;gt; OpenGL callouts for maximum control.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Map creation&#039;&#039;&#039;&lt;br /&gt;
: Creating your own map requires [https://github.com/beyond-all-reason/MapConv MapConv] (height and texture compilation) and a height editor such as [https://springrts.com/wiki/SpringMapEdit SpringMapEdit]. See the Recoil map documentation for the full pipeline.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Multiplayer and dedicated servers&#039;&#039;&#039;&lt;br /&gt;
: Replays are recorded automatically when &amp;lt;code&amp;gt;RecordDemo=1&amp;lt;/code&amp;gt; is set in the start script. For large-scale multiplayer, use the dedicated binary (&amp;lt;code&amp;gt;spring_dedicated&amp;lt;/code&amp;gt;) with an autohost such as [https://github.com/gajop/spads SPADS].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 17. Further Reading ==&lt;br /&gt;
&lt;br /&gt;
=== On this wiki ===&lt;br /&gt;
&lt;br /&gt;
* [[Recoil:VFS Basics]] — full documentation of the Virtual File System, modes, and load order&lt;br /&gt;
* [[Recoil:Unit Types Basics]] — deep dive into unit defs, post-processing, and the wupget-facing UnitDefs table&lt;br /&gt;
* [[Recoil:Widgets and Gadgets]] — the addon (wupget) system in full detail&lt;br /&gt;
* [[Recoil:Wupget Communication]] — passing data between Lua environments&lt;br /&gt;
* [[Recoil:RmlUI Starter Guide]] — reactive HTML/CSS-style UI for your game&lt;br /&gt;
* [[Recoil:Headless and Dedicated]] — server binaries and automated testing&lt;br /&gt;
* [[Recoil:Migrating From Spring]] — breaking changes from Spring 105 to Recoil&lt;br /&gt;
* [[Spring_guide!]] — the original Spring player guide (most strategy concepts still apply)&lt;br /&gt;
&lt;br /&gt;
=== External resources ===&lt;br /&gt;
&lt;br /&gt;
* [https://beyond-all-reason.github.io/spring/ldoc/ Recoil Lua API reference] — complete callout and callin documentation&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine RecoilEngine on GitHub] — source, releases, and issue tracker&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine/tree/master/cont/base/springcontent Basecontent source] — default wupget handlers, tree defs, and other baseline content&lt;br /&gt;
* [https://springrts.com/wiki/Simple_Game_Tutorial Spring Simple Game Tutorial] — older but still broadly applicable (Spring and Recoil are highly compatible)&lt;br /&gt;
* [https://springrts.com/wiki/ SpringRTS wiki] — extensive documentation covering unit defs, weapon types, movement types, and more that applies directly to Recoil&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Recoil]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2708</id>
		<title>Recoil:RmlUI Starter Guide</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2708"/>
		<updated>2026-03-06T06:18:21Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Undo revision 2705 by Qrow (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:RmlUI Starter Guide}}&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
= RmlUI Starter Guide =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;For RecoilEngine game developers — build reactive, HTML/CSS-style game UIs in Lua.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Attribute !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Engine || RecoilEngine (Spring)&lt;br /&gt;
|-&lt;br /&gt;
| Framework || RmlUi ([https://github.com/mikke89/RmlUi mikke89/RmlUi])&lt;br /&gt;
|-&lt;br /&gt;
| Language || Lua (via sol2 bindings)&lt;br /&gt;
|-&lt;br /&gt;
| Availability || LuaUI (LuaIntro / LuaMenu planned)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 1. Introduction ==&lt;br /&gt;
&lt;br /&gt;
RmlUI is a UI framework that lets you build reactive game interfaces using an HTML/CSS-style workflow — with Lua instead of JavaScript. If you have ever built a web page, the learning curve is gentle. If you haven&#039;t, the concepts are still approachable because the mental model (mark-up + style + logic) is widely documented.&lt;br /&gt;
&lt;br /&gt;
RecoilEngine ships with a full RmlUI integration including:&lt;br /&gt;
&lt;br /&gt;
* An OpenGL 3 renderer tightly coupled to the engine&#039;s rendering pipeline&lt;br /&gt;
* A virtual-filesystem-aware file loader so your &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files live inside game archives&lt;br /&gt;
* Complete Lua bindings via sol2 so every RmlUI object is accessible from Lua&lt;br /&gt;
* Custom elements: &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;texture&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; for engine textures and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;svg&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; for vector art&lt;br /&gt;
* A built-in DOM debugger for interactive inspection&lt;br /&gt;
&lt;br /&gt;
This guide walks you through everything you need to get a working, reactive widget running in LuaUI. The examples are game-engine-agnostic and safe to copy into any Recoil-based game.&lt;br /&gt;
&lt;br /&gt;
{{Note|RmlUI is currently available in &#039;&#039;&#039;LuaUI&#039;&#039;&#039; (the in-game UI). Support for LuaIntro and LuaMenu is planned for a future release.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 2. Key Concepts ==&lt;br /&gt;
&lt;br /&gt;
Before writing any code it helps to understand the five core objects:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Concept !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Context&#039;&#039;&#039; || A named container that holds documents and data models. Multiple contexts can exist simultaneously, each rendered independently.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Document&#039;&#039;&#039; || A parsed RML file representing a DOM tree. Documents live inside a Context and can be shown, hidden, or closed.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;RML&#039;&#039;&#039; || The markup language for documents. Very similar to XHTML (well-formed XML). The root tag is &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;rml&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;html&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;RCSS&#039;&#039;&#039; || The styling language. Similar to CSS2 with some differences (see section 11). File extension: &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Data Model&#039;&#039;&#039; || A Lua table exposed to the RML document via reactive data bindings. Changes to the model automatically update the rendered UI.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;&#039;Lua side&#039;&#039;&#039; you create contexts, attach data models to them, and load documents. On the &#039;&#039;&#039;RML side&#039;&#039;&#039; you declare your UI structure and reference data-model values through data bindings.&lt;br /&gt;
&lt;br /&gt;
Each widget will typically consist of at least three files: a &amp;lt;code&amp;gt;.lua&amp;lt;/code&amp;gt;, a &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt;, and optionally a &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file — grouping them in a folder per widget keeps things tidy.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 3. Setup ==&lt;br /&gt;
&lt;br /&gt;
RecoilEngine ships a minimal setup script at &amp;lt;code&amp;gt;cont/LuaUI/rml_setup.lua&amp;lt;/code&amp;gt;. It is included automatically by the base content handler but you should understand what it does so you can extend it for your game.&lt;br /&gt;
&lt;br /&gt;
=== 3.1 The rml_setup.lua script ===&lt;br /&gt;
&lt;br /&gt;
The script performs three tasks: guards against double-initialisation, loads font faces, and registers mouse-cursor aliases. Below is a more complete version that also creates a shared context and configures dp scaling:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--  luaui/rml_setup.lua&lt;br /&gt;
--  author:  lov + ChrisFloofyKitsune&lt;br /&gt;
--  License: GNU GPL, v2 or later&lt;br /&gt;
&lt;br /&gt;
if (RmlGuard or not RmlUi) then&lt;br /&gt;
    return&lt;br /&gt;
end&lt;br /&gt;
-- Prevent this from running more than once&lt;br /&gt;
RmlGuard = true&lt;br /&gt;
&lt;br /&gt;
-- Patch CreateContext to set dp_ratio automatically&lt;br /&gt;
local oldCreateContext = RmlUi.CreateContext&lt;br /&gt;
local function NewCreateContext(name)&lt;br /&gt;
    local context = oldCreateContext(name)&lt;br /&gt;
    local viewSizeX, viewSizeY = Spring.GetViewGeometry()&lt;br /&gt;
    local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
    local baseWidth, baseHeight = 1920, 1080&lt;br /&gt;
    local resFactor = math.min(viewSizeX / baseWidth, viewSizeY / baseHeight)&lt;br /&gt;
    -- Floor to 2 decimal places to avoid floating point drift&lt;br /&gt;
    context.dp_ratio = math.floor(resFactor * userScale * 100) / 100&lt;br /&gt;
    return context&lt;br /&gt;
end&lt;br /&gt;
RmlUi.CreateContext = NewCreateContext&lt;br /&gt;
&lt;br /&gt;
-- Load fonts (list your .ttf files here)&lt;br /&gt;
local font_files = {&lt;br /&gt;
    -- &amp;quot;Fonts/MyFont-Regular.ttf&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
for _, file in ipairs(font_files) do&lt;br /&gt;
    RmlUi.LoadFontFace(file, true)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Map CSS cursor names to engine cursor names&lt;br /&gt;
-- CSS cursor list: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;default&amp;quot;, &#039;cursornormal&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;move&amp;quot;,    &#039;uimove&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;pointer&amp;quot;, &#039;Move&#039;)&lt;br /&gt;
&lt;br /&gt;
-- Create the shared context used by all widgets&lt;br /&gt;
RmlUi.CreateContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3.2 Including the setup in main.lua ===&lt;br /&gt;
&lt;br /&gt;
Add a single line to &amp;lt;code&amp;gt;luaui/main.lua&amp;lt;/code&amp;gt; to run the setup before any widgets load:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- luaui/main.lua&lt;br /&gt;
VFS.Include(LUAUI_DIRNAME .. &amp;quot;rml_setup.lua&amp;quot;, nil, VFS.ZIP)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|The base-content &amp;lt;code&amp;gt;rml_setup.lua&amp;lt;/code&amp;gt; only loads a font and sets cursor aliases; it does &#039;&#039;&#039;not&#039;&#039;&#039; create a context. If you are building a new game you should add context creation here or let each widget create its own context.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 4. Writing Your First Document (.rml) ==&lt;br /&gt;
&lt;br /&gt;
Create a file at &amp;lt;code&amp;gt;luaui/widgets/my_widget/my_widget.rml&amp;lt;/code&amp;gt;. RML is well-formed XML, so every tag must be closed and attribute values must be quoted.&lt;br /&gt;
&lt;br /&gt;
=== 4.1 Root structure ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;rml&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;My Widget&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;!-- External stylesheet (optional) --&amp;gt;&lt;br /&gt;
    &amp;lt;link type=&amp;quot;text/rcss&amp;quot; href=&amp;quot;my_widget.rcss&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;!-- Inline styles --&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        body { margin: 0; padding: 0; }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;!-- Your content here --&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/rml&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4.2 Styles (inline and .rcss) ===&lt;br /&gt;
&lt;br /&gt;
RmlUI starts with &#039;&#039;&#039;no default styles&#039;&#039;&#039; at all — not even block/inline defaults. The RmlUI docs provide an HTML4 base stylesheet you can copy in, but you will need to add form element styles yourself.&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;dp&amp;lt;/code&amp;gt; units instead of &amp;lt;code&amp;gt;px&amp;lt;/code&amp;gt; so your UI scales correctly with the player&#039;s display and &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; setting.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;css&amp;quot;&amp;gt;&lt;br /&gt;
/* Typical widget container */&lt;br /&gt;
#my-widget {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    width: 400dp;&lt;br /&gt;
    right: 10dp;&lt;br /&gt;
    top: 50%;&lt;br /&gt;
    transform: translateY(-50%);&lt;br /&gt;
    pointer-events: auto;   /* required to receive mouse input */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#my-widget .panel {&lt;br /&gt;
    padding: 10dp;&lt;br /&gt;
    border-radius: 8dp;&lt;br /&gt;
    border: 1dp #6c7086;   /* note: no &#039;solid&#039; keyword – see section 11 */&lt;br /&gt;
    background-color: rgba(30, 30, 46, 200);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4.3 Data bindings in RML ===&lt;br /&gt;
&lt;br /&gt;
Attach a data model to a root element with &amp;lt;code&amp;gt;data-model=&amp;quot;model_name&amp;quot;&amp;lt;/code&amp;gt;. All data binding attributes inside that element can reference the model.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
  &amp;lt;!-- data-model scopes all bindings inside this div --&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;my-widget&amp;quot; data-model=&amp;quot;my_model&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Text interpolation --&amp;gt;&lt;br /&gt;
    &amp;lt;p&amp;gt;Status: {{status_message}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Conditional visibility --&amp;gt;&lt;br /&gt;
    &amp;lt;div data-if=&amp;quot;show_details&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Extra details go here.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Dynamic class based on model value --&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;panel&amp;quot; data-class-active=&amp;quot;is_active&amp;quot;&amp;gt;&lt;br /&gt;
        Active panel&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Loop over an array --&amp;gt;&lt;br /&gt;
    &amp;lt;ul&amp;gt;&lt;br /&gt;
        &amp;lt;li data-for=&amp;quot;item, i: items&amp;quot;&amp;gt;{{i}}: {{item.label}}&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Two-way checkbox binding --&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; data-checked=&amp;quot;is_enabled&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Data event: calls model function on click --&amp;gt;&lt;br /&gt;
    &amp;lt;button data-click=&amp;quot;on_button_click()&amp;quot;&amp;gt;Click me&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Data binding reference:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Binding !! Effect&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{value}}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || Interpolate model value as text&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-model=&amp;quot;name&amp;quot;&amp;lt;/code&amp;gt; || Attach named data model to this element and its children&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-if=&amp;quot;flag&amp;quot;&amp;lt;/code&amp;gt; || Show element only when flag is truthy&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-class-X=&amp;quot;flag&amp;quot;&amp;lt;/code&amp;gt; || Add class X when flag is truthy&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-for=&amp;quot;v, i: arr&amp;quot;&amp;lt;/code&amp;gt; || Repeat element for each item in array arr&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-checked=&amp;quot;val&amp;quot;&amp;lt;/code&amp;gt; || Two-way bind checkbox to boolean model value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Call model function on click (data event)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-attr-src=&amp;quot;val&amp;quot;&amp;lt;/code&amp;gt; || Dynamically set the src attribute from model value&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 5. The Lua Widget ==&lt;br /&gt;
&lt;br /&gt;
Each widget is a Lua file that the handler loads. The &amp;lt;code&amp;gt;widget&amp;lt;/code&amp;gt; global is injected by the widget handler and provides the lifecycle hooks.&lt;br /&gt;
&lt;br /&gt;
=== 5.1 Widget skeleton ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- luaui/widgets/my_widget/my_widget.lua&lt;br /&gt;
if not RmlUi then return end   -- guard: RmlUi not available&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name    = &amp;quot;My Widget&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Demonstrates RmlUI basics.&amp;quot;,&lt;br /&gt;
        author  = &amp;quot;YourName&amp;quot;,&lt;br /&gt;
        date    = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer   = 0,&lt;br /&gt;
        enabled = true,&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5.2 Loading the document ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local RML_FILE  = &amp;quot;luaui/widgets/my_widget/my_widget.rml&amp;quot;&lt;br /&gt;
local MODEL_NAME = &amp;quot;my_model&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local rml_ctx    -- the RmlUi Context&lt;br /&gt;
local dm_handle  -- the DataModel handle (MUST be kept – see section 6)&lt;br /&gt;
local document   -- the loaded Document&lt;br /&gt;
&lt;br /&gt;
-- Initial data model state&lt;br /&gt;
local init_model = {&lt;br /&gt;
    status_message = &amp;quot;Ready&amp;quot;,&lt;br /&gt;
    is_enabled     = false,&lt;br /&gt;
    show_details   = false,&lt;br /&gt;
    items = {&lt;br /&gt;
        { label = &amp;quot;Alpha&amp;quot;, value = 1 },&lt;br /&gt;
        { label = &amp;quot;Beta&amp;quot;,  value = 2 },&lt;br /&gt;
    },&lt;br /&gt;
    -- Functions can live in the model too (callable from data-events)&lt;br /&gt;
    on_button_click = function()&lt;br /&gt;
        Spring.Echo(&amp;quot;Button was clicked!&amp;quot;)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function widget:Initialize()&lt;br /&gt;
    rml_ctx = RmlUi.GetContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    -- IMPORTANT: assign dm_handle or the engine WILL crash on RML render&lt;br /&gt;
    dm_handle = rml_ctx:OpenDataModel(MODEL_NAME, init_model)&lt;br /&gt;
    if not dm_handle then&lt;br /&gt;
        Spring.Echo(&amp;quot;[my_widget] Failed to open data model&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    document = rml_ctx:LoadDocument(RML_FILE, widget)&lt;br /&gt;
    if not document then&lt;br /&gt;
        Spring.Echo(&amp;quot;[my_widget] Failed to load document&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    document:ReloadStyleSheet()&lt;br /&gt;
    document:Show()&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5.3 Shutting down cleanly ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function widget:Shutdown()&lt;br /&gt;
    if document then&lt;br /&gt;
        document:Close()&lt;br /&gt;
        document = nil&lt;br /&gt;
    end&lt;br /&gt;
    -- Remove the model from the context; frees memory&lt;br /&gt;
    rml_ctx:RemoveDataModel(MODEL_NAME)&lt;br /&gt;
    dm_handle = nil&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 6. Data Models in Depth ==&lt;br /&gt;
&lt;br /&gt;
=== 6.1 Opening a data model ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;context:OpenDataModel(name, table)&amp;lt;/code&amp;gt; copies the structure of the table into a reactive proxy. The name must match the &amp;lt;code&amp;gt;data-model=&amp;quot;name&amp;quot;&amp;lt;/code&amp;gt; attribute in your RML. It must be unique within the context.&lt;br /&gt;
&lt;br /&gt;
{{Warning|You &#039;&#039;&#039;must&#039;&#039;&#039; store the return value of &amp;lt;code&amp;gt;OpenDataModel&amp;lt;/code&amp;gt; in a variable. Letting it be garbage-collected while the document is open will crash the engine the next time RmlUI tries to render a data binding.}}&lt;br /&gt;
&lt;br /&gt;
=== 6.2 Reading and writing values ===&lt;br /&gt;
&lt;br /&gt;
For simple string/number/boolean values at the top level of the model you can use dot-notation directly on the handle:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Read a value&lt;br /&gt;
local msg = dm_handle.status_message   -- works&lt;br /&gt;
&lt;br /&gt;
-- Write a value (auto-marks the field dirty; UI updates next frame)&lt;br /&gt;
dm_handle.status_message = &amp;quot;Unit selected&amp;quot;&lt;br /&gt;
dm_handle.is_enabled     = true&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6.3 Iterating arrays ===&lt;br /&gt;
&lt;br /&gt;
Because the handle is a sol2 proxy, you cannot iterate it directly with &amp;lt;code&amp;gt;pairs&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ipairs&amp;lt;/code&amp;gt;. Call &amp;lt;code&amp;gt;dm_handle:__GetTable()&amp;lt;/code&amp;gt; to get the underlying Lua table:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local tbl = dm_handle:__GetTable()&lt;br /&gt;
&lt;br /&gt;
for i, item in ipairs(tbl.items) do&lt;br /&gt;
    Spring.Echo(i, item.label, item.value)&lt;br /&gt;
    item.value = item.value + 1   -- modify in place&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6.4 Marking dirty ===&lt;br /&gt;
&lt;br /&gt;
When you modify nested values (e.g. elements inside an array) through &amp;lt;code&amp;gt;__GetTable()&amp;lt;/code&amp;gt;, you must tell the model which top-level key changed so that RmlUI re-renders the bound elements:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- After modifying tbl.items …&lt;br /&gt;
dm_handle:__SetDirty(&amp;quot;items&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
-- For a simple top-level assignment via dm_handle.key = value,&lt;br /&gt;
-- dirty-marking is done automatically.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 7. Events ==&lt;br /&gt;
&lt;br /&gt;
Recoil&#039;s RmlUI supports two distinct event families. They look similar but have different scopes — mixing them up is a common source of confusion.&lt;br /&gt;
&lt;br /&gt;
=== 7.1 Normal events (on*) ===&lt;br /&gt;
&lt;br /&gt;
Written as &amp;lt;code&amp;gt;onclick=&amp;quot;myFunc()&amp;quot;&amp;lt;/code&amp;gt;. The function is resolved from the &#039;&#039;&#039;second argument&#039;&#039;&#039; passed to &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; — the widget table. These events do &#039;&#039;&#039;not&#039;&#039;&#039; have access to the data model.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Lua&lt;br /&gt;
local doc_scope = {&lt;br /&gt;
    greet = function(name)&lt;br /&gt;
        Spring.Echo(&amp;quot;Hello&amp;quot;, name)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
document = rml_ctx:LoadDocument(RML_FILE, doc_scope)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- RML --&amp;gt;&lt;br /&gt;
&amp;lt;button onclick=&amp;quot;greet(&#039;World&#039;)&amp;quot;&amp;gt;Say hello&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 7.2 Data events (data-*) ===&lt;br /&gt;
&lt;br /&gt;
Written as &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt;. The function is resolved from the &#039;&#039;&#039;data model&#039;&#039;&#039;. These events have full access to all model values.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Lua model&lt;br /&gt;
local init_model = {&lt;br /&gt;
    counter = 0,&lt;br /&gt;
    increment = function()&lt;br /&gt;
        -- dm_handle is captured in the closure&lt;br /&gt;
        dm_handle.counter = dm_handle.counter + 1&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- RML --&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Count: {{counter}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;button data-click=&amp;quot;increment()&amp;quot;&amp;gt;+1&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Event type comparison:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type !! Syntax example !! Scope&lt;br /&gt;
|-&lt;br /&gt;
| Normal || &amp;lt;code&amp;gt;onclick=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; second argument (widget table)&lt;br /&gt;
|-&lt;br /&gt;
| Data || &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Data model table&lt;br /&gt;
|-&lt;br /&gt;
| Normal || &amp;lt;code&amp;gt;onmouseover=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; second argument&lt;br /&gt;
|-&lt;br /&gt;
| Data || &amp;lt;code&amp;gt;data-mouseover=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Data model table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 8. Custom Recoil Elements ==&lt;br /&gt;
&lt;br /&gt;
Beyond standard HTML elements, Recoil adds two custom RML elements.&lt;br /&gt;
&lt;br /&gt;
=== 8.1 The &amp;amp;lt;texture&amp;amp;gt; element ===&lt;br /&gt;
&lt;br /&gt;
Renders any engine texture — unit icons, map thumbnails, GL4 render targets, etc. Behaves identically to &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;img&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; except the &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute takes a &#039;&#039;&#039;Recoil texture reference string&#039;&#039;&#039; (see engine docs for the full format).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- Load a file from VFS --&amp;gt;&lt;br /&gt;
&amp;lt;texture src=&amp;quot;unitpics/armcom.png&amp;quot; width=&amp;quot;64dp&amp;quot; height=&amp;quot;64dp&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- Reference a named GL texture --&amp;gt;&lt;br /&gt;
&amp;lt;texture src=&amp;quot;%luaui:myTextureName&amp;quot; width=&amp;quot;128dp&amp;quot; height=&amp;quot;128dp&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 8.2 The &amp;amp;lt;svg&amp;amp;gt; element ===&lt;br /&gt;
&lt;br /&gt;
Renders an SVG image. Unlike upstream RmlUI, the Recoil implementation accepts either a file path &#039;&#039;&#039;or raw inline SVG data&#039;&#039;&#039; in the &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- External file --&amp;gt;&lt;br /&gt;
&amp;lt;svg src=&amp;quot;images/icon.svg&amp;quot; width=&amp;quot;32dp&amp;quot; height=&amp;quot;32dp&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- Inline SVG data --&amp;gt;&lt;br /&gt;
&amp;lt;svg src=&amp;quot;&amp;lt;svg xmlns=&#039;http://www.w3.org/2000/svg&#039; viewBox=&#039;0 0 10 10&#039;&amp;gt;&amp;lt;circle cx=&#039;5&#039; cy=&#039;5&#039; r=&#039;4&#039; fill=&#039;red&#039;/&amp;gt;&amp;lt;/svg&amp;gt;&amp;quot;&lt;br /&gt;
     width=&amp;quot;32dp&amp;quot; height=&amp;quot;32dp&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 9. Debugging ==&lt;br /&gt;
&lt;br /&gt;
RmlUI ships with a DOM debugger that you can open in-game to inspect elements, check computed styles, and view event logs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Enable the debugger for the shared context.&lt;br /&gt;
-- Call this AFTER the context has been created.&lt;br /&gt;
RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
&lt;br /&gt;
-- A handy pattern: toggle via a build flag in rml_setup.lua&lt;br /&gt;
local DEBUG_RMLUI = true   -- set false before shipping&lt;br /&gt;
if DEBUG_RMLUI then&lt;br /&gt;
    RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once enabled, a small panel appears in the game window. Click &#039;&#039;&#039;Debug&#039;&#039;&#039; to open the inspector or &#039;&#039;&#039;Log&#039;&#039;&#039; for the event log.&lt;br /&gt;
&lt;br /&gt;
{{Note|The debugger attaches to one context at a time. Using the shared context means you see all widgets in one place — very useful for tracking cross-widget interactions.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 10. Best Practices ==&lt;br /&gt;
&lt;br /&gt;
; Use dp units everywhere&lt;br /&gt;
: The &amp;lt;code&amp;gt;dp&amp;lt;/code&amp;gt; unit scales with &amp;lt;code&amp;gt;context.dp_ratio&amp;lt;/code&amp;gt;, so your UI automatically adapts to different resolutions and the player&#039;s &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; setting.&lt;br /&gt;
&lt;br /&gt;
; One shared context per game&lt;br /&gt;
: Beyond All Reason and most community projects use a single &amp;lt;code&amp;gt;&#039;shared&#039;&amp;lt;/code&amp;gt; context. Documents in the same context can reference the same data models and are Z-ordered relative to each other.&lt;br /&gt;
&lt;br /&gt;
; Keep data models flat when possible&lt;br /&gt;
: Top-level writes via &amp;lt;code&amp;gt;dm_handle.key = value&amp;lt;/code&amp;gt; are automatically dirty-marked. Deeply nested structures require manual &amp;lt;code&amp;gt;__SetDirty&amp;lt;/code&amp;gt; calls — simpler models mean fewer bugs.&lt;br /&gt;
&lt;br /&gt;
; Separate per-document and shared styles&lt;br /&gt;
: Put styles unique to one document in a &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;style&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; block inside the &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; file. Put styles shared across multiple documents in a &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file and link it.&lt;br /&gt;
&lt;br /&gt;
; Guard against missing RmlUi&lt;br /&gt;
: Always start widgets with &amp;lt;code&amp;gt;if not RmlUi then return end&amp;lt;/code&amp;gt; to prevent errors in environments where RmlUI is unavailable.&lt;br /&gt;
&lt;br /&gt;
; Check return values&lt;br /&gt;
: Both &amp;lt;code&amp;gt;OpenDataModel&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; can fail silently if paths are wrong. Always check for nil and log an error so you know immediately what happened.&lt;br /&gt;
&lt;br /&gt;
; Organise by widget folder&lt;br /&gt;
: Group &amp;lt;code&amp;gt;luaui/widgets/my_widget/my_widget.lua&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; together. This makes the project navigable and each widget self-contained.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 11. RCSS Gotchas ==&lt;br /&gt;
&lt;br /&gt;
RCSS closely follows CSS2 but has several important differences that will trip up web developers:&lt;br /&gt;
&lt;br /&gt;
; No default stylesheet&lt;br /&gt;
: RmlUI ships with zero default styles. Block/inline, heading sizes, list bullets — none of it exists unless you add it. Copy the HTML4 base sheet from the RmlUI docs as a starting point.&lt;br /&gt;
&lt;br /&gt;
; RGBA alpha is 0–255&lt;br /&gt;
: &amp;lt;code&amp;gt;rgba(255, 0, 0, 128)&amp;lt;/code&amp;gt; means 50% transparent red. CSS uses 0.0–1.0 for alpha. The &amp;lt;code&amp;gt;opacity&amp;lt;/code&amp;gt; property still uses 0.0–1.0.&lt;br /&gt;
&lt;br /&gt;
; Border shorthand has no style keyword&lt;br /&gt;
: &amp;lt;code&amp;gt;border: 1dp #6c7086;&amp;lt;/code&amp;gt; works. &amp;lt;code&amp;gt;border: 1dp solid #6c7086;&amp;lt;/code&amp;gt; does &#039;&#039;&#039;not&#039;&#039;&#039; work. Only solid borders are supported; &amp;lt;code&amp;gt;border-style&amp;lt;/code&amp;gt; is unavailable.&lt;br /&gt;
&lt;br /&gt;
; background-* properties use decorators&lt;br /&gt;
: &amp;lt;code&amp;gt;background-color&amp;lt;/code&amp;gt; works normally. &amp;lt;code&amp;gt;background-image&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;background-size&amp;lt;/code&amp;gt;, etc. are replaced by the decorator system. See the RmlUI docs for syntax.&lt;br /&gt;
&lt;br /&gt;
; No list styling&lt;br /&gt;
: &amp;lt;code&amp;gt;list-style-type&amp;lt;/code&amp;gt; and friends are not supported. &amp;lt;code&amp;gt;ul&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;ol&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;li&amp;lt;/code&amp;gt; have no special rendering — treat them as plain div-like elements.&lt;br /&gt;
&lt;br /&gt;
; Input text is set by content, not value&lt;br /&gt;
: For &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;input type=&amp;quot;button&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;input type=&amp;quot;submit&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; the displayed text comes from the element&#039;s text content, not the &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; attribute (unlike HTML).&lt;br /&gt;
&lt;br /&gt;
; pointer-events: auto required&lt;br /&gt;
: By default elements do not receive mouse events. Add &amp;lt;code&amp;gt;pointer-events: auto&amp;lt;/code&amp;gt; to any element that needs to be interactive.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 12. Differences from Upstream RmlUI ==&lt;br /&gt;
&lt;br /&gt;
The Recoil implementation is based on the official RmlUI library but adds engine-specific features and changes some defaults:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Feature !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&amp;lt;texture&amp;gt;&amp;lt;/nowiki&amp;gt; element&#039;&#039;&#039; || A custom element that renders engine-managed textures via Recoil texture reference strings. Not present in upstream.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&amp;lt;svg&amp;gt;&amp;lt;/nowiki&amp;gt; inline data&#039;&#039;&#039; || The &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute accepts raw SVG markup in addition to file paths. Upstream only supports file paths.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;VFS file loader&#039;&#039;&#039; || All file paths are resolved through Recoil&#039;s Virtual File System, so &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files work inside &amp;lt;code&amp;gt;.sdz&amp;lt;/code&amp;gt; game archives transparently.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Lua bindings via sol2&#039;&#039;&#039; || The Lua API is provided by RmlSolLua (derived from [https://github.com/LoneBoco/RmlSolLua LoneBoco/RmlSolLua]) rather than the upstream Lua plugin, offering tighter engine integration.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dp_ratio via context&#039;&#039;&#039; || &amp;lt;code&amp;gt;context.dp_ratio&amp;lt;/code&amp;gt; is set from code rather than the system DPI, allowing games to honour the player&#039;s &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; preference.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{{Note|For everything not listed above, the upstream RmlUI documentation at [https://mikke89.github.io/RmlUiDoc/ mikke89.github.io/RmlUiDoc] applies directly.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 13. IDE Setup ==&lt;br /&gt;
&lt;br /&gt;
Configuring your editor for &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file extensions dramatically improves the authoring experience.&lt;br /&gt;
&lt;br /&gt;
=== VS Code ===&lt;br /&gt;
&lt;br /&gt;
# Open any &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; file in VS Code.&lt;br /&gt;
# Click the language mode indicator in the status bar (usually shows &#039;&#039;Plain Text&#039;&#039;).&lt;br /&gt;
# Choose &#039;&#039;&#039;Configure File Association for &#039;.rml&#039;&#039;&#039;&#039;&lt;br /&gt;
# Select &#039;&#039;&#039;HTML&#039;&#039;&#039; from the list.&lt;br /&gt;
# Repeat for &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files, selecting &#039;&#039;&#039;CSS&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Lua Language Server (lua-ls) ===&lt;br /&gt;
&lt;br /&gt;
Add the Recoil type annotations to your workspace for autocomplete on &amp;lt;code&amp;gt;RmlUi&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring&amp;lt;/code&amp;gt;, and all widget APIs. See the Recoil docs guide on the Lua Language Server for details.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 14. Quick Reference ==&lt;br /&gt;
&lt;br /&gt;
=== Lua API ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Call !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.CreateContext(name)&amp;lt;/code&amp;gt; || Create a new rendering context&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.GetContext(name)&amp;lt;/code&amp;gt; || Retrieve an existing context by name&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.LoadFontFace(path, fallback)&amp;lt;/code&amp;gt; || Register a &amp;lt;code&amp;gt;.ttf&amp;lt;/code&amp;gt; font (&amp;lt;code&amp;gt;fallback=true&amp;lt;/code&amp;gt; recommended)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.SetMouseCursorAlias(css, engine)&amp;lt;/code&amp;gt; || Map a CSS cursor name to an engine cursor name&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.SetDebugContext(name)&amp;lt;/code&amp;gt; || Attach the DOM debugger to a named context&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:OpenDataModel(name, tbl)&amp;lt;/code&amp;gt; || Create reactive data model from Lua table; &#039;&#039;&#039;store the return value!&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:RemoveDataModel(name)&amp;lt;/code&amp;gt; || Destroy a data model and free its memory&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:LoadDocument(path, scope)&amp;lt;/code&amp;gt; || Parse an RML file; scope is used for normal (&amp;lt;code&amp;gt;on*&amp;lt;/code&amp;gt;) events&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Show()&amp;lt;/code&amp;gt; || Make the document visible&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Hide()&amp;lt;/code&amp;gt; || Hide the document (keeps it in memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Close()&amp;lt;/code&amp;gt; || Destroy the document&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:ReloadStyleSheet()&amp;lt;/code&amp;gt; || Reload linked &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files (useful during development)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm:__GetTable()&amp;lt;/code&amp;gt; || Get the underlying Lua table for iteration&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm:__SetDirty(key)&amp;lt;/code&amp;gt; || Mark a top-level model key as changed; triggers re-render&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm.key = value&amp;lt;/code&amp;gt; || Write a top-level value; auto-marks dirty&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Useful Links ===&lt;br /&gt;
&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/ RmlUI official documentation]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/data_bindings/views_and_controllers.html RmlUI data bindings reference]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rcss.html RmlUI RCSS reference]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html RmlUI HTML4 base stylesheet]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rcss/decorators.html RmlUI decorators (backgrounds)]&lt;br /&gt;
* [https://github.com/mikke89/RmlUi RmlUI GitHub]&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine/tree/master/rts/Rml RecoilEngine source (rts/Rml/)]&lt;br /&gt;
* Recoil texture reference strings — see engine docs: &amp;lt;code&amp;gt;articles/texture-reference-strings&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:LuaUI]]&lt;br /&gt;
[[Category:RmlUI]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2707</id>
		<title>Recoil:RmlUI Starter Guide</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2707"/>
		<updated>2026-03-06T06:18:13Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Undo revision 2706 by Qrow (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;+++&lt;br /&gt;
title = &#039;RmlUi&#039;&lt;br /&gt;
date = 2025-05-11T13:17:05-07:00&lt;br /&gt;
draft = false&lt;br /&gt;
author = &amp;quot;Slashscreen&amp;quot;&lt;br /&gt;
+++&lt;br /&gt;
&lt;br /&gt;
RmlUi is a UI framework that is defined using a HTML/CSS style workflow (using Lua instead of JS) intended to simplify UI development especially for those already familiar with web development. It is designed for interactive applications, and so is reactive by default. You can learn more about it on the [RmlUI website] and [differences in the Recoil version here](#differences-between-upstream-rmlui-and-rmlui-in-recoil).&lt;br /&gt;
&lt;br /&gt;
## How does RmlUI Work?&lt;br /&gt;
&lt;br /&gt;
To get started, it&#039;s important to learn a few key concepts.&lt;br /&gt;
- Context: This is a bundle of documents and data models.&lt;br /&gt;
- Document: This is a document tree/DOM (document object model) holding the actual UI.&lt;br /&gt;
- RML: This is the markup language used to define the document, it is very similar to XHTML (HTML but must be well-formed XML).&lt;br /&gt;
- RCSS: This is the styling language used to style the document, it is very similar to CSS2 but does differ in some places.&lt;br /&gt;
- Data Model: This holds information (a Lua table) that is used in the UI code in data bindings.&lt;br /&gt;
&lt;br /&gt;
On the Lua side, you are creating 1 or more contexts and adding data models and documents to them.&lt;br /&gt;
&lt;br /&gt;
On the Rml side, you are creating documents to be loaded by the Lua which will likely include data bindings to show information from the data model, and references to lua functions for event handlers.&lt;br /&gt;
&lt;br /&gt;
As each widget/component you create will likely comprise of several files (.lua, .rml &amp;amp; .rcss) you may find having a folder for each widget/component a useful way to organise your files.&lt;br /&gt;
&lt;br /&gt;
## Getting Started&lt;br /&gt;
 &lt;br /&gt;
RmlUi is available in LuaUI (the game UI) with future availability in LuaIntro &amp;amp; LuaMenu planned, so it is already there for you to use. &lt;br /&gt;
However, to get going with it there is some setup code that should be considered for loading fonts, cursors and configuring ui scaling, an example script is provided below.&lt;br /&gt;
If you are working on a widget for an existing game, it is likely that the game already has some form of this setup code in, so you may be able to skip this section.&lt;br /&gt;
&lt;br /&gt;
### Setup script&lt;br /&gt;
&lt;br /&gt;
Here is the &amp;quot;[handler](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/rml_setup.lua)&amp;quot;, written by lov and ChrisFloofyKitsune, 2 of the people responsible for the RmlUi implementation.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
--  luaui/rml_setup.lua&lt;br /&gt;
--  author:  lov + ChrisFloofyKitsune&lt;br /&gt;
--&lt;br /&gt;
--  Copyright (C) 2024.&lt;br /&gt;
--  Licensed under the terms of the GNU GPL, v2 or later.&lt;br /&gt;
&lt;br /&gt;
if (RmlGuard or not RmlUi) then&lt;br /&gt;
	return&lt;br /&gt;
end&lt;br /&gt;
-- don&#039;t allow this initialization code to be run multiple times&lt;br /&gt;
RmlGuard = true&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	Recoil uses a custom set of Lua bindings (check out rts/Rml/SolLua/bind folder in the C++ engine code)&lt;br /&gt;
	Aside from the Lua API, the rest of the RmlUi documentation is still relevant&lt;br /&gt;
		https://mikke89.github.io/RmlUiDoc/index.html&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
--[[ create a common Context to be used for widgets&lt;br /&gt;
	pros:&lt;br /&gt;
		* Documents in the same Context can make use of the same DataModels, allowing for less duplicate data&lt;br /&gt;
		* Documents can be arranged in front/behind of each other dynamically&lt;br /&gt;
	cons:&lt;br /&gt;
		* Documents in the same Context can make use of the same data models, leading to side effects&lt;br /&gt;
		* DataModels must have unique names within the same Context&lt;br /&gt;
&lt;br /&gt;
	If you have lots of DataModel use you may want to create your own Context&lt;br /&gt;
	otherwise you should be able to just use the shared Context&lt;br /&gt;
&lt;br /&gt;
	Contexts created with the Lua API are automatically disposed of when the LuaUi environment is unloaded&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local oldCreateContext = RmlUi.CreateContext&lt;br /&gt;
&lt;br /&gt;
local function NewCreateContext(name)&lt;br /&gt;
	local context = oldCreateContext(name)&lt;br /&gt;
&lt;br /&gt;
	-- set up dp_ratio considering the user&#039;s UI scale preference and the screen resolution&lt;br /&gt;
	local viewSizeX, viewSizeY = Spring.GetViewGeometry()&lt;br /&gt;
&lt;br /&gt;
	local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
&lt;br /&gt;
	local baseWidth = 1920&lt;br /&gt;
	local baseHeight = 1080&lt;br /&gt;
	local resFactor = math.min(viewSizeX / baseWidth, viewSizeY / baseHeight)&lt;br /&gt;
&lt;br /&gt;
	context.dp_ratio = resFactor * userScale&lt;br /&gt;
&lt;br /&gt;
	context.dp_ratio = math.floor(context.dp_ratio * 100) / 100&lt;br /&gt;
	return context&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
RmlUi.CreateContext = NewCreateContext&lt;br /&gt;
&lt;br /&gt;
-- Load fonts&lt;br /&gt;
local font_files = {&lt;br /&gt;
}&lt;br /&gt;
for _, file in ipairs(font_files) do&lt;br /&gt;
	Spring.Echo(&amp;quot;loading font&amp;quot;, file)&lt;br /&gt;
	RmlUi.LoadFontFace(file, true)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Mouse Cursor Aliases&lt;br /&gt;
--[[&lt;br /&gt;
	These let standard CSS cursor names be used when doing styling.&lt;br /&gt;
	If a cursor set via RCSS does not have an alias, it is unchanged.&lt;br /&gt;
	CSS cursor list: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor&lt;br /&gt;
	RmlUi documentation: https://mikke89.github.io/RmlUiDoc/pages/rcss/user_interface.html#cursor&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- when &amp;quot;cursor: normal&amp;quot; is set via RCSS, &amp;quot;cursornormal&amp;quot; will be sent to the engine... and so on for the rest&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;default&amp;quot;, &#039;cursornormal&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;pointer&amp;quot;, &#039;Move&#039;) -- command cursors use the command name. TODO: replace with actual pointer cursor?&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;move&amp;quot;, &#039;uimove&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;nesw-resize&amp;quot;, &#039;uiresized2&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;nwse-resize&amp;quot;, &#039;uiresized1&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;ns-resize&amp;quot;, &#039;uiresizev&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;ew-resize&amp;quot;, &#039;uiresizeh&#039;)&lt;br /&gt;
&lt;br /&gt;
RmlUi.CreateContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
What this does is create a unified context &#039;shared&#039; for all your documents and data models, which is currently the recommended way to architect documents. If you have any custom font files, list them in `font_files`, otherwise leave it empty.&lt;br /&gt;
&lt;br /&gt;
The setup script above can then be included from your `luaui/main.lua` (main.lua is the entry point for the LuaUI environment).&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
VFS.Include(LUAUI_DIRNAME .. &amp;quot;rml_setup.lua&amp;quot;,  nil, VFS.ZIP) -- Runs the script&lt;br /&gt;
```&lt;br /&gt;
&amp;gt; [!NOTE] If you are working on a widget for an existing game, check whether the game already has a setup script — it may not include all of the pieces shown above (font loading, cursor aliases, context creation, dp_ratio).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### Keeping dp_ratio Updated&lt;br /&gt;
&lt;br /&gt;
The setup script sets `dp_ratio` once at context creation time, but the user may resize the window or change their `ui_scale` config during a session. To keep `dp_ratio` correct you need to recalculate and reapply it whenever the viewport changes.&lt;br /&gt;
&lt;br /&gt;
The `ViewResize` callin fires whenever the window is resized. Add it to your widget and update each context you own:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
function widget:ViewResize(newSizeX, newSizeY)&lt;br /&gt;
    local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
    local baseWidth = 1920&lt;br /&gt;
    local baseHeight = 1080&lt;br /&gt;
    local resFactor = math.min(newSizeX / baseWidth, newSizeY / baseHeight)&lt;br /&gt;
    local dp = math.floor(resFactor * userScale * 100) / 100&lt;br /&gt;
    widget.rmlContext.dp_ratio = dp&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
If you have multiple contexts, update each one. [Mupersega&#039;s `rml_context_manager.lua`](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/rml_context_manager.lua) in Beyond All Reason is a good reference implementation that handles context lifecycle and `dp_ratio` tracking together.&lt;br /&gt;
&lt;br /&gt;
### Writing Your First Document&lt;br /&gt;
&lt;br /&gt;
You will be creating files with .rml and .rcss extensions, as these closely resemble HTML and CSS it is worth configuring your editor to treat these file extensions as HTML and CSS respectively, see the [IDE Setup](#ide-setup) section for more information.&lt;br /&gt;
&lt;br /&gt;
Now, create an RML file somewhere under `luaui/widgets/`, like `luaui/widgets/getting_started.rml`. This is the UI document.&lt;br /&gt;
&lt;br /&gt;
Writing it is much like HTML by design. There are some differences, the most immediate being the root tag is called rml instead of html, most other RML/HTML differences relate to attributes for databindings and events, but for the time being, we don&#039;t need to worry about them.&lt;br /&gt;
&lt;br /&gt;
By default RmlUi has *NO* styles, this includes setting default element behaviour like block/inline and styles web developers would expect like input elements default appearances, as a starting point you can use [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though these do not include styles for form elements. &lt;br /&gt;
&lt;br /&gt;
Here&#039;s a basic widget written by Mupersega.&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;rml&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Rml Starter&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        #rml-starter-widget {&lt;br /&gt;
            pointer-events: auto;&lt;br /&gt;
            width: 400dp;&lt;br /&gt;
            right: 0;&lt;br /&gt;
            top: 50%;&lt;br /&gt;
            transform: translateY(-90%);&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            margin-right: 10dp;&lt;br /&gt;
        }&lt;br /&gt;
        #main-content {&lt;br /&gt;
            padding: 10dp;&lt;br /&gt;
            border-radius: 8dp;&lt;br /&gt;
            z-index: 1;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content {&lt;br /&gt;
            transform: translateY(0%);&lt;br /&gt;
            transition: top 0.1s linear-in-out;&lt;br /&gt;
            z-index: 0;&lt;br /&gt;
            height: 100%;&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            top: 0dp;&lt;br /&gt;
            left: 0dp;&lt;br /&gt;
            border-radius: 8dp;&lt;br /&gt;
            display: flex;&lt;br /&gt;
            flex-direction: column;&lt;br /&gt;
            justify-content: flex-end;&lt;br /&gt;
            align-items: center;&lt;br /&gt;
            padding-bottom: 20dp;&lt;br /&gt;
        }&lt;br /&gt;
        /* This is just a checkbox sitting above the entirety of the expanding content */&lt;br /&gt;
        /* It is bound directly with data-checked attr to the expanded value */&lt;br /&gt;
        #expanding-content&amp;gt;input {&lt;br /&gt;
            height: 100%;&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            z-index: 1;&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            top: 0dp;&lt;br /&gt;
            left: 0dp;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content.expanded {&lt;br /&gt;
            top: 90%;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content:hover {&lt;br /&gt;
            background-color: rgba(255, 0, 0, 125);&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;rml-starter-widget&amp;quot; class=&amp;quot;relative&amp;quot; data-model=&amp;quot;starter_model&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;div id=&amp;quot;main-content&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;h1 class=&amp;quot;text-primary&amp;quot;&amp;gt;Welcome to an Rml Starter Widget&amp;lt;/h1&amp;gt;&lt;br /&gt;
            &amp;lt;p&amp;gt;This is a simple example of an RMLUI widget.&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;div&amp;gt;&lt;br /&gt;
                &amp;lt;button onclick=&amp;quot;widget:Reload()&amp;quot;&amp;gt;reload widget&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;div id=&amp;quot;expanding-content&amp;quot; data-class-expanded=&amp;quot;expanded&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;&lt;br /&gt;
            &amp;lt;p&amp;gt;{{message}}&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;div&amp;gt;&lt;br /&gt;
                &amp;lt;span data-for=&amp;quot;test, i: testArray&amp;quot;&amp;gt;name:{{test.name}} index:{{i}}&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/rml&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
Let&#039;s take a look at different areas that are important to look at.&lt;br /&gt;
&lt;br /&gt;
`&amp;lt;div id=&amp;quot;rml-starter-widget&amp;quot; class=&amp;quot;relative&amp;quot; data-model=&amp;quot;starter_model&amp;quot;&amp;gt;`&lt;br /&gt;
1. Here, we bind to the data model using `data-model`. This is what we will need to name the data model in our Lua script later. Everything inside the model will be in scope and beneath the div.&lt;br /&gt;
2. Typically, it is recommended to bind your data model inside of a div beneath `body` rather than `body` itself.&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;div id=&amp;quot;expanding-content&amp;quot; data-class-expanded=&amp;quot;expanded&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
any attribute starting with `data-` is a &amp;quot;data event&amp;quot;. We will go through a couple below, but you can find out more here [on the RmlUi docs site](https://mikke89.github.io/RmlUiDoc/pages/data_bindings/views_and_controllers.html).&lt;br /&gt;
1. Double curly braces are used to show values from the data model within the document text. e.g. `{{message}}` shows the value of `message` in the data model.&lt;br /&gt;
2. `data-class-expanded=&amp;quot;expanded&amp;quot;` applies the `expanded` class to the div if the value `expanded` in the data model is `true`.&lt;br /&gt;
3. `&amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;` This is a two-way binding: the checkbox reflects the current value of `expanded` in the data model, and toggling it writes back `true` or `false` to that field.&lt;br /&gt;
4. When the data model is changed, the document is rerendered automatically. So, the expanding div will have the `expanded` class applied to it or removed whenever the check box is toggled.&lt;br /&gt;
&lt;br /&gt;
There are data bindings to allow you to loop through arrays (data-for) have conditional sections of the document (data-if) and many others. &lt;br /&gt;
&lt;br /&gt;
### The Lua&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE] For information on setting up your editor to provide intellisense behaviour see the [Lua Language Server guide]({{% ref &amp;quot;lua-language-server&amp;quot; %}}).&lt;br /&gt;
&lt;br /&gt;
To load your document into the shared context we created earlier and to define and add the data model you will need to have a lua script something like the one below.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
-- luaui/widgets/getting_started.lua&lt;br /&gt;
if not RmlUi then&lt;br /&gt;
    return&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name = &amp;quot;Rml Starter&amp;quot;,&lt;br /&gt;
        desc = &amp;quot;This widget is a starter example for RmlUi widgets.&amp;quot;,&lt;br /&gt;
        author = &amp;quot;Mupersega&amp;quot;,&lt;br /&gt;
        date = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer = -1000000,&lt;br /&gt;
        enabled = true&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local document&lt;br /&gt;
local dm_handle&lt;br /&gt;
local init_model = {&lt;br /&gt;
    expanded = false,&lt;br /&gt;
    message = &amp;quot;Hello, find my text in the data model!&amp;quot;,&lt;br /&gt;
    testArray = {&lt;br /&gt;
        { name = &amp;quot;Item 1&amp;quot;, value = 1 },&lt;br /&gt;
        { name = &amp;quot;Item 2&amp;quot;, value = 2 },&lt;br /&gt;
        { name = &amp;quot;Item 3&amp;quot;, value = 3 },&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local main_model_name = &amp;quot;starter_model&amp;quot;&lt;br /&gt;
&lt;br /&gt;
function widget:Initialize()&lt;br /&gt;
    widget.rmlContext = RmlUi.GetContext(&amp;quot;shared&amp;quot;) -- Get the context from the setup lua&lt;br /&gt;
&lt;br /&gt;
    -- Open the model, using init_model as the template. All values inside are copied.&lt;br /&gt;
    -- Returns a handle, which we will touch on later.&lt;br /&gt;
    dm_handle = widget.rmlContext:OpenDataModel(main_model_name, init_model)&lt;br /&gt;
    if not dm_handle then&lt;br /&gt;
        Spring.Echo(&amp;quot;RmlUi: Failed to open data model &amp;quot;, main_model_name)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Load the document we wrote earlier.&lt;br /&gt;
    document = widget.rmlContext:LoadDocument(&amp;quot;luaui/widgets/getting_started.rml&amp;quot;, widget)&lt;br /&gt;
    if not document then&lt;br /&gt;
        Spring.Echo(&amp;quot;Failed to load document&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- uncomment the line below to enable debugger&lt;br /&gt;
    -- RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
&lt;br /&gt;
    document:ReloadStyleSheet()&lt;br /&gt;
    document:Show()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function widget:Shutdown()&lt;br /&gt;
    widget.rmlContext:RemoveDataModel(main_model_name)&lt;br /&gt;
    if document then&lt;br /&gt;
        document:Close()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- This function is only for dev experience, ideally it would be a hot reload, and not required at all in a completed widget.&lt;br /&gt;
function widget:Reload(event)&lt;br /&gt;
    Spring.Echo(&amp;quot;Reloading&amp;quot;)&lt;br /&gt;
    Spring.Echo(event)&lt;br /&gt;
    widget:Shutdown()&lt;br /&gt;
    widget:Initialize()&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### The Data Model Handle&lt;br /&gt;
&lt;br /&gt;
In the script, we are given a data model handle. This is a proxy for the Lua table used as the data model; as the Recoil RmlUi integration uses Sol2 as a wrapper data cannot be accessed directly.&lt;br /&gt;
&lt;br /&gt;
In most cases, you can simply do `dm_handle.expanded = true`. Assigning to any field on the handle (or any nested table within it, at any depth) automatically marks the relevant parts of the model as dirty and triggers a UI update — you do not need to call `__SetDirty` manually.&lt;br /&gt;
&lt;br /&gt;
What if you have an array, like `testArray` above, and want to loop through it on the Lua side? You will need to get the underlying table:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local model_handle = dm_handle:__GetTable()&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
As of writing, this function is not documented in the Lua API, due to some problems with language server generics that haven&#039;t been sorted out yet. It is there, however, and will be added back in in the future. It returns the table with the shape of your initial model.&lt;br /&gt;
You can then iterate through it and change things as you please:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
for i, v in pairs(model_handle.testArray) do&lt;br /&gt;
    Spring.Echo(i, v.name)&lt;br /&gt;
    v.value = v.value + 1&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE] Use `pairs` rather than `ipairs` when iterating over data model arrays on the Lua side. The model handle does not currently implement `__len`, so `ipairs` will not traverse the array correctly. This will be resolved once `__len` is backported.&lt;br /&gt;
&lt;br /&gt;
### Debugging&lt;br /&gt;
&lt;br /&gt;
RmlUi comes with a debugger that lets you see and interact with the DOM. It&#039;s very handy! To use it, use this:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
RmlUi.SetDebugContext(DATA_MODEL_NAME)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
And it will appear in-game. A useful idiom I like is to put this in `rml_setup.lua`:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local DEBUG_RMLUI = true&lt;br /&gt;
&lt;br /&gt;
--...&lt;br /&gt;
&lt;br /&gt;
if DEBUG_RMLUI then&lt;br /&gt;
    RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
If you use the shared context, you can see everything that happens in it! Neat!&lt;br /&gt;
&lt;br /&gt;
## Things to Know and Best Practices&lt;br /&gt;
&lt;br /&gt;
### Things to know&lt;br /&gt;
Some of the rough edges you are likely to run into have already been discussed, like the data model thing, but here are some more:&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
Unlike a web browser a default set of styles is not included, as a starting point you can look at the [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though this doesn&#039;t provide styles for form elements.&lt;br /&gt;
&lt;br /&gt;
Input elements of type submit &amp;amp; button behave differently to HTML and more like Button elements in that their text is not set by the value attribute. (This is likely to be corrected in a future version)&lt;br /&gt;
&lt;br /&gt;
The alpha/transparency value of an RGBA colour is different to CSS (0-1) and instead uses 0-255. The css opacity does still use 0-1.&lt;br /&gt;
&lt;br /&gt;
List styling is unavailable (list-style-type etc.), you can still use UL/OL/LI elements but there is no special meaning to them, whilst you could use background images to replicate bullets there isn&#039;t a practical way to achieve numbered list items with RML/RCSS.&lt;br /&gt;
&lt;br /&gt;
Only solid borders are supported, so the border-style property is unavailable and the shorthand border property doesn&#039;t include a style part (```border: 1dp solid black;``` won&#039;t work, instead use ```border: 1dp black;```).&lt;br /&gt;
&lt;br /&gt;
background-color behaves as expected, all other background styles are different and use decorators instead see the [RmlUi documentation for more information on decorators.](https://mikke89.github.io/RmlUiDoc/pages/rcss/decorators.html) &lt;br /&gt;
&lt;br /&gt;
There are two kinds of events: data events, like `data-mouseover`, and normal events, like `onmouseover`. These have different data in their scopes.&lt;br /&gt;
- Data events have the data model in their scope.&lt;br /&gt;
- Normal events don&#039;t have the data model, but they *do* have whatever is passed into `widget` on `widget.rmlContext:LoadDocument`. `widget` doesn&#039;t have to be a widget, just any table with data in it.&lt;br /&gt;
&lt;br /&gt;
For example, take this:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local model = {&lt;br /&gt;
    add = function(a, b)&lt;br /&gt;
        Spring.Echo(a + b)&lt;br /&gt;
    end&lt;br /&gt;
}&lt;br /&gt;
local document_table = {&lt;br /&gt;
    print = function(msg)&lt;br /&gt;
        Spring.Echo(msg)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
dm_handle = widget.rmlContext:OpenDataModel(&amp;quot;test&amp;quot;, model)&lt;br /&gt;
document = widget.rmlContext:LoadDocument(&amp;quot;document.rml&amp;quot;, document_table)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;h1&amp;gt;Normal Events&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;add(1, 2)&amp;quot;&amp;gt;Won&#039;t work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;print(&#039;test&#039;)&amp;quot;&amp;gt;Will work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;h1&amp;gt;Data Events&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; data-click=&amp;quot;add(1, 2)&amp;quot;&amp;gt;Will work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; data-click=&amp;quot;print(&#039;test&#039;)&amp;quot;&amp;gt;Won&#039;t work!&amp;lt;/input&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Best Practices&lt;br /&gt;
&lt;br /&gt;
- To create a scalable interface the use of the dp unit over px is recommended as the scale can be set per context with SetDensityIndependentPixelRatio.&lt;br /&gt;
- For styles unique to a document, put them in a `style` tag. For shared styles, put them in an `rcss` file.&lt;br /&gt;
- Rely on Recoil&#039;s RmlUi Lua bindings doc for what you can and can&#039;t do. The Recoil implementation has some extra stuff the RmlUi docs don&#039;t.&lt;br /&gt;
- The Beyond All Reason devs prefer to use one shared context for all rmlui widgets.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### Differences between upstream RmlUI and RmlUI in Recoil&lt;br /&gt;
&lt;br /&gt;
- The SVG element allows either a filepath or raw SVG data in the src attribute, allowing for inline svg to be used (this may change to svg being supported between the opening and closing tag when implemented upstream)&lt;br /&gt;
- An additional element ```&amp;lt;texture&amp;gt;``` is available which allows for textures loaded in Recoil to be used, this behaves the same as an ```&amp;lt;img&amp;gt;``` element except the src attribute takes a [texture reference]({{% ref &amp;quot;articles/texture-reference-strings&amp;quot; %}})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### IDE Setup&lt;br /&gt;
&lt;br /&gt;
To get the best experience with RmlUi, you should set up your editor to use HTML syntax highlighting for .rml file extensions and CSS syntax highlighting for .rcss file extensions.&lt;br /&gt;
&lt;br /&gt;
In VS Code, this can be done by opening a file with the extension you want to setup, then clicking on the language mode  in the bottom right corner of the window (probably shows as Plain Text). From there, you can select &amp;quot;Configure File Association for &#039;.rml&#039;&amp;quot; from the top menu that appears and choose &amp;quot;HTML&amp;quot; from the list. Do the same for .rcss files, but select &amp;quot;CSS&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
[RmlUI website]: https://mikke89.github.io/RmlUiDoc/&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2706</id>
		<title>Recoil:RmlUI Starter Guide</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2706"/>
		<updated>2026-03-06T06:16:54Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
RmlUi is a UI framework that is defined using a HTML/CSS style workflow (using Lua instead of JS) intended to simplify UI development especially for those already familiar with web development. It is designed for interactive applications, and so is reactive by default. You can learn more about it on the [RmlUI website] and [differences in the Recoil version here](#differences-between-upstream-rmlui-and-rmlui-in-recoil).&lt;br /&gt;
&lt;br /&gt;
# How does RmlUI Work?&lt;br /&gt;
&lt;br /&gt;
To get started, it&#039;s important to learn a few key concepts.&lt;br /&gt;
- Context: This is a bundle of documents and data models.&lt;br /&gt;
- Document: This is a document tree/DOM (document object model) holding the actual UI.&lt;br /&gt;
- RML: This is the markup language used to define the document, it is very similar to XHTML (HTML but must be well-formed XML).&lt;br /&gt;
- RCSS: This is the styling language used to style the document, it is very similar to CSS2 but does differ in some places.&lt;br /&gt;
- Data Model: This holds information (a Lua table) that is used in the UI code in data bindings.&lt;br /&gt;
&lt;br /&gt;
On the Lua side, you are creating 1 or more contexts and adding data models and documents to them.&lt;br /&gt;
&lt;br /&gt;
On the Rml side, you are creating documents to be loaded by the Lua which will likely include data bindings to show information from the data model, and references to lua functions for event handlers.&lt;br /&gt;
&lt;br /&gt;
As each widget/component you create will likely comprise of several files (.lua, .rml &amp;amp; .rcss) you may find having a folder for each widget/component a useful way to organise your files.&lt;br /&gt;
&lt;br /&gt;
# Getting Started&lt;br /&gt;
 &lt;br /&gt;
RmlUi is available in LuaUI (the game UI) with future availability in LuaIntro &amp;amp; LuaMenu planned, so it is already there for you to use. &lt;br /&gt;
However, to get going with it there is some setup code that should be considered for loading fonts, cursors and configuring ui scaling, an example script is provided below.&lt;br /&gt;
If you are working on a widget for an existing game, it is likely that the game already has some form of this setup code in, so you may be able to skip this section.&lt;br /&gt;
&lt;br /&gt;
# Setup script&lt;br /&gt;
&lt;br /&gt;
Here is the &amp;quot;[handler](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/rml_setup.lua)&amp;quot;, written by lov and ChrisFloofyKitsune, 2 of the people responsible for the RmlUi implementation.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
--  luaui/rml_setup.lua&lt;br /&gt;
--  author:  lov + ChrisFloofyKitsune&lt;br /&gt;
--&lt;br /&gt;
--  Copyright (C) 2024.&lt;br /&gt;
--  Licensed under the terms of the GNU GPL, v2 or later.&lt;br /&gt;
&lt;br /&gt;
if (RmlGuard or not RmlUi) then&lt;br /&gt;
	return&lt;br /&gt;
end&lt;br /&gt;
-- don&#039;t allow this initialization code to be run multiple times&lt;br /&gt;
RmlGuard = true&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	Recoil uses a custom set of Lua bindings (check out rts/Rml/SolLua/bind folder in the C++ engine code)&lt;br /&gt;
	Aside from the Lua API, the rest of the RmlUi documentation is still relevant&lt;br /&gt;
		https://mikke89.github.io/RmlUiDoc/index.html&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
--[[ create a common Context to be used for widgets&lt;br /&gt;
	pros:&lt;br /&gt;
		* Documents in the same Context can make use of the same DataModels, allowing for less duplicate data&lt;br /&gt;
		* Documents can be arranged in front/behind of each other dynamically&lt;br /&gt;
	cons:&lt;br /&gt;
		* Documents in the same Context can make use of the same data models, leading to side effects&lt;br /&gt;
		* DataModels must have unique names within the same Context&lt;br /&gt;
&lt;br /&gt;
	If you have lots of DataModel use you may want to create your own Context&lt;br /&gt;
	otherwise you should be able to just use the shared Context&lt;br /&gt;
&lt;br /&gt;
	Contexts created with the Lua API are automatically disposed of when the LuaUi environment is unloaded&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local oldCreateContext = RmlUi.CreateContext&lt;br /&gt;
&lt;br /&gt;
local function NewCreateContext(name)&lt;br /&gt;
	local context = oldCreateContext(name)&lt;br /&gt;
&lt;br /&gt;
	-- set up dp_ratio considering the user&#039;s UI scale preference and the screen resolution&lt;br /&gt;
	local viewSizeX, viewSizeY = Spring.GetViewGeometry()&lt;br /&gt;
&lt;br /&gt;
	local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
&lt;br /&gt;
	local baseWidth = 1920&lt;br /&gt;
	local baseHeight = 1080&lt;br /&gt;
	local resFactor = math.min(viewSizeX / baseWidth, viewSizeY / baseHeight)&lt;br /&gt;
&lt;br /&gt;
	context.dp_ratio = resFactor * userScale&lt;br /&gt;
&lt;br /&gt;
	context.dp_ratio = math.floor(context.dp_ratio * 100) / 100&lt;br /&gt;
	return context&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
RmlUi.CreateContext = NewCreateContext&lt;br /&gt;
&lt;br /&gt;
-- Load fonts&lt;br /&gt;
local font_files = {&lt;br /&gt;
}&lt;br /&gt;
for _, file in ipairs(font_files) do&lt;br /&gt;
	Spring.Echo(&amp;quot;loading font&amp;quot;, file)&lt;br /&gt;
	RmlUi.LoadFontFace(file, true)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Mouse Cursor Aliases&lt;br /&gt;
--[[&lt;br /&gt;
	These let standard CSS cursor names be used when doing styling.&lt;br /&gt;
	If a cursor set via RCSS does not have an alias, it is unchanged.&lt;br /&gt;
	CSS cursor list: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor&lt;br /&gt;
	RmlUi documentation: https://mikke89.github.io/RmlUiDoc/pages/rcss/user_interface.html#cursor&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- when &amp;quot;cursor: normal&amp;quot; is set via RCSS, &amp;quot;cursornormal&amp;quot; will be sent to the engine... and so on for the rest&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;default&amp;quot;, &#039;cursornormal&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;pointer&amp;quot;, &#039;Move&#039;) -- command cursors use the command name. TODO: replace with actual pointer cursor?&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;move&amp;quot;, &#039;uimove&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;nesw-resize&amp;quot;, &#039;uiresized2&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;nwse-resize&amp;quot;, &#039;uiresized1&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;ns-resize&amp;quot;, &#039;uiresizev&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;ew-resize&amp;quot;, &#039;uiresizeh&#039;)&lt;br /&gt;
&lt;br /&gt;
RmlUi.CreateContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
What this does is create a unified context &#039;shared&#039; for all your documents and data models, which is currently the recommended way to architect documents. If you have any custom font files, list them in `font_files`, otherwise leave it empty.&lt;br /&gt;
&lt;br /&gt;
The setup script above can then be included from your `luaui/main.lua` (main.lua is the entry point for the LuaUI environment).&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
VFS.Include(LUAUI_DIRNAME .. &amp;quot;rml_setup.lua&amp;quot;,  nil, VFS.ZIP) -- Runs the script&lt;br /&gt;
```&lt;br /&gt;
&amp;gt; [!NOTE] If you are working on a widget for an existing game, check whether the game already has a setup script — it may not include all of the pieces shown above (font loading, cursor aliases, context creation, dp_ratio).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### Keeping dp_ratio Updated&lt;br /&gt;
&lt;br /&gt;
The setup script sets `dp_ratio` once at context creation time, but the user may resize the window or change their `ui_scale` config during a session. To keep `dp_ratio` correct you need to recalculate and reapply it whenever the viewport changes.&lt;br /&gt;
&lt;br /&gt;
The `ViewResize` callin fires whenever the window is resized. Add it to your widget and update each context you own:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
function widget:ViewResize(newSizeX, newSizeY)&lt;br /&gt;
    local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
    local baseWidth = 1920&lt;br /&gt;
    local baseHeight = 1080&lt;br /&gt;
    local resFactor = math.min(newSizeX / baseWidth, newSizeY / baseHeight)&lt;br /&gt;
    local dp = math.floor(resFactor * userScale * 100) / 100&lt;br /&gt;
    widget.rmlContext.dp_ratio = dp&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
If you have multiple contexts, update each one. [Mupersega&#039;s `rml_context_manager.lua`](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/rml_context_manager.lua) in Beyond All Reason is a good reference implementation that handles context lifecycle and `dp_ratio` tracking together.&lt;br /&gt;
&lt;br /&gt;
### Writing Your First Document&lt;br /&gt;
&lt;br /&gt;
You will be creating files with .rml and .rcss extensions, as these closely resemble HTML and CSS it is worth configuring your editor to treat these file extensions as HTML and CSS respectively, see the [IDE Setup](#ide-setup) section for more information.&lt;br /&gt;
&lt;br /&gt;
Now, create an RML file somewhere under `luaui/widgets/`, like `luaui/widgets/getting_started.rml`. This is the UI document.&lt;br /&gt;
&lt;br /&gt;
Writing it is much like HTML by design. There are some differences, the most immediate being the root tag is called rml instead of html, most other RML/HTML differences relate to attributes for databindings and events, but for the time being, we don&#039;t need to worry about them.&lt;br /&gt;
&lt;br /&gt;
By default RmlUi has *NO* styles, this includes setting default element behaviour like block/inline and styles web developers would expect like input elements default appearances, as a starting point you can use [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though these do not include styles for form elements. &lt;br /&gt;
&lt;br /&gt;
Here&#039;s a basic widget written by Mupersega.&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;rml&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Rml Starter&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        #rml-starter-widget {&lt;br /&gt;
            pointer-events: auto;&lt;br /&gt;
            width: 400dp;&lt;br /&gt;
            right: 0;&lt;br /&gt;
            top: 50%;&lt;br /&gt;
            transform: translateY(-90%);&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            margin-right: 10dp;&lt;br /&gt;
        }&lt;br /&gt;
        #main-content {&lt;br /&gt;
            padding: 10dp;&lt;br /&gt;
            border-radius: 8dp;&lt;br /&gt;
            z-index: 1;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content {&lt;br /&gt;
            transform: translateY(0%);&lt;br /&gt;
            transition: top 0.1s linear-in-out;&lt;br /&gt;
            z-index: 0;&lt;br /&gt;
            height: 100%;&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            top: 0dp;&lt;br /&gt;
            left: 0dp;&lt;br /&gt;
            border-radius: 8dp;&lt;br /&gt;
            display: flex;&lt;br /&gt;
            flex-direction: column;&lt;br /&gt;
            justify-content: flex-end;&lt;br /&gt;
            align-items: center;&lt;br /&gt;
            padding-bottom: 20dp;&lt;br /&gt;
        }&lt;br /&gt;
        /* This is just a checkbox sitting above the entirety of the expanding content */&lt;br /&gt;
        /* It is bound directly with data-checked attr to the expanded value */&lt;br /&gt;
        #expanding-content&amp;gt;input {&lt;br /&gt;
            height: 100%;&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            z-index: 1;&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            top: 0dp;&lt;br /&gt;
            left: 0dp;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content.expanded {&lt;br /&gt;
            top: 90%;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content:hover {&lt;br /&gt;
            background-color: rgba(255, 0, 0, 125);&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;rml-starter-widget&amp;quot; class=&amp;quot;relative&amp;quot; data-model=&amp;quot;starter_model&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;div id=&amp;quot;main-content&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;h1 class=&amp;quot;text-primary&amp;quot;&amp;gt;Welcome to an Rml Starter Widget&amp;lt;/h1&amp;gt;&lt;br /&gt;
            &amp;lt;p&amp;gt;This is a simple example of an RMLUI widget.&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;div&amp;gt;&lt;br /&gt;
                &amp;lt;button onclick=&amp;quot;widget:Reload()&amp;quot;&amp;gt;reload widget&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;div id=&amp;quot;expanding-content&amp;quot; data-class-expanded=&amp;quot;expanded&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;&lt;br /&gt;
            &amp;lt;p&amp;gt;{{message}}&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;div&amp;gt;&lt;br /&gt;
                &amp;lt;span data-for=&amp;quot;test, i: testArray&amp;quot;&amp;gt;name:{{test.name}} index:{{i}}&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/rml&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
Let&#039;s take a look at different areas that are important to look at.&lt;br /&gt;
&lt;br /&gt;
`&amp;lt;div id=&amp;quot;rml-starter-widget&amp;quot; class=&amp;quot;relative&amp;quot; data-model=&amp;quot;starter_model&amp;quot;&amp;gt;`&lt;br /&gt;
1. Here, we bind to the data model using `data-model`. This is what we will need to name the data model in our Lua script later. Everything inside the model will be in scope and beneath the div.&lt;br /&gt;
2. Typically, it is recommended to bind your data model inside of a div beneath `body` rather than `body` itself.&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;div id=&amp;quot;expanding-content&amp;quot; data-class-expanded=&amp;quot;expanded&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
any attribute starting with `data-` is a &amp;quot;data event&amp;quot;. We will go through a couple below, but you can find out more here [on the RmlUi docs site](https://mikke89.github.io/RmlUiDoc/pages/data_bindings/views_and_controllers.html).&lt;br /&gt;
1. Double curly braces are used to show values from the data model within the document text. e.g. `{{message}}` shows the value of `message` in the data model.&lt;br /&gt;
2. `data-class-expanded=&amp;quot;expanded&amp;quot;` applies the `expanded` class to the div if the value `expanded` in the data model is `true`.&lt;br /&gt;
3. `&amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;` This is a two-way binding: the checkbox reflects the current value of `expanded` in the data model, and toggling it writes back `true` or `false` to that field.&lt;br /&gt;
4. When the data model is changed, the document is rerendered automatically. So, the expanding div will have the `expanded` class applied to it or removed whenever the check box is toggled.&lt;br /&gt;
&lt;br /&gt;
There are data bindings to allow you to loop through arrays (data-for) have conditional sections of the document (data-if) and many others. &lt;br /&gt;
&lt;br /&gt;
### The Lua&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE] For information on setting up your editor to provide intellisense behaviour see the [Lua Language Server guide]({{% ref &amp;quot;lua-language-server&amp;quot; %}}).&lt;br /&gt;
&lt;br /&gt;
To load your document into the shared context we created earlier and to define and add the data model you will need to have a lua script something like the one below.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
-- luaui/widgets/getting_started.lua&lt;br /&gt;
if not RmlUi then&lt;br /&gt;
    return&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name = &amp;quot;Rml Starter&amp;quot;,&lt;br /&gt;
        desc = &amp;quot;This widget is a starter example for RmlUi widgets.&amp;quot;,&lt;br /&gt;
        author = &amp;quot;Mupersega&amp;quot;,&lt;br /&gt;
        date = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer = -1000000,&lt;br /&gt;
        enabled = true&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local document&lt;br /&gt;
local dm_handle&lt;br /&gt;
local init_model = {&lt;br /&gt;
    expanded = false,&lt;br /&gt;
    message = &amp;quot;Hello, find my text in the data model!&amp;quot;,&lt;br /&gt;
    testArray = {&lt;br /&gt;
        { name = &amp;quot;Item 1&amp;quot;, value = 1 },&lt;br /&gt;
        { name = &amp;quot;Item 2&amp;quot;, value = 2 },&lt;br /&gt;
        { name = &amp;quot;Item 3&amp;quot;, value = 3 },&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local main_model_name = &amp;quot;starter_model&amp;quot;&lt;br /&gt;
&lt;br /&gt;
function widget:Initialize()&lt;br /&gt;
    widget.rmlContext = RmlUi.GetContext(&amp;quot;shared&amp;quot;) -- Get the context from the setup lua&lt;br /&gt;
&lt;br /&gt;
    -- Open the model, using init_model as the template. All values inside are copied.&lt;br /&gt;
    -- Returns a handle, which we will touch on later.&lt;br /&gt;
    dm_handle = widget.rmlContext:OpenDataModel(main_model_name, init_model)&lt;br /&gt;
    if not dm_handle then&lt;br /&gt;
        Spring.Echo(&amp;quot;RmlUi: Failed to open data model &amp;quot;, main_model_name)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Load the document we wrote earlier.&lt;br /&gt;
    document = widget.rmlContext:LoadDocument(&amp;quot;luaui/widgets/getting_started.rml&amp;quot;, widget)&lt;br /&gt;
    if not document then&lt;br /&gt;
        Spring.Echo(&amp;quot;Failed to load document&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- uncomment the line below to enable debugger&lt;br /&gt;
    -- RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
&lt;br /&gt;
    document:ReloadStyleSheet()&lt;br /&gt;
    document:Show()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function widget:Shutdown()&lt;br /&gt;
    widget.rmlContext:RemoveDataModel(main_model_name)&lt;br /&gt;
    if document then&lt;br /&gt;
        document:Close()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- This function is only for dev experience, ideally it would be a hot reload, and not required at all in a completed widget.&lt;br /&gt;
function widget:Reload(event)&lt;br /&gt;
    Spring.Echo(&amp;quot;Reloading&amp;quot;)&lt;br /&gt;
    Spring.Echo(event)&lt;br /&gt;
    widget:Shutdown()&lt;br /&gt;
    widget:Initialize()&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### The Data Model Handle&lt;br /&gt;
&lt;br /&gt;
In the script, we are given a data model handle. This is a proxy for the Lua table used as the data model; as the Recoil RmlUi integration uses Sol2 as a wrapper data cannot be accessed directly.&lt;br /&gt;
&lt;br /&gt;
In most cases, you can simply do `dm_handle.expanded = true`. Assigning to any field on the handle (or any nested table within it, at any depth) automatically marks the relevant parts of the model as dirty and triggers a UI update — you do not need to call `__SetDirty` manually.&lt;br /&gt;
&lt;br /&gt;
What if you have an array, like `testArray` above, and want to loop through it on the Lua side? You will need to get the underlying table:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local model_handle = dm_handle:__GetTable()&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
As of writing, this function is not documented in the Lua API, due to some problems with language server generics that haven&#039;t been sorted out yet. It is there, however, and will be added back in in the future. It returns the table with the shape of your initial model.&lt;br /&gt;
You can then iterate through it and change things as you please:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
for i, v in pairs(model_handle.testArray) do&lt;br /&gt;
    Spring.Echo(i, v.name)&lt;br /&gt;
    v.value = v.value + 1&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE] Use `pairs` rather than `ipairs` when iterating over data model arrays on the Lua side. The model handle does not currently implement `__len`, so `ipairs` will not traverse the array correctly. This will be resolved once `__len` is backported.&lt;br /&gt;
&lt;br /&gt;
### Debugging&lt;br /&gt;
&lt;br /&gt;
RmlUi comes with a debugger that lets you see and interact with the DOM. It&#039;s very handy! To use it, use this:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
RmlUi.SetDebugContext(DATA_MODEL_NAME)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
And it will appear in-game. A useful idiom I like is to put this in `rml_setup.lua`:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local DEBUG_RMLUI = true&lt;br /&gt;
&lt;br /&gt;
--...&lt;br /&gt;
&lt;br /&gt;
if DEBUG_RMLUI then&lt;br /&gt;
    RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
If you use the shared context, you can see everything that happens in it! Neat!&lt;br /&gt;
&lt;br /&gt;
## Things to Know and Best Practices&lt;br /&gt;
&lt;br /&gt;
### Things to know&lt;br /&gt;
Some of the rough edges you are likely to run into have already been discussed, like the data model thing, but here are some more:&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
Unlike a web browser a default set of styles is not included, as a starting point you can look at the [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though this doesn&#039;t provide styles for form elements.&lt;br /&gt;
&lt;br /&gt;
Input elements of type submit &amp;amp; button behave differently to HTML and more like Button elements in that their text is not set by the value attribute. (This is likely to be corrected in a future version)&lt;br /&gt;
&lt;br /&gt;
The alpha/transparency value of an RGBA colour is different to CSS (0-1) and instead uses 0-255. The css opacity does still use 0-1.&lt;br /&gt;
&lt;br /&gt;
List styling is unavailable (list-style-type etc.), you can still use UL/OL/LI elements but there is no special meaning to them, whilst you could use background images to replicate bullets there isn&#039;t a practical way to achieve numbered list items with RML/RCSS.&lt;br /&gt;
&lt;br /&gt;
Only solid borders are supported, so the border-style property is unavailable and the shorthand border property doesn&#039;t include a style part (```border: 1dp solid black;``` won&#039;t work, instead use ```border: 1dp black;```).&lt;br /&gt;
&lt;br /&gt;
background-color behaves as expected, all other background styles are different and use decorators instead see the [RmlUi documentation for more information on decorators.](https://mikke89.github.io/RmlUiDoc/pages/rcss/decorators.html) &lt;br /&gt;
&lt;br /&gt;
There are two kinds of events: data events, like `data-mouseover`, and normal events, like `onmouseover`. These have different data in their scopes.&lt;br /&gt;
- Data events have the data model in their scope.&lt;br /&gt;
- Normal events don&#039;t have the data model, but they *do* have whatever is passed into `widget` on `widget.rmlContext:LoadDocument`. `widget` doesn&#039;t have to be a widget, just any table with data in it.&lt;br /&gt;
&lt;br /&gt;
For example, take this:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local model = {&lt;br /&gt;
    add = function(a, b)&lt;br /&gt;
        Spring.Echo(a + b)&lt;br /&gt;
    end&lt;br /&gt;
}&lt;br /&gt;
local document_table = {&lt;br /&gt;
    print = function(msg)&lt;br /&gt;
        Spring.Echo(msg)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
dm_handle = widget.rmlContext:OpenDataModel(&amp;quot;test&amp;quot;, model)&lt;br /&gt;
document = widget.rmlContext:LoadDocument(&amp;quot;document.rml&amp;quot;, document_table)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;h1&amp;gt;Normal Events&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;add(1, 2)&amp;quot;&amp;gt;Won&#039;t work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;print(&#039;test&#039;)&amp;quot;&amp;gt;Will work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;h1&amp;gt;Data Events&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; data-click=&amp;quot;add(1, 2)&amp;quot;&amp;gt;Will work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; data-click=&amp;quot;print(&#039;test&#039;)&amp;quot;&amp;gt;Won&#039;t work!&amp;lt;/input&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Best Practices&lt;br /&gt;
&lt;br /&gt;
- To create a scalable interface the use of the dp unit over px is recommended as the scale can be set per context with SetDensityIndependentPixelRatio.&lt;br /&gt;
- For styles unique to a document, put them in a `style` tag. For shared styles, put them in an `rcss` file.&lt;br /&gt;
- Rely on Recoil&#039;s RmlUi Lua bindings doc for what you can and can&#039;t do. The Recoil implementation has some extra stuff the RmlUi docs don&#039;t.&lt;br /&gt;
- The Beyond All Reason devs prefer to use one shared context for all rmlui widgets.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### Differences between upstream RmlUI and RmlUI in Recoil&lt;br /&gt;
&lt;br /&gt;
- The SVG element allows either a filepath or raw SVG data in the src attribute, allowing for inline svg to be used (this may change to svg being supported between the opening and closing tag when implemented upstream)&lt;br /&gt;
- An additional element ```&amp;lt;texture&amp;gt;``` is available which allows for textures loaded in Recoil to be used, this behaves the same as an ```&amp;lt;img&amp;gt;``` element except the src attribute takes a [texture reference]({{% ref &amp;quot;articles/texture-reference-strings&amp;quot; %}})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### IDE Setup&lt;br /&gt;
&lt;br /&gt;
To get the best experience with RmlUi, you should set up your editor to use HTML syntax highlighting for .rml file extensions and CSS syntax highlighting for .rcss file extensions.&lt;br /&gt;
&lt;br /&gt;
In VS Code, this can be done by opening a file with the extension you want to setup, then clicking on the language mode  in the bottom right corner of the window (probably shows as Plain Text). From there, you can select &amp;quot;Configure File Association for &#039;.rml&#039;&amp;quot; from the top menu that appears and choose &amp;quot;HTML&amp;quot; from the list. Do the same for .rcss files, but select &amp;quot;CSS&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
[RmlUI website]: https://mikke89.github.io/RmlUiDoc/&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2705</id>
		<title>Recoil:RmlUI Starter Guide</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2705"/>
		<updated>2026-03-06T06:16:34Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;+++&lt;br /&gt;
title = &#039;RmlUi&#039;&lt;br /&gt;
date = 2025-05-11T13:17:05-07:00&lt;br /&gt;
draft = false&lt;br /&gt;
author = &amp;quot;Slashscreen&amp;quot;&lt;br /&gt;
+++&lt;br /&gt;
&lt;br /&gt;
RmlUi is a UI framework that is defined using a HTML/CSS style workflow (using Lua instead of JS) intended to simplify UI development especially for those already familiar with web development. It is designed for interactive applications, and so is reactive by default. You can learn more about it on the [RmlUI website] and [differences in the Recoil version here](#differences-between-upstream-rmlui-and-rmlui-in-recoil).&lt;br /&gt;
&lt;br /&gt;
## How does RmlUI Work?&lt;br /&gt;
&lt;br /&gt;
To get started, it&#039;s important to learn a few key concepts.&lt;br /&gt;
- Context: This is a bundle of documents and data models.&lt;br /&gt;
- Document: This is a document tree/DOM (document object model) holding the actual UI.&lt;br /&gt;
- RML: This is the markup language used to define the document, it is very similar to XHTML (HTML but must be well-formed XML).&lt;br /&gt;
- RCSS: This is the styling language used to style the document, it is very similar to CSS2 but does differ in some places.&lt;br /&gt;
- Data Model: This holds information (a Lua table) that is used in the UI code in data bindings.&lt;br /&gt;
&lt;br /&gt;
On the Lua side, you are creating 1 or more contexts and adding data models and documents to them.&lt;br /&gt;
&lt;br /&gt;
On the Rml side, you are creating documents to be loaded by the Lua which will likely include data bindings to show information from the data model, and references to lua functions for event handlers.&lt;br /&gt;
&lt;br /&gt;
As each widget/component you create will likely comprise of several files (.lua, .rml &amp;amp; .rcss) you may find having a folder for each widget/component a useful way to organise your files.&lt;br /&gt;
&lt;br /&gt;
## Getting Started&lt;br /&gt;
 &lt;br /&gt;
RmlUi is available in LuaUI (the game UI) with future availability in LuaIntro &amp;amp; LuaMenu planned, so it is already there for you to use. &lt;br /&gt;
However, to get going with it there is some setup code that should be considered for loading fonts, cursors and configuring ui scaling, an example script is provided below.&lt;br /&gt;
If you are working on a widget for an existing game, it is likely that the game already has some form of this setup code in, so you may be able to skip this section.&lt;br /&gt;
&lt;br /&gt;
### Setup script&lt;br /&gt;
&lt;br /&gt;
Here is the &amp;quot;[handler](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/rml_setup.lua)&amp;quot;, written by lov and ChrisFloofyKitsune, 2 of the people responsible for the RmlUi implementation.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
--  luaui/rml_setup.lua&lt;br /&gt;
--  author:  lov + ChrisFloofyKitsune&lt;br /&gt;
--&lt;br /&gt;
--  Copyright (C) 2024.&lt;br /&gt;
--  Licensed under the terms of the GNU GPL, v2 or later.&lt;br /&gt;
&lt;br /&gt;
if (RmlGuard or not RmlUi) then&lt;br /&gt;
	return&lt;br /&gt;
end&lt;br /&gt;
-- don&#039;t allow this initialization code to be run multiple times&lt;br /&gt;
RmlGuard = true&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	Recoil uses a custom set of Lua bindings (check out rts/Rml/SolLua/bind folder in the C++ engine code)&lt;br /&gt;
	Aside from the Lua API, the rest of the RmlUi documentation is still relevant&lt;br /&gt;
		https://mikke89.github.io/RmlUiDoc/index.html&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
--[[ create a common Context to be used for widgets&lt;br /&gt;
	pros:&lt;br /&gt;
		* Documents in the same Context can make use of the same DataModels, allowing for less duplicate data&lt;br /&gt;
		* Documents can be arranged in front/behind of each other dynamically&lt;br /&gt;
	cons:&lt;br /&gt;
		* Documents in the same Context can make use of the same data models, leading to side effects&lt;br /&gt;
		* DataModels must have unique names within the same Context&lt;br /&gt;
&lt;br /&gt;
	If you have lots of DataModel use you may want to create your own Context&lt;br /&gt;
	otherwise you should be able to just use the shared Context&lt;br /&gt;
&lt;br /&gt;
	Contexts created with the Lua API are automatically disposed of when the LuaUi environment is unloaded&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local oldCreateContext = RmlUi.CreateContext&lt;br /&gt;
&lt;br /&gt;
local function NewCreateContext(name)&lt;br /&gt;
	local context = oldCreateContext(name)&lt;br /&gt;
&lt;br /&gt;
	-- set up dp_ratio considering the user&#039;s UI scale preference and the screen resolution&lt;br /&gt;
	local viewSizeX, viewSizeY = Spring.GetViewGeometry()&lt;br /&gt;
&lt;br /&gt;
	local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
&lt;br /&gt;
	local baseWidth = 1920&lt;br /&gt;
	local baseHeight = 1080&lt;br /&gt;
	local resFactor = math.min(viewSizeX / baseWidth, viewSizeY / baseHeight)&lt;br /&gt;
&lt;br /&gt;
	context.dp_ratio = resFactor * userScale&lt;br /&gt;
&lt;br /&gt;
	context.dp_ratio = math.floor(context.dp_ratio * 100) / 100&lt;br /&gt;
	return context&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
RmlUi.CreateContext = NewCreateContext&lt;br /&gt;
&lt;br /&gt;
-- Load fonts&lt;br /&gt;
local font_files = {&lt;br /&gt;
}&lt;br /&gt;
for _, file in ipairs(font_files) do&lt;br /&gt;
	Spring.Echo(&amp;quot;loading font&amp;quot;, file)&lt;br /&gt;
	RmlUi.LoadFontFace(file, true)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Mouse Cursor Aliases&lt;br /&gt;
--[[&lt;br /&gt;
	These let standard CSS cursor names be used when doing styling.&lt;br /&gt;
	If a cursor set via RCSS does not have an alias, it is unchanged.&lt;br /&gt;
	CSS cursor list: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor&lt;br /&gt;
	RmlUi documentation: https://mikke89.github.io/RmlUiDoc/pages/rcss/user_interface.html#cursor&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- when &amp;quot;cursor: normal&amp;quot; is set via RCSS, &amp;quot;cursornormal&amp;quot; will be sent to the engine... and so on for the rest&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;default&amp;quot;, &#039;cursornormal&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;pointer&amp;quot;, &#039;Move&#039;) -- command cursors use the command name. TODO: replace with actual pointer cursor?&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;move&amp;quot;, &#039;uimove&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;nesw-resize&amp;quot;, &#039;uiresized2&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;nwse-resize&amp;quot;, &#039;uiresized1&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;ns-resize&amp;quot;, &#039;uiresizev&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;ew-resize&amp;quot;, &#039;uiresizeh&#039;)&lt;br /&gt;
&lt;br /&gt;
RmlUi.CreateContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
What this does is create a unified context &#039;shared&#039; for all your documents and data models, which is currently the recommended way to architect documents. If you have any custom font files, list them in `font_files`, otherwise leave it empty.&lt;br /&gt;
&lt;br /&gt;
The setup script above can then be included from your `luaui/main.lua` (main.lua is the entry point for the LuaUI environment).&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
VFS.Include(LUAUI_DIRNAME .. &amp;quot;rml_setup.lua&amp;quot;,  nil, VFS.ZIP) -- Runs the script&lt;br /&gt;
```&lt;br /&gt;
&amp;gt; [!NOTE] If you are working on a widget for an existing game, check whether the game already has a setup script — it may not include all of the pieces shown above (font loading, cursor aliases, context creation, dp_ratio).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### Keeping dp_ratio Updated&lt;br /&gt;
&lt;br /&gt;
The setup script sets `dp_ratio` once at context creation time, but the user may resize the window or change their `ui_scale` config during a session. To keep `dp_ratio` correct you need to recalculate and reapply it whenever the viewport changes.&lt;br /&gt;
&lt;br /&gt;
The `ViewResize` callin fires whenever the window is resized. Add it to your widget and update each context you own:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
function widget:ViewResize(newSizeX, newSizeY)&lt;br /&gt;
    local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
    local baseWidth = 1920&lt;br /&gt;
    local baseHeight = 1080&lt;br /&gt;
    local resFactor = math.min(newSizeX / baseWidth, newSizeY / baseHeight)&lt;br /&gt;
    local dp = math.floor(resFactor * userScale * 100) / 100&lt;br /&gt;
    widget.rmlContext.dp_ratio = dp&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
If you have multiple contexts, update each one. [Mupersega&#039;s `rml_context_manager.lua`](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/rml_context_manager.lua) in Beyond All Reason is a good reference implementation that handles context lifecycle and `dp_ratio` tracking together.&lt;br /&gt;
&lt;br /&gt;
### Writing Your First Document&lt;br /&gt;
&lt;br /&gt;
You will be creating files with .rml and .rcss extensions, as these closely resemble HTML and CSS it is worth configuring your editor to treat these file extensions as HTML and CSS respectively, see the [IDE Setup](#ide-setup) section for more information.&lt;br /&gt;
&lt;br /&gt;
Now, create an RML file somewhere under `luaui/widgets/`, like `luaui/widgets/getting_started.rml`. This is the UI document.&lt;br /&gt;
&lt;br /&gt;
Writing it is much like HTML by design. There are some differences, the most immediate being the root tag is called rml instead of html, most other RML/HTML differences relate to attributes for databindings and events, but for the time being, we don&#039;t need to worry about them.&lt;br /&gt;
&lt;br /&gt;
By default RmlUi has *NO* styles, this includes setting default element behaviour like block/inline and styles web developers would expect like input elements default appearances, as a starting point you can use [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though these do not include styles for form elements. &lt;br /&gt;
&lt;br /&gt;
Here&#039;s a basic widget written by Mupersega.&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;rml&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Rml Starter&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        #rml-starter-widget {&lt;br /&gt;
            pointer-events: auto;&lt;br /&gt;
            width: 400dp;&lt;br /&gt;
            right: 0;&lt;br /&gt;
            top: 50%;&lt;br /&gt;
            transform: translateY(-90%);&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            margin-right: 10dp;&lt;br /&gt;
        }&lt;br /&gt;
        #main-content {&lt;br /&gt;
            padding: 10dp;&lt;br /&gt;
            border-radius: 8dp;&lt;br /&gt;
            z-index: 1;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content {&lt;br /&gt;
            transform: translateY(0%);&lt;br /&gt;
            transition: top 0.1s linear-in-out;&lt;br /&gt;
            z-index: 0;&lt;br /&gt;
            height: 100%;&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            top: 0dp;&lt;br /&gt;
            left: 0dp;&lt;br /&gt;
            border-radius: 8dp;&lt;br /&gt;
            display: flex;&lt;br /&gt;
            flex-direction: column;&lt;br /&gt;
            justify-content: flex-end;&lt;br /&gt;
            align-items: center;&lt;br /&gt;
            padding-bottom: 20dp;&lt;br /&gt;
        }&lt;br /&gt;
        /* This is just a checkbox sitting above the entirety of the expanding content */&lt;br /&gt;
        /* It is bound directly with data-checked attr to the expanded value */&lt;br /&gt;
        #expanding-content&amp;gt;input {&lt;br /&gt;
            height: 100%;&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            z-index: 1;&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            top: 0dp;&lt;br /&gt;
            left: 0dp;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content.expanded {&lt;br /&gt;
            top: 90%;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content:hover {&lt;br /&gt;
            background-color: rgba(255, 0, 0, 125);&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;rml-starter-widget&amp;quot; class=&amp;quot;relative&amp;quot; data-model=&amp;quot;starter_model&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;div id=&amp;quot;main-content&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;h1 class=&amp;quot;text-primary&amp;quot;&amp;gt;Welcome to an Rml Starter Widget&amp;lt;/h1&amp;gt;&lt;br /&gt;
            &amp;lt;p&amp;gt;This is a simple example of an RMLUI widget.&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;div&amp;gt;&lt;br /&gt;
                &amp;lt;button onclick=&amp;quot;widget:Reload()&amp;quot;&amp;gt;reload widget&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;div id=&amp;quot;expanding-content&amp;quot; data-class-expanded=&amp;quot;expanded&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;&lt;br /&gt;
            &amp;lt;p&amp;gt;{{message}}&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;div&amp;gt;&lt;br /&gt;
                &amp;lt;span data-for=&amp;quot;test, i: testArray&amp;quot;&amp;gt;name:{{test.name}} index:{{i}}&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/rml&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
Let&#039;s take a look at different areas that are important to look at.&lt;br /&gt;
&lt;br /&gt;
`&amp;lt;div id=&amp;quot;rml-starter-widget&amp;quot; class=&amp;quot;relative&amp;quot; data-model=&amp;quot;starter_model&amp;quot;&amp;gt;`&lt;br /&gt;
1. Here, we bind to the data model using `data-model`. This is what we will need to name the data model in our Lua script later. Everything inside the model will be in scope and beneath the div.&lt;br /&gt;
2. Typically, it is recommended to bind your data model inside of a div beneath `body` rather than `body` itself.&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;div id=&amp;quot;expanding-content&amp;quot; data-class-expanded=&amp;quot;expanded&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
any attribute starting with `data-` is a &amp;quot;data event&amp;quot;. We will go through a couple below, but you can find out more here [on the RmlUi docs site](https://mikke89.github.io/RmlUiDoc/pages/data_bindings/views_and_controllers.html).&lt;br /&gt;
1. Double curly braces are used to show values from the data model within the document text. e.g. `{{message}}` shows the value of `message` in the data model.&lt;br /&gt;
2. `data-class-expanded=&amp;quot;expanded&amp;quot;` applies the `expanded` class to the div if the value `expanded` in the data model is `true`.&lt;br /&gt;
3. `&amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;` This is a two-way binding: the checkbox reflects the current value of `expanded` in the data model, and toggling it writes back `true` or `false` to that field.&lt;br /&gt;
4. When the data model is changed, the document is rerendered automatically. So, the expanding div will have the `expanded` class applied to it or removed whenever the check box is toggled.&lt;br /&gt;
&lt;br /&gt;
There are data bindings to allow you to loop through arrays (data-for) have conditional sections of the document (data-if) and many others. &lt;br /&gt;
&lt;br /&gt;
### The Lua&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE] For information on setting up your editor to provide intellisense behaviour see the [Lua Language Server guide]({{% ref &amp;quot;lua-language-server&amp;quot; %}}).&lt;br /&gt;
&lt;br /&gt;
To load your document into the shared context we created earlier and to define and add the data model you will need to have a lua script something like the one below.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
-- luaui/widgets/getting_started.lua&lt;br /&gt;
if not RmlUi then&lt;br /&gt;
    return&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name = &amp;quot;Rml Starter&amp;quot;,&lt;br /&gt;
        desc = &amp;quot;This widget is a starter example for RmlUi widgets.&amp;quot;,&lt;br /&gt;
        author = &amp;quot;Mupersega&amp;quot;,&lt;br /&gt;
        date = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer = -1000000,&lt;br /&gt;
        enabled = true&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local document&lt;br /&gt;
local dm_handle&lt;br /&gt;
local init_model = {&lt;br /&gt;
    expanded = false,&lt;br /&gt;
    message = &amp;quot;Hello, find my text in the data model!&amp;quot;,&lt;br /&gt;
    testArray = {&lt;br /&gt;
        { name = &amp;quot;Item 1&amp;quot;, value = 1 },&lt;br /&gt;
        { name = &amp;quot;Item 2&amp;quot;, value = 2 },&lt;br /&gt;
        { name = &amp;quot;Item 3&amp;quot;, value = 3 },&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local main_model_name = &amp;quot;starter_model&amp;quot;&lt;br /&gt;
&lt;br /&gt;
function widget:Initialize()&lt;br /&gt;
    widget.rmlContext = RmlUi.GetContext(&amp;quot;shared&amp;quot;) -- Get the context from the setup lua&lt;br /&gt;
&lt;br /&gt;
    -- Open the model, using init_model as the template. All values inside are copied.&lt;br /&gt;
    -- Returns a handle, which we will touch on later.&lt;br /&gt;
    dm_handle = widget.rmlContext:OpenDataModel(main_model_name, init_model)&lt;br /&gt;
    if not dm_handle then&lt;br /&gt;
        Spring.Echo(&amp;quot;RmlUi: Failed to open data model &amp;quot;, main_model_name)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Load the document we wrote earlier.&lt;br /&gt;
    document = widget.rmlContext:LoadDocument(&amp;quot;luaui/widgets/getting_started.rml&amp;quot;, widget)&lt;br /&gt;
    if not document then&lt;br /&gt;
        Spring.Echo(&amp;quot;Failed to load document&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- uncomment the line below to enable debugger&lt;br /&gt;
    -- RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
&lt;br /&gt;
    document:ReloadStyleSheet()&lt;br /&gt;
    document:Show()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function widget:Shutdown()&lt;br /&gt;
    widget.rmlContext:RemoveDataModel(main_model_name)&lt;br /&gt;
    if document then&lt;br /&gt;
        document:Close()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- This function is only for dev experience, ideally it would be a hot reload, and not required at all in a completed widget.&lt;br /&gt;
function widget:Reload(event)&lt;br /&gt;
    Spring.Echo(&amp;quot;Reloading&amp;quot;)&lt;br /&gt;
    Spring.Echo(event)&lt;br /&gt;
    widget:Shutdown()&lt;br /&gt;
    widget:Initialize()&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### The Data Model Handle&lt;br /&gt;
&lt;br /&gt;
In the script, we are given a data model handle. This is a proxy for the Lua table used as the data model; as the Recoil RmlUi integration uses Sol2 as a wrapper data cannot be accessed directly.&lt;br /&gt;
&lt;br /&gt;
In most cases, you can simply do `dm_handle.expanded = true`. Assigning to any field on the handle (or any nested table within it, at any depth) automatically marks the relevant parts of the model as dirty and triggers a UI update — you do not need to call `__SetDirty` manually.&lt;br /&gt;
&lt;br /&gt;
What if you have an array, like `testArray` above, and want to loop through it on the Lua side? You will need to get the underlying table:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local model_handle = dm_handle:__GetTable()&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
As of writing, this function is not documented in the Lua API, due to some problems with language server generics that haven&#039;t been sorted out yet. It is there, however, and will be added back in in the future. It returns the table with the shape of your initial model.&lt;br /&gt;
You can then iterate through it and change things as you please:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
for i, v in pairs(model_handle.testArray) do&lt;br /&gt;
    Spring.Echo(i, v.name)&lt;br /&gt;
    v.value = v.value + 1&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE] Use `pairs` rather than `ipairs` when iterating over data model arrays on the Lua side. The model handle does not currently implement `__len`, so `ipairs` will not traverse the array correctly. This will be resolved once `__len` is backported.&lt;br /&gt;
&lt;br /&gt;
### Debugging&lt;br /&gt;
&lt;br /&gt;
RmlUi comes with a debugger that lets you see and interact with the DOM. It&#039;s very handy! To use it, use this:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
RmlUi.SetDebugContext(DATA_MODEL_NAME)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
And it will appear in-game. A useful idiom I like is to put this in `rml_setup.lua`:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local DEBUG_RMLUI = true&lt;br /&gt;
&lt;br /&gt;
--...&lt;br /&gt;
&lt;br /&gt;
if DEBUG_RMLUI then&lt;br /&gt;
    RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
If you use the shared context, you can see everything that happens in it! Neat!&lt;br /&gt;
&lt;br /&gt;
## Things to Know and Best Practices&lt;br /&gt;
&lt;br /&gt;
### Things to know&lt;br /&gt;
Some of the rough edges you are likely to run into have already been discussed, like the data model thing, but here are some more:&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
Unlike a web browser a default set of styles is not included, as a starting point you can look at the [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though this doesn&#039;t provide styles for form elements.&lt;br /&gt;
&lt;br /&gt;
Input elements of type submit &amp;amp; button behave differently to HTML and more like Button elements in that their text is not set by the value attribute. (This is likely to be corrected in a future version)&lt;br /&gt;
&lt;br /&gt;
The alpha/transparency value of an RGBA colour is different to CSS (0-1) and instead uses 0-255. The css opacity does still use 0-1.&lt;br /&gt;
&lt;br /&gt;
List styling is unavailable (list-style-type etc.), you can still use UL/OL/LI elements but there is no special meaning to them, whilst you could use background images to replicate bullets there isn&#039;t a practical way to achieve numbered list items with RML/RCSS.&lt;br /&gt;
&lt;br /&gt;
Only solid borders are supported, so the border-style property is unavailable and the shorthand border property doesn&#039;t include a style part (```border: 1dp solid black;``` won&#039;t work, instead use ```border: 1dp black;```).&lt;br /&gt;
&lt;br /&gt;
background-color behaves as expected, all other background styles are different and use decorators instead see the [RmlUi documentation for more information on decorators.](https://mikke89.github.io/RmlUiDoc/pages/rcss/decorators.html) &lt;br /&gt;
&lt;br /&gt;
There are two kinds of events: data events, like `data-mouseover`, and normal events, like `onmouseover`. These have different data in their scopes.&lt;br /&gt;
- Data events have the data model in their scope.&lt;br /&gt;
- Normal events don&#039;t have the data model, but they *do* have whatever is passed into `widget` on `widget.rmlContext:LoadDocument`. `widget` doesn&#039;t have to be a widget, just any table with data in it.&lt;br /&gt;
&lt;br /&gt;
For example, take this:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local model = {&lt;br /&gt;
    add = function(a, b)&lt;br /&gt;
        Spring.Echo(a + b)&lt;br /&gt;
    end&lt;br /&gt;
}&lt;br /&gt;
local document_table = {&lt;br /&gt;
    print = function(msg)&lt;br /&gt;
        Spring.Echo(msg)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
dm_handle = widget.rmlContext:OpenDataModel(&amp;quot;test&amp;quot;, model)&lt;br /&gt;
document = widget.rmlContext:LoadDocument(&amp;quot;document.rml&amp;quot;, document_table)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;h1&amp;gt;Normal Events&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;add(1, 2)&amp;quot;&amp;gt;Won&#039;t work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;print(&#039;test&#039;)&amp;quot;&amp;gt;Will work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;h1&amp;gt;Data Events&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; data-click=&amp;quot;add(1, 2)&amp;quot;&amp;gt;Will work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; data-click=&amp;quot;print(&#039;test&#039;)&amp;quot;&amp;gt;Won&#039;t work!&amp;lt;/input&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Best Practices&lt;br /&gt;
&lt;br /&gt;
- To create a scalable interface the use of the dp unit over px is recommended as the scale can be set per context with SetDensityIndependentPixelRatio.&lt;br /&gt;
- For styles unique to a document, put them in a `style` tag. For shared styles, put them in an `rcss` file.&lt;br /&gt;
- Rely on Recoil&#039;s RmlUi Lua bindings doc for what you can and can&#039;t do. The Recoil implementation has some extra stuff the RmlUi docs don&#039;t.&lt;br /&gt;
- The Beyond All Reason devs prefer to use one shared context for all rmlui widgets.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### Differences between upstream RmlUI and RmlUI in Recoil&lt;br /&gt;
&lt;br /&gt;
- The SVG element allows either a filepath or raw SVG data in the src attribute, allowing for inline svg to be used (this may change to svg being supported between the opening and closing tag when implemented upstream)&lt;br /&gt;
- An additional element ```&amp;lt;texture&amp;gt;``` is available which allows for textures loaded in Recoil to be used, this behaves the same as an ```&amp;lt;img&amp;gt;``` element except the src attribute takes a [texture reference]({{% ref &amp;quot;articles/texture-reference-strings&amp;quot; %}})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### IDE Setup&lt;br /&gt;
&lt;br /&gt;
To get the best experience with RmlUi, you should set up your editor to use HTML syntax highlighting for .rml file extensions and CSS syntax highlighting for .rcss file extensions.&lt;br /&gt;
&lt;br /&gt;
In VS Code, this can be done by opening a file with the extension you want to setup, then clicking on the language mode  in the bottom right corner of the window (probably shows as Plain Text). From there, you can select &amp;quot;Configure File Association for &#039;.rml&#039;&amp;quot; from the top menu that appears and choose &amp;quot;HTML&amp;quot; from the list. Do the same for .rcss files, but select &amp;quot;CSS&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
[RmlUI website]: https://mikke89.github.io/RmlUiDoc/&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2704</id>
		<title>Recoil:RmlUI Starter Guide</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2704"/>
		<updated>2026-03-06T06:15:42Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Undo revision 2703 by Qrow (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:RmlUI Starter Guide}}&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
= RmlUI Starter Guide =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;For RecoilEngine game developers — build reactive, HTML/CSS-style game UIs in Lua.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Attribute !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Engine || RecoilEngine (Spring)&lt;br /&gt;
|-&lt;br /&gt;
| Framework || RmlUi ([https://github.com/mikke89/RmlUi mikke89/RmlUi])&lt;br /&gt;
|-&lt;br /&gt;
| Language || Lua (via sol2 bindings)&lt;br /&gt;
|-&lt;br /&gt;
| Availability || LuaUI (LuaIntro / LuaMenu planned)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 1. Introduction ==&lt;br /&gt;
&lt;br /&gt;
RmlUI is a UI framework that lets you build reactive game interfaces using an HTML/CSS-style workflow — with Lua instead of JavaScript. If you have ever built a web page, the learning curve is gentle. If you haven&#039;t, the concepts are still approachable because the mental model (mark-up + style + logic) is widely documented.&lt;br /&gt;
&lt;br /&gt;
RecoilEngine ships with a full RmlUI integration including:&lt;br /&gt;
&lt;br /&gt;
* An OpenGL 3 renderer tightly coupled to the engine&#039;s rendering pipeline&lt;br /&gt;
* A virtual-filesystem-aware file loader so your &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files live inside game archives&lt;br /&gt;
* Complete Lua bindings via sol2 so every RmlUI object is accessible from Lua&lt;br /&gt;
* Custom elements: &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;texture&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; for engine textures and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;svg&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; for vector art&lt;br /&gt;
* A built-in DOM debugger for interactive inspection&lt;br /&gt;
&lt;br /&gt;
This guide walks you through everything you need to get a working, reactive widget running in LuaUI. The examples are game-engine-agnostic and safe to copy into any Recoil-based game.&lt;br /&gt;
&lt;br /&gt;
{{Note|RmlUI is currently available in &#039;&#039;&#039;LuaUI&#039;&#039;&#039; (the in-game UI). Support for LuaIntro and LuaMenu is planned for a future release.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 2. Key Concepts ==&lt;br /&gt;
&lt;br /&gt;
Before writing any code it helps to understand the five core objects:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Concept !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Context&#039;&#039;&#039; || A named container that holds documents and data models. Multiple contexts can exist simultaneously, each rendered independently.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Document&#039;&#039;&#039; || A parsed RML file representing a DOM tree. Documents live inside a Context and can be shown, hidden, or closed.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;RML&#039;&#039;&#039; || The markup language for documents. Very similar to XHTML (well-formed XML). The root tag is &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;rml&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;html&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;RCSS&#039;&#039;&#039; || The styling language. Similar to CSS2 with some differences (see section 11). File extension: &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Data Model&#039;&#039;&#039; || A Lua table exposed to the RML document via reactive data bindings. Changes to the model automatically update the rendered UI.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;&#039;Lua side&#039;&#039;&#039; you create contexts, attach data models to them, and load documents. On the &#039;&#039;&#039;RML side&#039;&#039;&#039; you declare your UI structure and reference data-model values through data bindings.&lt;br /&gt;
&lt;br /&gt;
Each widget will typically consist of at least three files: a &amp;lt;code&amp;gt;.lua&amp;lt;/code&amp;gt;, a &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt;, and optionally a &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file — grouping them in a folder per widget keeps things tidy.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 3. Setup ==&lt;br /&gt;
&lt;br /&gt;
RecoilEngine ships a minimal setup script at &amp;lt;code&amp;gt;cont/LuaUI/rml_setup.lua&amp;lt;/code&amp;gt;. It is included automatically by the base content handler but you should understand what it does so you can extend it for your game.&lt;br /&gt;
&lt;br /&gt;
=== 3.1 The rml_setup.lua script ===&lt;br /&gt;
&lt;br /&gt;
The script performs three tasks: guards against double-initialisation, loads font faces, and registers mouse-cursor aliases. Below is a more complete version that also creates a shared context and configures dp scaling:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--  luaui/rml_setup.lua&lt;br /&gt;
--  author:  lov + ChrisFloofyKitsune&lt;br /&gt;
--  License: GNU GPL, v2 or later&lt;br /&gt;
&lt;br /&gt;
if (RmlGuard or not RmlUi) then&lt;br /&gt;
    return&lt;br /&gt;
end&lt;br /&gt;
-- Prevent this from running more than once&lt;br /&gt;
RmlGuard = true&lt;br /&gt;
&lt;br /&gt;
-- Patch CreateContext to set dp_ratio automatically&lt;br /&gt;
local oldCreateContext = RmlUi.CreateContext&lt;br /&gt;
local function NewCreateContext(name)&lt;br /&gt;
    local context = oldCreateContext(name)&lt;br /&gt;
    local viewSizeX, viewSizeY = Spring.GetViewGeometry()&lt;br /&gt;
    local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
    local baseWidth, baseHeight = 1920, 1080&lt;br /&gt;
    local resFactor = math.min(viewSizeX / baseWidth, viewSizeY / baseHeight)&lt;br /&gt;
    -- Floor to 2 decimal places to avoid floating point drift&lt;br /&gt;
    context.dp_ratio = math.floor(resFactor * userScale * 100) / 100&lt;br /&gt;
    return context&lt;br /&gt;
end&lt;br /&gt;
RmlUi.CreateContext = NewCreateContext&lt;br /&gt;
&lt;br /&gt;
-- Load fonts (list your .ttf files here)&lt;br /&gt;
local font_files = {&lt;br /&gt;
    -- &amp;quot;Fonts/MyFont-Regular.ttf&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
for _, file in ipairs(font_files) do&lt;br /&gt;
    RmlUi.LoadFontFace(file, true)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Map CSS cursor names to engine cursor names&lt;br /&gt;
-- CSS cursor list: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;default&amp;quot;, &#039;cursornormal&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;move&amp;quot;,    &#039;uimove&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;pointer&amp;quot;, &#039;Move&#039;)&lt;br /&gt;
&lt;br /&gt;
-- Create the shared context used by all widgets&lt;br /&gt;
RmlUi.CreateContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3.2 Including the setup in main.lua ===&lt;br /&gt;
&lt;br /&gt;
Add a single line to &amp;lt;code&amp;gt;luaui/main.lua&amp;lt;/code&amp;gt; to run the setup before any widgets load:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- luaui/main.lua&lt;br /&gt;
VFS.Include(LUAUI_DIRNAME .. &amp;quot;rml_setup.lua&amp;quot;, nil, VFS.ZIP)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|The base-content &amp;lt;code&amp;gt;rml_setup.lua&amp;lt;/code&amp;gt; only loads a font and sets cursor aliases; it does &#039;&#039;&#039;not&#039;&#039;&#039; create a context. If you are building a new game you should add context creation here or let each widget create its own context.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 4. Writing Your First Document (.rml) ==&lt;br /&gt;
&lt;br /&gt;
Create a file at &amp;lt;code&amp;gt;luaui/widgets/my_widget/my_widget.rml&amp;lt;/code&amp;gt;. RML is well-formed XML, so every tag must be closed and attribute values must be quoted.&lt;br /&gt;
&lt;br /&gt;
=== 4.1 Root structure ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;rml&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;My Widget&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;!-- External stylesheet (optional) --&amp;gt;&lt;br /&gt;
    &amp;lt;link type=&amp;quot;text/rcss&amp;quot; href=&amp;quot;my_widget.rcss&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;!-- Inline styles --&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        body { margin: 0; padding: 0; }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;!-- Your content here --&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/rml&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4.2 Styles (inline and .rcss) ===&lt;br /&gt;
&lt;br /&gt;
RmlUI starts with &#039;&#039;&#039;no default styles&#039;&#039;&#039; at all — not even block/inline defaults. The RmlUI docs provide an HTML4 base stylesheet you can copy in, but you will need to add form element styles yourself.&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;dp&amp;lt;/code&amp;gt; units instead of &amp;lt;code&amp;gt;px&amp;lt;/code&amp;gt; so your UI scales correctly with the player&#039;s display and &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; setting.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;css&amp;quot;&amp;gt;&lt;br /&gt;
/* Typical widget container */&lt;br /&gt;
#my-widget {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    width: 400dp;&lt;br /&gt;
    right: 10dp;&lt;br /&gt;
    top: 50%;&lt;br /&gt;
    transform: translateY(-50%);&lt;br /&gt;
    pointer-events: auto;   /* required to receive mouse input */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#my-widget .panel {&lt;br /&gt;
    padding: 10dp;&lt;br /&gt;
    border-radius: 8dp;&lt;br /&gt;
    border: 1dp #6c7086;   /* note: no &#039;solid&#039; keyword – see section 11 */&lt;br /&gt;
    background-color: rgba(30, 30, 46, 200);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4.3 Data bindings in RML ===&lt;br /&gt;
&lt;br /&gt;
Attach a data model to a root element with &amp;lt;code&amp;gt;data-model=&amp;quot;model_name&amp;quot;&amp;lt;/code&amp;gt;. All data binding attributes inside that element can reference the model.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
  &amp;lt;!-- data-model scopes all bindings inside this div --&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;my-widget&amp;quot; data-model=&amp;quot;my_model&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Text interpolation --&amp;gt;&lt;br /&gt;
    &amp;lt;p&amp;gt;Status: {{status_message}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Conditional visibility --&amp;gt;&lt;br /&gt;
    &amp;lt;div data-if=&amp;quot;show_details&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Extra details go here.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Dynamic class based on model value --&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;panel&amp;quot; data-class-active=&amp;quot;is_active&amp;quot;&amp;gt;&lt;br /&gt;
        Active panel&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Loop over an array --&amp;gt;&lt;br /&gt;
    &amp;lt;ul&amp;gt;&lt;br /&gt;
        &amp;lt;li data-for=&amp;quot;item, i: items&amp;quot;&amp;gt;{{i}}: {{item.label}}&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Two-way checkbox binding --&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; data-checked=&amp;quot;is_enabled&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Data event: calls model function on click --&amp;gt;&lt;br /&gt;
    &amp;lt;button data-click=&amp;quot;on_button_click()&amp;quot;&amp;gt;Click me&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Data binding reference:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Binding !! Effect&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{value}}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || Interpolate model value as text&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-model=&amp;quot;name&amp;quot;&amp;lt;/code&amp;gt; || Attach named data model to this element and its children&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-if=&amp;quot;flag&amp;quot;&amp;lt;/code&amp;gt; || Show element only when flag is truthy&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-class-X=&amp;quot;flag&amp;quot;&amp;lt;/code&amp;gt; || Add class X when flag is truthy&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-for=&amp;quot;v, i: arr&amp;quot;&amp;lt;/code&amp;gt; || Repeat element for each item in array arr&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-checked=&amp;quot;val&amp;quot;&amp;lt;/code&amp;gt; || Two-way bind checkbox to boolean model value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Call model function on click (data event)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-attr-src=&amp;quot;val&amp;quot;&amp;lt;/code&amp;gt; || Dynamically set the src attribute from model value&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 5. The Lua Widget ==&lt;br /&gt;
&lt;br /&gt;
Each widget is a Lua file that the handler loads. The &amp;lt;code&amp;gt;widget&amp;lt;/code&amp;gt; global is injected by the widget handler and provides the lifecycle hooks.&lt;br /&gt;
&lt;br /&gt;
=== 5.1 Widget skeleton ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- luaui/widgets/my_widget/my_widget.lua&lt;br /&gt;
if not RmlUi then return end   -- guard: RmlUi not available&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name    = &amp;quot;My Widget&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Demonstrates RmlUI basics.&amp;quot;,&lt;br /&gt;
        author  = &amp;quot;YourName&amp;quot;,&lt;br /&gt;
        date    = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer   = 0,&lt;br /&gt;
        enabled = true,&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5.2 Loading the document ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local RML_FILE  = &amp;quot;luaui/widgets/my_widget/my_widget.rml&amp;quot;&lt;br /&gt;
local MODEL_NAME = &amp;quot;my_model&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local rml_ctx    -- the RmlUi Context&lt;br /&gt;
local dm_handle  -- the DataModel handle (MUST be kept – see section 6)&lt;br /&gt;
local document   -- the loaded Document&lt;br /&gt;
&lt;br /&gt;
-- Initial data model state&lt;br /&gt;
local init_model = {&lt;br /&gt;
    status_message = &amp;quot;Ready&amp;quot;,&lt;br /&gt;
    is_enabled     = false,&lt;br /&gt;
    show_details   = false,&lt;br /&gt;
    items = {&lt;br /&gt;
        { label = &amp;quot;Alpha&amp;quot;, value = 1 },&lt;br /&gt;
        { label = &amp;quot;Beta&amp;quot;,  value = 2 },&lt;br /&gt;
    },&lt;br /&gt;
    -- Functions can live in the model too (callable from data-events)&lt;br /&gt;
    on_button_click = function()&lt;br /&gt;
        Spring.Echo(&amp;quot;Button was clicked!&amp;quot;)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function widget:Initialize()&lt;br /&gt;
    rml_ctx = RmlUi.GetContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    -- IMPORTANT: assign dm_handle or the engine WILL crash on RML render&lt;br /&gt;
    dm_handle = rml_ctx:OpenDataModel(MODEL_NAME, init_model)&lt;br /&gt;
    if not dm_handle then&lt;br /&gt;
        Spring.Echo(&amp;quot;[my_widget] Failed to open data model&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    document = rml_ctx:LoadDocument(RML_FILE, widget)&lt;br /&gt;
    if not document then&lt;br /&gt;
        Spring.Echo(&amp;quot;[my_widget] Failed to load document&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    document:ReloadStyleSheet()&lt;br /&gt;
    document:Show()&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5.3 Shutting down cleanly ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function widget:Shutdown()&lt;br /&gt;
    if document then&lt;br /&gt;
        document:Close()&lt;br /&gt;
        document = nil&lt;br /&gt;
    end&lt;br /&gt;
    -- Remove the model from the context; frees memory&lt;br /&gt;
    rml_ctx:RemoveDataModel(MODEL_NAME)&lt;br /&gt;
    dm_handle = nil&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 6. Data Models in Depth ==&lt;br /&gt;
&lt;br /&gt;
=== 6.1 Opening a data model ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;context:OpenDataModel(name, table)&amp;lt;/code&amp;gt; copies the structure of the table into a reactive proxy. The name must match the &amp;lt;code&amp;gt;data-model=&amp;quot;name&amp;quot;&amp;lt;/code&amp;gt; attribute in your RML. It must be unique within the context.&lt;br /&gt;
&lt;br /&gt;
{{Warning|You &#039;&#039;&#039;must&#039;&#039;&#039; store the return value of &amp;lt;code&amp;gt;OpenDataModel&amp;lt;/code&amp;gt; in a variable. Letting it be garbage-collected while the document is open will crash the engine the next time RmlUI tries to render a data binding.}}&lt;br /&gt;
&lt;br /&gt;
=== 6.2 Reading and writing values ===&lt;br /&gt;
&lt;br /&gt;
For simple string/number/boolean values at the top level of the model you can use dot-notation directly on the handle:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Read a value&lt;br /&gt;
local msg = dm_handle.status_message   -- works&lt;br /&gt;
&lt;br /&gt;
-- Write a value (auto-marks the field dirty; UI updates next frame)&lt;br /&gt;
dm_handle.status_message = &amp;quot;Unit selected&amp;quot;&lt;br /&gt;
dm_handle.is_enabled     = true&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6.3 Iterating arrays ===&lt;br /&gt;
&lt;br /&gt;
Because the handle is a sol2 proxy, you cannot iterate it directly with &amp;lt;code&amp;gt;pairs&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ipairs&amp;lt;/code&amp;gt;. Call &amp;lt;code&amp;gt;dm_handle:__GetTable()&amp;lt;/code&amp;gt; to get the underlying Lua table:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local tbl = dm_handle:__GetTable()&lt;br /&gt;
&lt;br /&gt;
for i, item in ipairs(tbl.items) do&lt;br /&gt;
    Spring.Echo(i, item.label, item.value)&lt;br /&gt;
    item.value = item.value + 1   -- modify in place&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6.4 Marking dirty ===&lt;br /&gt;
&lt;br /&gt;
When you modify nested values (e.g. elements inside an array) through &amp;lt;code&amp;gt;__GetTable()&amp;lt;/code&amp;gt;, you must tell the model which top-level key changed so that RmlUI re-renders the bound elements:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- After modifying tbl.items …&lt;br /&gt;
dm_handle:__SetDirty(&amp;quot;items&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
-- For a simple top-level assignment via dm_handle.key = value,&lt;br /&gt;
-- dirty-marking is done automatically.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 7. Events ==&lt;br /&gt;
&lt;br /&gt;
Recoil&#039;s RmlUI supports two distinct event families. They look similar but have different scopes — mixing them up is a common source of confusion.&lt;br /&gt;
&lt;br /&gt;
=== 7.1 Normal events (on*) ===&lt;br /&gt;
&lt;br /&gt;
Written as &amp;lt;code&amp;gt;onclick=&amp;quot;myFunc()&amp;quot;&amp;lt;/code&amp;gt;. The function is resolved from the &#039;&#039;&#039;second argument&#039;&#039;&#039; passed to &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; — the widget table. These events do &#039;&#039;&#039;not&#039;&#039;&#039; have access to the data model.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Lua&lt;br /&gt;
local doc_scope = {&lt;br /&gt;
    greet = function(name)&lt;br /&gt;
        Spring.Echo(&amp;quot;Hello&amp;quot;, name)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
document = rml_ctx:LoadDocument(RML_FILE, doc_scope)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- RML --&amp;gt;&lt;br /&gt;
&amp;lt;button onclick=&amp;quot;greet(&#039;World&#039;)&amp;quot;&amp;gt;Say hello&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 7.2 Data events (data-*) ===&lt;br /&gt;
&lt;br /&gt;
Written as &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt;. The function is resolved from the &#039;&#039;&#039;data model&#039;&#039;&#039;. These events have full access to all model values.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Lua model&lt;br /&gt;
local init_model = {&lt;br /&gt;
    counter = 0,&lt;br /&gt;
    increment = function()&lt;br /&gt;
        -- dm_handle is captured in the closure&lt;br /&gt;
        dm_handle.counter = dm_handle.counter + 1&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- RML --&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Count: {{counter}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;button data-click=&amp;quot;increment()&amp;quot;&amp;gt;+1&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Event type comparison:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type !! Syntax example !! Scope&lt;br /&gt;
|-&lt;br /&gt;
| Normal || &amp;lt;code&amp;gt;onclick=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; second argument (widget table)&lt;br /&gt;
|-&lt;br /&gt;
| Data || &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Data model table&lt;br /&gt;
|-&lt;br /&gt;
| Normal || &amp;lt;code&amp;gt;onmouseover=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; second argument&lt;br /&gt;
|-&lt;br /&gt;
| Data || &amp;lt;code&amp;gt;data-mouseover=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Data model table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 8. Custom Recoil Elements ==&lt;br /&gt;
&lt;br /&gt;
Beyond standard HTML elements, Recoil adds two custom RML elements.&lt;br /&gt;
&lt;br /&gt;
=== 8.1 The &amp;amp;lt;texture&amp;amp;gt; element ===&lt;br /&gt;
&lt;br /&gt;
Renders any engine texture — unit icons, map thumbnails, GL4 render targets, etc. Behaves identically to &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;img&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; except the &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute takes a &#039;&#039;&#039;Recoil texture reference string&#039;&#039;&#039; (see engine docs for the full format).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- Load a file from VFS --&amp;gt;&lt;br /&gt;
&amp;lt;texture src=&amp;quot;unitpics/armcom.png&amp;quot; width=&amp;quot;64dp&amp;quot; height=&amp;quot;64dp&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- Reference a named GL texture --&amp;gt;&lt;br /&gt;
&amp;lt;texture src=&amp;quot;%luaui:myTextureName&amp;quot; width=&amp;quot;128dp&amp;quot; height=&amp;quot;128dp&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 8.2 The &amp;amp;lt;svg&amp;amp;gt; element ===&lt;br /&gt;
&lt;br /&gt;
Renders an SVG image. Unlike upstream RmlUI, the Recoil implementation accepts either a file path &#039;&#039;&#039;or raw inline SVG data&#039;&#039;&#039; in the &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- External file --&amp;gt;&lt;br /&gt;
&amp;lt;svg src=&amp;quot;images/icon.svg&amp;quot; width=&amp;quot;32dp&amp;quot; height=&amp;quot;32dp&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- Inline SVG data --&amp;gt;&lt;br /&gt;
&amp;lt;svg src=&amp;quot;&amp;lt;svg xmlns=&#039;http://www.w3.org/2000/svg&#039; viewBox=&#039;0 0 10 10&#039;&amp;gt;&amp;lt;circle cx=&#039;5&#039; cy=&#039;5&#039; r=&#039;4&#039; fill=&#039;red&#039;/&amp;gt;&amp;lt;/svg&amp;gt;&amp;quot;&lt;br /&gt;
     width=&amp;quot;32dp&amp;quot; height=&amp;quot;32dp&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 9. Debugging ==&lt;br /&gt;
&lt;br /&gt;
RmlUI ships with a DOM debugger that you can open in-game to inspect elements, check computed styles, and view event logs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Enable the debugger for the shared context.&lt;br /&gt;
-- Call this AFTER the context has been created.&lt;br /&gt;
RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
&lt;br /&gt;
-- A handy pattern: toggle via a build flag in rml_setup.lua&lt;br /&gt;
local DEBUG_RMLUI = true   -- set false before shipping&lt;br /&gt;
if DEBUG_RMLUI then&lt;br /&gt;
    RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once enabled, a small panel appears in the game window. Click &#039;&#039;&#039;Debug&#039;&#039;&#039; to open the inspector or &#039;&#039;&#039;Log&#039;&#039;&#039; for the event log.&lt;br /&gt;
&lt;br /&gt;
{{Note|The debugger attaches to one context at a time. Using the shared context means you see all widgets in one place — very useful for tracking cross-widget interactions.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 10. Best Practices ==&lt;br /&gt;
&lt;br /&gt;
; Use dp units everywhere&lt;br /&gt;
: The &amp;lt;code&amp;gt;dp&amp;lt;/code&amp;gt; unit scales with &amp;lt;code&amp;gt;context.dp_ratio&amp;lt;/code&amp;gt;, so your UI automatically adapts to different resolutions and the player&#039;s &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; setting.&lt;br /&gt;
&lt;br /&gt;
; One shared context per game&lt;br /&gt;
: Beyond All Reason and most community projects use a single &amp;lt;code&amp;gt;&#039;shared&#039;&amp;lt;/code&amp;gt; context. Documents in the same context can reference the same data models and are Z-ordered relative to each other.&lt;br /&gt;
&lt;br /&gt;
; Keep data models flat when possible&lt;br /&gt;
: Top-level writes via &amp;lt;code&amp;gt;dm_handle.key = value&amp;lt;/code&amp;gt; are automatically dirty-marked. Deeply nested structures require manual &amp;lt;code&amp;gt;__SetDirty&amp;lt;/code&amp;gt; calls — simpler models mean fewer bugs.&lt;br /&gt;
&lt;br /&gt;
; Separate per-document and shared styles&lt;br /&gt;
: Put styles unique to one document in a &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;style&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; block inside the &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; file. Put styles shared across multiple documents in a &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file and link it.&lt;br /&gt;
&lt;br /&gt;
; Guard against missing RmlUi&lt;br /&gt;
: Always start widgets with &amp;lt;code&amp;gt;if not RmlUi then return end&amp;lt;/code&amp;gt; to prevent errors in environments where RmlUI is unavailable.&lt;br /&gt;
&lt;br /&gt;
; Check return values&lt;br /&gt;
: Both &amp;lt;code&amp;gt;OpenDataModel&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; can fail silently if paths are wrong. Always check for nil and log an error so you know immediately what happened.&lt;br /&gt;
&lt;br /&gt;
; Organise by widget folder&lt;br /&gt;
: Group &amp;lt;code&amp;gt;luaui/widgets/my_widget/my_widget.lua&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; together. This makes the project navigable and each widget self-contained.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 11. RCSS Gotchas ==&lt;br /&gt;
&lt;br /&gt;
RCSS closely follows CSS2 but has several important differences that will trip up web developers:&lt;br /&gt;
&lt;br /&gt;
; No default stylesheet&lt;br /&gt;
: RmlUI ships with zero default styles. Block/inline, heading sizes, list bullets — none of it exists unless you add it. Copy the HTML4 base sheet from the RmlUI docs as a starting point.&lt;br /&gt;
&lt;br /&gt;
; RGBA alpha is 0–255&lt;br /&gt;
: &amp;lt;code&amp;gt;rgba(255, 0, 0, 128)&amp;lt;/code&amp;gt; means 50% transparent red. CSS uses 0.0–1.0 for alpha. The &amp;lt;code&amp;gt;opacity&amp;lt;/code&amp;gt; property still uses 0.0–1.0.&lt;br /&gt;
&lt;br /&gt;
; Border shorthand has no style keyword&lt;br /&gt;
: &amp;lt;code&amp;gt;border: 1dp #6c7086;&amp;lt;/code&amp;gt; works. &amp;lt;code&amp;gt;border: 1dp solid #6c7086;&amp;lt;/code&amp;gt; does &#039;&#039;&#039;not&#039;&#039;&#039; work. Only solid borders are supported; &amp;lt;code&amp;gt;border-style&amp;lt;/code&amp;gt; is unavailable.&lt;br /&gt;
&lt;br /&gt;
; background-* properties use decorators&lt;br /&gt;
: &amp;lt;code&amp;gt;background-color&amp;lt;/code&amp;gt; works normally. &amp;lt;code&amp;gt;background-image&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;background-size&amp;lt;/code&amp;gt;, etc. are replaced by the decorator system. See the RmlUI docs for syntax.&lt;br /&gt;
&lt;br /&gt;
; No list styling&lt;br /&gt;
: &amp;lt;code&amp;gt;list-style-type&amp;lt;/code&amp;gt; and friends are not supported. &amp;lt;code&amp;gt;ul&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;ol&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;li&amp;lt;/code&amp;gt; have no special rendering — treat them as plain div-like elements.&lt;br /&gt;
&lt;br /&gt;
; Input text is set by content, not value&lt;br /&gt;
: For &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;input type=&amp;quot;button&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;input type=&amp;quot;submit&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; the displayed text comes from the element&#039;s text content, not the &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; attribute (unlike HTML).&lt;br /&gt;
&lt;br /&gt;
; pointer-events: auto required&lt;br /&gt;
: By default elements do not receive mouse events. Add &amp;lt;code&amp;gt;pointer-events: auto&amp;lt;/code&amp;gt; to any element that needs to be interactive.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 12. Differences from Upstream RmlUI ==&lt;br /&gt;
&lt;br /&gt;
The Recoil implementation is based on the official RmlUI library but adds engine-specific features and changes some defaults:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Feature !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&amp;lt;texture&amp;gt;&amp;lt;/nowiki&amp;gt; element&#039;&#039;&#039; || A custom element that renders engine-managed textures via Recoil texture reference strings. Not present in upstream.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&amp;lt;svg&amp;gt;&amp;lt;/nowiki&amp;gt; inline data&#039;&#039;&#039; || The &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute accepts raw SVG markup in addition to file paths. Upstream only supports file paths.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;VFS file loader&#039;&#039;&#039; || All file paths are resolved through Recoil&#039;s Virtual File System, so &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files work inside &amp;lt;code&amp;gt;.sdz&amp;lt;/code&amp;gt; game archives transparently.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Lua bindings via sol2&#039;&#039;&#039; || The Lua API is provided by RmlSolLua (derived from [https://github.com/LoneBoco/RmlSolLua LoneBoco/RmlSolLua]) rather than the upstream Lua plugin, offering tighter engine integration.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dp_ratio via context&#039;&#039;&#039; || &amp;lt;code&amp;gt;context.dp_ratio&amp;lt;/code&amp;gt; is set from code rather than the system DPI, allowing games to honour the player&#039;s &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; preference.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{{Note|For everything not listed above, the upstream RmlUI documentation at [https://mikke89.github.io/RmlUiDoc/ mikke89.github.io/RmlUiDoc] applies directly.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 13. IDE Setup ==&lt;br /&gt;
&lt;br /&gt;
Configuring your editor for &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file extensions dramatically improves the authoring experience.&lt;br /&gt;
&lt;br /&gt;
=== VS Code ===&lt;br /&gt;
&lt;br /&gt;
# Open any &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; file in VS Code.&lt;br /&gt;
# Click the language mode indicator in the status bar (usually shows &#039;&#039;Plain Text&#039;&#039;).&lt;br /&gt;
# Choose &#039;&#039;&#039;Configure File Association for &#039;.rml&#039;&#039;&#039;&#039;&lt;br /&gt;
# Select &#039;&#039;&#039;HTML&#039;&#039;&#039; from the list.&lt;br /&gt;
# Repeat for &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files, selecting &#039;&#039;&#039;CSS&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Lua Language Server (lua-ls) ===&lt;br /&gt;
&lt;br /&gt;
Add the Recoil type annotations to your workspace for autocomplete on &amp;lt;code&amp;gt;RmlUi&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring&amp;lt;/code&amp;gt;, and all widget APIs. See the Recoil docs guide on the Lua Language Server for details.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 14. Quick Reference ==&lt;br /&gt;
&lt;br /&gt;
=== Lua API ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Call !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.CreateContext(name)&amp;lt;/code&amp;gt; || Create a new rendering context&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.GetContext(name)&amp;lt;/code&amp;gt; || Retrieve an existing context by name&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.LoadFontFace(path, fallback)&amp;lt;/code&amp;gt; || Register a &amp;lt;code&amp;gt;.ttf&amp;lt;/code&amp;gt; font (&amp;lt;code&amp;gt;fallback=true&amp;lt;/code&amp;gt; recommended)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.SetMouseCursorAlias(css, engine)&amp;lt;/code&amp;gt; || Map a CSS cursor name to an engine cursor name&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.SetDebugContext(name)&amp;lt;/code&amp;gt; || Attach the DOM debugger to a named context&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:OpenDataModel(name, tbl)&amp;lt;/code&amp;gt; || Create reactive data model from Lua table; &#039;&#039;&#039;store the return value!&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:RemoveDataModel(name)&amp;lt;/code&amp;gt; || Destroy a data model and free its memory&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:LoadDocument(path, scope)&amp;lt;/code&amp;gt; || Parse an RML file; scope is used for normal (&amp;lt;code&amp;gt;on*&amp;lt;/code&amp;gt;) events&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Show()&amp;lt;/code&amp;gt; || Make the document visible&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Hide()&amp;lt;/code&amp;gt; || Hide the document (keeps it in memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Close()&amp;lt;/code&amp;gt; || Destroy the document&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:ReloadStyleSheet()&amp;lt;/code&amp;gt; || Reload linked &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files (useful during development)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm:__GetTable()&amp;lt;/code&amp;gt; || Get the underlying Lua table for iteration&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm:__SetDirty(key)&amp;lt;/code&amp;gt; || Mark a top-level model key as changed; triggers re-render&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm.key = value&amp;lt;/code&amp;gt; || Write a top-level value; auto-marks dirty&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Useful Links ===&lt;br /&gt;
&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/ RmlUI official documentation]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/data_bindings/views_and_controllers.html RmlUI data bindings reference]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rcss.html RmlUI RCSS reference]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html RmlUI HTML4 base stylesheet]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rcss/decorators.html RmlUI decorators (backgrounds)]&lt;br /&gt;
* [https://github.com/mikke89/RmlUi RmlUI GitHub]&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine/tree/master/rts/Rml RecoilEngine source (rts/Rml/)]&lt;br /&gt;
* Recoil texture reference strings — see engine docs: &amp;lt;code&amp;gt;articles/texture-reference-strings&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:LuaUI]]&lt;br /&gt;
[[Category:RmlUI]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2703</id>
		<title>Recoil:RmlUI Starter Guide</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2703"/>
		<updated>2026-03-06T06:02:34Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:Making A Basic Recoil Game}}&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
= Making A Basic Recoil Game =&lt;br /&gt;
&lt;br /&gt;
This guide walks you through creating a minimal but functional game with the Recoil engine.&lt;br /&gt;
By the end you will have a working game archive that loads in the engine, with a playable Commander unit, a simple weapon, and a basic Lua gadget.&lt;br /&gt;
No prior Spring or Recoil experience is assumed, though familiarity with Lua is helpful.&lt;br /&gt;
&lt;br /&gt;
{{warning|1=This guide targets the current Recoil engine. If you are migrating an existing Spring 105 game, see [[Recoil:Migrating From Spring]] for a list of breaking changes.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 1. What is Recoil? ==&lt;br /&gt;
&lt;br /&gt;
Recoil is an open-source, cross-platform RTS game engine descended from Spring (which itself descended from Total Annihilation).&lt;br /&gt;
It provides out of the box:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Physics simulation&#039;&#039;&#039; — projectile trajectories, terrain collision, unit movement&lt;br /&gt;
* &#039;&#039;&#039;Resource system&#039;&#039;&#039; — metal and energy by default, fully customisable&lt;br /&gt;
* &#039;&#039;&#039;Networking&#039;&#039;&#039; — authoritative server, deterministic lockstep simulation, replays&lt;br /&gt;
* &#039;&#039;&#039;Lua scripting&#039;&#039;&#039; — every mechanic is overridable via Lua gadgets and widgets&lt;br /&gt;
* &#039;&#039;&#039;VFS&#039;&#039;&#039; — a virtual file system that loads content from game archives, maps, and basecontent&lt;br /&gt;
&lt;br /&gt;
Your game is a Lua + asset package that rides on top of the engine.&lt;br /&gt;
You control the rules, the units, the UI, and everything else; the engine provides the platform.&lt;br /&gt;
&lt;br /&gt;
=== Key terminology ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Term !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Game&#039;&#039;&#039; || Your content archive loaded by the engine (units, Lua scripts, textures, etc.)&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Map&#039;&#039;&#039; || The terrain archive (.smf height data + textures). Distributed separately from the game.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Gadget&#039;&#039;&#039; || A Lua script that runs game logic (synced or unsynced). Lives in &amp;lt;code&amp;gt;luarules/gadgets/&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Widget&#039;&#039;&#039; || A Lua script that runs UI logic. Lives in &amp;lt;code&amp;gt;luaui/widgets/&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Wupget&#039;&#039;&#039; || Informal collective term for widgets and gadgets.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Callin&#039;&#039;&#039; || A function in your Lua code that the engine calls (e.g. &amp;lt;code&amp;gt;gadget:UnitCreated&amp;lt;/code&amp;gt;).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Callout&#039;&#039;&#039; || A function the engine exposes to Lua (e.g. &amp;lt;code&amp;gt;Spring.GetUnitPosition&amp;lt;/code&amp;gt;).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;VFS&#039;&#039;&#039; || Virtual File System — unified view of game archive, map archive, basecontent.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Basecontent&#039;&#039;&#039; || Engine-bundled archive with default handlers, water textures, and other bare necessities.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;.sdd&#039;&#039;&#039; || Development game folder (name must end in &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt;). Equivalent to an archive but uncompressed.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;.sdz&#039;&#039;&#039; || Production game archive (renamed .zip). Faster to distribute and load.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 2. Prerequisites ==&lt;br /&gt;
&lt;br /&gt;
Before starting you need:&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;The Recoil engine binary.&#039;&#039;&#039; Download a pre-built release from [https://github.com/beyond-all-reason/RecoilEngine/releases GitHub Releases] or build from source. The main executable is &amp;lt;code&amp;gt;spring&amp;lt;/code&amp;gt; (or &amp;lt;code&amp;gt;spring.exe&amp;lt;/code&amp;gt; on Windows).&lt;br /&gt;
# &#039;&#039;&#039;A map archive&#039;&#039;&#039; (&amp;lt;code&amp;gt;.smf&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;.sd7&amp;lt;/code&amp;gt;) placed in the &amp;lt;code&amp;gt;maps/&amp;lt;/code&amp;gt; subfolder of your Recoil data directory. Any map from an existing Recoil game works for testing. Map archives and game archives live in separate directories.&lt;br /&gt;
# &#039;&#039;&#039;A text editor.&#039;&#039;&#039; Any editor works. For the best experience, set up the [[Recoil:Lua Language Server|Lua Language Server]] with Recoil type stubs so you get autocomplete on all &amp;lt;code&amp;gt;Spring.*&amp;lt;/code&amp;gt; functions.&lt;br /&gt;
&lt;br /&gt;
Your Recoil data directory typically looks like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
recoil-data/&lt;br /&gt;
├── spring           ← engine binary (linux) or spring.exe (windows)&lt;br /&gt;
├── spring_headless  ← headless binary (no rendering)&lt;br /&gt;
├── spring_dedicated ← dedicated server binary&lt;br /&gt;
├── games/           ← your game archives / folders go here&lt;br /&gt;
├── maps/            ← map archives go here&lt;br /&gt;
└── (engine config files)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 3. Game Archive Structure ==&lt;br /&gt;
&lt;br /&gt;
During development use an &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; folder — it&#039;s just a normal directory the engine treats as an archive.&lt;br /&gt;
Create it inside your &amp;lt;code&amp;gt;games/&amp;lt;/code&amp;gt; directory:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
games/&lt;br /&gt;
└── mygame.sdd/&lt;br /&gt;
    ├── modinfo.lua          ← required: identifies your game to the engine&lt;br /&gt;
    ├── units/               ← unit definition files&lt;br /&gt;
    │   └── commander.lua&lt;br /&gt;
    ├── weapons/             ← weapon definition files&lt;br /&gt;
    │   └── laser.lua&lt;br /&gt;
    ├── features/            ← feature (prop/wreck) definitions&lt;br /&gt;
    ├── gamedata/            ← def pre/post processing, options&lt;br /&gt;
    │   ├── unitdefs_post.lua&lt;br /&gt;
    │   └── modrules.lua&lt;br /&gt;
    ├── luarules/&lt;br /&gt;
    │   └── gadgets/         ← synced and unsynced game logic&lt;br /&gt;
    │       └── game_end.lua&lt;br /&gt;
    ├── luaui/&lt;br /&gt;
    │   └── widgets/         ← UI scripts&lt;br /&gt;
    ├── objects3d/           ← unit models (.dae / .obj / .s3o)&lt;br /&gt;
    ├── unittextures/        ← unit icon textures (unitname.png)&lt;br /&gt;
    └── maps/                ← embedded map resources (optional)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=The &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; extension on the folder name is mandatory. Without it the engine will not recognise it as a game archive.}}&lt;br /&gt;
&lt;br /&gt;
For production, compress the contents into a &amp;lt;code&amp;gt;.sdz&amp;lt;/code&amp;gt; (zip) archive or &amp;lt;code&amp;gt;.sd7&amp;lt;/code&amp;gt; (7-zip) archive.&lt;br /&gt;
The folder contents become the archive root — &amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt; should be at the top level.&lt;br /&gt;
&lt;br /&gt;
=== VFS and dependencies ===&lt;br /&gt;
&lt;br /&gt;
Your game archive can declare &#039;&#039;&#039;dependencies&#039;&#039;&#039; on other archives (including other games) via &amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
The engine merges all archives into the VFS.&lt;br /&gt;
Files in your game take priority over dependencies.&lt;br /&gt;
This means you can build on an existing game without copying its assets.&lt;br /&gt;
&lt;br /&gt;
See [[Recoil:VFS Basics]] for full details on VFS modes and the load order.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 4. modinfo.lua ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt; is the single most important file — without it the engine will not recognise your archive as a game.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- games/mygame.sdd/modinfo.lua&lt;br /&gt;
&lt;br /&gt;
local modinfo = {&lt;br /&gt;
    -- Displayed in lobbies and the engine title bar&lt;br /&gt;
    name    = &amp;quot;My Recoil Game&amp;quot;,&lt;br /&gt;
    -- Short unique identifier used internally; no spaces, keep it stable&lt;br /&gt;
    shortName = &amp;quot;MRG&amp;quot;,&lt;br /&gt;
    -- Shown to players in lobby browsers and descriptions&lt;br /&gt;
    description = &amp;quot;A minimal Recoil game for learning purposes.&amp;quot;,&lt;br /&gt;
    -- Semantic version string&lt;br /&gt;
    version = &amp;quot;0.1.0&amp;quot;,&lt;br /&gt;
    -- Mutator name; leave empty for base games (not mods on top of other games)&lt;br /&gt;
    mutator = &amp;quot;&amp;quot;,&lt;br /&gt;
    -- Archive names this game depends on; engine always loads basecontent itself&lt;br /&gt;
    depend  = {},&lt;br /&gt;
    -- Replaces list: names of games this game supersedes in rapid/lobby&lt;br /&gt;
    replace = {},&lt;br /&gt;
    -- URL shown in lobby if a player does not have the game&lt;br /&gt;
    modtype = 1,  -- 1 = game/mod, 0 = hidden&lt;br /&gt;
    -- Primary unit sides available to players&lt;br /&gt;
    -- These correspond to the &amp;quot;Side&amp;quot; key in start scripts and unit defs&lt;br /&gt;
    sides   = { &amp;quot;MyFaction&amp;quot; },&lt;br /&gt;
    -- For each side, which unit the engine spawns as the starting unit&lt;br /&gt;
    startscreens = {&lt;br /&gt;
        MyFaction = &amp;quot;commander&amp;quot;,   -- references a unit def name&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return modinfo&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=&amp;lt;code&amp;gt;shortName&amp;lt;/code&amp;gt; must be unique across all games the engine knows about. Pick something distinctive. Changing it later breaks lobby integrations and replays.}}&lt;br /&gt;
&lt;br /&gt;
=== ModOptions.lua ===&lt;br /&gt;
&lt;br /&gt;
To expose configurable options in lobbies (e.g. starting resources, game mode), add a &amp;lt;code&amp;gt;gamedata/ModOptions.lua&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- gamedata/ModOptions.lua&lt;br /&gt;
-- Returned options appear as sliders/checkboxes/dropdowns in lobby UIs.&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    {&lt;br /&gt;
        key     = &amp;quot;startmetal&amp;quot;,&lt;br /&gt;
        name    = &amp;quot;Starting Metal&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Amount of metal each player starts with.&amp;quot;,&lt;br /&gt;
        type    = &amp;quot;number&amp;quot;,&lt;br /&gt;
        def     = 1000,&lt;br /&gt;
        min     = 0,&lt;br /&gt;
        max     = 10000,&lt;br /&gt;
        step    = 100,&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        key     = &amp;quot;commanderdie&amp;quot;,&lt;br /&gt;
        name    = &amp;quot;Commander Death Ends Game&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Lose when your Commander is destroyed.&amp;quot;,&lt;br /&gt;
        type    = &amp;quot;bool&amp;quot;,&lt;br /&gt;
        def     = true,&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Options are readable in gadgets via &amp;lt;code&amp;gt;Spring.GetModOptions()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 5. Your First Unit Definition ==&lt;br /&gt;
&lt;br /&gt;
Unit defs are Lua files in &amp;lt;code&amp;gt;units/&amp;lt;/code&amp;gt; that return a table of unit type definitions.&lt;br /&gt;
The convention is one unit per file, where the filename matches the unit def name.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- units/commander.lua&lt;br /&gt;
-- The starting unit for the MyFaction side.&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    commander = {&lt;br /&gt;
        -- ── Display ──────────────────────────────────────────────────&lt;br /&gt;
        name        = &amp;quot;Commander&amp;quot;,&lt;br /&gt;
        description = &amp;quot;The starting unit. Its death ends the game.&amp;quot;,&lt;br /&gt;
        -- Path to the icon shown in the unit HUD (unittextures/commander.png)&lt;br /&gt;
        buildPic    = &amp;quot;commander.png&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- ── Model ────────────────────────────────────────────────────&lt;br /&gt;
        -- Path inside the archive (objects3d/commander.dae)&lt;br /&gt;
        objectName  = &amp;quot;commander.dae&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- ── Physics ──────────────────────────────────────────────────&lt;br /&gt;
        -- Maximum health points&lt;br /&gt;
        health      = 3000,&lt;br /&gt;
        -- Armour class name; controls damage reduction from weapon types&lt;br /&gt;
        armorType   = &amp;quot;commander&amp;quot;,&lt;br /&gt;
        -- Size of the footprint in map squares (8 elmos each)&lt;br /&gt;
        footprintX  = 2,&lt;br /&gt;
        footprintZ  = 2,&lt;br /&gt;
        -- The height of the collision sphere&lt;br /&gt;
        collisionVolumeScales = &amp;quot;30 50 30&amp;quot;,&lt;br /&gt;
        collisionVolumeType   = &amp;quot;ellipsoid&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- ── Movement ─────────────────────────────────────────────────&lt;br /&gt;
        -- Movement class name; references a MoveClass def (see MoveClasses below)&lt;br /&gt;
        moveDef = {&lt;br /&gt;
            family      = &amp;quot;tank&amp;quot;,&lt;br /&gt;
            -- Units above this slope can&#039;t be traversed&lt;br /&gt;
            maxSlope    = 36,&lt;br /&gt;
            -- Turns per second at max speed&lt;br /&gt;
            turnRate    = 1024,&lt;br /&gt;
        },&lt;br /&gt;
        -- Maximum speed in elmos/second&lt;br /&gt;
        maxVelocity = 42,&lt;br /&gt;
        -- Acceleration in elmos/second²&lt;br /&gt;
        maxAcc      = 0.5,&lt;br /&gt;
        -- Braking in elmos/second²&lt;br /&gt;
        maxDec      = 1.5,&lt;br /&gt;
&lt;br /&gt;
        -- ── Construction ─────────────────────────────────────────────&lt;br /&gt;
        -- How many build power units this unit provides&lt;br /&gt;
        workerTime  = 200,&lt;br /&gt;
        -- How much metal it costs to build&lt;br /&gt;
        buildCostMetal  = 0,     -- Commanders are free (spawned by engine)&lt;br /&gt;
        buildCostEnergy = 0,&lt;br /&gt;
        -- Build time in seconds at 1 build power&lt;br /&gt;
        buildTime   = 1,&lt;br /&gt;
&lt;br /&gt;
        -- ── Energy and Metal Production ───────────────────────────────&lt;br /&gt;
        metalMake   = 1,      -- passively trickles 1 metal/second&lt;br /&gt;
        energyMake  = 20,     -- passively generates 20 energy/second&lt;br /&gt;
&lt;br /&gt;
        -- ── Weapons ──────────────────────────────────────────────────&lt;br /&gt;
        -- Up to 3 weapons can be listed; name references a weapon def&lt;br /&gt;
        weapons = {&lt;br /&gt;
            { def = &amp;quot;COMMANDER_LASER&amp;quot; },&lt;br /&gt;
        },&lt;br /&gt;
&lt;br /&gt;
        -- ── Self-Destruct ─────────────────────────────────────────────&lt;br /&gt;
        selfDExplosion = &amp;quot;commander_sdc&amp;quot;,  -- optional; a weapon def name&lt;br /&gt;
&lt;br /&gt;
        -- ── Misc ─────────────────────────────────────────────────────&lt;br /&gt;
        -- Used in start scripts to assign this unit as Commander&lt;br /&gt;
        commander   = true,&lt;br /&gt;
        -- Sight radius in elmos&lt;br /&gt;
        sightDistance = 660,&lt;br /&gt;
        -- Radar range in elmos (0 = no radar)&lt;br /&gt;
        radarDistance = 0,&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=Key names in unit def files are &#039;&#039;&#039;case-insensitive&#039;&#039;&#039; as far as the engine is concerned, but Lua itself is case-sensitive. Adopt a consistent convention (all lowercase is common) and use &amp;lt;code&amp;gt;lowerkeys()&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;gamedata/unitdefs_post.lua&amp;lt;/code&amp;gt; if you mix styles.}}&lt;br /&gt;
&lt;br /&gt;
=== Notable unit def keys ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Key !! Type !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; || string || Display name in UI. Not the def name (the Lua table key).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;health&amp;lt;/code&amp;gt; || number || Max HP.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;moveDef&amp;lt;/code&amp;gt; || table || Inline move class definition. See engine docs for all fields.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;maxVelocity&amp;lt;/code&amp;gt; || number || Elmos per second. 1 elmo ≈ 8 map height units.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;buildCostMetal&amp;lt;/code&amp;gt; || number || Metal cost. Constructor must have enough resources to start building.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;buildTime&amp;lt;/code&amp;gt; || number || Seconds to build at 1 build power. Actual time = buildTime / workerTime.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;workerTime&amp;lt;/code&amp;gt; || number || Build power this unit contributes when constructing.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;weapons&amp;lt;/code&amp;gt; || table || Up to 3 entries; each entry is a table with a &amp;lt;code&amp;gt;def&amp;lt;/code&amp;gt; key naming a WeaponDef.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;commander&amp;lt;/code&amp;gt; || bool || Marks this unit as a Commander. Lobby UI and engine use this.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;customParams&amp;lt;/code&amp;gt; || table || Arbitrary key/value pairs preserved by the engine for gadget use.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
For the full list of unit def keys and their effects, see the [https://beyond-all-reason.github.io/spring/ldoc/ Recoil Lua API documentation].&lt;br /&gt;
&lt;br /&gt;
=== Def pre- and post-processing ===&lt;br /&gt;
&lt;br /&gt;
The engine reads &amp;lt;code&amp;gt;gamedata/defs.lua&amp;lt;/code&amp;gt; (provided by basecontent) which orchestrates def loading.&lt;br /&gt;
It first executes &amp;lt;code&amp;gt;gamedata/unitdefs_pre.lua&amp;lt;/code&amp;gt; (if present) to populate a &amp;lt;code&amp;gt;Shared&amp;lt;/code&amp;gt; table visible to all unit def files, then loads every &amp;lt;code&amp;gt;units/*.lua&amp;lt;/code&amp;gt; file, and finally runs &amp;lt;code&amp;gt;gamedata/unitdefs_post.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- gamedata/unitdefs_post.lua&lt;br /&gt;
-- Normalize key casing and apply game-wide tweaks.&lt;br /&gt;
&lt;br /&gt;
for _, unitDef in pairs(UnitDefs) do&lt;br /&gt;
    -- lowerkeys() is provided by basecontent; makes all keys lowercase&lt;br /&gt;
    lowerkeys(unitDef)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Example: give all units 10% extra health&lt;br /&gt;
for _, unitDef in pairs(UnitDefs) do&lt;br /&gt;
    if unitDef.health then&lt;br /&gt;
        unitDef.health = unitDef.health * 1.1&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=The &amp;lt;code&amp;gt;UnitDefs&amp;lt;/code&amp;gt; table inside gadgets/widgets is &#039;&#039;&#039;not the same&#039;&#039;&#039; as the one in post-processing. After loading, the engine parses the def tables into internal structures and re-exposes them with different key names and value scales. Never copy-paste keys between def files and wupget code.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 6. Weapon Definitions ==&lt;br /&gt;
&lt;br /&gt;
Weapon defs follow the same pattern as unit defs — Lua files in &amp;lt;code&amp;gt;weapons/&amp;lt;/code&amp;gt; returning tables.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- weapons/laser.lua&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    COMMANDER_LASER = {&lt;br /&gt;
        name        = &amp;quot;Commander Laser&amp;quot;,&lt;br /&gt;
        description = &amp;quot;Short-range direct-fire laser.&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- Weapon category controls which targets it can engage&lt;br /&gt;
        -- Options: ground, air, sea, land, building, etc. (combinable)&lt;br /&gt;
        onlyTargetCategory = &amp;quot;SURFACE&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
        -- ── Visuals ───────────────────────────────────────────────────&lt;br /&gt;
        -- Weapon class drives the physics model&lt;br /&gt;
        -- Options: Cannon, LaserCannon, MissileLauncher, AircraftBomb,&lt;br /&gt;
        --          BeamLaser, LightningCannon, Flame, TorpedoLauncher, ...&lt;br /&gt;
        weaponType    = &amp;quot;LaserCannon&amp;quot;,&lt;br /&gt;
        -- Colour of the beam/tracer (R G B A each 0-255)&lt;br /&gt;
        rgbColor      = &amp;quot;1 0.2 0.2&amp;quot;,&lt;br /&gt;
        -- Colour at the other end of the beam (optional)&lt;br /&gt;
        rgbColor2     = &amp;quot;1 0.9 0.9&amp;quot;,&lt;br /&gt;
        -- Visual length of the laser beam in elmos&lt;br /&gt;
        beamtime      = 1,&lt;br /&gt;
        -- Thickness of the beam in pixels&lt;br /&gt;
        thickness     = 3,&lt;br /&gt;
        corethickness = 0.3,&lt;br /&gt;
        laserflaresize = 8,&lt;br /&gt;
&lt;br /&gt;
        -- ── Ballistics ────────────────────────────────────────────────&lt;br /&gt;
        -- Projectile/beam speed in elmos/second&lt;br /&gt;
        projectilespeed  = 800,&lt;br /&gt;
        -- Range in elmos&lt;br /&gt;
        range            = 350,&lt;br /&gt;
        -- Reload time in seconds&lt;br /&gt;
        reloadtime       = 1.5,&lt;br /&gt;
        -- Burst: number of shots per fire command&lt;br /&gt;
        burst            = 1,&lt;br /&gt;
        -- Spread in degrees (0 = perfectly accurate)&lt;br /&gt;
        accuracy         = 0,&lt;br /&gt;
&lt;br /&gt;
        -- ── Damage ────────────────────────────────────────────────────&lt;br /&gt;
        damage = {&lt;br /&gt;
            -- &#039;default&#039; applies to all armour types not explicitly listed&lt;br /&gt;
            default = 50,&lt;br /&gt;
        },&lt;br /&gt;
        -- Area of effect in elmos (0 = single target)&lt;br /&gt;
        areaOfEffect     = 8,&lt;br /&gt;
        -- Impulse applied to targets on hit (0 = none)&lt;br /&gt;
        impulse          = 0,&lt;br /&gt;
        -- Does this weapon set things on fire?&lt;br /&gt;
        firestarter      = 0,&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Like unit defs, you can post-process weapon defs via &amp;lt;code&amp;gt;gamedata/weapondefs_post.lua&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 7. Feature Definitions ==&lt;br /&gt;
&lt;br /&gt;
Features are non-commandable props: trees, rocks, wrecks, and so on.&lt;br /&gt;
They occupy the map by default via map configuration, or can be spawned by Lua.&lt;br /&gt;
A minimal feature def:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- features/trees.lua&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    tree = {&lt;br /&gt;
        description    = &amp;quot;A tree.&amp;quot;,&lt;br /&gt;
        -- Model to render&lt;br /&gt;
        object         = &amp;quot;tree.dae&amp;quot;,&lt;br /&gt;
        -- Health before the feature is destroyed&lt;br /&gt;
        damage         = 50,&lt;br /&gt;
        -- Metal reclaimed when a constructor takes it apart&lt;br /&gt;
        metal          = 0,&lt;br /&gt;
        -- Energy reclaimed when destroyed or reclaimed&lt;br /&gt;
        energy         = 0,&lt;br /&gt;
        -- Blocks ground unit pathfinding&lt;br /&gt;
        blocking       = true,&lt;br /&gt;
        -- Visible on radar/sonar&lt;br /&gt;
        geoThermal     = false,&lt;br /&gt;
        -- What this feature becomes when destroyed (nil = nothing)&lt;br /&gt;
        deathFeature   = &amp;quot;&amp;quot;,&lt;br /&gt;
        -- Whether this can be reclaimed by a constructor&lt;br /&gt;
        reclaimable    = true,&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 8. Game Rules (modrules.lua) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;gamedata/modrules.lua&amp;lt;/code&amp;gt; is read by basecontent&#039;s &amp;lt;code&amp;gt;defs.lua&amp;lt;/code&amp;gt; to configure fundamental engine behaviour that is not driven by unit/weapon defs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- gamedata/modrules.lua&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
    system = {&lt;br /&gt;
        -- Starting resources (also overridable via ModOptions / start script)&lt;br /&gt;
        startMetal  = 1000,&lt;br /&gt;
        startEnergy = 1000,&lt;br /&gt;
&lt;br /&gt;
        -- Maximum units per team (0 = unlimited)&lt;br /&gt;
        maxUnits    = 1000,&lt;br /&gt;
&lt;br /&gt;
        -- Commander handling: 0 = game continues, 1 = game ends, 2 = lineage&lt;br /&gt;
        gameMode    = 1,&lt;br /&gt;
&lt;br /&gt;
        -- Limit on repair speed (% of max health per second; 0 = unlimited)&lt;br /&gt;
        repairSpeed = 0,&lt;br /&gt;
&lt;br /&gt;
        -- Whether resurrect restores health&lt;br /&gt;
        resurrectHealth = false,&lt;br /&gt;
&lt;br /&gt;
        -- Resource multiplier for reclaiming metal from living units&lt;br /&gt;
        reclaimUnitMethod = 1,&lt;br /&gt;
&lt;br /&gt;
        -- Whether ground features leave wrecks&lt;br /&gt;
        featureDeathResurrect = true,&lt;br /&gt;
    },&lt;br /&gt;
    sound = {&lt;br /&gt;
        -- Optional: override specific engine sound events&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 9. Running Your Game ==&lt;br /&gt;
&lt;br /&gt;
The engine is launched with a &#039;&#039;&#039;start script&#039;&#039;&#039; — a plain text file (conventionally &amp;lt;code&amp;gt;.sdf&amp;lt;/code&amp;gt;) that describes who is playing, which game, and which map.&lt;br /&gt;
&lt;br /&gt;
=== Minimal start script ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ini&amp;quot;&amp;gt;&lt;br /&gt;
[GAME]&lt;br /&gt;
{&lt;br /&gt;
    GameType = My Recoil Game;   -- must match modinfo.lua name or shortName&lt;br /&gt;
    MapName  = my_map.smf;       -- the .smf file inside the map archive&lt;br /&gt;
&lt;br /&gt;
    IsHost   = 1;&lt;br /&gt;
    MyPlayerName = Dev;&lt;br /&gt;
&lt;br /&gt;
    [PLAYER0]&lt;br /&gt;
    {&lt;br /&gt;
        Name      = Dev;&lt;br /&gt;
        Password  = ;&lt;br /&gt;
        Spectator = 0;&lt;br /&gt;
        Team      = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    [TEAM0]&lt;br /&gt;
    {&lt;br /&gt;
        TeamLeader = 0;&lt;br /&gt;
        AllyTeam   = 0;&lt;br /&gt;
        RgbColor   = 0.1 0.4 1.0;&lt;br /&gt;
        Side       = MyFaction;    -- must match a side declared in modinfo.lua&lt;br /&gt;
        StartPosX  = 4000;&lt;br /&gt;
        StartPosZ  = 4000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    [ALLYTEAM0]&lt;br /&gt;
    {&lt;br /&gt;
        NumAllies = 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Save this as &amp;lt;code&amp;gt;game.sdf&amp;lt;/code&amp;gt; in your Recoil data directory, then launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./spring game.sdf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On Windows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spring.exe game.sdf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The engine will:&lt;br /&gt;
# Read the start script&lt;br /&gt;
# Mount your game archive from &amp;lt;code&amp;gt;games/mygame.sdd&amp;lt;/code&amp;gt;&lt;br /&gt;
# Mount the map archive from &amp;lt;code&amp;gt;maps/my_map.sd7&amp;lt;/code&amp;gt;&lt;br /&gt;
# Load basecontent (default widget handlers, etc.)&lt;br /&gt;
# Start the simulation and call into your Lua scripts&lt;br /&gt;
&lt;br /&gt;
{{warning|1=If the engine cannot find your game archive, check that the folder name ends in exactly &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; (case-sensitive on Linux) and that &amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt; is at the root of that folder, &#039;&#039;&#039;not&#039;&#039;&#039; in a subfolder.}}&lt;br /&gt;
&lt;br /&gt;
=== Quick test with headless ===&lt;br /&gt;
&lt;br /&gt;
For automated testing (or when you don&#039;t need graphics), use the headless binary:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./spring_headless game.sdf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Widgets still run, so you can run AI vs AI matches or data-gathering scripts without a window.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 10. Lua Scripting Basics ==&lt;br /&gt;
&lt;br /&gt;
Lua is the scripting language for everything above the C++ engine layer.&lt;br /&gt;
The engine provides several isolated Lua environments:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Environment !! Location !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaRules&#039;&#039;&#039; (synced) || &amp;lt;code&amp;gt;luarules/main.lua&amp;lt;/code&amp;gt; || Authoritative game logic. Desync if misused.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaRules&#039;&#039;&#039; (unsynced) || &amp;lt;code&amp;gt;luarules/draw.lua&amp;lt;/code&amp;gt; || Unsynced effects and UI driven by game logic.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaUI&#039;&#039;&#039; || &amp;lt;code&amp;gt;luaui/main.lua&amp;lt;/code&amp;gt; || Player-facing UI: HUD, minimap overlays, etc.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaGaia&#039;&#039;&#039; || &amp;lt;code&amp;gt;luagaia/main.lua&amp;lt;/code&amp;gt; || Logic for the neutral Gaia team (wrecks, features, world events).&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaIntro&#039;&#039;&#039; || &amp;lt;code&amp;gt;luaintro/main.lua&amp;lt;/code&amp;gt; || Loading screen, briefings.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;LuaMenu&#039;&#039;&#039; || &amp;lt;code&amp;gt;luamenu/main.lua&amp;lt;/code&amp;gt; || Main menu (pre-game). Requires a menu archive.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Basecontent provides default &amp;lt;code&amp;gt;main.lua&amp;lt;/code&amp;gt; entry points and wupget handler boilerplate so you can start writing gadgets and widgets without building the handler yourself.&lt;br /&gt;
&lt;br /&gt;
=== Your first gadget ===&lt;br /&gt;
&lt;br /&gt;
A gadget is a Lua file the gadget handler loads from &amp;lt;code&amp;gt;luarules/gadgets/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- luarules/gadgets/game_end.lua&lt;br /&gt;
-- Ends the game when a player&#039;s Commander is destroyed.&lt;br /&gt;
&lt;br /&gt;
local gadget = gadget ---@type Gadget&lt;br /&gt;
&lt;br /&gt;
function gadget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name    = &amp;quot;Game End Conditions&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Ends the game on Commander death.&amp;quot;,&lt;br /&gt;
        author  = &amp;quot;YourName&amp;quot;,&lt;br /&gt;
        date    = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer   = 0,&lt;br /&gt;
        enabled = true,&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Only run the logic in synced mode&lt;br /&gt;
if not gadgetHandler:IsSyncedCode() then return end&lt;br /&gt;
&lt;br /&gt;
-- Track alive Commanders per team&lt;br /&gt;
local commanderByTeam = {}&lt;br /&gt;
&lt;br /&gt;
function gadget:UnitFinished(unitID, unitDefID, teamID)&lt;br /&gt;
    local def = UnitDefs[unitDefID]&lt;br /&gt;
    -- UnitDefs inside wupgets is indexed by numeric ID, not name&lt;br /&gt;
    if def and def.isCommander then&lt;br /&gt;
        commanderByTeam[teamID] = unitID&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function gadget:UnitDestroyed(unitID, unitDefID, teamID)&lt;br /&gt;
    if commanderByTeam[teamID] == unitID then&lt;br /&gt;
        commanderByTeam[teamID] = nil&lt;br /&gt;
        -- Check whether any other living Commander still belongs to this allyteam&lt;br /&gt;
        local allyTeamID = Spring.GetTeamAllyTeamID(teamID)&lt;br /&gt;
        local hasCommander = false&lt;br /&gt;
        for _, ally in ipairs(Spring.GetTeamList(allyTeamID)) do&lt;br /&gt;
            if commanderByTeam[ally] then&lt;br /&gt;
                hasCommander = true&lt;br /&gt;
                break&lt;br /&gt;
            end&lt;br /&gt;
        end&lt;br /&gt;
        if not hasCommander then&lt;br /&gt;
            -- GameOver sends the end-of-game event to all clients&lt;br /&gt;
            Spring.GameOver({ allyTeamID })&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Your first widget ===&lt;br /&gt;
&lt;br /&gt;
A widget is a Lua file the widget handler loads from &amp;lt;code&amp;gt;luaui/widgets/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- luaui/widgets/resources_display.lua&lt;br /&gt;
-- Draws current metal and energy income in the corner of the screen.&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name    = &amp;quot;Resources Display&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Shows current resource income.&amp;quot;,&lt;br /&gt;
        author  = &amp;quot;YourName&amp;quot;,&lt;br /&gt;
        date    = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer   = 0,&lt;br /&gt;
        enabled = true,&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function widget:DrawScreen()&lt;br /&gt;
    local metal, energy = Spring.GetTeamResources(Spring.GetMyTeamID())&lt;br /&gt;
    if not metal then return end&lt;br /&gt;
&lt;br /&gt;
    gl.Color(1, 1, 1, 1)&lt;br /&gt;
    gl.Text(string.format(&amp;quot;Metal: %.0f  Energy: %.0f&amp;quot;, metal, energy),&lt;br /&gt;
            10, 40, 16, &amp;quot;s&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Callins and callouts ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Callins&#039;&#039;&#039; are functions in your code the engine calls at the right moment: &amp;lt;code&amp;gt;UnitCreated&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;UnitDestroyed&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GameOver&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;DrawScreen&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Update&amp;lt;/code&amp;gt;, etc.&lt;br /&gt;
Check the full list at runtime with &amp;lt;code&amp;gt;Script.GetCallInList()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Callouts&#039;&#039;&#039; are &amp;lt;code&amp;gt;Spring.*&amp;lt;/code&amp;gt; functions you call out to the engine: &amp;lt;code&amp;gt;Spring.GetTeamResources&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.GiveOrderToUnit&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring.Echo&amp;lt;/code&amp;gt;, etc.&lt;br /&gt;
Full documentation is at [https://beyond-all-reason.github.io/spring/ldoc/ beyond-all-reason.github.io/spring/ldoc/].&lt;br /&gt;
&lt;br /&gt;
{{warning|1=Synced gadgets have strict rules — only call synced-safe functions. Calling unsynced functions from synced code causes a desync error. When in doubt, check &amp;lt;code&amp;gt;gadgetHandler:IsSyncedCode()&amp;lt;/code&amp;gt; and gate appropriately.}}&lt;br /&gt;
&lt;br /&gt;
=== Communicating between environments ===&lt;br /&gt;
&lt;br /&gt;
The Lua environments are isolated from each other by design.&lt;br /&gt;
Common bridges:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Direction !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;WG&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;GG&amp;lt;/code&amp;gt; table || Within one environment || Widgets share &amp;lt;code&amp;gt;WG&amp;lt;/code&amp;gt;; gadgets share &amp;lt;code&amp;gt;GG&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;SendToUnsynced&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;RecvFromSynced&amp;lt;/code&amp;gt; || Synced → Unsynced || Pass data from game logic to rendering/UI.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Spring.SendLuaRulesMsg&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;gadget:RecvLuaMsg&amp;lt;/code&amp;gt; || LuaUI → LuaRules || UI triggers a game action beyond unit orders.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Spring.SendLuaUIMsg&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;widget:RecvLuaMsg&amp;lt;/code&amp;gt; || LuaUI ↔ LuaUI (all players) || Broadcast UI state to other players.&lt;br /&gt;
|-&lt;br /&gt;
| Rules params (&amp;lt;code&amp;gt;Spring.SetRulesParam&amp;lt;/code&amp;gt;) || LuaRules → all || Expose synced data as read-only key/values.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
See [[Recoil:Wupget Communication]] for detailed examples.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 11. Useful Debug Commands ==&lt;br /&gt;
&lt;br /&gt;
In-game chat commands prefixed with &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt; control engine and game state.&lt;br /&gt;
With cheats enabled (&amp;lt;code&amp;gt;/cheat&amp;lt;/code&amp;gt;) additional commands become available:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Command !! Effect&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/cheat&amp;lt;/code&amp;gt; || Toggle cheats on/off. Required for most debug commands.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/give [unitname]&amp;lt;/code&amp;gt; || Spawn a unit at the cursor position.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/destroy&amp;lt;/code&amp;gt; || Destroy selected units immediately.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/invincible&amp;lt;/code&amp;gt; || Toggle invincibility for selected units.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/godmode&amp;lt;/code&amp;gt; || Toggle god mode (all units obey all players).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/speed [x]&amp;lt;/code&amp;gt; || Set game speed multiplier (e.g. &amp;lt;code&amp;gt;/speed 5&amp;lt;/code&amp;gt;).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/nopause&amp;lt;/code&amp;gt; || Disallow pausing.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/luarules reload&amp;lt;/code&amp;gt; || Reload LuaRules scripts without restarting.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/luaui reload&amp;lt;/code&amp;gt; || Reload LuaUI scripts without restarting.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;/editdefs&amp;lt;/code&amp;gt; || Enter def-editing mode (requires cheats). See unit def docs.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Spring.Echo(&amp;quot;msg&amp;quot;)&amp;lt;/code&amp;gt; || Print a message to the in-game console from Lua.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 12. Maps and Map Integration ==&lt;br /&gt;
&lt;br /&gt;
Maps are separate archives (&amp;lt;code&amp;gt;.sd7&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;.sdz&amp;lt;/code&amp;gt;) placed in &amp;lt;code&amp;gt;maps/&amp;lt;/code&amp;gt;.&lt;br /&gt;
They are not shipped with your game.&lt;br /&gt;
The map defines the terrain height data (&amp;lt;code&amp;gt;.smf&amp;lt;/code&amp;gt;), sky, water, and can optionally define starting metal spots and features.&lt;br /&gt;
&lt;br /&gt;
=== Controlling what maps do to your game ===&lt;br /&gt;
&lt;br /&gt;
Maps can include a &amp;lt;code&amp;gt;mapinfo.lua&amp;lt;/code&amp;gt; which may set atmosphere, gravity, and other properties.&lt;br /&gt;
A map can also override unit def values via &amp;lt;code&amp;gt;MapOptions.lua&amp;lt;/code&amp;gt;, which your game can read with &amp;lt;code&amp;gt;Spring.GetMapOptions()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
You can &#039;&#039;&#039;restrict which maps are compatible&#039;&#039;&#039; by checking map properties in a gadget at startup and calling &amp;lt;code&amp;gt;Spring.GameOver&amp;lt;/code&amp;gt; if requirements are not met.&lt;br /&gt;
&lt;br /&gt;
=== Metal spots ===&lt;br /&gt;
&lt;br /&gt;
Metal spots (mexes) are typically defined in the map as SMF/SMD metal map data, or via a Lua file in the map archive.&lt;br /&gt;
Your gadgets can read them with &amp;lt;code&amp;gt;Spring.GetMetalMapSize()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Spring.ExtractorInRangeMetalMap()&amp;lt;/code&amp;gt;.&lt;br /&gt;
Extractor units that generate metal must have a &amp;lt;code&amp;gt;extractsMetal&amp;lt;/code&amp;gt; unit def key &amp;gt; 0 and be placed over a metal spot.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 13. Packaging and Distribution ==&lt;br /&gt;
&lt;br /&gt;
When your game is ready for distribution, compress the &amp;lt;code&amp;gt;.sdd&amp;lt;/code&amp;gt; folder contents into an &amp;lt;code&amp;gt;.sdz&amp;lt;/code&amp;gt; (zip) or &amp;lt;code&amp;gt;.sd7&amp;lt;/code&amp;gt; (7-zip) file.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Linux: create .sdz (zip)&lt;br /&gt;
cd games/mygame.sdd&lt;br /&gt;
zip -r ../mygame-0.1.0.sdz .&lt;br /&gt;
&lt;br /&gt;
# Linux: create .sd7 (7-zip, better compression)&lt;br /&gt;
cd games/mygame.sdd&lt;br /&gt;
7z a ../mygame-0.1.0.sd7 .&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning|1=Make sure &amp;lt;code&amp;gt;modinfo.lua&amp;lt;/code&amp;gt; ends up at the &#039;&#039;&#039;root&#039;&#039;&#039; of the archive, not inside a subfolder. Some zip tools create an extra folder level automatically.}}&lt;br /&gt;
&lt;br /&gt;
=== Rapid ===&lt;br /&gt;
&lt;br /&gt;
[https://github.com/beyond-all-reason/rapid Rapid] is the standard package distribution system for Recoil games.&lt;br /&gt;
It allows lobby clients (e.g. Chobby) to download and update games automatically.&lt;br /&gt;
See the Rapid documentation and the [https://github.com/beyond-all-reason/RecoilEngine/blob/master/doc/site/content/articles/start-script-format.md start script format] for integration details.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 14. Next Steps ==&lt;br /&gt;
&lt;br /&gt;
With a working minimal game, the typical next steps are:&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;More unit types&#039;&#039;&#039;&lt;br /&gt;
: Add builders, resource extractors, power generators, turrets, and mobile combat units. Follow the same unit def pattern — each is a Lua file in &amp;lt;code&amp;gt;units/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Resource extractors&#039;&#039;&#039;&lt;br /&gt;
: A unit with &amp;lt;code&amp;gt;extractsMetal &amp;gt; 0&amp;lt;/code&amp;gt; placed over a metal spot generates metal. Add an energy converter unit to turn metal into energy and vice versa.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Pathfinding and MoveClasses&#039;&#039;&#039;&lt;br /&gt;
: Define &amp;lt;code&amp;gt;movedefs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;gamedata/movedefs.lua&amp;lt;/code&amp;gt; to control how different unit types traverse terrain (hover, boat, amphibious, kbot, tank, aircraft).&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;AI support&#039;&#039;&#039;&lt;br /&gt;
: The Skirmish AI interface lets players add bot opponents without any extra gadget work. Beyond that, you can write a [[https://springrts.com/wiki/Lua_AI Lua AI]] for fully custom bot behaviour inside your game&#039;s Lua scripting.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Full UI&#039;&#039;&#039;&lt;br /&gt;
: The basecontent widget handler gives you a basic HUD for free. For a polished interface use [[Recoil:RmlUI Starter Guide|RmlUI]] for HTML/CSS-style widgets, or raw OpenGL via &amp;lt;code&amp;gt;gl.*&amp;lt;/code&amp;gt; callouts.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Maps&#039;&#039;&#039;&lt;br /&gt;
: Creating your own map requires tools like [https://github.com/beyond-all-reason/MapConv MapConv] (height and texture compilation) and [https://springrts.com/wiki/SpringMapEdit SpringMapEdit]. See the Recoil map documentation for details.&lt;br /&gt;
&lt;br /&gt;
; &#039;&#039;&#039;Replays and dedicated servers&#039;&#039;&#039;&lt;br /&gt;
: Replays are recorded automatically when &amp;lt;code&amp;gt;RecordDemo=1&amp;lt;/code&amp;gt; is set in the start script. For large-scale multiplayer use the dedicated binary (&amp;lt;code&amp;gt;spring_dedicated&amp;lt;/code&amp;gt;) and an autohost such as [https://springrts.com/wiki/SPADS SPADS] or [https://springrts.com/wiki/Springie Springie].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 15. Further Reading ==&lt;br /&gt;
&lt;br /&gt;
=== On this wiki ===&lt;br /&gt;
&lt;br /&gt;
* [[Recoil:VFS Basics]] — full documentation of the Virtual File System, modes, and load order&lt;br /&gt;
* [[Recoil:Unit Types Basics]] — deep dive into unit defs, post-processing, and the wupget-facing UnitDefs table&lt;br /&gt;
* [[Recoil:Widgets and Gadgets]] — the addon (wupget) system in full detail&lt;br /&gt;
* [[Recoil:Wupget Communication]] — how to pass data between Lua environments&lt;br /&gt;
* [[Recoil:RmlUI Starter Guide]] — reactive HTML/CSS-style UI for your game&lt;br /&gt;
* [[Recoil:Headless and Dedicated]] — server binaries and automated testing&lt;br /&gt;
* [[Recoil:Migrating From Spring]] — for existing Spring 105 games&lt;br /&gt;
&lt;br /&gt;
=== External resources ===&lt;br /&gt;
&lt;br /&gt;
* [https://beyond-all-reason.github.io/spring/ldoc/ Recoil Lua API reference] — complete callout/callin documentation&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine RecoilEngine on GitHub] — source, releases, and issue tracker&lt;br /&gt;
* [https://recoilengine.org/ recoilengine.org] — official website with downloads and changelog&lt;br /&gt;
* [https://discord.gg/recoil Recoil Discord] — community support and engine developer discussions&lt;br /&gt;
* [https://springrts.com/wiki/Simple_Game_Tutorial Spring Simple Game Tutorial] — older but still broadly applicable tutorial (Spring and Recoil are highly compatible)&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:Recoil]]&lt;br /&gt;
[[Category:Game Development]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil&amp;diff=2702</id>
		<title>Recoil</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil&amp;diff=2702"/>
		<updated>2026-03-06T06:01:55Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;h2 data-notoc=&amp;quot;&amp;quot;&amp;gt;Recoil is a battle tested open-source RTS engine that, allied with a flexible Lua API, allows you to implement the perfect UI and mechanics for your game with the ability to support thousands of complex units simultaneously.&amp;lt;/h2&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=Recoil is a recent hard fork of [Spring](https://github.com/spring/spring) from the [105 tree](https://github.com/spring/spring/releases/tag/105.0.1), many references to it might and will be present. Overall most documented Spring API and tutorials are compatible with Recoil since they are based on the 105 tree.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
--&amp;gt;{|border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;4&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
| width=&amp;quot;50%&amp;quot; valign=&amp;quot;top&amp;quot;  |&lt;br /&gt;
== For Players ==&lt;br /&gt;
&lt;br /&gt;
=== Content ===&lt;br /&gt;
&#039;&#039;&#039;[[Recoil Games]]&#039;&#039;&#039; Games that use the Recoil engine.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Maps]]&#039;&#039;&#039; All about maps for the Spring engine and where to get them.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Getting Recoil Content]]&#039;&#039;&#039; Sites from which you can download spring Games, Maps, and more.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[AI:Skirmish|AIs]]&#039;&#039;&#039; Skirmish AIs for Singleplayer.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lua Widgets]]&#039;&#039;&#039; All about installing and using LuaUI widgets.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Recoil:Replays|Replays]]&#039;&#039;&#039; Watching recordings of previously played matches.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Community:Recoil| Community]]&#039;&#039;&#039; Documents that shape our online community.&lt;br /&gt;
&lt;br /&gt;
=== Help ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[FAQ]]&#039;&#039;&#039; Has it been asked before?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Hosting Recoil]]&#039;&#039;&#039; Autohosting and related topics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Spring_on_MacOSX |Mac OS]]&#039;&#039;&#039; Mac OSX information.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[SetupGuide |Linux]]&#039;&#039;&#039; NetBSD / Solaris / Linux / Cross-platform information.&lt;br /&gt;
&lt;br /&gt;
| width=&amp;quot;50%&amp;quot; valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
== For Developers ==&lt;br /&gt;
&lt;br /&gt;
=== Content Development ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Gamedev:Main|Game Development]]&#039;&#039;&#039; Lots of links to valuable Articles and forum threads regarding Game/Unit Development.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Mapdev:Main|Map Development]]&#039;&#039;&#039; Tutorials and other useful information for creating maps.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lua Scripting]]&#039;&#039;&#039; Information regarding the use of the supplementary Lua scripting language for Widgets and beyond.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Chili|Chili UI framework]]&#039;&#039;&#039; Framework for creating GUIs in Lua.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Map, Mod, And Unit Development Programs|Content Development Programs]]&#039;&#039;&#039; Links to all kind of useful programs.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[General Resources]]&#039;&#039;&#039; Usable textures, sounds and code snippets by the community, for the community.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Associated Development Groups]]&#039;&#039;&#039; Teams, Forums and Groups associated with Spring content and code development.&lt;br /&gt;
&lt;br /&gt;
=== Engine and Native Development ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Development:Getting_Started|Engine Development]]&#039;&#039;&#039; Starting point for engine development.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Building spring|Compiling Engine]]&#039;&#039;&#039; How to build the Spring engine from source.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[Lobby_Development|Lobby Development (Clients and Servers)]]&#039;&#039;&#039; Information regarding contributing to the source code of the different lobby clients and servers available for Spring.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[[AI:Development|AI Development]]&#039;&#039;&#039; Lots of information about developing Skirmish, and Unit AIs for Spring.&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{||&lt;br /&gt;
|&lt;br /&gt;
=This wiki needs your help!=&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&amp;quot;A wiki is a web application that allows users to add content, as on an Internet forum, but also allows anyone to edit the content.&amp;quot;&#039;&#039;&lt;br /&gt;
- [http://en.wikipedia.org/wiki/Wiki Wikipedia.org]&lt;br /&gt;
&lt;br /&gt;
The Spring project is very dependent on its community of users to help new players and developers. [[FightOrderEditing|Learn more here.]]&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| |&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
 __NOTOC__&lt;br /&gt;
__NOEDITSECTION__&lt;br /&gt;
[[Category:Spring]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Hosting_Spring/Springie&amp;diff=2701</id>
		<title>Hosting Spring/Springie</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Hosting_Spring/Springie&amp;diff=2701"/>
		<updated>2026-03-06T05:55:27Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;Springie is an Autohost for Spring, it creates a battleroom and starts automatically when enough people have joined. It also reacts to a number of chat commands.  ==Requirements== *Spring *Open UDP Port *Springie  ==Using== Springie is easy to use but you will need an open UDP port to be able to host a game. You can download it using the link at the end of the page, no install is required.  ===Creating=== After opening an UDP port, you need to create a new account fo...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Springie]] is an Autohost for Spring, it creates a battleroom and starts automatically when enough people have joined. It also reacts to a number of chat commands.&lt;br /&gt;
&lt;br /&gt;
==Requirements==&lt;br /&gt;
*Spring&lt;br /&gt;
*Open UDP Port&lt;br /&gt;
*Springie&lt;br /&gt;
&lt;br /&gt;
==Using==&lt;br /&gt;
Springie is easy to use but you will need an open UDP port to be able to host a game. You can download it using the link at the end of the page, no install is required.&lt;br /&gt;
&lt;br /&gt;
===Creating===&lt;br /&gt;
After opening an UDP port, you need to create a new account for the bot and ask a GO (Game Operator) to put a bot flag on it. Now you only need to log in with Springie and configure the map and other settings!&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Note:&#039;&#039;&#039; Spring changes Spring settings to 40 x 40 pixels and windowed mode, so if you want to play you will need to reconfigure it.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===Commands===&lt;br /&gt;
&#039;&#039;&#039;HELP&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
*!help - lists the commands that are available to you&lt;br /&gt;
*!helpall - lists all commands (sorted by their level) - you may not be able to use all of them&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;COMMUNICATION&#039;&#039;&#039;&lt;br /&gt;
*!ring [&amp;lt;filters&amp;gt;..]- rings either all unready or specified users&lt;br /&gt;
*!say - allows you to talk between lobby and game (not needed if chat is enabled)&lt;br /&gt;
*!notify - Springie will notify you when game ends&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;TEAM AND ALLY MANAGEMENT&#039;&#039;&#039;&lt;br /&gt;
*!fix - fixes teamnumbers&lt;br /&gt;
*!fixcolors - fixes team colors (similar colors are changed)&lt;br /&gt;
*!random [&amp;lt;allycount&amp;gt;] - assigns people to &amp;lt;allycount&amp;gt; alliances (default: 2 alliances)&lt;br /&gt;
*!balance [&amp;lt;allycount&amp;gt;] - scans all ally combinations and randomly chose one which is balanced (eg regarding ranks) (warning: not tested for large teams, might take a long time)&lt;br /&gt;
*!cbalance [&amp;lt;allycount&amp;gt;] - assigns people to allycount random balanced alliances, attempting to put clanmates on the same alliance&lt;br /&gt;
*!ally &amp;lt;allynum&amp;gt; [&amp;lt;player&amp;gt;..] - forces ally number&lt;br /&gt;
*!team &amp;lt;teamnum&amp;gt; [&amp;lt;player&amp;gt;..] - forces team number&lt;br /&gt;
*!spec &amp;lt;username&amp;gt; - forces a player to become spectator&lt;br /&gt;
*!specafk - forces all AFK players to become spectators&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;GAME CONTROL&#039;&#039;&#039;&lt;br /&gt;
*!start - starts game (checks for duplicate team numbers and ally fairness)&lt;br /&gt;
*!forcestart - overrides ordinary !start checks (duplicate team numbers and ally fairness)&lt;br /&gt;
*!force - makes spring force start (ctrl+enter in-game)&lt;br /&gt;
*!exit - exits the game&lt;br /&gt;
*!manage &amp;lt;minplayer&amp;gt; [&amp;lt;maxplayers&amp;gt;] - automanage game for min to max players. If minplayers is 0, it won&#039;t manage. In this mode, Springie keeps alliances even, colors fixed, autorings and spec afk or even kicks them.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;VOTING&#039;&#039;&#039;&lt;br /&gt;
*!voteforcestart - starts vote to execute !forcestart&lt;br /&gt;
*!voteforce - starts vote to force (in-game)&lt;br /&gt;
*!voteexit - starts vote to exit game&lt;br /&gt;
*!votekick [&amp;lt;filters&amp;gt;] - starts vote to kick player&lt;br /&gt;
*!votemap [&amp;lt;filters&amp;gt;] - starts vote for given map&lt;br /&gt;
*!voterehost [&amp;lt;filter&amp;gt;] - starts vote to rehost the game&lt;br /&gt;
*!votepreset [&amp;lt;presetname&amp;gt;..] - starts a vote to apply the given preset&lt;br /&gt;
*!voteboss &amp;lt;name&amp;gt; - sets &amp;lt;name&amp;gt; as a new boss, use without parameter to remove any current boss. If there is a boss on server, other non-admin people have their rights reduced&lt;br /&gt;
*!votekickspec - starts a vote to enables or disable automatic spectator kicking&lt;br /&gt;
*!votesetoptions &amp;lt;name&amp;gt;=&amp;lt;value&amp;gt;[,&amp;lt;name&amp;gt;=&amp;lt;value&amp;gt;] - starts a vote to apply the given option(s)&lt;br /&gt;
*!vote &amp;lt;number&amp;gt; - casts your vote (you must say it in battle window)&lt;br /&gt;
*!endvote - ends current poll&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BATTLE LOCKING&#039;&#039;&#039;&lt;br /&gt;
*!lock - locks battle&lt;br /&gt;
*!unlock - unlocks battle&lt;br /&gt;
*!autolock [&amp;lt;players&amp;gt;] - sets desired number of players in game. If this number is reached, Springie will lock the server, and if someone leaves, it will unlock it again. !autolock without parameter disables auto locking.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PRESETS&#039;&#039;&#039;&lt;br /&gt;
*!listpresets [&amp;lt;presetname&amp;gt;..] - lists all the known presets (with name filtering)&lt;br /&gt;
*!presetdetails [&amp;lt;presetname&amp;gt;..] - shows the details of the given preset&lt;br /&gt;
*!preset [&amp;lt;presetname&amp;gt;..] - applies the given preset to current battle&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BOXES&#039;&#039;&#039;&lt;br /&gt;
*!addbox &amp;lt;left&amp;gt; &amp;lt;top&amp;gt; &amp;lt;width&amp;gt; &amp;lt;height&amp;gt; [&amp;lt;number&amp;gt;] - adds a new box rectangle&lt;br /&gt;
*!clearbox [&amp;lt;number&amp;gt;] - removes a box (or removes all boxes if no number is specified)&lt;br /&gt;
*!split &amp;lt;&amp;quot;h&amp;quot;/&amp;quot;v&amp;quot;&amp;gt; &amp;lt;percent&amp;gt; - makes 2 boxes in h or v direction&lt;br /&gt;
*!corners &amp;lt;&amp;quot;a&amp;quot;/&amp;quot;b&amp;quot;&amp;gt; &amp;lt;percent&amp;gt; - makes 4 corners, a/b determines ordering&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;MAPS&#039;&#039;&#039;&lt;br /&gt;
*!listmaps [&amp;lt;filters&amp;gt;] - lists maps (can apply filtering)&lt;br /&gt;
*!map &amp;lt;filters&amp;gt; - changes map, and prints the new maplink in the battle&lt;br /&gt;
*!maplink - looks for map links on unknown files &lt;br /&gt;
*!dlmap &amp;lt;mapname/dllid/url&amp;gt; - downloads map to server. You can either specify map name or map id (from unknown files) or map URL&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;MODS&#039;&#039;&#039;&lt;br /&gt;
*!listmods [&amp;lt;filters&amp;gt;] - lists mods (can apply filtering)&lt;br /&gt;
*!modlink - looks for mod links on unknown files &lt;br /&gt;
*!dlmod &amp;lt;modname/dllid/url&amp;gt; - downloads mod to server. You can either specify mod name or mod id (from unknown files) or mod URL&lt;br /&gt;
*!rehost [&amp;lt;filter&amp;gt;] - rehosts game (optionally with new mod)&lt;br /&gt;
*!listoptions - lists all mod options&lt;br /&gt;
*!setoptions &amp;lt;name&amp;gt;=&amp;lt;value&amp;gt;[,&amp;lt;name&amp;gt;=&amp;lt;value&amp;gt;] - applies mod/map options&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ADMINISTERING&#039;&#039;&#039; &lt;br /&gt;
*!admins - list privileged users&lt;br /&gt;
*!boss &amp;lt;name&amp;gt; - sets &amp;lt;name&amp;gt; as a new boss, use without parameter to remove any current boss. If there is a boss on server, other non-admin people have their rights reduced.&lt;br /&gt;
*!kick &amp;lt;filters&amp;gt; - kicks specified players from server (both game and lobby)&lt;br /&gt;
*!setlevel &amp;lt;username&amp;gt; &amp;lt;level&amp;gt; - sets rights level for given player &lt;br /&gt;
*!ban &amp;lt;username&amp;gt; [&amp;lt;duration&amp;gt;] [&amp;lt;reason&amp;gt;...] - bans user username for duration (in minutes) with given reason. Duration 0 = ban for 1000 years&lt;br /&gt;
*!unban &amp;lt;username&amp;gt; - unbans user&lt;br /&gt;
*!listbans - lists currently banned users&lt;br /&gt;
*!reload - reloads mod and map list&lt;br /&gt;
*!kickspec [0|1] - enables or disables automatic spectator kicking&lt;br /&gt;
*!mincpuspeed &amp;lt;GHz&amp;gt; - sets minimum CPU for this host - players with CPU speed below this value are auto-kicked, 0 = no limit&lt;br /&gt;
*!setpassword &amp;lt;newpassword&amp;gt; - sets server password (needs !rehost to apply)&lt;br /&gt;
*!setminrank &amp;lt;minrank&amp;gt; - sets server minimum rank (needs !rehost to apply)&lt;br /&gt;
*!setmaxplayers &amp;lt;maxplayers&amp;gt; - sets server size (needs !rehost to apply)&lt;br /&gt;
*!setgametitle &amp;lt;new title&amp;gt; - sets server game title (needs !rehost to apply)&lt;br /&gt;
*!springie - displays basic springie information&lt;br /&gt;
*!cheats - enables/disables .cheats in game&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;STATISTICS&#039;&#039;&#039;&lt;br /&gt;
*!stats - lists various stats&lt;br /&gt;
*!smurfs - lists smurfs&lt;br /&gt;
&lt;br /&gt;
==Links==&lt;br /&gt;
*[http://spring.clan-sy.com/phpbb/viewtopic.php?t=7921 Springie Topic]&lt;br /&gt;
&lt;br /&gt;
[[Category: Autohosts]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Hosting_Spring/Autohost&amp;diff=2700</id>
		<title>Hosting Spring/Autohost</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Hosting_Spring/Autohost&amp;diff=2700"/>
		<updated>2026-03-06T05:55:08Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Redirected page to Hosting Spring/Springie&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Hosting Spring/Springie]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Ubuntu_install&amp;diff=2699</id>
		<title>Ubuntu install</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Ubuntu_install&amp;diff=2699"/>
		<updated>2026-03-06T05:54:06Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;Linux Setup Guide &amp;lt; {{FULLPAGENAME}}  __TOC__ Image:Ubuntulogo.png  packages are available for: 12.10, 12.04, 11.04, 10.10, 10.04, 9.10, 9.04, 8.10  For packaging &amp;lt;font color=&amp;quot;#00DD00&amp;quot;&amp;gt;requests&amp;lt;/font&amp;gt; or &amp;lt;font color=&amp;quot;#FF0000&amp;quot;&amp;gt;complaints&amp;lt;/font&amp;gt;, please contact  the packagers.  = Ubuntu 9.10 and newer =  10.04 &amp;amp; 10.10 have propretary drivers for ATI &amp;amp; Nvidia GPU&amp;#039;s in the menu aplication / system / additionnal drivers (they are not...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Linux|Linux Setup Guide]] &amp;lt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
[[Image:Ubuntulogo.png]]&lt;br /&gt;
&lt;br /&gt;
packages are available for: 12.10, 12.04, 11.04, 10.10, 10.04, 9.10, 9.04, 8.10&lt;br /&gt;
&lt;br /&gt;
For packaging &amp;lt;font color=&amp;quot;#00DD00&amp;quot;&amp;gt;requests&amp;lt;/font&amp;gt; or &amp;lt;font color=&amp;quot;#FF0000&amp;quot;&amp;gt;complaints&amp;lt;/font&amp;gt;, please contact [[Linux:Packaging#Ubuntu | the packagers]].&lt;br /&gt;
&lt;br /&gt;
= Ubuntu 9.10 and newer =&lt;br /&gt;
&lt;br /&gt;
10.04 &amp;amp; 10.10 have propretary drivers for ATI &amp;amp; Nvidia GPU&#039;s in the menu aplication / system / additionnal drivers (they are not free but mandatory if spring is very slow and the ground of the map ingame is full white );&lt;br /&gt;
ATI catalyst config panel ask password for be edited but always get a bad password error for open it it need to open console and write :&amp;quot; sudo amdcccle &amp;quot;then that will ask the password and accept it&lt;br /&gt;
&lt;br /&gt;
== Installation Instructions ==&lt;br /&gt;
Go to &#039;&#039;Applications - Ubuntu Software Center&#039;&#039;, search for &#039;&#039;springlobby&#039;&#039; and click go.&lt;br /&gt;
If you want to be able to play online, see [[#Updating to newer releases|the next section]].&lt;br /&gt;
&lt;br /&gt;
== Updating to newer releases ==&lt;br /&gt;
Go to &#039;&#039;System - Administration - Software Sources&#039;&#039;, click the Other tab, click &#039;&#039;Add&#039;&#039;.  Type &#039;&#039;&#039;ppa:spring/ppa&#039;&#039;&#039; and close the window. Allow it to reload the packages, and you will find the new version of Spring and Spring Lobby in update manager.&lt;br /&gt;
&lt;br /&gt;
If you&#039;re running Ubuntu 10.10, see &amp;quot;Missing repositories in Ubuntu 10.10 on this help page for software sources: https://help.ubuntu.com/community/Repositories/Ubuntu.&lt;br /&gt;
&lt;br /&gt;
= Ubuntu 9.04 and 8.10 =&lt;br /&gt;
We now have a maintained and up to date APT repository for Ubuntu 9.04 (Jaunty) and 8.10 (Intrepid). If you have some older version of Ubuntu, you can easily upgrade with these instructions: http://www.ubuntu.com/getubuntu/upgrading.&lt;br /&gt;
&lt;br /&gt;
You will need to add the Spring PPA below and install the &#039;&#039;springlobby&#039;&#039; package.  Or you can upgrade to Ubuntu 9.10 and just install the &#039;&#039;springlobby&#039;&#039; package from the new Software Center.&lt;br /&gt;
&lt;br /&gt;
== Installing the repository key ==&lt;br /&gt;
First, we need to add the Launchpad PPA package-signing key; this allows Ubuntu to know the packages have been provided by the right people can can be trusted to be installed. If you skip this step then Ubuntu will complain about an unverifiable package every time there&#039;s an update for Spring.&lt;br /&gt;
&lt;br /&gt;
Go to:&lt;br /&gt;
[http://keyserver.ubuntu.com:11371/pks/lookup?op=get&amp;amp;search=0xFC66403D8670A035]&lt;br /&gt;
&lt;br /&gt;
Cut and paste the page contents to a file called &#039;&#039;ppa-spring.pgp&#039;&#039; or something recognizable like that. Do not use &#039;&#039;Save As&#039;&#039; in your browser, as this will likely not work because of added HTML formatting. You can use &#039;&#039;Applications - Accessories - Text Editor&#039;&#039; to copy&amp;amp;paste into and save to disk.&lt;br /&gt;
&lt;br /&gt;
Security-conscious people can go to the Spring Developers PPA page at [https://launchpad.net/~spring/+archive/ppa launchpad.net] to verify that the key mentioned above is indeed the right one to trust. &lt;br /&gt;
&lt;br /&gt;
Now go to &#039;&#039;System - Administration - Software Sources&#039;&#039; and click on the &#039;&#039;Authentication&#039;&#039; tab to add the saved &#039;&#039;ppa-spring.pgp&#039;&#039; with the &#039;&#039;Import Key File&#039;&#039; button. The new key should now pop up in the list.&lt;br /&gt;
&lt;br /&gt;
== Installing the repository ==&lt;br /&gt;
In &#039;&#039;System - Administration - Software Sources&#039;&#039;, click on the &#039;&#039;Third-Party Software&#039;&#039; tab. Then, click the &#039;&#039;Add&#039;&#039; button, and paste one of the two lines corresponding to your Ubuntu release (9.04 or 8.10).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ubuntu 9.04 &amp;quot;Jaunty Jackalope&amp;quot;&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
deb http://ppa.launchpad.net/spring/ubuntu jaunty main&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ubuntu 8.10 &amp;quot;Intrepid Ibex&amp;quot;&#039;&#039;&#039;:&lt;br /&gt;
Note: Please note if out of date. (It i easy enough to try.)&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
deb http://ppa.launchpad.net/spring/ubuntu intrepid main&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Close the dialog, and click on &#039;&#039;Reload&#039;&#039; when it asks.&lt;br /&gt;
&lt;br /&gt;
After that is done, you can just [http://springoo.mooo.com CLICK HERE TO INSTALL EVERYTHING]. Alternatively, you can head to &#039;&#039;System - Administration - Synaptic Package Manager&#039;&#039;, and search for &#039;&#039;spring&#039;&#039;. For the bare essentials, you will need to install the &#039;&#039;spring-engine&#039;&#039; and &#039;&#039;springlobby&#039;&#039; packages.&lt;br /&gt;
&lt;br /&gt;
SpringLobby can be found in &#039;&#039;Applications - Games&#039;&#039; after install. (Please note that you also need mods and maps to play. The easiest way to get them is to download them from within SpringLobby itself.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Alternative installation using the terminal =&lt;br /&gt;
Press &#039;&#039;Alt-F2&#039;&#039; to run a command. Enter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
gksudo -- wget -O - &amp;quot;http://keyserver.ubuntu.com:11371/pks/lookup?op=get&amp;amp;search=0xFC66403D8670A035&amp;quot; | \&lt;br /&gt;
apt-key add -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will add the Spring-Developers PPA package-signing key to the list of keys that Ubuntu should trust for software packages. (See above for more info).&lt;br /&gt;
&lt;br /&gt;
==Ubuntu 10.10 and newer==&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo add-apt-repository ppa:spring&lt;br /&gt;
sudo apt-get update&lt;br /&gt;
sudo apt-get install spring&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Older==&lt;br /&gt;
&lt;br /&gt;
Then do:&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
gksudo gedit /etc/apt/sources.list.d/springproject.list&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Copy and paste the following into the file:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Ubuntu 9.04:===&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
deb http://ppa.launchpad.net/spring/ubuntu jaunty main&lt;br /&gt;
deb-src http://ppa.launchpad.net/spring/ubuntu jaunty main&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Ubuntu 8.10:===&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
deb http://ppa.launchpad.net/spring/ubuntu intrepid main&lt;br /&gt;
deb-src http://ppa.launchpad.net/spring/ubuntu intrepid main&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Save the file. You can then either use the graphical package manager Synaptic (&#039;&#039;System - Administration - Synaptic Package Manager&#039;&#039;) and search for &#039;&#039;spring&#039;&#039; related packages, or just install spring from a terminal using:&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt-get update&lt;br /&gt;
sudo apt-get install spring spring-maps-default&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Other information ==&lt;br /&gt;
You can view the web page for the repository [https://edge.launchpad.net/~spring/+archive here]. Additional packages (such as one for Kernel Panic) are there as well.&lt;br /&gt;
&lt;br /&gt;
The package installs the Spring executable at &amp;lt;code&amp;gt;/usr/games/spring&amp;lt;/code&amp;gt; and read-only data in &amp;lt;code&amp;gt;/usr/share/games/spring&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= See Also: =&lt;br /&gt;
* [[Finalizing_linux_install]]&lt;br /&gt;
* [[Troubleshooting_linux]]&lt;br /&gt;
&lt;br /&gt;
[[Category: Linux]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=LINUX:SetupSpringie&amp;diff=2698</id>
		<title>LINUX:SetupSpringie</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=LINUX:SetupSpringie&amp;diff=2698"/>
		<updated>2026-03-06T05:53:36Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;= Basics =  Springie is a standalone windows/linux application written in C# by Licho. It is an alternative to the SPADS Autohost written by bibim. There is a [http://springrts.com/phpbb/viewtopic.php?f=1&amp;amp;t=7921&amp;amp;st=0&amp;amp;sk=t&amp;amp;sd=a forum-thread] dealing with springie. More information can be found in &amp;quot;How to use Springie&amp;quot;.  In order to run the application on Linux, you need to have mono installed.   It is possible to run Springie on a dedicated sev...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Basics =&lt;br /&gt;
&lt;br /&gt;
Springie is a standalone windows/linux application written in C# by Licho. It is an alternative to the SPADS Autohost written by bibim. There is a [http://springrts.com/phpbb/viewtopic.php?f=1&amp;amp;t=7921&amp;amp;st=0&amp;amp;sk=t&amp;amp;sd=a forum-thread] dealing with springie. More information can be found in &amp;quot;[[Hosting Spring/Autohost|How to use Springie]]&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
In order to run the application on Linux, you need to have mono installed. &lt;br /&gt;
&lt;br /&gt;
It is possible to run Springie on a dedicated sever without graphical interface.&lt;br /&gt;
&lt;br /&gt;
= Springie on Ubuntu =&lt;br /&gt;
&lt;br /&gt;
The following steps have led to a successful installation of Springie on an Ubuntu 9.04 (Jaunty) server.&lt;br /&gt;
&lt;br /&gt;
1. Install Spring as described [[Ubuntu_install|here]].&lt;br /&gt;
&lt;br /&gt;
2. Install mono:&lt;br /&gt;
&lt;br /&gt;
 sudo apt-get install mono-common libmono-winforms2.0-cil&lt;br /&gt;
&lt;br /&gt;
3. Get Springie:&lt;br /&gt;
 wget http://springie.licho.eu/springie.zip&lt;br /&gt;
&lt;br /&gt;
4. Extract &amp;quot;springie.zip&amp;quot; in a separate directory, e.g.:&lt;br /&gt;
&lt;br /&gt;
In your home-directory:&lt;br /&gt;
 mkdir springie&lt;br /&gt;
 mv path/to/springie.zip springie&lt;br /&gt;
 cd springie&lt;br /&gt;
 unzip springie.zip&lt;br /&gt;
&lt;br /&gt;
5. Read the readme.txt file.&lt;br /&gt;
&lt;br /&gt;
6. Edit the main.xml file in your springie directory:&lt;br /&gt;
&lt;br /&gt;
Replace:&lt;br /&gt;
 &amp;lt;ExecutableName&amp;gt;spring-dedicated.exe&amp;lt;/ExecutableName&amp;gt;&lt;br /&gt;
with:&lt;br /&gt;
 &amp;lt;ExecutableName&amp;gt;/usr/games/spring-dedicated&amp;lt;/ExecutableName&amp;gt;&lt;br /&gt;
&lt;br /&gt;
7. Run Springie.&lt;br /&gt;
&lt;br /&gt;
When running Springie for the first time, it creates a bunch of files in its  directory. &lt;br /&gt;
&lt;br /&gt;
Run Springie either with&lt;br /&gt;
 mono Springie.exe&lt;br /&gt;
or &lt;br /&gt;
 mono Springie.exe -nogui&lt;br /&gt;
in case of a headless display.&lt;br /&gt;
&lt;br /&gt;
8. Stop Springie.&lt;br /&gt;
&lt;br /&gt;
Use Ctrl+C (Strg+C) to stop Springie.&lt;br /&gt;
&lt;br /&gt;
9. Configure Springie as described below.&lt;br /&gt;
&lt;br /&gt;
10. Run Springie as described in item number 7. &lt;br /&gt;
&lt;br /&gt;
= Configure Springie =&lt;br /&gt;
&lt;br /&gt;
== Bot account ==&lt;br /&gt;
In order to be able to play Spring and to host games at the same time, you need a new spring account.The easiest way to register a new account is to use your Multiplayer Lobby client, e.g. SpringLobby. &lt;br /&gt;
&lt;br /&gt;
== Server Settings ==&lt;br /&gt;
In case you are hosting on a server with a display attached, you simply can use the Springie GUI for configuration.&lt;br /&gt;
&lt;br /&gt;
If you set Springie up on a headless server, you have to manually edit autohost.xml to set the configuration options. Of course, you can use Springie on a host with a display, use the GUI for configuration and then copy over the autohost.xml file.&lt;br /&gt;
&lt;br /&gt;
=== Add admin users ===&lt;br /&gt;
Between the closing &amp;lt;/DefaultRectangles&amp;gt;-tag and the opening &amp;lt;AutoLockMinPlayers&amp;gt;-tag insert:&lt;br /&gt;
  &amp;lt;PrivilegedUsers&amp;gt;&lt;br /&gt;
  &amp;lt;/PrivilegedUsers&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then for each admin insert between these tags:&lt;br /&gt;
    &amp;lt;PrivilegedUser&amp;gt;&lt;br /&gt;
      &amp;lt;Level&amp;gt;LEVEL&amp;lt;/Level&amp;gt;&lt;br /&gt;
      &amp;lt;Name&amp;gt;ADMIN&amp;lt;/Name&amp;gt;&lt;br /&gt;
    &amp;lt;/PrivilegedUser&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where LEVEL is a number from 1 to 5 and ADMIN is a player&#039;s name who should be able to perform admin tasks.&lt;br /&gt;
&lt;br /&gt;
The result could look like this:&lt;br /&gt;
  &amp;lt;PrivilegedUsers&amp;gt;&lt;br /&gt;
    &amp;lt;PrivilegedUser&amp;gt;&lt;br /&gt;
      &amp;lt;Level&amp;gt;5&amp;lt;/Level&amp;gt;&lt;br /&gt;
      &amp;lt;Name&amp;gt;YOUR-SPRING-PLAYER-ACCOUNT&amp;lt;/Name&amp;gt;&lt;br /&gt;
    &amp;lt;/PrivilegedUser&amp;gt;&lt;br /&gt;
    &amp;lt;PrivilegedUser&amp;gt;&lt;br /&gt;
      &amp;lt;Level&amp;gt;4&amp;lt;/Level&amp;gt;&lt;br /&gt;
      &amp;lt;Name&amp;gt;admin1&amp;lt;/Name&amp;gt;&lt;br /&gt;
    &amp;lt;/PrivilegedUser&amp;gt;&lt;br /&gt;
  &amp;lt;/PrivilegedUsers&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Set a password ===&lt;br /&gt;
To set a password replace&lt;br /&gt;
 &amp;lt;Password&amp;gt;*&amp;lt;/Password&amp;gt;&lt;br /&gt;
with&lt;br /&gt;
 &amp;lt;Password&amp;gt;YOUR-PASSWORD&amp;lt;/Password&amp;gt;&lt;br /&gt;
in autohost.xml.&lt;br /&gt;
&lt;br /&gt;
=== Set default map and mod ===&lt;br /&gt;
The tags &lt;br /&gt;
 &amp;lt;DefaultMap&amp;gt;&amp;lt;/DefaultMap&amp;gt;&lt;br /&gt;
enclose the default map, the tags&lt;br /&gt;
 &amp;lt;DefaultMod&amp;gt;&amp;lt;/DefaultMod&amp;gt;&lt;br /&gt;
the default mod.&lt;br /&gt;
&lt;br /&gt;
An example for hosting a BA 6.95 mod with the SmallDivide map is:&lt;br /&gt;
 &amp;lt;DefaultMap&amp;gt;SmallDivide.smf&amp;lt;/DefaultMap&amp;gt;&lt;br /&gt;
 &amp;lt;DefaultMod&amp;gt;Balanced Annihilation V6.95&amp;lt;/DefaultMod&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Change the game title ===&lt;br /&gt;
Use the &lt;br /&gt;
 &amp;lt;GameTitle&amp;gt;&amp;lt;/GameTitle&amp;gt;&lt;br /&gt;
tags to set the game title, e.g.:&lt;br /&gt;
 &amp;lt;GameTitle&amp;gt;MYCOOLSERVER!!!1!&amp;lt;/GameTitle&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Administer Springie ===&lt;br /&gt;
You can use the commands listed in the readme.txt to administer Springie by  &amp;quot;talking&amp;quot; to the bot user. The commands !help and !helpall provide lists of accepted commands.&lt;br /&gt;
&lt;br /&gt;
== Add mods ==&lt;br /&gt;
&#039;&#039;TO DO&#039;&#039;&lt;br /&gt;
[[Category:Linux]]&lt;br /&gt;
[[Category: Autohosts]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Map_Making_Guide&amp;diff=2697</id>
		<title>Map Making Guide</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Map_Making_Guide&amp;diff=2697"/>
		<updated>2026-03-06T05:52:58Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Redirected page to Tutorial:MapMakingGuide(aGorm)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Tutorial:MapMakingGuide(aGorm)]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=New_to_xta&amp;diff=2696</id>
		<title>New to xta</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=New_to_xta&amp;diff=2696"/>
		<updated>2026-03-06T00:54:02Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;                       Image:Xta_logo_white.jpg  &amp;#039;&amp;#039;&amp;#039;Here you can find a neat guide for beginners to XTA&amp;#039;&amp;#039;&amp;#039;   == Light Laser Tower ==   Light laser towers (LLTs) have a much different role in XTA.  They cost 250+ metal and should &amp;#039;&amp;#039;&amp;#039;NOT&amp;#039;&amp;#039;&amp;#039; be spammed unless in specific circumstances.  It is widely thought that 2-3 well spread LLTs will kill a Commander.  They are more for holding a strategic point than defending a small area.  Units will defeat equal metal cost in def...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;                       [[Image:Xta_logo_white.jpg]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Here you can find a neat guide for beginners to XTA&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Light Laser Tower == &lt;br /&gt;
&lt;br /&gt;
Light laser towers (LLTs) have a much different role in XTA.&lt;br /&gt;
&lt;br /&gt;
They cost 250+ metal and should &#039;&#039;&#039;NOT&#039;&#039;&#039; be spammed unless in specific circumstances.&lt;br /&gt;
&lt;br /&gt;
It is widely thought that 2-3 well spread LLTs will kill a Commander.&lt;br /&gt;
&lt;br /&gt;
They are more for holding a strategic point than defending a small area.&lt;br /&gt;
&lt;br /&gt;
Units will defeat equal metal cost in defence.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Commander ==&lt;br /&gt;
&lt;br /&gt;
XTA is built around the Commanders and Commander Pushing is common.&lt;br /&gt;
&lt;br /&gt;
For a specific guide on Commanders, go [http://springrts.com/wiki/Xta_commander here].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Unlike BA, there are differences between Core and Arm Commanders:&lt;br /&gt;
*Arm is faster moving&lt;br /&gt;
*Core has bigger decloak radius (easy to decloak)&lt;br /&gt;
*Core has longer D-Gun (260 compared to Arm&#039;s 240)&lt;br /&gt;
At morphing, they are also different: &lt;br /&gt;
*Core gets a heavier laser, closer range = more damage at Level Two&lt;br /&gt;
*Arm gets a paralyser at Level Two&lt;br /&gt;
*Core gets a plasma shot at Level Four&lt;br /&gt;
*Arm gets three raven rockets at Level Four&lt;br /&gt;
&lt;br /&gt;
The Commanders have shorter range than BA Commanders.&lt;br /&gt;
&lt;br /&gt;
It costs 200 Energy to cloak a stationary unmorphed Commander, and 1000 a moving one.&lt;br /&gt;
&lt;br /&gt;
The D-Gun also has a wider impact.&lt;br /&gt;
&lt;br /&gt;
Commanders can make all Tech one and Tech Two labs, like all constructors.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT DO NOT GO TECH TWO AT START&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Commanders do NOT leave a wreckage. &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Morphing ==&lt;br /&gt;
&lt;br /&gt;
Commanders can be morphed in XTA (toggled On/Off in host options).&lt;br /&gt;
Commanders can be morphed 4 times. The first level requires 0.01 experience.&lt;br /&gt;
First level gives: &lt;br /&gt;
*+3 metal, +75 Energy (starts at +2 and +50)&lt;br /&gt;
*2,000 metal + Energy storage (starts at 1,000)&lt;br /&gt;
*Build speed increases by 25, gets full Tech one radar as well as increased LoS&lt;br /&gt;
*Range increases by 20, more powerful primary laser and +250 extra health&lt;br /&gt;
*When cloaked and moving Commander uses only 700 energy, not 1000&lt;br /&gt;
*Level Two requires 0.25 experience and gives improvement on level 1&lt;br /&gt;
*Level Three requires 0.10 experience (will go to 0.09 if excess after Level Two)&lt;br /&gt;
*Level Three gives improvement on Level Two as well as self repair option&lt;br /&gt;
*Level Four requires 0.25 experience and gives improvement on Level Three&lt;br /&gt;
*&#039;&#039;&#039;Commander will be paralysed during morphing!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Vehicles or Kbots? ==&lt;br /&gt;
&lt;br /&gt;
In XTA both types are useful.&lt;br /&gt;
&lt;br /&gt;
It is best to go Kbots on hilly areas and Vehicles on flat areas.&lt;br /&gt;
&lt;br /&gt;
Flash are not as powerful and a wide spread of units is best.&lt;br /&gt;
&lt;br /&gt;
Vehicle Constructors are faster builders but cost more.&lt;br /&gt;
&lt;br /&gt;
All AA shoots ground.&lt;br /&gt;
&lt;br /&gt;
Scouts in XTA are very strong and can survive a lot of damage, however have low damage output.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Nanos == &lt;br /&gt;
&lt;br /&gt;
XTA Nano Towers have shorter range but more build power than in BA.&lt;br /&gt;
&lt;br /&gt;
They chain explode, so place them at your own risk!&lt;br /&gt;
&lt;br /&gt;
Try space them out and not so close to your lab (but within range).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Anti Air == &lt;br /&gt;
&lt;br /&gt;
Unlike BA, Vehicle and Kbot AA, Tech One and Two shoots ground and air.&lt;br /&gt;
&lt;br /&gt;
All stationary Anti Air shoots ground.&lt;br /&gt;
&lt;br /&gt;
Anti Air ships also shoot ground (and surface sea).&lt;br /&gt;
&lt;br /&gt;
There is only one Tech one and Tech Two stationary Land Anti Air for both factions.&lt;br /&gt;
&lt;br /&gt;
There is only one Tech one and Tech Two stationary Sea Anti Air for both factions.&lt;br /&gt;
&lt;br /&gt;
There is no Tech Two Kbot Anti Air, although Armâ€™s Pelican has a weak missile.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Difference in Techs ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tech one&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
It is usually best to only have one Tech One land lab, with assisters to improve build power.&lt;br /&gt;
&lt;br /&gt;
XTA is very similar to OTA with Tech one units (less than BA).&lt;br /&gt;
&lt;br /&gt;
Toaster/Ambusher is Tech One; Guardian/Punisher is Tech Two.&lt;br /&gt;
&lt;br /&gt;
Dragonâ€™s Teeth blocks Rockets and majority of Missiles (terrain dependant).&lt;br /&gt;
&lt;br /&gt;
All Tech One Constructors can make all Tech one and Tech Two labs.&lt;br /&gt;
&lt;br /&gt;
Sub Pen and Shipyard are both sea Tech one.&lt;br /&gt;
&lt;br /&gt;
Level one Metal Makers do not chain explode.&lt;br /&gt;
&lt;br /&gt;
Popups do not have &amp;quot;High Trajectory&amp;quot; function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tech 1.5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Hovercraft and Sea Planes are Tech 1.5&lt;br /&gt;
&lt;br /&gt;
Labs cost 1,700 metal.&lt;br /&gt;
&lt;br /&gt;
The Constructors can make Mohos and Anti Nukes.&lt;br /&gt;
&lt;br /&gt;
Hover Platform can be built on Land and Sea.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Tech Two&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Tech Two is very different in XTA, and is usually made earlier.&lt;br /&gt;
&lt;br /&gt;
Once Tech Two is out, Tech one units will suffer heavily in head on combat.&lt;br /&gt;
&lt;br /&gt;
Resurrection Kbots are Tech Two and can make structures.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Mohos cost 300 Energy to run not 25!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Zippers/Freakers are in Tech one and 2 labs.&lt;br /&gt;
&lt;br /&gt;
Fleas have 700 HP and are less a scout unit more Skirmish.&lt;br /&gt;
&lt;br /&gt;
Guardians/Punishers do not have &amp;quot;High Trajectory&amp;quot; function.&lt;br /&gt;
&lt;br /&gt;
There are no Plasma Shields.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tech Three&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
All Core land  and air Tech Two cons can make the Krogoth Gantry.&lt;br /&gt;
&lt;br /&gt;
Arm has no Tech Three.&lt;br /&gt;
&lt;br /&gt;
Krogoth is only Tech Three unit (30,000+ metal).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Resources ==&lt;br /&gt;
&lt;br /&gt;
There is no Advanced Solar.&lt;br /&gt;
&lt;br /&gt;
Cloakable Fusions give 800 Energy&lt;br /&gt;
*Arm takes 125 Energy to Cloak&lt;br /&gt;
*Core takes 150 Energy to Cloak&lt;br /&gt;
*Arm takes 500 Energy to Stealth (625 for Cloak and Stealth)&lt;br /&gt;
*Core takes 550 Energy to Stealth (725 for Cloak and Stealth)&lt;br /&gt;
&lt;br /&gt;
Heavy Fusions give 3500 Energy.&lt;br /&gt;
&lt;br /&gt;
Arm have Mobile Fusions (260 E) in Advanced Vehicle Lab.&lt;br /&gt;
&lt;br /&gt;
Core have Mini Fusions (300 E) built by all Tech Two Constructors.&lt;br /&gt;
&lt;br /&gt;
NEVER start with a Heavy Fusion.&lt;br /&gt;
&lt;br /&gt;
Moho Metal Extractors cost 300 Energy per second to maintain.&lt;br /&gt;
&lt;br /&gt;
There is no gradual reclaim.&lt;br /&gt;
&lt;br /&gt;
Metal storages give 10,000 storage extra.&lt;br /&gt;
&lt;br /&gt;
Energy Storages give 30,000 extra storage.&lt;br /&gt;
&lt;br /&gt;
No Tech Two Storages (there are Tech one underwater ones).&lt;br /&gt;
&lt;br /&gt;
Underwater Storages give increased amounts of storage, but cost a little more.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Micro ==&lt;br /&gt;
&lt;br /&gt;
XTA is more about Micro as opposed to Macro.&lt;br /&gt;
&lt;br /&gt;
Controlling a select amount of units can win games.&lt;br /&gt;
&lt;br /&gt;
Units have more Health and are harder to kill.&lt;br /&gt;
&lt;br /&gt;
Wasting units by not microing &#039;&#039;&#039;WILL&#039;&#039;&#039; cost games.&lt;br /&gt;
&lt;br /&gt;
Commanders are widely microed.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:xta]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Category:NOTA&amp;diff=2695</id>
		<title>Category:NOTA</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Category:NOTA&amp;diff=2695"/>
		<updated>2026-03-06T00:53:09Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created blank page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=IceXuick_Map_Design_Help&amp;diff=2694</id>
		<title>IceXuick Map Design Help</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=IceXuick_Map_Design_Help&amp;diff=2694"/>
		<updated>2026-03-06T00:52:26Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Redirected page to Tutorial:MapDesignHelp(IceXuick)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Tutorial:MapDesignHelp(IceXuick)]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Category:AI&amp;diff=2693</id>
		<title>Category:AI</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Category:AI&amp;diff=2693"/>
		<updated>2026-03-06T00:51:21Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created blank page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=File:Distributions-mac-2.png&amp;diff=2692</id>
		<title>File:Distributions-mac-2.png</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=File:Distributions-mac-2.png&amp;diff=2692"/>
		<updated>2026-03-06T00:50:28Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=File:Distributions-windows.png&amp;diff=2691</id>
		<title>File:Distributions-windows.png</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=File:Distributions-windows.png&amp;diff=2691"/>
		<updated>2026-03-06T00:50:13Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=File:Tux-G2.png&amp;diff=2690</id>
		<title>File:Tux-G2.png</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=File:Tux-G2.png&amp;diff=2690"/>
		<updated>2026-03-06T00:49:52Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Template:EngineBranch:Testing&amp;diff=2689</id>
		<title>Template:EngineBranch:Testing</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Template:EngineBranch:Testing&amp;diff=2689"/>
		<updated>2026-03-06T00:45:25Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;103.0.1-1328-g882485f&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;103.0.1-1328-g882485f&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Template:EngineVersion:Testing:ReleaseDate&amp;diff=2688</id>
		<title>Template:EngineVersion:Testing:ReleaseDate</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Template:EngineVersion:Testing:ReleaseDate&amp;diff=2688"/>
		<updated>2026-03-06T00:45:03Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;02. September 2017&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;02. September 2017&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Template:EngineVersion:Testing&amp;diff=2687</id>
		<title>Template:EngineVersion:Testing</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Template:EngineVersion:Testing&amp;diff=2687"/>
		<updated>2026-03-06T00:44:07Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;develop&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;develop&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Download_Testing&amp;diff=2686</id>
		<title>Download Testing</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Download_Testing&amp;diff=2686"/>
		<updated>2026-03-06T00:43:49Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;= Spring Engine Testing Release  =  Current test version: {{EngineVersion:Testing}} ({{EngineVersion:Testing:ReleaseDate}})  == Get It ==  === SpringLobby ===  Go to Tools-&amp;gt;Download Archives, then enter  &amp;lt;code&amp;gt; engine:spring {{EngineVersion:Testing}} {{EngineBranch:Testing}} &amp;lt;/code&amp;gt;  Click ok.  === pr-downloader === easiest way is by using pr-downloader (which is included into spring)  &amp;lt;code&amp;gt; pr-downloader --download-engine &amp;quot;{{EngineVersion:Testing}} {{EngineBran...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Spring Engine Testing Release  =&lt;br /&gt;
&lt;br /&gt;
Current test version: {{EngineVersion:Testing}} ({{EngineVersion:Testing:ReleaseDate}})&lt;br /&gt;
&lt;br /&gt;
== Get It ==&lt;br /&gt;
&lt;br /&gt;
=== [[SpringLobby]] ===&lt;br /&gt;
&lt;br /&gt;
Go to Tools-&amp;gt;Download Archives, then enter&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
engine:spring {{EngineVersion:Testing}} {{EngineBranch:Testing}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Click ok.&lt;br /&gt;
&lt;br /&gt;
=== [[pr-downloader]] ===&lt;br /&gt;
easiest way is by using pr-downloader (which is included into spring)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
pr-downloader --download-engine &amp;quot;{{EngineVersion:Testing}} {{EngineBranch:Testing}}&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| style=&amp;quot;padding-right:10px&amp;quot;| [[Image:Distributions-windows.png]]&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; [https://springrts.com/dl/buildbot/default/{{EngineBranch:Testing}}/{{EngineVersion:Testing}}/win32/spring_&amp;amp;#123;{{EngineBranch:Testing}}&amp;amp;#125;{{EngineVersion:Testing}}_win32.exe installer]&lt;br /&gt;
&amp;lt;li&amp;gt; [https://springrts.com/dl/buildbot/default/{{EngineBranch:Testing}}/{{EngineVersion:Testing}}/win32/spring_&amp;amp;#123;{{EngineBranch:Testing}}&amp;amp;#125;{{EngineVersion:Testing}}_win32-minimal-portable.7z portable] (Lobby not included)&lt;br /&gt;
&amp;lt;li&amp;gt; [https://springrts.com/dl/buildbot/default/{{EngineBranch:Testing}}/{{EngineVersion:Testing}}/win32/ whole build]&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Linux ===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| style=&amp;quot;padding-right:10px&amp;quot;| [[Image:Tux-G2.png]]&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;ol&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; &#039;&#039;&#039;get the source&#039;&#039;&#039;&lt;br /&gt;
  &amp;lt;ol&amp;gt;&lt;br /&gt;
  &amp;lt;li&amp;gt; creating the repo (first time only):&amp;lt;br&amp;gt;&lt;br /&gt;
    &amp;lt;code&amp;gt;git clone git://github.com/spring/spring.git spring_src&amp;lt;/code&amp;gt;&lt;br /&gt;
  &amp;lt;li&amp;gt; update to latest sources:&amp;lt;br&amp;gt;&lt;br /&gt;
    &amp;lt;code&amp;gt;cd spring_src &amp;amp;&amp;amp; git fetch &amp;amp;&amp;amp; git checkout {{EngineVersion:Testing}}&amp;lt;/code&amp;gt;&lt;br /&gt;
  &amp;lt;/ol&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; &#039;&#039;&#039;build&#039;&#039;&#039; by following the [[Building_Spring_on_Linux|build from source]] guide, or in short:&amp;lt;br&amp;gt;&lt;br /&gt;
  &amp;lt;code&amp;gt;cmake -G &amp;quot;Unix Makefiles&amp;quot; . &amp;amp;&amp;amp; make spring &amp;amp;&amp;amp; sudo make install-spring&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;/ol&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===static builds===&lt;br /&gt;
as alternative you can try the one of the linux static builds:&lt;br /&gt;
&lt;br /&gt;
* [https://springrts.com/dl/buildbot/default/{{EngineBranch:Testing}}/{{EngineVersion:Testing}}/linux32/spring_%7b{{EngineBranch:Testing}}%7d{{EngineVersion:Testing}}_minimal-portable-linux32-static.7z static linux build (32 bit)]&lt;br /&gt;
&lt;br /&gt;
* [https://springrts.com/dl/buildbot/default/{{EngineBranch:Testing}}/{{EngineVersion:Testing}}/linux64/spring_%7b{{EngineBranch:Testing}}%7d{{EngineVersion:Testing}}_minimal-portable-linux64-static.7z static linux build (64 bit)]&lt;br /&gt;
&lt;br /&gt;
=== Mac OS X ===&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| style=&amp;quot;padding-right:10px&amp;quot;| [[Image:Distributions-mac-2.png]]&lt;br /&gt;
|&lt;br /&gt;
[https://springrts.com/dl/buildbot/default/{{EngineBranch:Testing}}/{{EngineVersion:Testing}}/osx64/spring_%7b{{EngineBranch:Testing}}%7d{{EngineVersion:Testing}}_MacOSX-10.6-SnowLeopard.zip &#039;&#039;&#039;App Bundle&#039;&#039;&#039;] (tested on 10.6 Snow-Leopard only)&lt;br /&gt;
&lt;br /&gt;
In case the above fails, check out [[Spring_on_MacOSX|alternative ways]].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Lobby ==&lt;br /&gt;
&lt;br /&gt;
Make sure your lobby client is configured to use the correct spring binary &amp;amp; unitsync!&lt;br /&gt;
On Linux, this will usually be &#039;&#039;/usr/local/bin/spring&#039;&#039; &amp;amp; &#039;&#039;/usr/local/lib/libunitsync.so&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Use one of the test lobby servers (recommended):&lt;br /&gt;
* &#039;&#039;lobby1.springlobby.info:8200&#039;&#039;&lt;br /&gt;
* &#039;&#039;lobby2.springlobby.info:8200&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Or alternatively, configure your lobby so it does not insist using the release version of the engine:&lt;br /&gt;
* SpringLobby: set &amp;lt;code&amp;gt;DisableVersionCheck=1&amp;lt;/code&amp;gt; in &#039;&#039;springlobby.conf&#039;&#039;&lt;br /&gt;
* TASClient: press CTRL+SHIFT+F6 and enable &amp;quot;Ignore server version incompatibility&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== Bugs and issues ==&lt;br /&gt;
&lt;br /&gt;
Report on [https://springrts.com/mantis/ Mantis].&lt;br /&gt;
Remember to upload [[infolog.txt]] if you crash!&lt;br /&gt;
&lt;br /&gt;
== Changelog ==&lt;br /&gt;
&lt;br /&gt;
For changes please look into the [https://github.com/spring/spring/blob/{{EngineVersion:Testing}}/doc/changelog.txt Change Log] or the [https://github.com/spring/spring/compare/{{EngineVersion:Stable}}...{{EngineVersion:Testing}} diff log] for details.&lt;br /&gt;
&lt;br /&gt;
See the topmost for changes to the upcoming version.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category: Development]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Complete_Annihilation&amp;diff=2685</id>
		<title>Complete Annihilation</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Complete_Annihilation&amp;diff=2685"/>
		<updated>2026-03-06T00:43:17Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;CA is now Zero-K&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;CA is now [[Zero-K]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Create_maps&amp;diff=2684</id>
		<title>Create maps</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Create_maps&amp;diff=2684"/>
		<updated>2026-03-06T00:42:53Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Redirected page to Tutorial:CreateMaps(Fnordia et al)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Tutorial:CreateMaps(Fnordia et al)]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Template:Template_doc&amp;diff=2683</id>
		<title>Template:Template doc</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Template:Template_doc&amp;diff=2683"/>
		<updated>2026-03-05T05:22:39Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;{{#invoke:documentation|main|_content={{ {{#invoke:documentation|contentTitle}}}}}}&amp;lt;noinclude&amp;gt; &amp;lt;!-- Add categories to the /doc subpage --&amp;gt; &amp;lt;/noinclude&amp;gt;&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{#invoke:documentation|main|_content={{ {{#invoke:documentation|contentTitle}}}}}}&amp;lt;noinclude&amp;gt;&lt;br /&gt;
&amp;lt;!-- Add categories to the /doc subpage --&amp;gt;&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Template:Counter&amp;diff=2682</id>
		<title>Template:Counter</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Template:Counter&amp;diff=2682"/>
		<updated>2026-03-05T05:22:15Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;{{#switch:{{{1}}} |GA={{formatnum:{{#expr:{{PAGESINCATEGORY:Wikipedia_good_articles|R}}-15}}}} |FA={{formatnum:{{#expr:{{PAGESINCATEGORY:Featured_articles|R}}}}}} |FL={{formatnum:{{#expr:{{PAGESINCATEGORY:Wikipedia_featured_lists|R}}-5}}}} |SUM={{formatnum:{{#expr:{{PAGESINCATEGORY:Wikipedia_good_articles|R}}-15+{{PAGESINCATEGORY:Featured_articles|R}}+{{PAGESINCATEGORY:Wikipedia_featured_lists|R}}-5}}}} |RGA={{formatnum:{{#expr:ceil({{NUMBEROFARTICLES:R}}/({{PAGESINCATEG...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{#switch:{{{1}}}&lt;br /&gt;
|GA={{formatnum:{{#expr:{{PAGESINCATEGORY:Wikipedia_good_articles|R}}-15}}}}&lt;br /&gt;
|FA={{formatnum:{{#expr:{{PAGESINCATEGORY:Featured_articles|R}}}}}}&lt;br /&gt;
|FL={{formatnum:{{#expr:{{PAGESINCATEGORY:Wikipedia_featured_lists|R}}-5}}}}&lt;br /&gt;
|SUM={{formatnum:{{#expr:{{PAGESINCATEGORY:Wikipedia_good_articles|R}}-15+{{PAGESINCATEGORY:Featured_articles|R}}+{{PAGESINCATEGORY:Wikipedia_featured_lists|R}}-5}}}}&lt;br /&gt;
|RGA={{formatnum:{{#expr:ceil({{NUMBEROFARTICLES:R}}/({{PAGESINCATEGORY:Wikipedia_good_articles|R}}-15))}}}}&lt;br /&gt;
|RFA={{formatnum:{{#expr:ceil({{NUMBEROFARTICLES:R}}/{{PAGESINCATEGORY:Featured_articles|R}})}}}}&lt;br /&gt;
|RFL={{formatnum:{{#expr:ceil({{NUMBEROFARTICLES:R}}/({{PAGESINCATEGORY:Wikipedia_featured_lists|R}}-5))}}}}&lt;br /&gt;
|RSUM={{formatnum:{{#expr:ceil({{NUMBEROFARTICLES:R}}/({{PAGESINCATEGORY:Wikipedia_good_articles|R}}-15+{{PAGESINCATEGORY:Featured_articles|R}}+{{PAGESINCATEGORY:Wikipedia_featured_lists|R}}-5))}}}}&lt;br /&gt;
|{{NUMBEROFARTICLES}}}}&amp;lt;noinclude&amp;gt;&lt;br /&gt;
{{template doc}}&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Template:Warning&amp;diff=2681</id>
		<title>Template:Warning</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Template:Warning&amp;diff=2681"/>
		<updated>2026-03-05T05:21:29Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;&amp;lt;languages/&amp;gt; &amp;lt;onlyinclude&amp;gt;{{#if: {{{msg|{{{1|}}}}}}|&amp;lt;table class=&amp;quot;warning-message&amp;quot; style=&amp;quot;background-color: var( --background-color-error-subtle, #ffe9e5 ); color: inherit; border: 1px var( --border-color-error, #9f3526 ) solid; box-sizing: border-box; margin: 0.5em 0; padding: 0.5em;&amp;quot;&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td style=&amp;quot;white-space: nowrap; vertical-align: top;&amp;quot;&amp;gt;}}&amp;lt;span style=&amp;quot;position: relative; top: -2px;&amp;quot;&amp;gt;File:OOjs_UI_icon_notice-destructive.svg|18px|alt=&amp;lt;translate&amp;gt;&amp;lt;!--T:1--&amp;gt; Warning&amp;lt;/...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;languages/&amp;gt;&lt;br /&gt;
&amp;lt;onlyinclude&amp;gt;{{#if: {{{msg|{{{1|}}}}}}|&amp;lt;table class=&amp;quot;warning-message&amp;quot; style=&amp;quot;background-color: var( --background-color-error-subtle, #ffe9e5 ); color: inherit; border: 1px var( --border-color-error, #9f3526 ) solid; box-sizing: border-box; margin: 0.5em 0; padding: 0.5em;&amp;quot;&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td style=&amp;quot;white-space: nowrap; vertical-align: top;&amp;quot;&amp;gt;}}&amp;lt;span style=&amp;quot;position: relative; top: -2px;&amp;quot;&amp;gt;[[File:OOjs_UI_icon_notice-destructive.svg|18px|alt=&amp;lt;translate&amp;gt;&amp;lt;!--T:1--&amp;gt; Warning&amp;lt;/translate&amp;gt;|link=]]&amp;lt;/span&amp;gt; &#039;&#039;&#039;&amp;lt;translate&amp;gt;&amp;lt;!--T:2--&amp;gt; Warning:&amp;lt;/translate&amp;gt;&#039;&#039;&#039; {{#if: {{{msg|{{{1|}}}}}}|&amp;lt;/td&amp;gt;&amp;lt;td style=&amp;quot;padding-{{dir|{{pagelang}}|right|left}}: 0.5em vertical-align: top;&amp;quot;&amp;gt;{{{msg|{{{1|}}}}}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/table&amp;gt;}}&amp;lt;/onlyinclude&amp;gt;&lt;br /&gt;
{{Documentation|content=&lt;br /&gt;
&amp;lt;translate&amp;gt;&lt;br /&gt;
== Usage == &amp;lt;!--T:3--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--T:4--&amp;gt;&lt;br /&gt;
This template has two possible usages:&lt;br /&gt;
&amp;lt;/translate&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;translate&amp;gt;&amp;lt;!--T:5--&amp;gt; Will have the text flow below when the warning text is too long&amp;lt;/translate&amp;gt;&lt;br /&gt;
# &amp;lt;translate&amp;gt;&amp;lt;!--T:6--&amp;gt; Uses a table to indent the text.&amp;lt;/translate&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;translate&amp;gt;&lt;br /&gt;
=== Method 1 === &amp;lt;!--T:7--&amp;gt;&lt;br /&gt;
&amp;lt;/translate&amp;gt;&lt;br /&gt;
&amp;lt;translate&amp;gt;&amp;lt;!--T:8--&amp;gt; The following displays the warning icon and the word &amp;quot;Warning:&amp;quot;.&amp;lt;/translate&amp;gt;&lt;br /&gt;
&amp;lt;translate&amp;gt;&amp;lt;!--T:17--&amp;gt; You can follow this with whatever text/images/markup you like.&amp;lt;/translate&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;translate&amp;gt;&amp;lt;!--T:9--&amp;gt; Wiki Code:&amp;lt;/translate&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;{{warning}}&amp;lt;/nowiki&amp;gt; &amp;lt;translate&amp;gt;&amp;lt;!--T:10--&amp;gt; Don&#039;t do that!&amp;lt;/translate&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{warning}} &amp;lt;translate&amp;gt;&amp;lt;!--T:11--&amp;gt; Don&#039;t do that!&amp;lt;/translate&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;translate&amp;gt;&lt;br /&gt;
=== Method 2 === &amp;lt;!--T:12--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--T:13--&amp;gt;&lt;br /&gt;
The following includes the text passed to the template, and uses a table to stop the text flowing round the icon.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--T:14--&amp;gt;&lt;br /&gt;
Wiki Code:&lt;br /&gt;
&amp;lt;/translate&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;{{warning|1=&amp;lt;/nowiki&amp;gt;&amp;lt;translate&amp;gt;&amp;lt;!--T:15--&amp;gt; Here is a long warning, which is sufficiently wordy to run onto a second line (unless you have a really large screen!), which would normally cause it to wrap round the icon, but because we passed it as a parameter it keeps its left alignment straight.&amp;lt;/translate&amp;gt;&amp;lt;nowiki&amp;gt;}}&amp;lt;/nowiki&amp;gt; &lt;br /&gt;
&lt;br /&gt;
{{warning|1=&amp;lt;translate&amp;gt;&amp;lt;!--T:16--&amp;gt; Here is a long warning, which is sufficiently wordy to run onto a second line (unless you have a really large screen!), which would normally cause it to wrap round the icon, but because we passed it as a parameter it keeps its left alignment straight.&amp;lt;/translate&amp;gt;}}&lt;br /&gt;
&amp;lt;translate&amp;gt;&lt;br /&gt;
== See also == &amp;lt;!--T:18--&amp;gt;&lt;br /&gt;
&amp;lt;/translate&amp;gt;&lt;br /&gt;
* {{tl|Caution}}&lt;br /&gt;
* {{tl|Fixtext}}&lt;br /&gt;
* {{tl|Note}}&lt;br /&gt;
* {{tl|Tip}}&lt;br /&gt;
}}&lt;br /&gt;
[[Category:Warning templates{{#translation:}}]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2680</id>
		<title>Recoil:RmlUI Starter Guide</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUI_Starter_Guide&amp;diff=2680"/>
		<updated>2026-03-05T05:20:03Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;{{DISPLAYTITLE:RmlUI Starter Guide}} __TOC__  = RmlUI Starter Guide =  &amp;#039;&amp;#039;&amp;#039;&amp;#039;&amp;#039;For RecoilEngine game developers — build reactive, HTML/CSS-style game UIs in Lua.&amp;#039;&amp;#039;&amp;#039;&amp;#039;&amp;#039;  {| class=&amp;quot;wikitable&amp;quot; ! Attribute !! Value |- | Engine || RecoilEngine (Spring) |- | Framework || RmlUi ([https://github.com/mikke89/RmlUi mikke89/RmlUi]) |- | Language || Lua (via sol2 bindings) |- | Availability || LuaUI (LuaIntro / LuaMenu planned) |}  ----  == 1. Introduction ==  RmlUI is a UI framework...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:RmlUI Starter Guide}}&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
= RmlUI Starter Guide =&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;For RecoilEngine game developers — build reactive, HTML/CSS-style game UIs in Lua.&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Attribute !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Engine || RecoilEngine (Spring)&lt;br /&gt;
|-&lt;br /&gt;
| Framework || RmlUi ([https://github.com/mikke89/RmlUi mikke89/RmlUi])&lt;br /&gt;
|-&lt;br /&gt;
| Language || Lua (via sol2 bindings)&lt;br /&gt;
|-&lt;br /&gt;
| Availability || LuaUI (LuaIntro / LuaMenu planned)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 1. Introduction ==&lt;br /&gt;
&lt;br /&gt;
RmlUI is a UI framework that lets you build reactive game interfaces using an HTML/CSS-style workflow — with Lua instead of JavaScript. If you have ever built a web page, the learning curve is gentle. If you haven&#039;t, the concepts are still approachable because the mental model (mark-up + style + logic) is widely documented.&lt;br /&gt;
&lt;br /&gt;
RecoilEngine ships with a full RmlUI integration including:&lt;br /&gt;
&lt;br /&gt;
* An OpenGL 3 renderer tightly coupled to the engine&#039;s rendering pipeline&lt;br /&gt;
* A virtual-filesystem-aware file loader so your &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files live inside game archives&lt;br /&gt;
* Complete Lua bindings via sol2 so every RmlUI object is accessible from Lua&lt;br /&gt;
* Custom elements: &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;texture&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; for engine textures and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;svg&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; for vector art&lt;br /&gt;
* A built-in DOM debugger for interactive inspection&lt;br /&gt;
&lt;br /&gt;
This guide walks you through everything you need to get a working, reactive widget running in LuaUI. The examples are game-engine-agnostic and safe to copy into any Recoil-based game.&lt;br /&gt;
&lt;br /&gt;
{{Note|RmlUI is currently available in &#039;&#039;&#039;LuaUI&#039;&#039;&#039; (the in-game UI). Support for LuaIntro and LuaMenu is planned for a future release.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 2. Key Concepts ==&lt;br /&gt;
&lt;br /&gt;
Before writing any code it helps to understand the five core objects:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Concept !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Context&#039;&#039;&#039; || A named container that holds documents and data models. Multiple contexts can exist simultaneously, each rendered independently.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Document&#039;&#039;&#039; || A parsed RML file representing a DOM tree. Documents live inside a Context and can be shown, hidden, or closed.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;RML&#039;&#039;&#039; || The markup language for documents. Very similar to XHTML (well-formed XML). The root tag is &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;rml&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;html&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;RCSS&#039;&#039;&#039; || The styling language. Similar to CSS2 with some differences (see section 11). File extension: &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Data Model&#039;&#039;&#039; || A Lua table exposed to the RML document via reactive data bindings. Changes to the model automatically update the rendered UI.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
On the &#039;&#039;&#039;Lua side&#039;&#039;&#039; you create contexts, attach data models to them, and load documents. On the &#039;&#039;&#039;RML side&#039;&#039;&#039; you declare your UI structure and reference data-model values through data bindings.&lt;br /&gt;
&lt;br /&gt;
Each widget will typically consist of at least three files: a &amp;lt;code&amp;gt;.lua&amp;lt;/code&amp;gt;, a &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt;, and optionally a &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file — grouping them in a folder per widget keeps things tidy.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 3. Setup ==&lt;br /&gt;
&lt;br /&gt;
RecoilEngine ships a minimal setup script at &amp;lt;code&amp;gt;cont/LuaUI/rml_setup.lua&amp;lt;/code&amp;gt;. It is included automatically by the base content handler but you should understand what it does so you can extend it for your game.&lt;br /&gt;
&lt;br /&gt;
=== 3.1 The rml_setup.lua script ===&lt;br /&gt;
&lt;br /&gt;
The script performs three tasks: guards against double-initialisation, loads font faces, and registers mouse-cursor aliases. Below is a more complete version that also creates a shared context and configures dp scaling:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
--  luaui/rml_setup.lua&lt;br /&gt;
--  author:  lov + ChrisFloofyKitsune&lt;br /&gt;
--  License: GNU GPL, v2 or later&lt;br /&gt;
&lt;br /&gt;
if (RmlGuard or not RmlUi) then&lt;br /&gt;
    return&lt;br /&gt;
end&lt;br /&gt;
-- Prevent this from running more than once&lt;br /&gt;
RmlGuard = true&lt;br /&gt;
&lt;br /&gt;
-- Patch CreateContext to set dp_ratio automatically&lt;br /&gt;
local oldCreateContext = RmlUi.CreateContext&lt;br /&gt;
local function NewCreateContext(name)&lt;br /&gt;
    local context = oldCreateContext(name)&lt;br /&gt;
    local viewSizeX, viewSizeY = Spring.GetViewGeometry()&lt;br /&gt;
    local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
    local baseWidth, baseHeight = 1920, 1080&lt;br /&gt;
    local resFactor = math.min(viewSizeX / baseWidth, viewSizeY / baseHeight)&lt;br /&gt;
    -- Floor to 2 decimal places to avoid floating point drift&lt;br /&gt;
    context.dp_ratio = math.floor(resFactor * userScale * 100) / 100&lt;br /&gt;
    return context&lt;br /&gt;
end&lt;br /&gt;
RmlUi.CreateContext = NewCreateContext&lt;br /&gt;
&lt;br /&gt;
-- Load fonts (list your .ttf files here)&lt;br /&gt;
local font_files = {&lt;br /&gt;
    -- &amp;quot;Fonts/MyFont-Regular.ttf&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
for _, file in ipairs(font_files) do&lt;br /&gt;
    RmlUi.LoadFontFace(file, true)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Map CSS cursor names to engine cursor names&lt;br /&gt;
-- CSS cursor list: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;default&amp;quot;, &#039;cursornormal&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;move&amp;quot;,    &#039;uimove&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;pointer&amp;quot;, &#039;Move&#039;)&lt;br /&gt;
&lt;br /&gt;
-- Create the shared context used by all widgets&lt;br /&gt;
RmlUi.CreateContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 3.2 Including the setup in main.lua ===&lt;br /&gt;
&lt;br /&gt;
Add a single line to &amp;lt;code&amp;gt;luaui/main.lua&amp;lt;/code&amp;gt; to run the setup before any widgets load:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- luaui/main.lua&lt;br /&gt;
VFS.Include(LUAUI_DIRNAME .. &amp;quot;rml_setup.lua&amp;quot;, nil, VFS.ZIP)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|The base-content &amp;lt;code&amp;gt;rml_setup.lua&amp;lt;/code&amp;gt; only loads a font and sets cursor aliases; it does &#039;&#039;&#039;not&#039;&#039;&#039; create a context. If you are building a new game you should add context creation here or let each widget create its own context.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 4. Writing Your First Document (.rml) ==&lt;br /&gt;
&lt;br /&gt;
Create a file at &amp;lt;code&amp;gt;luaui/widgets/my_widget/my_widget.rml&amp;lt;/code&amp;gt;. RML is well-formed XML, so every tag must be closed and attribute values must be quoted.&lt;br /&gt;
&lt;br /&gt;
=== 4.1 Root structure ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;rml&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;My Widget&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;!-- External stylesheet (optional) --&amp;gt;&lt;br /&gt;
    &amp;lt;link type=&amp;quot;text/rcss&amp;quot; href=&amp;quot;my_widget.rcss&amp;quot;/&amp;gt;&lt;br /&gt;
    &amp;lt;!-- Inline styles --&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        body { margin: 0; padding: 0; }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;!-- Your content here --&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/rml&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4.2 Styles (inline and .rcss) ===&lt;br /&gt;
&lt;br /&gt;
RmlUI starts with &#039;&#039;&#039;no default styles&#039;&#039;&#039; at all — not even block/inline defaults. The RmlUI docs provide an HTML4 base stylesheet you can copy in, but you will need to add form element styles yourself.&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;dp&amp;lt;/code&amp;gt; units instead of &amp;lt;code&amp;gt;px&amp;lt;/code&amp;gt; so your UI scales correctly with the player&#039;s display and &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; setting.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;css&amp;quot;&amp;gt;&lt;br /&gt;
/* Typical widget container */&lt;br /&gt;
#my-widget {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    width: 400dp;&lt;br /&gt;
    right: 10dp;&lt;br /&gt;
    top: 50%;&lt;br /&gt;
    transform: translateY(-50%);&lt;br /&gt;
    pointer-events: auto;   /* required to receive mouse input */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#my-widget .panel {&lt;br /&gt;
    padding: 10dp;&lt;br /&gt;
    border-radius: 8dp;&lt;br /&gt;
    border: 1dp #6c7086;   /* note: no &#039;solid&#039; keyword – see section 11 */&lt;br /&gt;
    background-color: rgba(30, 30, 46, 200);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 4.3 Data bindings in RML ===&lt;br /&gt;
&lt;br /&gt;
Attach a data model to a root element with &amp;lt;code&amp;gt;data-model=&amp;quot;model_name&amp;quot;&amp;lt;/code&amp;gt;. All data binding attributes inside that element can reference the model.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
  &amp;lt;!-- data-model scopes all bindings inside this div --&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;my-widget&amp;quot; data-model=&amp;quot;my_model&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Text interpolation --&amp;gt;&lt;br /&gt;
    &amp;lt;p&amp;gt;Status: {{status_message}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Conditional visibility --&amp;gt;&lt;br /&gt;
    &amp;lt;div data-if=&amp;quot;show_details&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Extra details go here.&amp;lt;/p&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Dynamic class based on model value --&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;panel&amp;quot; data-class-active=&amp;quot;is_active&amp;quot;&amp;gt;&lt;br /&gt;
        Active panel&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Loop over an array --&amp;gt;&lt;br /&gt;
    &amp;lt;ul&amp;gt;&lt;br /&gt;
        &amp;lt;li data-for=&amp;quot;item, i: items&amp;quot;&amp;gt;{{i}}: {{item.label}}&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Two-way checkbox binding --&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; data-checked=&amp;quot;is_enabled&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- Data event: calls model function on click --&amp;gt;&lt;br /&gt;
    &amp;lt;button data-click=&amp;quot;on_button_click()&amp;quot;&amp;gt;Click me&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Data binding reference:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Binding !! Effect&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{value}}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; || Interpolate model value as text&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-model=&amp;quot;name&amp;quot;&amp;lt;/code&amp;gt; || Attach named data model to this element and its children&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-if=&amp;quot;flag&amp;quot;&amp;lt;/code&amp;gt; || Show element only when flag is truthy&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-class-X=&amp;quot;flag&amp;quot;&amp;lt;/code&amp;gt; || Add class X when flag is truthy&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-for=&amp;quot;v, i: arr&amp;quot;&amp;lt;/code&amp;gt; || Repeat element for each item in array arr&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-checked=&amp;quot;val&amp;quot;&amp;lt;/code&amp;gt; || Two-way bind checkbox to boolean model value&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Call model function on click (data event)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data-attr-src=&amp;quot;val&amp;quot;&amp;lt;/code&amp;gt; || Dynamically set the src attribute from model value&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 5. The Lua Widget ==&lt;br /&gt;
&lt;br /&gt;
Each widget is a Lua file that the handler loads. The &amp;lt;code&amp;gt;widget&amp;lt;/code&amp;gt; global is injected by the widget handler and provides the lifecycle hooks.&lt;br /&gt;
&lt;br /&gt;
=== 5.1 Widget skeleton ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- luaui/widgets/my_widget/my_widget.lua&lt;br /&gt;
if not RmlUi then return end   -- guard: RmlUi not available&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name    = &amp;quot;My Widget&amp;quot;,&lt;br /&gt;
        desc    = &amp;quot;Demonstrates RmlUI basics.&amp;quot;,&lt;br /&gt;
        author  = &amp;quot;YourName&amp;quot;,&lt;br /&gt;
        date    = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer   = 0,&lt;br /&gt;
        enabled = true,&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5.2 Loading the document ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local RML_FILE  = &amp;quot;luaui/widgets/my_widget/my_widget.rml&amp;quot;&lt;br /&gt;
local MODEL_NAME = &amp;quot;my_model&amp;quot;&lt;br /&gt;
&lt;br /&gt;
local rml_ctx    -- the RmlUi Context&lt;br /&gt;
local dm_handle  -- the DataModel handle (MUST be kept – see section 6)&lt;br /&gt;
local document   -- the loaded Document&lt;br /&gt;
&lt;br /&gt;
-- Initial data model state&lt;br /&gt;
local init_model = {&lt;br /&gt;
    status_message = &amp;quot;Ready&amp;quot;,&lt;br /&gt;
    is_enabled     = false,&lt;br /&gt;
    show_details   = false,&lt;br /&gt;
    items = {&lt;br /&gt;
        { label = &amp;quot;Alpha&amp;quot;, value = 1 },&lt;br /&gt;
        { label = &amp;quot;Beta&amp;quot;,  value = 2 },&lt;br /&gt;
    },&lt;br /&gt;
    -- Functions can live in the model too (callable from data-events)&lt;br /&gt;
    on_button_click = function()&lt;br /&gt;
        Spring.Echo(&amp;quot;Button was clicked!&amp;quot;)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function widget:Initialize()&lt;br /&gt;
    rml_ctx = RmlUi.GetContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    -- IMPORTANT: assign dm_handle or the engine WILL crash on RML render&lt;br /&gt;
    dm_handle = rml_ctx:OpenDataModel(MODEL_NAME, init_model)&lt;br /&gt;
    if not dm_handle then&lt;br /&gt;
        Spring.Echo(&amp;quot;[my_widget] Failed to open data model&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    document = rml_ctx:LoadDocument(RML_FILE, widget)&lt;br /&gt;
    if not document then&lt;br /&gt;
        Spring.Echo(&amp;quot;[my_widget] Failed to load document&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    document:ReloadStyleSheet()&lt;br /&gt;
    document:Show()&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 5.3 Shutting down cleanly ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
function widget:Shutdown()&lt;br /&gt;
    if document then&lt;br /&gt;
        document:Close()&lt;br /&gt;
        document = nil&lt;br /&gt;
    end&lt;br /&gt;
    -- Remove the model from the context; frees memory&lt;br /&gt;
    rml_ctx:RemoveDataModel(MODEL_NAME)&lt;br /&gt;
    dm_handle = nil&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 6. Data Models in Depth ==&lt;br /&gt;
&lt;br /&gt;
=== 6.1 Opening a data model ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;context:OpenDataModel(name, table)&amp;lt;/code&amp;gt; copies the structure of the table into a reactive proxy. The name must match the &amp;lt;code&amp;gt;data-model=&amp;quot;name&amp;quot;&amp;lt;/code&amp;gt; attribute in your RML. It must be unique within the context.&lt;br /&gt;
&lt;br /&gt;
{{Warning|You &#039;&#039;&#039;must&#039;&#039;&#039; store the return value of &amp;lt;code&amp;gt;OpenDataModel&amp;lt;/code&amp;gt; in a variable. Letting it be garbage-collected while the document is open will crash the engine the next time RmlUI tries to render a data binding.}}&lt;br /&gt;
&lt;br /&gt;
=== 6.2 Reading and writing values ===&lt;br /&gt;
&lt;br /&gt;
For simple string/number/boolean values at the top level of the model you can use dot-notation directly on the handle:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Read a value&lt;br /&gt;
local msg = dm_handle.status_message   -- works&lt;br /&gt;
&lt;br /&gt;
-- Write a value (auto-marks the field dirty; UI updates next frame)&lt;br /&gt;
dm_handle.status_message = &amp;quot;Unit selected&amp;quot;&lt;br /&gt;
dm_handle.is_enabled     = true&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6.3 Iterating arrays ===&lt;br /&gt;
&lt;br /&gt;
Because the handle is a sol2 proxy, you cannot iterate it directly with &amp;lt;code&amp;gt;pairs&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ipairs&amp;lt;/code&amp;gt;. Call &amp;lt;code&amp;gt;dm_handle:__GetTable()&amp;lt;/code&amp;gt; to get the underlying Lua table:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local tbl = dm_handle:__GetTable()&lt;br /&gt;
&lt;br /&gt;
for i, item in ipairs(tbl.items) do&lt;br /&gt;
    Spring.Echo(i, item.label, item.value)&lt;br /&gt;
    item.value = item.value + 1   -- modify in place&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 6.4 Marking dirty ===&lt;br /&gt;
&lt;br /&gt;
When you modify nested values (e.g. elements inside an array) through &amp;lt;code&amp;gt;__GetTable()&amp;lt;/code&amp;gt;, you must tell the model which top-level key changed so that RmlUI re-renders the bound elements:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- After modifying tbl.items …&lt;br /&gt;
dm_handle:__SetDirty(&amp;quot;items&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
-- For a simple top-level assignment via dm_handle.key = value,&lt;br /&gt;
-- dirty-marking is done automatically.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 7. Events ==&lt;br /&gt;
&lt;br /&gt;
Recoil&#039;s RmlUI supports two distinct event families. They look similar but have different scopes — mixing them up is a common source of confusion.&lt;br /&gt;
&lt;br /&gt;
=== 7.1 Normal events (on*) ===&lt;br /&gt;
&lt;br /&gt;
Written as &amp;lt;code&amp;gt;onclick=&amp;quot;myFunc()&amp;quot;&amp;lt;/code&amp;gt;. The function is resolved from the &#039;&#039;&#039;second argument&#039;&#039;&#039; passed to &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; — the widget table. These events do &#039;&#039;&#039;not&#039;&#039;&#039; have access to the data model.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Lua&lt;br /&gt;
local doc_scope = {&lt;br /&gt;
    greet = function(name)&lt;br /&gt;
        Spring.Echo(&amp;quot;Hello&amp;quot;, name)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
document = rml_ctx:LoadDocument(RML_FILE, doc_scope)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- RML --&amp;gt;&lt;br /&gt;
&amp;lt;button onclick=&amp;quot;greet(&#039;World&#039;)&amp;quot;&amp;gt;Say hello&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 7.2 Data events (data-*) ===&lt;br /&gt;
&lt;br /&gt;
Written as &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt;. The function is resolved from the &#039;&#039;&#039;data model&#039;&#039;&#039;. These events have full access to all model values.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Lua model&lt;br /&gt;
local init_model = {&lt;br /&gt;
    counter = 0,&lt;br /&gt;
    increment = function()&lt;br /&gt;
        -- dm_handle is captured in the closure&lt;br /&gt;
        dm_handle.counter = dm_handle.counter + 1&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- RML --&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Count: {{counter}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;button data-click=&amp;quot;increment()&amp;quot;&amp;gt;+1&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Event type comparison:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Type !! Syntax example !! Scope&lt;br /&gt;
|-&lt;br /&gt;
| Normal || &amp;lt;code&amp;gt;onclick=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; second argument (widget table)&lt;br /&gt;
|-&lt;br /&gt;
| Data || &amp;lt;code&amp;gt;data-click=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Data model table&lt;br /&gt;
|-&lt;br /&gt;
| Normal || &amp;lt;code&amp;gt;onmouseover=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; second argument&lt;br /&gt;
|-&lt;br /&gt;
| Data || &amp;lt;code&amp;gt;data-mouseover=&amp;quot;fn()&amp;quot;&amp;lt;/code&amp;gt; || Data model table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 8. Custom Recoil Elements ==&lt;br /&gt;
&lt;br /&gt;
Beyond standard HTML elements, Recoil adds two custom RML elements.&lt;br /&gt;
&lt;br /&gt;
=== 8.1 The &amp;amp;lt;texture&amp;amp;gt; element ===&lt;br /&gt;
&lt;br /&gt;
Renders any engine texture — unit icons, map thumbnails, GL4 render targets, etc. Behaves identically to &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;img&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; except the &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute takes a &#039;&#039;&#039;Recoil texture reference string&#039;&#039;&#039; (see engine docs for the full format).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- Load a file from VFS --&amp;gt;&lt;br /&gt;
&amp;lt;texture src=&amp;quot;unitpics/armcom.png&amp;quot; width=&amp;quot;64dp&amp;quot; height=&amp;quot;64dp&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- Reference a named GL texture --&amp;gt;&lt;br /&gt;
&amp;lt;texture src=&amp;quot;%luaui:myTextureName&amp;quot; width=&amp;quot;128dp&amp;quot; height=&amp;quot;128dp&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 8.2 The &amp;amp;lt;svg&amp;amp;gt; element ===&lt;br /&gt;
&lt;br /&gt;
Renders an SVG image. Unlike upstream RmlUI, the Recoil implementation accepts either a file path &#039;&#039;&#039;or raw inline SVG data&#039;&#039;&#039; in the &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;!-- External file --&amp;gt;&lt;br /&gt;
&amp;lt;svg src=&amp;quot;images/icon.svg&amp;quot; width=&amp;quot;32dp&amp;quot; height=&amp;quot;32dp&amp;quot;/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- Inline SVG data --&amp;gt;&lt;br /&gt;
&amp;lt;svg src=&amp;quot;&amp;lt;svg xmlns=&#039;http://www.w3.org/2000/svg&#039; viewBox=&#039;0 0 10 10&#039;&amp;gt;&amp;lt;circle cx=&#039;5&#039; cy=&#039;5&#039; r=&#039;4&#039; fill=&#039;red&#039;/&amp;gt;&amp;lt;/svg&amp;gt;&amp;quot;&lt;br /&gt;
     width=&amp;quot;32dp&amp;quot; height=&amp;quot;32dp&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 9. Debugging ==&lt;br /&gt;
&lt;br /&gt;
RmlUI ships with a DOM debugger that you can open in-game to inspect elements, check computed styles, and view event logs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Enable the debugger for the shared context.&lt;br /&gt;
-- Call this AFTER the context has been created.&lt;br /&gt;
RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
&lt;br /&gt;
-- A handy pattern: toggle via a build flag in rml_setup.lua&lt;br /&gt;
local DEBUG_RMLUI = true   -- set false before shipping&lt;br /&gt;
if DEBUG_RMLUI then&lt;br /&gt;
    RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once enabled, a small panel appears in the game window. Click &#039;&#039;&#039;Debug&#039;&#039;&#039; to open the inspector or &#039;&#039;&#039;Log&#039;&#039;&#039; for the event log.&lt;br /&gt;
&lt;br /&gt;
{{Note|The debugger attaches to one context at a time. Using the shared context means you see all widgets in one place — very useful for tracking cross-widget interactions.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 10. Best Practices ==&lt;br /&gt;
&lt;br /&gt;
; Use dp units everywhere&lt;br /&gt;
: The &amp;lt;code&amp;gt;dp&amp;lt;/code&amp;gt; unit scales with &amp;lt;code&amp;gt;context.dp_ratio&amp;lt;/code&amp;gt;, so your UI automatically adapts to different resolutions and the player&#039;s &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; setting.&lt;br /&gt;
&lt;br /&gt;
; One shared context per game&lt;br /&gt;
: Beyond All Reason and most community projects use a single &amp;lt;code&amp;gt;&#039;shared&#039;&amp;lt;/code&amp;gt; context. Documents in the same context can reference the same data models and are Z-ordered relative to each other.&lt;br /&gt;
&lt;br /&gt;
; Keep data models flat when possible&lt;br /&gt;
: Top-level writes via &amp;lt;code&amp;gt;dm_handle.key = value&amp;lt;/code&amp;gt; are automatically dirty-marked. Deeply nested structures require manual &amp;lt;code&amp;gt;__SetDirty&amp;lt;/code&amp;gt; calls — simpler models mean fewer bugs.&lt;br /&gt;
&lt;br /&gt;
; Separate per-document and shared styles&lt;br /&gt;
: Put styles unique to one document in a &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;style&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; block inside the &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; file. Put styles shared across multiple documents in a &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file and link it.&lt;br /&gt;
&lt;br /&gt;
; Guard against missing RmlUi&lt;br /&gt;
: Always start widgets with &amp;lt;code&amp;gt;if not RmlUi then return end&amp;lt;/code&amp;gt; to prevent errors in environments where RmlUI is unavailable.&lt;br /&gt;
&lt;br /&gt;
; Check return values&lt;br /&gt;
: Both &amp;lt;code&amp;gt;OpenDataModel&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;LoadDocument&amp;lt;/code&amp;gt; can fail silently if paths are wrong. Always check for nil and log an error so you know immediately what happened.&lt;br /&gt;
&lt;br /&gt;
; Organise by widget folder&lt;br /&gt;
: Group &amp;lt;code&amp;gt;luaui/widgets/my_widget/my_widget.lua&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; together. This makes the project navigable and each widget self-contained.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 11. RCSS Gotchas ==&lt;br /&gt;
&lt;br /&gt;
RCSS closely follows CSS2 but has several important differences that will trip up web developers:&lt;br /&gt;
&lt;br /&gt;
; No default stylesheet&lt;br /&gt;
: RmlUI ships with zero default styles. Block/inline, heading sizes, list bullets — none of it exists unless you add it. Copy the HTML4 base sheet from the RmlUI docs as a starting point.&lt;br /&gt;
&lt;br /&gt;
; RGBA alpha is 0–255&lt;br /&gt;
: &amp;lt;code&amp;gt;rgba(255, 0, 0, 128)&amp;lt;/code&amp;gt; means 50% transparent red. CSS uses 0.0–1.0 for alpha. The &amp;lt;code&amp;gt;opacity&amp;lt;/code&amp;gt; property still uses 0.0–1.0.&lt;br /&gt;
&lt;br /&gt;
; Border shorthand has no style keyword&lt;br /&gt;
: &amp;lt;code&amp;gt;border: 1dp #6c7086;&amp;lt;/code&amp;gt; works. &amp;lt;code&amp;gt;border: 1dp solid #6c7086;&amp;lt;/code&amp;gt; does &#039;&#039;&#039;not&#039;&#039;&#039; work. Only solid borders are supported; &amp;lt;code&amp;gt;border-style&amp;lt;/code&amp;gt; is unavailable.&lt;br /&gt;
&lt;br /&gt;
; background-* properties use decorators&lt;br /&gt;
: &amp;lt;code&amp;gt;background-color&amp;lt;/code&amp;gt; works normally. &amp;lt;code&amp;gt;background-image&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;background-size&amp;lt;/code&amp;gt;, etc. are replaced by the decorator system. See the RmlUI docs for syntax.&lt;br /&gt;
&lt;br /&gt;
; No list styling&lt;br /&gt;
: &amp;lt;code&amp;gt;list-style-type&amp;lt;/code&amp;gt; and friends are not supported. &amp;lt;code&amp;gt;ul&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;ol&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;li&amp;lt;/code&amp;gt; have no special rendering — treat them as plain div-like elements.&lt;br /&gt;
&lt;br /&gt;
; Input text is set by content, not value&lt;br /&gt;
: For &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;input type=&amp;quot;button&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;input type=&amp;quot;submit&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; the displayed text comes from the element&#039;s text content, not the &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; attribute (unlike HTML).&lt;br /&gt;
&lt;br /&gt;
; pointer-events: auto required&lt;br /&gt;
: By default elements do not receive mouse events. Add &amp;lt;code&amp;gt;pointer-events: auto&amp;lt;/code&amp;gt; to any element that needs to be interactive.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 12. Differences from Upstream RmlUI ==&lt;br /&gt;
&lt;br /&gt;
The Recoil implementation is based on the official RmlUI library but adds engine-specific features and changes some defaults:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Feature !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&amp;lt;texture&amp;gt;&amp;lt;/nowiki&amp;gt; element&#039;&#039;&#039; || A custom element that renders engine-managed textures via Recoil texture reference strings. Not present in upstream.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&amp;lt;nowiki&amp;gt;&amp;lt;svg&amp;gt;&amp;lt;/nowiki&amp;gt; inline data&#039;&#039;&#039; || The &amp;lt;code&amp;gt;src&amp;lt;/code&amp;gt; attribute accepts raw SVG markup in addition to file paths. Upstream only supports file paths.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;VFS file loader&#039;&#039;&#039; || All file paths are resolved through Recoil&#039;s Virtual File System, so &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files work inside &amp;lt;code&amp;gt;.sdz&amp;lt;/code&amp;gt; game archives transparently.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;Lua bindings via sol2&#039;&#039;&#039; || The Lua API is provided by RmlSolLua (derived from [https://github.com/LoneBoco/RmlSolLua LoneBoco/RmlSolLua]) rather than the upstream Lua plugin, offering tighter engine integration.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;dp_ratio via context&#039;&#039;&#039; || &amp;lt;code&amp;gt;context.dp_ratio&amp;lt;/code&amp;gt; is set from code rather than the system DPI, allowing games to honour the player&#039;s &amp;lt;code&amp;gt;ui_scale&amp;lt;/code&amp;gt; preference.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{{Note|For everything not listed above, the upstream RmlUI documentation at [https://mikke89.github.io/RmlUiDoc/ mikke89.github.io/RmlUiDoc] applies directly.}}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 13. IDE Setup ==&lt;br /&gt;
&lt;br /&gt;
Configuring your editor for &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; file extensions dramatically improves the authoring experience.&lt;br /&gt;
&lt;br /&gt;
=== VS Code ===&lt;br /&gt;
&lt;br /&gt;
# Open any &amp;lt;code&amp;gt;.rml&amp;lt;/code&amp;gt; file in VS Code.&lt;br /&gt;
# Click the language mode indicator in the status bar (usually shows &#039;&#039;Plain Text&#039;&#039;).&lt;br /&gt;
# Choose &#039;&#039;&#039;Configure File Association for &#039;.rml&#039;&#039;&#039;&#039;&lt;br /&gt;
# Select &#039;&#039;&#039;HTML&#039;&#039;&#039; from the list.&lt;br /&gt;
# Repeat for &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files, selecting &#039;&#039;&#039;CSS&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Lua Language Server (lua-ls) ===&lt;br /&gt;
&lt;br /&gt;
Add the Recoil type annotations to your workspace for autocomplete on &amp;lt;code&amp;gt;RmlUi&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Spring&amp;lt;/code&amp;gt;, and all widget APIs. See the Recoil docs guide on the Lua Language Server for details.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 14. Quick Reference ==&lt;br /&gt;
&lt;br /&gt;
=== Lua API ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Call !! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.CreateContext(name)&amp;lt;/code&amp;gt; || Create a new rendering context&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.GetContext(name)&amp;lt;/code&amp;gt; || Retrieve an existing context by name&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.LoadFontFace(path, fallback)&amp;lt;/code&amp;gt; || Register a &amp;lt;code&amp;gt;.ttf&amp;lt;/code&amp;gt; font (&amp;lt;code&amp;gt;fallback=true&amp;lt;/code&amp;gt; recommended)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.SetMouseCursorAlias(css, engine)&amp;lt;/code&amp;gt; || Map a CSS cursor name to an engine cursor name&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;RmlUi.SetDebugContext(name)&amp;lt;/code&amp;gt; || Attach the DOM debugger to a named context&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:OpenDataModel(name, tbl)&amp;lt;/code&amp;gt; || Create reactive data model from Lua table; &#039;&#039;&#039;store the return value!&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:RemoveDataModel(name)&amp;lt;/code&amp;gt; || Destroy a data model and free its memory&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:LoadDocument(path, scope)&amp;lt;/code&amp;gt; || Parse an RML file; scope is used for normal (&amp;lt;code&amp;gt;on*&amp;lt;/code&amp;gt;) events&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Show()&amp;lt;/code&amp;gt; || Make the document visible&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Hide()&amp;lt;/code&amp;gt; || Hide the document (keeps it in memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:Close()&amp;lt;/code&amp;gt; || Destroy the document&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;doc:ReloadStyleSheet()&amp;lt;/code&amp;gt; || Reload linked &amp;lt;code&amp;gt;.rcss&amp;lt;/code&amp;gt; files (useful during development)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm:__GetTable()&amp;lt;/code&amp;gt; || Get the underlying Lua table for iteration&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm:__SetDirty(key)&amp;lt;/code&amp;gt; || Mark a top-level model key as changed; triggers re-render&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;dm.key = value&amp;lt;/code&amp;gt; || Write a top-level value; auto-marks dirty&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Useful Links ===&lt;br /&gt;
&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/ RmlUI official documentation]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/data_bindings/views_and_controllers.html RmlUI data bindings reference]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rcss.html RmlUI RCSS reference]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html RmlUI HTML4 base stylesheet]&lt;br /&gt;
* [https://mikke89.github.io/RmlUiDoc/pages/rcss/decorators.html RmlUI decorators (backgrounds)]&lt;br /&gt;
* [https://github.com/mikke89/RmlUi RmlUI GitHub]&lt;br /&gt;
* [https://github.com/beyond-all-reason/RecoilEngine/tree/master/rts/Rml RecoilEngine source (rts/Rml/)]&lt;br /&gt;
* Recoil texture reference strings — see engine docs: &amp;lt;code&amp;gt;articles/texture-reference-strings&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Guides]]&lt;br /&gt;
[[Category:LuaUI]]&lt;br /&gt;
[[Category:RmlUI]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Finalizing_linux_install&amp;diff=2679</id>
		<title>Finalizing linux install</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Finalizing_linux_install&amp;diff=2679"/>
		<updated>2026-03-05T05:16:55Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;= Finalizing installation = You should have Spring installed by now; if not, please see the SetupGuide.  == Maps &amp;amp; Mods ==  Since Spring is only an engine, you need to download games for it, and the maps that go with those games.   SpringLobby will automatically do this for you when you attempt to join a game with a map or mod that you do not currently have, using an integrated downloader.  If you want to download it manually or browse existing maps, there are se...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Finalizing installation =&lt;br /&gt;
You should have Spring installed by now; if not, please see the [[SetupGuide]].&lt;br /&gt;
&lt;br /&gt;
== Maps &amp;amp; Mods ==&lt;br /&gt;
&lt;br /&gt;
Since Spring is only an engine, you need to download [[games]] for it, and the maps that go with those games. &lt;br /&gt;
&lt;br /&gt;
SpringLobby will automatically do this for you when you attempt to join a game with a map or mod that you do not currently have, using an integrated downloader.&lt;br /&gt;
&lt;br /&gt;
If you want to download it manually or browse existing maps, there are several easy to use websites:&lt;br /&gt;
*[http://spring.jobjol.nl/subcategory.php?id=1 SpringFiles]&lt;br /&gt;
*[http://spring-portal.com/ Spring-Portal]&lt;br /&gt;
*[http://www.darkstars.co.uk/downloads/index.php?dir=spring Darkstars]&lt;br /&gt;
&lt;br /&gt;
Pick any of those sites, download a map or mod, and then double click it. This will move it to your Spring folder, using the spring map/mod installer program. If you wish to move maps/mods by hand, they are located under &amp;lt;code&amp;gt;~/.spring/maps/&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;~/.spring/mods/&amp;lt;/code&amp;gt; (if the folder does not exist - create it). Do not unpack the file in any way though, just download and save it. Reload your maps/mods in SpringLobby, (&#039;&#039;Tools - Reload maps/mods&#039;&#039;) and they will be ready to use.&lt;br /&gt;
&lt;br /&gt;
These sites also may provide other neat things like Lua widgets, epic replays, AI bots (for playing offline or to team up on with a friend). Generally the installation requires you to download the file and place it in the appropriate folder in your &#039;&#039;~/.spring&#039;&#039; directory. (you may have to create the folder)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Hint:&#039;&#039; If you know the name of the map you want, there is a Search function provided in those websites. Just write a part of the name, like one word, and you will easily find the map you were looking for.&lt;br /&gt;
&lt;br /&gt;
[[Category:Linux]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Main_Page&amp;diff=2678</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Main_Page&amp;diff=2678"/>
		<updated>2026-03-05T05:13:43Z</updated>

		<summary type="html">&lt;p&gt;Qrow: /* Welcome to Fightorder! */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:&amp;lt;span style=&amp;quot;position: absolute; clip: rect(1px 1px 1px 1px); clip: rect(1px, 1px, 1px, 1px);&amp;quot;&amp;gt;{{FULLPAGENAME}}&amp;lt;/span&amp;gt;}}&lt;br /&gt;
[[File:Fightorderlonglogo.png]]&lt;br /&gt;
&lt;br /&gt;
== Welcome to Fightorder! ==&lt;br /&gt;
&lt;br /&gt;
We are a resource repository for SpringRTS and Recoil Engine powered games. &lt;br /&gt;
We primarily host information pertaining to Zero-K and BAR, and for development of resources of those games.&lt;br /&gt;
&lt;br /&gt;
More than that planned for future development!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Click on a game or feature below to see its page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;imagemap&amp;gt;Image:menu.png|800px|&lt;br /&gt;
rect 80 6 223 655 [[Spring Engine|Spring Engine]]&lt;br /&gt;
rect 325 1 470 672 [[Recoil|Recoil]]&lt;br /&gt;
rect 569 7 721 666 [[Zero-K|Zero-K]]&lt;br /&gt;
rect 798 3 944 667 [[BAR|BAR]]&lt;br /&gt;
rect 1036 3 1186 666 [[Metal Factions|Metal Factions]]&lt;br /&gt;
rect 1273 7 1416 671 [[EVO RTS|EVO RTS]]&lt;br /&gt;
rect 1510 1 1713 666 [[Kernel Panic|Kernel Panic]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/imagemap&amp;gt;&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUi&amp;diff=2677</id>
		<title>Recoil:RmlUi</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Recoil:RmlUi&amp;diff=2677"/>
		<updated>2026-03-05T04:48:07Z</updated>

		<summary type="html">&lt;p&gt;Qrow: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;RmlUi is a UI framework that is defined using a HTML/CSS style workflow (using Lua instead of JS) intended to simplify UI development especially for those already familiar with web development. It is designed for interactive applications, and so is reactive by default. You can learn more about it on the [RmlUI website] and [differences in the Recoil version here](#differences-between-upstream-rmlui-and-rmlui-in-recoil).&lt;br /&gt;
&lt;br /&gt;
== How does RmlUI Work? ==&lt;br /&gt;
&lt;br /&gt;
To get started, it&#039;s important to learn a few key concepts.&lt;br /&gt;
- Context: This is a bundle of documents and data models.&lt;br /&gt;
- Document: This is a document tree/DOM (document object model) holding the actual UI.&lt;br /&gt;
- RML: This is the markup language used to define the document, it is very similar to XHTML (HTML but must be well-formed XML).&lt;br /&gt;
- RCSS: This is the styling language used to style the document, it is very similar to CSS2 but does differ in some places.&lt;br /&gt;
- Data Model: This holds information (a Lua table) that is used in the UI code in data bindings.&lt;br /&gt;
&lt;br /&gt;
On the Lua side, you are creating 1 or more contexts and adding data models and documents to them.&lt;br /&gt;
&lt;br /&gt;
On the Rml side, you are creating documents to be loaded by the Lua which will likely include data bindings to show information from the data model, and references to lua functions for event handlers.&lt;br /&gt;
&lt;br /&gt;
As each widget/component you create will likely comprise of several files (.lua, .rml &amp;amp; .rcss) you may find having a folder for each widget/component a useful way to organise your files.&lt;br /&gt;
&lt;br /&gt;
# Getting Started&lt;br /&gt;
 &lt;br /&gt;
RmlUi is available in LuaUI (the game UI) with future availability in LuaIntro &amp;amp; LuaMenu planned, so it is already there for you to use. &lt;br /&gt;
However, to get going with it there is some setup code that should be considered for loading fonts, cursors and configuring ui scaling, an example script is provided below.&lt;br /&gt;
If you are working on a widget for an existing game, it is likely that the game already has some form of this setup code in, so you may be able to skip this section.&lt;br /&gt;
&lt;br /&gt;
### Setup script&lt;br /&gt;
&lt;br /&gt;
Here is the &amp;quot;[handler](https://github.com/beyond-all-reason/Beyond-All-Reason/blob/master/luaui/rml_setup.lua)&amp;quot;, written by lov and ChrisFloofyKitsune, 2 of the people responsible for the RmlUi implementation.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
--  luaui/rml_setup.lua&lt;br /&gt;
--  author:  lov + ChrisFloofyKitsune&lt;br /&gt;
--&lt;br /&gt;
--  Copyright (C) 2024.&lt;br /&gt;
--  Licensed under the terms of the GNU GPL, v2 or later.&lt;br /&gt;
&lt;br /&gt;
if (RmlGuard or not RmlUi) then&lt;br /&gt;
	return&lt;br /&gt;
end&lt;br /&gt;
-- don&#039;t allow this initialization code to be run multiple times&lt;br /&gt;
RmlGuard = true&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
	Recoil uses a custom set of Lua bindings (check out rts/Rml/SolLua/bind folder in the C++ engine code)&lt;br /&gt;
	Aside from the Lua API, the rest of the RmlUi documentation is still relevant&lt;br /&gt;
		https://mikke89.github.io/RmlUiDoc/index.html&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
--[[ create a common Context to be used for widgets&lt;br /&gt;
	pros:&lt;br /&gt;
		* Documents in the same Context can make use of the same DataModels, allowing for less duplicate data&lt;br /&gt;
		* Documents can be arranged in front/behind of each other dynamically&lt;br /&gt;
	cons:&lt;br /&gt;
		* Documents in the same Context can make use of the same data models, leading to side effects&lt;br /&gt;
		* DataModels must have unique names within the same Context&lt;br /&gt;
&lt;br /&gt;
	If you have lots of DataModel use you may want to create your own Context&lt;br /&gt;
	otherwise you should be able to just use the shared Context&lt;br /&gt;
&lt;br /&gt;
	Contexts created with the Lua API are automatically disposed of when the LuaUi environment is unloaded&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local oldCreateContext = RmlUi.CreateContext&lt;br /&gt;
&lt;br /&gt;
local function NewCreateContext(name)&lt;br /&gt;
	local context = oldCreateContext(name)&lt;br /&gt;
&lt;br /&gt;
	-- set up dp_ratio considering the user&#039;s UI scale preference and the screen resolution&lt;br /&gt;
	local viewSizeX, viewSizeY = Spring.GetViewGeometry()&lt;br /&gt;
&lt;br /&gt;
	local userScale = Spring.GetConfigFloat(&amp;quot;ui_scale&amp;quot;, 1)&lt;br /&gt;
&lt;br /&gt;
	local baseWidth = 1920&lt;br /&gt;
	local baseHeight = 1080&lt;br /&gt;
	local resFactor = math.min(viewSizeX / baseWidth, viewSizeY / baseHeight)&lt;br /&gt;
&lt;br /&gt;
	context.dp_ratio = resFactor * userScale&lt;br /&gt;
&lt;br /&gt;
	context.dp_ratio = math.floor(context.dp_ratio * 100) / 100&lt;br /&gt;
	return context&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
RmlUi.CreateContext = NewCreateContext&lt;br /&gt;
&lt;br /&gt;
-- Load fonts&lt;br /&gt;
local font_files = {&lt;br /&gt;
}&lt;br /&gt;
for _, file in ipairs(font_files) do&lt;br /&gt;
	Spring.Echo(&amp;quot;loading font&amp;quot;, file)&lt;br /&gt;
	RmlUi.LoadFontFace(file, true)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Mouse Cursor Aliases&lt;br /&gt;
--[[&lt;br /&gt;
	These let standard CSS cursor names be used when doing styling.&lt;br /&gt;
	If a cursor set via RCSS does not have an alias, it is unchanged.&lt;br /&gt;
	CSS cursor list: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor&lt;br /&gt;
	RmlUi documentation: https://mikke89.github.io/RmlUiDoc/pages/rcss/user_interface.html#cursor&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- when &amp;quot;cursor: normal&amp;quot; is set via RCSS, &amp;quot;cursornormal&amp;quot; will be sent to the engine... and so on for the rest&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;default&amp;quot;, &#039;cursornormal&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;pointer&amp;quot;, &#039;Move&#039;) -- command cursors use the command name. TODO: replace with actual pointer cursor?&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;move&amp;quot;, &#039;uimove&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;nesw-resize&amp;quot;, &#039;uiresized2&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;nwse-resize&amp;quot;, &#039;uiresized1&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;ns-resize&amp;quot;, &#039;uiresizev&#039;)&lt;br /&gt;
RmlUi.SetMouseCursorAlias(&amp;quot;ew-resize&amp;quot;, &#039;uiresizeh&#039;)&lt;br /&gt;
&lt;br /&gt;
RmlUi.CreateContext(&amp;quot;shared&amp;quot;)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
What this does is create a unified context &#039;shared&#039; for all your documents and data models, which is currently the recommended way to architect documents. If you have any custom font files, list them in `font_files`, otherwise leave it empty.&lt;br /&gt;
&lt;br /&gt;
The setup script above can then be included from your `luaui/main.lua` (main.lua is the entry point for the LuaUI environment).&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
VFS.Include(LUAUI_DIRNAME .. &amp;quot;rml_setup.lua&amp;quot;,  nil, VFS.ZIP) -- Runs the script&lt;br /&gt;
```&lt;br /&gt;
&amp;gt; [!NOTE] The rml_setup.lua script included in base content only imports a font and cursor and doesn&#039;t create a context or set scaling&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### Writing Your First Document&lt;br /&gt;
&lt;br /&gt;
You will be creating files with .rml and .rcss extensions, as these closely resemble HTML and CSS it is worth configuring your editor to treat these file extensions as HTML and CSS respectively, see the [IDE Setup](#ide-setup) section for more information.&lt;br /&gt;
&lt;br /&gt;
Now, create an RML file somewhere under `luaui/widgets/`, like `luaui/widgets/getting_started.rml`. This is the UI document.&lt;br /&gt;
&lt;br /&gt;
Writing it is much like HTML by design. There are some differences, the most immediate being the root tag is called rml instead of html, most other RML/HTML differences relate to attributes for databindings and events, but for the time being, we don&#039;t need to worry about them.&lt;br /&gt;
&lt;br /&gt;
By default RmlUi has *NO* styles, this includes setting default element behaviour like block/inline and styles web developers would expect like input elements default appearances, as a starting point you can use [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though these do not include styles for form elements. &lt;br /&gt;
&lt;br /&gt;
Here&#039;s a basic widget written by Mupersega.&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;rml&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Rml Starter&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        #rml-starter-widget {&lt;br /&gt;
            pointer-events: auto;&lt;br /&gt;
            width: 400dp;&lt;br /&gt;
            right: 0;&lt;br /&gt;
            top: 50%;&lt;br /&gt;
            transform: translateY(-90%);&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            margin-right: 10dp;&lt;br /&gt;
        }&lt;br /&gt;
        #main-content {&lt;br /&gt;
            padding: 10dp;&lt;br /&gt;
            border-radius: 8dp;&lt;br /&gt;
            z-index: 1;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content {&lt;br /&gt;
            transform: translateY(0%);&lt;br /&gt;
            transition: top 0.1s linear-in-out;&lt;br /&gt;
            z-index: 0;&lt;br /&gt;
            height: 100%;&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            top: 0dp;&lt;br /&gt;
            left: 0dp;&lt;br /&gt;
            border-radius: 8dp;&lt;br /&gt;
            display: flex;&lt;br /&gt;
            flex-direction: column;&lt;br /&gt;
            justify-content: flex-end;&lt;br /&gt;
            align-items: center;&lt;br /&gt;
            padding-bottom: 20dp;&lt;br /&gt;
        }&lt;br /&gt;
        /* This is just a checkbox sitting above the entirety of the expanding content */&lt;br /&gt;
        /* It is bound directly with data-checked attr to the expanded value */&lt;br /&gt;
        #expanding-content&amp;gt;input {&lt;br /&gt;
            height: 100%;&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            z-index: 1;&lt;br /&gt;
            position: absolute;&lt;br /&gt;
            top: 0dp;&lt;br /&gt;
            left: 0dp;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content.expanded {&lt;br /&gt;
            top: 90%;&lt;br /&gt;
        }&lt;br /&gt;
        #expanding-content:hover {&lt;br /&gt;
            background-color: rgba(255, 0, 0, 125);&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;rml-starter-widget&amp;quot; class=&amp;quot;relative&amp;quot; data-model=&amp;quot;starter_model&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;div id=&amp;quot;main-content&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;h1 class=&amp;quot;text-primary&amp;quot;&amp;gt;Welcome to an Rml Starter Widget&amp;lt;/h1&amp;gt;&lt;br /&gt;
            &amp;lt;p&amp;gt;This is a simple example of an RMLUI widget.&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;div&amp;gt;&lt;br /&gt;
                &amp;lt;button onclick=&amp;quot;widget:Reload()&amp;quot;&amp;gt;reload widget&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;div id=&amp;quot;expanding-content&amp;quot; data-class-expanded=&amp;quot;expanded&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;&lt;br /&gt;
            &amp;lt;p&amp;gt;{{message}}&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;div&amp;gt;&lt;br /&gt;
                &amp;lt;span data-for=&amp;quot;test, i: testArray&amp;quot;&amp;gt;name:{{test.name}} index:{{i}}&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/rml&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
Let&#039;s take a look at different areas that are important to look at.&lt;br /&gt;
&lt;br /&gt;
`&amp;lt;div id=&amp;quot;rml-starter-widget&amp;quot; class=&amp;quot;relative&amp;quot; data-model=&amp;quot;starter_model&amp;quot;&amp;gt;`&lt;br /&gt;
1. Here, we bind to the data model using `data-model`. This is what we will need to name the data model in our Lua script later. Everything inside the model will be in scope and beneath the div.&lt;br /&gt;
2. Typically, it is recommended to bind your data model inside of a div beneath `body` rather than `body` itself.&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;div id=&amp;quot;expanding-content&amp;quot; data-class-expanded=&amp;quot;expanded&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
any attribute starting with `data-` is a &amp;quot;data event&amp;quot;. We will go through a couple below, but you can find out more here [on the RmlUi docs site](https://mikke89.github.io/RmlUiDoc/pages/data_bindings/views_and_controllers.html).&lt;br /&gt;
1. Double curly braces are used to show values from the data model within the document text. e.g. `{{message}}` shows the value of `message` in the data model.&lt;br /&gt;
2. `data-class-expanded=&amp;quot;expanded&amp;quot;` applies the `expanded` class to the div if the value `expanded` in the data model is `true`.&lt;br /&gt;
3. `&amp;lt;input type=&amp;quot;checkbox&amp;quot; value=&amp;quot;expanded&amp;quot; data-checked=&amp;quot;expanded&amp;quot;/&amp;gt;` This checkbox will set the data model value in `data-checked` to true.&lt;br /&gt;
4. When the data model is changed, the document is rerendered automatically. So, the expanding div will have the `expanded` class applied to it or removed whenever the check box is toggled.&lt;br /&gt;
&lt;br /&gt;
There are data bindings to allow you to loop through arrays (data-for) have conditional sections of the document (data-if) and many others. &lt;br /&gt;
&lt;br /&gt;
### The Lua&lt;br /&gt;
&lt;br /&gt;
&amp;gt; [!NOTE] For information on setting up your editor to provide intellisense behaviour see the [Lua Language Server guide]({{% ref &amp;quot;lua-language-server&amp;quot; %}}).&lt;br /&gt;
&lt;br /&gt;
To load your document into the shared context we created earlier and to define and add the data model you will need to have a lua script something like the one below.&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
-- luaui/widgets/getting_started.lua&lt;br /&gt;
if not RmlUi then&lt;br /&gt;
    return&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local widget = widget ---@type Widget&lt;br /&gt;
&lt;br /&gt;
function widget:GetInfo()&lt;br /&gt;
    return {&lt;br /&gt;
        name = &amp;quot;Rml Starter&amp;quot;,&lt;br /&gt;
        desc = &amp;quot;This widget is a starter example for RmlUi widgets.&amp;quot;,&lt;br /&gt;
        author = &amp;quot;Mupersega&amp;quot;,&lt;br /&gt;
        date = &amp;quot;2025&amp;quot;,&lt;br /&gt;
        license = &amp;quot;GNU GPL, v2 or later&amp;quot;,&lt;br /&gt;
        layer = -1000000,&lt;br /&gt;
        enabled = true&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local document&lt;br /&gt;
local dm_handle&lt;br /&gt;
local init_model = {&lt;br /&gt;
    expanded = false,&lt;br /&gt;
    message = &amp;quot;Hello, find my text in the data model!&amp;quot;,&lt;br /&gt;
    testArray = {&lt;br /&gt;
        { name = &amp;quot;Item 1&amp;quot;, value = 1 },&lt;br /&gt;
        { name = &amp;quot;Item 2&amp;quot;, value = 2 },&lt;br /&gt;
        { name = &amp;quot;Item 3&amp;quot;, value = 3 },&lt;br /&gt;
    },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local main_model_name = &amp;quot;starter_model&amp;quot;&lt;br /&gt;
&lt;br /&gt;
function widget:Initialize()&lt;br /&gt;
    widget.rmlContext = RmlUi.GetContext(&amp;quot;shared&amp;quot;) -- Get the context from the setup lua&lt;br /&gt;
&lt;br /&gt;
    -- Open the model, using init_model as the template. All values inside are copied.&lt;br /&gt;
    -- Returns a handle, which we will touch on later.&lt;br /&gt;
    dm_handle = widget.rmlContext:OpenDataModel(main_model_name, init_model)&lt;br /&gt;
    if not dm_handle then&lt;br /&gt;
        Spring.Echo(&amp;quot;RmlUi: Failed to open data model &amp;quot;, main_model_name)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Load the document we wrote earlier.&lt;br /&gt;
    document = widget.rmlContext:LoadDocument(&amp;quot;luaui/widgets/getting_started.rml&amp;quot;, widget)&lt;br /&gt;
    if not document then&lt;br /&gt;
        Spring.Echo(&amp;quot;Failed to load document&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- uncomment the line below to enable debugger&lt;br /&gt;
    -- RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
&lt;br /&gt;
    document:ReloadStyleSheet()&lt;br /&gt;
    document:Show()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function widget:Shutdown()&lt;br /&gt;
    widget.rmlContext:RemoveDataModel(main_model_name)&lt;br /&gt;
    if document then&lt;br /&gt;
        document:Close()&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- This function is only for dev experience, ideally it would be a hot reload, and not required at all in a completed widget.&lt;br /&gt;
function widget:Reload(event)&lt;br /&gt;
    Spring.Echo(&amp;quot;Reloading&amp;quot;)&lt;br /&gt;
    Spring.Echo(event)&lt;br /&gt;
    widget:Shutdown()&lt;br /&gt;
    widget:Initialize()&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### The Data Model Handle&lt;br /&gt;
&lt;br /&gt;
In the script, we are given a data model handle. This is a proxy for the Lua table used as the data model; as the Recoil RmlUi integration uses Sol2 as a wrapper data cannot be accessed directly.&lt;br /&gt;
&lt;br /&gt;
In most cases, you can simply do `dm_handle.expanded = true`, but this only works for table entries with string keys. What if you have an array, like `testArray` above? To loop through on the Lua side, you will need to get the underlying table:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local model_handle = dm_handle:__GetTable()&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
As of writing, this function is not documented in the Lua API, due to some problems with language server generics that haven&#039;t been sorted out yet. It is there, however, and will be added back in in the future. it returns the table with the shape of your inital model.&lt;br /&gt;
You can then iterate through it, and change things as you please:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
for i, v in pairs(model_handle.testArray) do&lt;br /&gt;
    Spring.Echo(i, v.name)&lt;br /&gt;
    v.value = v.value + 1&lt;br /&gt;
end&lt;br /&gt;
-- If you modified anything in the table, as we did here, we need to set the model handle &amp;quot;dirty&amp;quot;, so it refreshes.&lt;br /&gt;
dm_handle:__SetDirty(&amp;quot;testArray&amp;quot;) -- We modified something nested in the array, so we mark the top level table entry as dirty&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Debugging&lt;br /&gt;
&lt;br /&gt;
RmlUi comes with a debugger that lets you see and interact with the DOM. It&#039;s very handy! To use it, use this:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
RmlUi.SetDebugContext(DATA_MODEL_NAME)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
And it will appear in-game. A useful idiom I like is to put this in `rml_setup.lua`:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local DEBUG_RMLUI = true&lt;br /&gt;
&lt;br /&gt;
--...&lt;br /&gt;
&lt;br /&gt;
if DEBUG_RMLUI then&lt;br /&gt;
    RmlUi.SetDebugContext(&#039;shared&#039;)&lt;br /&gt;
end&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
If you use the shared context, you can see everything that happens in it! Neat!&lt;br /&gt;
&lt;br /&gt;
## Things to Know and Best Practices&lt;br /&gt;
&lt;br /&gt;
### Things to know&lt;br /&gt;
Some of the rough edges you are likely to run into have already been discussed, like the data model thing, but here are some more:&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
Unlike a web browser a default set of styles is not included, as a starting point you can look at the [RmlUi documentation](https://mikke89.github.io/RmlUiDoc/pages/rml/html4_style_sheet.html) though this doesn&#039;t provide styles for form elements.&lt;br /&gt;
&lt;br /&gt;
When using Context:OpenDataModel in Lua you must assign the return value to a variable, not doing so will cause the engine to crash when the model is reference in RML.&lt;br /&gt;
&lt;br /&gt;
Input elements of type submit &amp;amp; button behave differently to HTML and more like Button elements in that their text is not set by the value attribute. (This is likely to be corrected in a future version)&lt;br /&gt;
&lt;br /&gt;
The alpha/transparency value of an RGBA colour is different to CSS (0-1) and instead uses 0-255. The css opacity does still use 0-1.&lt;br /&gt;
&lt;br /&gt;
List styling is unavailable (list-style-type etc.), you can still use UL/OL/LI elements but there is no special meaning to them, whilst you could use background images to replicate bullets there isn&#039;t a practical way to achieve numbered list items with RML/RCSS.&lt;br /&gt;
&lt;br /&gt;
Only solid borders are supported, so the border-style property is unavailable and the shorthand border property doesn&#039;t include a style part (```border: 1dp solid black;``` won&#039;t work, instead use ```border: 1dp black;```).&lt;br /&gt;
&lt;br /&gt;
background-color behaves as expected, all other background styles are different and use decorators instead see the [RmlUi documentation for more information on decorators.](https://mikke89.github.io/RmlUiDoc/pages/rcss/decorators.html) &lt;br /&gt;
&lt;br /&gt;
There are two kinds of events: data events, like `data-mouseover`, and normal events, like `onmouseover`. These have different data in their scopes.&lt;br /&gt;
- Data events have the data model in their scope.&lt;br /&gt;
- Normal events don&#039;t have the data model, but they *do* have whatever is passed into `widget` on `widget.rmlContext:LoadDocument`. `widget` doesn&#039;t have to be a widget, just any table with data in it.&lt;br /&gt;
&lt;br /&gt;
For example, take this:&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
local model = {&lt;br /&gt;
    add = function(a, b)&lt;br /&gt;
        Spring.Echo(a + b)&lt;br /&gt;
    end&lt;br /&gt;
}&lt;br /&gt;
local document_table = {&lt;br /&gt;
    print = function(msg)&lt;br /&gt;
        Spring.Echo(msg)&lt;br /&gt;
    end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
dm_handle = widget.rmlContext:OpenDataModel(&amp;quot;test&amp;quot;, model)&lt;br /&gt;
document = widget.rmlContext:LoadDocument(&amp;quot;document.rml&amp;quot;, document_table)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;h1&amp;gt;Normal Events&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;add(1, 2)&amp;quot;&amp;gt;Won&#039;t work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;print(&#039;test&#039;)&amp;quot;&amp;gt;Will work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;h1&amp;gt;Data Events&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; data-click=&amp;quot;add(1, 2)&amp;quot;&amp;gt;Will work!&amp;lt;/input&amp;gt;&lt;br /&gt;
&amp;lt;input type=&amp;quot;button&amp;quot; data-click=&amp;quot;print(&#039;test&#039;)&amp;quot;&amp;gt;Won&#039;t work!&amp;lt;/input&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Best Practices&lt;br /&gt;
&lt;br /&gt;
- To create a scalable interface the use of the dp unit over px is recommended as the scale can be set per context with SetDensityIndependentPixelRatio.&lt;br /&gt;
- For styles unique to a document, put them in a `style` tag. For shared styles, put them in an `rcss` file.&lt;br /&gt;
- Rely on Recoil&#039;s RmlUi Lua bindings doc for what you can and can&#039;t do. The Recoil implementation has some extra stuff the RmlUi docs don&#039;t.&lt;br /&gt;
- The Beyond All Reason devs prefer to use one shared context for all rmlui widgets.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### Differences between upstream RmlUI and RmlUI in Recoil&lt;br /&gt;
&lt;br /&gt;
- The SVG element allows either a filepath or raw SVG data in the src attribute, allowing for inline svg to be used (this may change to svg being supported between the opening and closing tag when implemented upstream)&lt;br /&gt;
- An additional element ```&amp;lt;texture&amp;gt;``` is available which allows for textures loaded in Recoil to be used, this behaves the same as an ```&amp;lt;img&amp;gt;``` element except the src attribute takes a [texture reference]({{% ref &amp;quot;articles/texture-reference-strings&amp;quot; %}})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### IDE Setup&lt;br /&gt;
&lt;br /&gt;
To get the best experience with RmlUi, you should set up your editor to use HTML syntax highlighting for .rml file extensions and CSS syntax highlighting for .rcss file extensions.&lt;br /&gt;
&lt;br /&gt;
In VS Code, this can be done by opening a file with the extension you want to setup, then clicking on the language mode  in the bottom right corner of the window (probably shows as Plain Text). From there, you can select &amp;quot;Configure File Association for &#039;.rml&#039;&amp;quot; from the top menu that appears and choose &amp;quot;HTML&amp;quot; from the list. Do the same for .rcss files, but select &amp;quot;CSS&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
[RmlUI website]: https://mikke89.github.io/RmlUiDoc/&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Spring_guide!&amp;diff=2676</id>
		<title>Spring guide!</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Spring_guide!&amp;diff=2676"/>
		<updated>2026-03-05T02:17:08Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;{{deprecated}} &amp;#039;&amp;#039;&amp;#039; This page describes the gameplay of TA-based games on the Spring engine. The Spring engine can run a wide selection of Games and thus this text does not apply to every one of them. ----  Keep in mind that Spring is still in beta, therefore crashes can occur (a lot) and we (well, not me!) haven&amp;#039;t got everything fixed yet.  Before we start, let&amp;#039;s have some background info.  == TA: so what&amp;#039;s so cool about this game? ==  OTA | TA (Total Annihilation)...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{deprecated}}&lt;br /&gt;
&#039;&#039;&#039;&lt;br /&gt;
This page describes the gameplay of TA-based games on the Spring engine.&lt;br /&gt;
The Spring engine can run a wide selection of [[Games]] and thus this text does not apply to every one of them.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
Keep in mind that Spring is still in beta, therefore crashes can occur (a lot) and we (well, not me!) haven&#039;t got everything fixed yet.&lt;br /&gt;
&lt;br /&gt;
Before we start, let&#039;s have some background info.&lt;br /&gt;
&lt;br /&gt;
== TA: so what&#039;s so cool about this game? ==&lt;br /&gt;
&lt;br /&gt;
[[OTA | TA (Total Annihilation)]] was created by Cavedog (R.I.P) in 1997, about half a year before(!!!) Starcraft was released. It was revolutionary in many ways, with 3D graphics (but a fixed camera), good animations (aircraft barrel rolled and leaned when they turned etc.), 3D terrain and many units (150 in the original game.) But in my not so humble opinion, the 3 greatest things that set TA apart were...&lt;br /&gt;
&lt;br /&gt;
* Its physics! Every shot trajectory was calculated! An artillery shell could try to get over a hill and hit an aircraft, which exploded into bits, damaging other troops nearby, which could also explode and set forests on fire. Yeah, might not sound that big after 7 years, but back then we had Starcraft, which defied at least a half dozen laws of physics.&lt;br /&gt;
&lt;br /&gt;
(Example of the tentacle through space. Yeah, really realistic)&lt;br /&gt;
[http://fast.filespace.org/Kixxe/WTF.png Click here]&lt;br /&gt;
&lt;br /&gt;
* The order system! You could have 300 units waiting to be built, and when they were done, they could go and patrol an area and attack anyone in it. Order queues were limitless. You could build your whole base and drink tea while it was getting built. Construction units on patrol would repair damaged units, help build things and reclaim stuff (sucking it in and transforming it into resources.)&lt;br /&gt;
&lt;br /&gt;
* The resource system! Contrary to other RTS games, the resources in TA are infinite. What limits resource consumption is the rate at which they can be gathered. This sole fact would make for longer and more epic games, with hundreds or thousands of units fighting in the endgame.&lt;br /&gt;
&lt;br /&gt;
Pretty cool game. If you want, check out their old website for more info. [http://www.fileuniverse.com/Total_Annihilation_Mirror/totala/index.html Click here]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Anyway, enough of my rambling, onto Spring!&lt;br /&gt;
&lt;br /&gt;
Now, as I told you, Spring was inspired by TA and has the same features TA had. Plus a few new things, check out the Spring features page for more info.&lt;br /&gt;
[http://taspring.clan-sy.com/wiki/Features Click here]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==Controls==&lt;br /&gt;
Keyboard controls here:&lt;br /&gt;
[http://img108.imagevenue.com/img.php?image=03721_PK06A_Compact_Keyboard_ProdImage_Black_V2_122_972lo.jpg]&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget: shift+ right click to queue orders.&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==Resources==&lt;br /&gt;
&lt;br /&gt;
There are two types of resources in Spring, namely &#039;&#039;metal&#039;&#039; and &#039;&#039;energy&#039;&#039;. Each of these two resources can be collected in a number of different ways. The amount of each resource that you currently have stockpiled is represented by a colored bar, one for each resource, along the upper edge of the screen. The rate at which you are presently collecting and consuming each of the two types is represented by a number and a + or - sign. If you are collecting at a faster rate then the rate at which you are consuming you will see your colored bar grow as resources accumulate. Your stockpiled resources are being depleted whenever the colored bar is shrinking. There are storage buildings which you can build for each type of resource and these will increase the amount of each which can be stored. Units and structures cost both metal and energy to build, while some units or structures also require energy to walk, shoot or operate in general.&lt;br /&gt;
&lt;br /&gt;
Metal is collected continuously from either a metal extractor, often called &amp;quot;mexes&amp;quot; which extract from certain areas of the map, or by using a metal maker, abbreviated as &amp;quot;MM&amp;quot; which convert energy into metal. Both will only operate so long as there is sufficient energy to power them. However, metal makers require a lot of energy. &lt;br /&gt;
A faster way to gather metal is by using either your commander or a construction bot to &#039;reclaim&#039; it from the remains of destroyed units and buildings! They can even reclaim buildings which have not been destroyed and they don&#039;t have to belong to your enemy to be reclaimed.&lt;br /&gt;
&lt;br /&gt;
Energy can be produced by a number of different buildings or it can be reclaimed (you can reclaim trees to gather energy). There are many different types of energy producers, including solar collectors and power plants, but they will be covered in another section.&lt;br /&gt;
&lt;br /&gt;
In a game, always keep an eye on your metal and energy bar because if one of them goes to zero, particularly energy, your building rate will stall,(metal makers and extractors will stop producing) and some units will be unable to fire their energy weapons (eg. LRPCs, Annihilators, Doomsday Machines). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==A Normal Spring Game==&lt;br /&gt;
&lt;br /&gt;
Anyway, now you will learn the basics of a normal Spring game, as I see it. &lt;br /&gt;
Every Spring game starts with all players only having:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The COMMANDER&#039;&#039;&#039;! (Insert DUN DUN DUUUUUNN)&lt;br /&gt;
&lt;br /&gt;
The Commander is the unit you start with and the most important one in the game. He is unique and cannot be rebuilt. In fact, there is a mode where the game ends when the commander dies, so never leave him alone.&lt;br /&gt;
&lt;br /&gt;
The fastest builder, he can walk and build underwater, he makes a nuke-sized explosion when he dies, he has a laser and the privilege of a Disintegrator Gun (alias D-Gun). &lt;br /&gt;
The D-Gun is the single most powerful weapon in the whole game, which kills ANY (ANY, I mean it!) unit in one hit (friend or foe no difference, be careful!). Even if that sounds a like a little overkill, keep in mind that the Commander has a very limited range, is defenceless against any aircraft and long range artillery, and when submerged, he cannot shoot. (Subs! Onoz!)&lt;br /&gt;
&lt;br /&gt;
The Commander is used for setting up a base, and early base defences. The D-Gun should protect him against any level 1 unit that tries to kill him, so if you lose your Commander in the beginning of the game, then you&#039;re dead. You lose your best (only?) builder and attacker, plus you have a nuke-sized explosion that may destroy half or all of your base.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Anyway, if you&#039;re seeing everything in a top down perspective, click the Commander (type Ctrl+C if you can&#039;t find him) with left mouse button to select him.&lt;br /&gt;
&lt;br /&gt;
(If you have a crosshair as a mouse, press j. If your camera is zoomed out or all jerky, press ctrl+j until you find the OTA(Original Total Annihilation) camera.)&lt;br /&gt;
&lt;br /&gt;
Okay, let&#039;s now discuss &#039;&#039;&#039;how to build stuff&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Like any normal RTS, it&#039;s choose a unit and click a location, but there is more with Spring. Hold shift and click on different locations to make the unit build the same building there. Hold shift and drag and you will make a line of the building. Hold ctrl and shift to make boxes/rectangular shapes of buildings. Use these! Make a line of DT (Dragon Teeth, small piece of wall/cover) instead of clicking each and every one. The controls are there to help you.&lt;br /&gt;
This is called queuing up stuff. You can use almost every single command in a queue. Buildings can be queued to build lots of different units, and then the other orders in the behaviour of the unit. You can make them walk to something, or guard/follow a unit by pressing G and selecting the unit to guard. Try it.&lt;br /&gt;
Selecting a unit and pressing shift key will show the orders queue.&lt;br /&gt;
&lt;br /&gt;
Build 2 solar generators, or 4 wind generators to gather energy. Keep in mind that wind is pretty unstable, and that a number of maps (like lunar ones) have no wind at all! (The level of wind and tides in the map is indicated in the Map Selector in the Lobby).&lt;br /&gt;
&lt;br /&gt;
Now select the Metal extractors and the whole screen turns black and green. Okay, place one on the green stuff. (The greener, the better! Like broccoli!)&lt;br /&gt;
&lt;br /&gt;
Oh, and the minimap controls in spring are reversed. Meaning that you have to right-click to move the screen to the location. Once the new GUI is out and official, this will be fixed.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==The TA Bestiary==&lt;br /&gt;
&lt;br /&gt;
Okay, time to describe the cool units that are at your disposal to make your enemies bleed. 8)&lt;br /&gt;
&lt;br /&gt;
(Disclaimer : This will discuss the types of units, not every unit. Therefore, if I say that vehicles have large armour, I don&#039;t mean artillery of course.)&lt;br /&gt;
&lt;br /&gt;
On to the first level 1 lab/plant to build. The categories are:&lt;br /&gt;
&lt;br /&gt;
===Vehicles===&lt;br /&gt;
&lt;br /&gt;
These babies are all about armour. The level 1 units are pretty basic, with more armour and (build) speed and than their K-bot counterparts. Level 2 vehicles show even more of the same pattern: better armour than their K-bot counterparts. Level 2 also offers the mobile Flak unit, which is the only real level 2 mobile AA.&lt;br /&gt;
Their weakness is that they are clumsy, turn slowly, and most of them can&#039;t climb mountains at all. Leads to trouble when an explosion makes a crater; units survive but can get stuck in the crater. Or just when they are trying to get up a steep hill. But sooner or later, you&#039;ll learn what kind of destruction a rush of 30 Goliaths can do!&lt;br /&gt;
&lt;br /&gt;
===K-Bots===&lt;br /&gt;
&lt;br /&gt;
K-bots generally have less powerful weapons than tanks, and are also less heavily armored.&lt;br /&gt;
So why build them in the first place?&lt;br /&gt;
Because they are cheaper!  And since they are cheaper, you can build more and outnumber your enemies. This doesn&#039;t work out that good in game though, since tanks ARE stronger. K-bots are also smaller, and will slip through DT and such alike easier than tanks.&lt;br /&gt;
And finally, the third advantage (which IMO is the biggest) of K-bots are that they can climb most mountains with ease! Climb by defences, up on mountains, through holes, these are all-purpose units.&lt;br /&gt;
&lt;br /&gt;
===Aircraft===&lt;br /&gt;
&lt;br /&gt;
Planes are planes. In Spring, there&#039;s a plane for every role. Bombers, fighters, gunships, flying radar, transports, stealth fighters and torpedo bombers. Planes are fast, and are not affected by terrain. There is no wall or relief that can stop a plane. You can just swoop into a base and take out AA with your gunship, and then bomb the crap out of their important structures. To stop them, you&#039;ve got to rely on Anti Air (AA) units.&lt;br /&gt;
&lt;br /&gt;
On the downside, planes are quite expensive to build and are relatively fragile. They are also the only units that have an anti unit against them (Except subs). When someone finds out you&#039;re building a large air fleet, they just cram their base full of AA and their army with mobile AA. And the only counter is more planes than their AA can handle, or a ground troop to take the AA down. A combined ground and air attack can be very destructive. Good luck!&lt;br /&gt;
&lt;br /&gt;
===Ships===&lt;br /&gt;
&lt;br /&gt;
Ships are good on a map with water. (DUH!) Ships come in many different kinds, but mostly for taking out other ships or bombing the crap out of land units in some way or another. The ships&#039; strength is their powerful weaponry and their long range. All ships made to take out land have long range. Ships also have lots of armor. Another thing that&#039;s good about ships is that there is not much you can do to stop them because they are usually out of range. Once you see that the enemy has ships, either build a dock and a navy real fast or build planes and pray that he doesn&#039;t have too many AA ships. &lt;br /&gt;
Their weaknesses are slowness (especially when turning), they can be taken out by submarines, and of course they are confined to water, meaning that they are often stuck with units just outside of their reach.&lt;br /&gt;
&lt;br /&gt;
And they are *extremely* expensive, metal-wise.&lt;br /&gt;
&lt;br /&gt;
===Hovercraft===&lt;br /&gt;
&lt;br /&gt;
(These are counted as level 2 units, so I would not recommend them as the first to build unless it&#039;s the perfect map for them)&lt;br /&gt;
&lt;br /&gt;
Hovercrafts are a tank and a ship(some say plane, but I find ships to be more accurate for some reason O.o) mixed. Sure it&#039;s weaker than your average tank, and slower than your average boat. Whats their use? They are meant for swampy maps that means that both tanks and ships are a weakness, and offcourse since none can&#039;t be used, everyone just spams out AA since they think aircrafts are the only way. Great suprise for the enemy when 50 hovercrafts comes in agsint their flakkers and MT...&lt;br /&gt;
Long story short, Hovercrafts are good for those who like (or must) attack from the ground, but can&#039;t since there is too much water. &lt;br /&gt;
&lt;br /&gt;
Their weakness is their low climbing ability, and that they&#039;re weaker than a tank and boats. (Don&#039;t build them on a land map, tanks ARE better.)&lt;br /&gt;
&lt;br /&gt;
===Towers and Turrets===&lt;br /&gt;
&lt;br /&gt;
The impressive range of fixed towers, cannons and turrets from Total Annihilation is again at your disposal. Proper use of these structures will help you control a map and stop your opponents in their tracks.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Light Laser Towers (LLTs):&#039;&#039;&#039;&lt;br /&gt;
These cheap and fast lasers boast pinpoint accuracy and respectable damage against level 1 units. They suffer from short range, and are vulnerable to artillery and missile units which can kill them from out of reach. Since they fire faster than their bigger brothers, LLTs have an advantage against hordes of small weak units.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Heavy Laser Towers (HLTs):&#039;&#039;&#039;&lt;br /&gt;
With a lower rate of fire but much more power than LLTs, these upgraded lasers are ideal for those pesky level 2 units that just don&#039;t want to die. They also have a longer range, and can be quite effective against aircraft. They cost a fair amount and take a long time to build, but in the right situation they can be extremely dangerous.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Missile Towers (MTs):&#039;&#039;&#039;&lt;br /&gt;
The cheap yet deadly missile tower is guaranteed to shred airborne attackers, and its low cost and long range also makes it a good early-game point defense against weaker level 1 units. MTs are very fragile, and tend to chain-explode if placed too close together; they work best when scattered as random surprises for invading aircraft or with natural terrain or dragon&#039;s teeth to provide some cover from ground attacks. The huge damage bonus of missiles against aircraft makes these effective against even level 2 planes, though it would be wise to back them up with a few flak towers as well.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Pop-up Cannons:&#039;&#039;&#039;&lt;br /&gt;
Pop-ups are the smallest and wiliest fixed plasma cannons. When not firing they retreat down into the ground, where they are extremely resistant to damage; a closed pop-up in XTA 9.1 can survive two consecutive direct nuclear missile impacts! They can be installed by lowly level 1 construction units, and outclass virtually all level 1 opponents in range and firepower. Their durability and effectiveness is not without cost, however, as even a single pop-up represents a huge gamble of time and resources in the early stages of a game. Consider carefully whether rushing a pop-up will cripple the enemy forces or your own economy...&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Medium Range Plasma Cannons (MRPCs):&#039;&#039;&#039;&lt;br /&gt;
MRPCs are huge, brutal plasma turrets that really suck on your energy reserves but inflict serious pain over a wide area. Fast and accurate targeting combined with excellent damage and range make them a sound defensive investment against level 2 armies. Remember that a smart enemy will look for a way to simply bypass your expensive static emplacements, and that even if you don&#039;t leave any way around these guns still make big fat targets; they work best when used to lock down choke points with overlapping fields of fire, and deserve supporting defenses against air raids and skirmishers. Put one on a hill, cover it with a HLT and some flak or missile towers behind a wall of DT, and don&#039;t forget to occasionally send in a builder to reclaim the resulting wreckage.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Long Range Plasma Cannons (LRPCs):&#039;&#039;&#039;&lt;br /&gt;
When you want to hammer an enemy installation into gravel from the comfort of your own base, an Intimidator or Big Bertha might be the tool for the job. The enormous range and hitting power of a single LRPC can dominate the whole of a small map or take an enthusiastic bite out of a large one, and a battery of them can obliterate just about anything in only a few volleys. Too slow to effectively track moving targets, the LRPC is not a defensive emplacement but an offensive weapon of mass destruction; as such, building one is a challenge that simply can&#039;t be ignored. Surround these with AA and ground troops to fend off your victim&#039;s inevitable desperate attempt to halt the rain of destruction.&lt;br /&gt;
&lt;br /&gt;
*&#039;&#039;&#039;Very Long Range Cannons (VLRCs):&#039;&#039;&#039;&lt;br /&gt;
The ultimate, absurd limit of long-range artillery is the repeating Buzzsaw or Vulcan. These guns are incredibly expensive and ludicrously energy hungry (be prepared to dedicate a couple of fusion plants to powering just one such monster) but have &#039;&#039;double&#039;&#039; the range of a &amp;quot;mere&amp;quot; LRPC and a withering rate of fire. Just don&#039;t forget to go equally overboard defending your Uber-gun, because it doesn&#039;t have very much health compared to its build cost and it screams &amp;quot;kick me&amp;quot; like nothing else.&lt;br /&gt;
&lt;br /&gt;
===The first steps===&lt;br /&gt;
&lt;br /&gt;
Now comes the tricky part. The first minute is always the same really, so what after that?&lt;br /&gt;
Well, it&#039;s hard to judge. But here are some paths that can be taken.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1.&#039;&#039;&#039; Build many construction units and send them out to build mexxes and Geothermal Power Plants. Then a defence and maybe some DT.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2.&#039;&#039;&#039; Build some construction units, and then start building defences. Follow the line defence guide lines.&lt;br /&gt;
Build Fusion Power Plants and Metal makers, and then an army. After that, use the army how armies are used best.&lt;br /&gt;
This strategy is called PORCING, and is considered by many as BAD.&lt;br /&gt;
There are disadvantages with this. You can&#039;t use many metal extractors or Geos, so you have slim to no resources to build your base. A popular strategy is to kill the anti-nuke, and then blow the whole compact base to bits.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3.&#039;&#039;&#039; Build a small army of 3-6 units and attack the enemy. The goal isn&#039;t to kill him, but to do as much damage as possible to his resources and hinder his economy. Mexxes are the most important, and then look for wind or solar generators.&lt;br /&gt;
Try to keep on attacking him, but remember to still tech up and build defences.&lt;br /&gt;
&lt;br /&gt;
Anyway, use your construction units to build defences of SOME kind. Take advantage of Geothermal gases. Yeah the smoking stuff. You can place Geo plants there for good, cheap and space saving energy. If you&#039;re expanding, use every Mex&#039; positions, and try to find a place to settle your defence line.&lt;br /&gt;
&lt;br /&gt;
When you can defend yourself from most level 1 units, start on your level 2 building.&lt;br /&gt;
&lt;br /&gt;
When it&#039;s done, ALWAYS build a construction unit first. Use it for your first mini Fusion. (Or build some mobile fusion at the vehicle plant if you&#039;re Arm)&lt;br /&gt;
Build level 2 units in the meantime.&lt;br /&gt;
Low on metal? Upgrade the finest of your Mexxes to Moho Mexxes. They are bigger and better (but they suck up energy).&lt;br /&gt;
You can also build a Moho Metal maker if you have enough energy to support it. (MMM :p)&lt;br /&gt;
Okay, if you made this far, you must really start thinking about how to defeat the enemy. Look for his weak spot. Maybe a K-bot assault from the mountains ? Maybe planes. Maybe using amphibious units. That part is for you to figure out.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
==STRATEGY==&lt;br /&gt;
&lt;br /&gt;
I read through this guide, and suddenly realized. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;where is the strategy?&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
So I fiddled this section up. Hope you&#039;ll learn something from it and thereby increase your Spring gaming experience.&lt;br /&gt;
&lt;br /&gt;
===Defence===&lt;br /&gt;
&lt;br /&gt;
Ah, the overall defence. There are many different ways to make a good defence, but here are 4 oft seen approaches.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Compact defence!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The most common type of defence is a single line of defence, often with Dragon Teeth (DT) in front. Inside your base is some AA, and that&#039;s about it. The good thing about this is that it&#039;s pretty easy and fast to set up, and you know where your territory ends, which can make the game much easier. Problem is, if something makes it past the line of defence, then you&#039;re screwed, unless you&#039;re building line after line of defence, or have some units to stop them.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Spread out defence!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
I saw a guy pull this off one time, and he won by a large margin. The trick with this is to never really have a defined line of defence, just a Heavy Laser Tower (HLT) here, a radar there, some DT there, a mine here and there.&lt;br /&gt;
Basically, you expand every time you build a defence building, so in the end you have ENORMOUS space to move in. Since the defence is spread out, no one is gonna rush past it with some Goliaths. You can have some units hide behind mountains and attack. He used Merls who he trapped in fortification walls. I tried to run past the defence with an army of 30 Bulldogs. His HLT and missile/plasma batteries stopped me before I even reached his final defence line.&lt;br /&gt;
Even though this defence is the best of them all, it&#039;s hard to build up, and requires lots of resources and time. IF you&#039;re able to pull it off, the only problem may be if they are fast enough to run by all of it, or if you don&#039;t have much AA and 30 Brawlers are coming.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;No defence at all?&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Yes, this is an option. Okay, you may want some mines here and there, and some DT to stop them from running right through the door and AA in your base if they&#039;re building planes. But how do you defend with no defence?&lt;br /&gt;
With units; the whole concept is that you put all your effort into making units. This works pretty well, since when you see where the enemy is going to attack, you can MOVE your whole defence to that location.&lt;br /&gt;
The problem with this defence is that when you are attacking, you lose most of your defence. Sure, you can leave units behind, but I find it hard mentally to leave half the army at home when I am attacking. &lt;br /&gt;
If you manage to pull this off, and make enough units to both attack and defend, then you have not wasted any money on defence, and you have a strong army!&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;The All Out Defence&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;-- addendum by Cain&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The opposite of the &amp;quot;no defence&amp;quot; strategy, mixed with the &amp;quot;spread out defence&amp;quot; strategy: the main concept is to invest resources in lots of level 1 build planes, and use them to quickly gather huge amounts of territory and resources, while keeping a lot of planes building/repairing turrets all over the map. This could be very effective on maps with few choke points where your defence can be concentrated in a small area, or mostly flat maps where the overlapping range of defensive structures will create a large grid of protected territory. The concept is to expand the defence line until you can shoot straight into the enemy base. When using this strategy, it is wise to have a lot of level 2 aircraft defending the construction point, as you will need to constantly move your mobile defences around the map, protecting under-contruction defensive areas. I usually end up with a hundred builder planes and 20 to 50 Brawlers floating around... keep them away from Kroghot!&lt;br /&gt;
&lt;br /&gt;
These are the defensive strategies I have found effective.&lt;br /&gt;
&lt;br /&gt;
Also, one thing to remember is to eliminate the largest threat first. If you&#039;re relying on DT, take out the FARK or construction planes first.&lt;br /&gt;
&lt;br /&gt;
Okay, you&#039;ve built a good defence.&lt;br /&gt;
The next thing you should do is attempt to get into the mindset of your enemy.  Put yourself in his shoes, and determine how he would most likely proceed given your defensive strategy.  Would he attempt a gunship raid, or launch an attack from a defenceless mountain position?  Once you know what his most likely plan of attack will be, rethink your defences with this in mind.  If there is an area that is defenceless under your current strategy, and you believe it is a prime spot for your enemy to attack, set up some sort of defence there as well.&lt;br /&gt;
&lt;br /&gt;
===Moving===&lt;br /&gt;
Moving or attacking is pretty easy right? Just move from point a to point b and attack c?&lt;br /&gt;
Nope.&lt;br /&gt;
First, you have to have your units move TOGETHER. . If some are trailing behind, then you just have to wait a little for them. A way of having your units stick together is to take a pretty slow unit, like a Can, and have others guard it.&lt;br /&gt;
Pressing control while moving units will make them keep their position in the formation, but not their speed. (they will spread out along the way, but &#039;&#039;try&#039;&#039; to get back into the position they were in when reaching the destination)&lt;br /&gt;
The &#039;&#039;Simple formation&#039;&#039; is also a way of doing this, and the units will automatically form a couple of lines, often with the right units first, too!&lt;br /&gt;
Transports are useful for moving units, but use the air transports (Valkyrie /Atlas) intelligently and protect them. The other transportation units are harder to use, since they take more time to load/unload.&lt;br /&gt;
&lt;br /&gt;
===Attacking===&lt;br /&gt;
&lt;br /&gt;
When attacking, remember what each unit&#039;s role is in the fight. Farks are used to repair important units that have been damaged. Bulldogs are meant to take damage and give damage back. Merls are used to shoot missiles from a distance, and don&#039;t require a clear LOS (line of sight). The question is what to repair. Merls die quickly, so one shot = dead. Knowing this, your first instinct might be to repair a Bulldog instead. Bulldogs are often the first thing that the enemy targets, and thus their HP often drops too rapidly to repair. Therefore, it is often wise to repair the middle-range units; not the giant HP monsters, not the units with a weak defence and strong/useful attack, but the average units that have acceptable HP and are not an enemy&#039;s first choice when selecting targets. One exception to this is mobile artillery. While it only has enough HP to survive one or two attacks, its attacks are well worth it, and the enemy still may not target them first, giving them a chance to deal massive damage.&lt;br /&gt;
&lt;br /&gt;
===Army vs Army===&lt;br /&gt;
&lt;br /&gt;
Many units attacking one unit is the best thing you can do, as when you kill one of their units, they lose one source of firepower. Focusing your fire is very important. Using focus fire in a group of weak units is very effective. Focus fire is, however, less effective on HP powerhouses.&lt;br /&gt;
When you&#039;re facing an army, always try to attack from behind or the side. Keep your units a little spread out when attacking. Not to far, but just so that area of effect weapons don&#039;t do as much damage. Have your HP powerhouses at the front, or at the front and sides. Keep em pretty close together but not side by side. Having your units spread out in this manner allows the units in the back to attack, and prevents splash damage units such as Zippers and Pyros from damaging many of your units at once.&lt;br /&gt;
&lt;br /&gt;
This is the normal way of attacking an enemy army. There is the classical pincher movement too.&lt;br /&gt;
In this, you run one third of your HP powerhouses to the left, one third to the right and have the last third stay where they are. &lt;br /&gt;
This gives you 3 advantages: &lt;br /&gt;
*it&#039;s easier to get past the units and attack their weak units, or just run past them into the base&lt;br /&gt;
*you do more damage, since their units will have to change their target all the time and can&#039;t escape&lt;br /&gt;
*it makes it MUCH harder for the enemy to run away or try to get past you.&lt;br /&gt;
&lt;br /&gt;
===Army vs Base===&lt;br /&gt;
&lt;br /&gt;
Okay, you may have made it past the ground troops, but the fight is not over (unless your enemy used up his units to defend.) Now you must face his base defence, and act depending on the type of defence he has.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Line defence&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Gather your army and set up for battle a bit away from his base, and get ready to move in. Have the FARKS guard your main HP powerhouses, so they are in the front but can still hide behind em. Everything else behind that. When attacking, have several units attacking the HLT and plasma batteries, and use the FARKS to reclaim his Dragon Teeth. When you have a hole in the DT wall, just run into the heart of the base like a maniac. Split your tanks into 2 groups, 1 guarding your long range/high damage units and the other going into the heart of the base. Find the Fusion reactors with your second group of tanks, and try to move the other big group to a place that is easy to defend while hitting the Fusions. Behind buildings is perfect! Kill as much as you can, or kill everything ^^ &lt;br /&gt;
If there are several lines of defence, make a hole in each DT wall and repeat the same tactics.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Spread out defence&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Argh The tough one. While the individual towers are easy to beat, you can&#039;t run past his defences unless you enjoy suicide. (and if you do, ctrl+a and then ctrl+d will do the trick. A slice of the wrist works well, too.)&lt;br /&gt;
Use your army to begin killing the defence step by step, structure by structure. Keep building units, and moving them into the fray (you can use air transports with the repeat command and make them drop the units a little ways from the fight). Once you start getting close to his last line of defence, think of it as a line defence and act accordingly.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Units defence&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Think army vs army. Don&#039;t rush past them with all your units. Your units will spread out and try to go through holes in the enemy&#039;s formation. In a word, they are dead meat. If you MUST get to the base, have half of the tanks go around the units.&lt;br /&gt;
&lt;br /&gt;
===Naval Battles===&lt;br /&gt;
&lt;br /&gt;
Naval battles are hard to predict if you don&#039;t play island maps a lot, since there are many factors to take into account. Since land units are removed, you have to think in a WHOLE new perspective. No land means that only Sea, Air and Hover units can fight (and LRC of course).&lt;br /&gt;
Anyway, try to have air units scouting land and sea, and determine how much AA the enemy has. Ships roam around assaulting other ships, with a mix of subs, sub killers, AA ships and normal ships. Hovercrafts are the unit to use when assaulting land that&#039;s a little far from shore. Provide them with an escort consisting of planes and/or ships.&lt;br /&gt;
&lt;br /&gt;
====Ships====&lt;br /&gt;
When using ships, have scout ships or planes scout the area in front, then send in subs and sub killers. Use long range ships at the sides to protect anything of value you might have with you (a sea transport or an aircraft carrier). When in battle, try to have the subs run away from sub killers, while your sub killers kill the enemy subs. Your subs should try to attack the most important ship in the area, and then just go crazy.&lt;br /&gt;
When facing land, you&#039;re pretty much in for an easy win. Just have your ships blow it all up. If you face LRC, take them out first. The rest of it is out of your hands.&lt;br /&gt;
Remember if your opponent has air superiority or LRPCs use subs as they can&#039;t hit them :)&lt;br /&gt;
&lt;br /&gt;
====Hovercraft====&lt;br /&gt;
Use the Army vs Army or Army vs Base strategy here.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;-- addendum by Cain.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Hovercraft are quite expensive compared to other land units, but are very useful in maps with a bit of water, able to target enemies without worrying about those pesky Goliaths... note that they&#039;re much cheaper than ships, anyway.&lt;br /&gt;
&lt;br /&gt;
===Planes===&lt;br /&gt;
Hohoho, planes. Fun stuff to attack with. Anyway, most of the strategy involving planes is simple.&lt;br /&gt;
First, always attack the AA first, unless you are going to do a suicide mission. Kill the AA with gunships, and use bombers to kill structures. Many players use gunships only, which is a bad idea. Bombers take a little time to master but combined with gunships, they do a very good job. Scout early, so you can quickly see WHAT the enemy has and WHERE he has it. Use gunships to take out AA and act as overall decoys. Have Bombers fly in and bomb the crap out of his Fusion or whatever strategic point, and use the fighters to guard the bombers.&lt;br /&gt;
When facing units, do the same.&lt;br /&gt;
When facing sea, do the same thing, but add Torpedoes to bombers and gunships.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;-- addendum by Cain&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Know where the enemy commander is! In games where the commander&#039;s death is a winning condition, a lot of bombers could just converge on him, killing him on the first run.. especially the 2nd level bombers that have a nice turreted fast targeting laser! Also, gunships could create a diversion to keep the main enemy defences away from your main attack force, or you could use them to make the defences of distracted players shoot their own base...&lt;br /&gt;
&lt;br /&gt;
===Using Terrain===&lt;br /&gt;
&lt;br /&gt;
Using terrain wisely is very important. The issue of a game can depend on a good use of terrain.&lt;br /&gt;
First, Line of Sight (LOS) and RADAR. Both are blocked by terrain. So build your radars as high up as possible, otherwise you may not see anything. Use the relief to your advantage to hide an army or your important buildings from enemy radars.&lt;br /&gt;
Of course, one can also take advantage of altitude to increase fire range. A couple of Guardians on top of two high hills can easily devastate a small army.&lt;br /&gt;
Terrain also helps shield your units against attacks. Therefore, when you&#039;re building something high on top of a hill, try placing it a little behind the top. This is like crouching behind a crate where just your head and gun sticks up. But remember that terrain in Spring is deformable and can be leveled by impacts!&lt;br /&gt;
Know the climbing ability of your units. Hovercrafts and vehicles have pretty low climbing ability. But if a foe thinks he has sealed up all exits, your K-bots can still climb the mountains where he hasn&#039;t put any Dragon Teeth&lt;br /&gt;
Another good thing about terrain is that it mentally makes you feel safe. If there is a water barrier between me and the enemy, with 3 land bridges here and there, I probably won&#039;t have much ground defence at a location AWAY from the bridges. Presuming the map is pretty flat (like &amp;quot;Flooded Desert&amp;quot;) a hovercraft assault would do A LOT of damage, right?&lt;br /&gt;
&lt;br /&gt;
===Energy and all the problems with it===&lt;br /&gt;
(in XTA)&lt;br /&gt;
As I said before, most level 2 units require energy to move, shoot and operate in general, meaning that if you have no energy to support your army, you can&#039;t do anything! You can use this in your favor, by attacking your enemy&#039;s Fusions, or&lt;br /&gt;
by &#039;&#039;giving&#039;&#039; Metal makers to your enemy when you are attacking him(although Metal makers are now turned off when given to an enemy), or he is attacking you!  Things that drain a lot of energy include LRPC, Goliaths, HLT, and most level 2 units.&lt;br /&gt;
(/in XTA)&lt;br /&gt;
&lt;br /&gt;
===Paper-rock-scissors?===&lt;br /&gt;
&lt;br /&gt;
Try to look at all units as individuals. Even if I didn&#039;t write much about every single unit, know your favorite ones and how to use them effectively in conjunction with the others. You&#039;ll find that some units are very complementary. The only real paper-rock relationships are AA and planes... Maybe subs vs sub killers...&lt;br /&gt;
&lt;br /&gt;
I didn&#039;t even mention cloaking, radar jamming, troops behaviour facing attacks, mines, etc, etc. The possibilities are immense, but I don&#039;t want to spoil your pleasure any longer, so I&#039;ll let you discover them by yourself.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Have fun !!!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
===Strategy Tips===&lt;br /&gt;
&lt;br /&gt;
* Expand! Metal is scarce. The more you get, the more you can build. Even if you have a good base, making it bigger is not a bad thing.&lt;br /&gt;
* Scout! Knowledge is power. You must know what your enemy is planning, and what the enemy knows about you. (Or thinks he knows about you) Check what he has, and what he is building. If he&#039;s building aircrafts, do something about it. Look for weaknesses in defense and such. If you or your ally are scouting, don&#039;t look away. Gather as much information as possible. ALL buildings once in sight range will remain visible as &#039;ghost&#039; buildings if this option isn&#039;t turned off by the host.&lt;br /&gt;
* Radar = GOOD ^^ Radar checks areas for units, increases your units LOS and is, overall, quite helpful. A radar with your army makes a big difference when attacking. But so is Jamming. Jamming is good for sneak attacks. They are cheap, so just build one and make your units guard it.&lt;br /&gt;
* Work Together! Often I see allies who just mind their own business and don&#039;t care what the other person&#039;s doing. DON&#039;T DO THIS!!! Listen to each other, make up a plan. Prepare combined attacks. If there is a 2v2, then it&#039;s 2 allies vs 2 allies. Not a FFA with neutral partners. Type h to share units. Left click on your resource bars to auto share resources with allies.&lt;br /&gt;
* Power Plants. In order to level out the building costs, it is important to make a smooth transition from the least powerful generators to the most powerful ones in succession. First solar, then Geo, then mobile/mini Fusion then Fusion.&lt;br /&gt;
* Check your resource bar. Try to keep a fine line between having too many resources/wasting, and having a nanostall. Use your factories and such accordingly. If you have a lot of resources, try using your old level 1 factories to build some units. Also, try to find the time to build an energy/metal storage...&lt;br /&gt;
* Spread out your buildings/units. Since every unit explodes, they damage units close to them. This is especially important with Fusions... (I once saw someone put 13 mobile fusions close to each other... blew up half his base with 2 (!!!) LRPC shots...)&lt;br /&gt;
* Queue (yeah, easy word for it too :/) up orders! You can almost win the game by pausing, queuing up some stuff, and then go eat lunch. Not really, but holding shift is good whilst putting up buildings.&lt;br /&gt;
* Guard = following and protection. Use planes to guard your tanks, subs to guard your transports, construction units to automatically repair important units and so on. They will choose to attack whatever is hurting their guard object first. You can even do this to structures, for units who are built to move to a unit&#039;s position or AA to attack whatever is shooting on the Fusion reactors, etc... Overall, this makes working with several unit types easier.&lt;br /&gt;
* Reclaim stuff (sucking it in and transforming it into resources). When you&#039;re starting to run low on metal, reclaim the enemy&#039;s and your own corpses. Trees give energy, so if you&#039;re out of energy in the beginning... You can reclaim a whole area by shift-clicking.&lt;br /&gt;
* The key for porcers are to build LRPC. Or even better, build a nuke and use LOTS of planes to kill his anti-nuke. Then BOOM! Also as you have way loads more metal and space to move in than your opponent, you can easily afford 2 nukes. An anti-nuke can&#039;t deal with 2 nukes heading towards it(and if you aim the nukes at the anti-nuke...^^!&lt;br /&gt;
* Experiment! I suggest you go into single player and try every unit. Use commanders for arm, and small battle (watch that Krogoth!) for core. Try every approach to a problem. Remember, just because a unit is good, it&#039;s not invincible. Also, try how much metal and energy a building uses while in operation. For instance, a level 1 factory uses about 50 energy and 5 metal. This varies depending on what you build. This could help you prevent nanostalling.&lt;br /&gt;
* Several types of factories. Yeah, even if it costs money. An advanced building costs as much as a Bulldog or 2... You&#039;ve got the time, and the money. Build it.&lt;br /&gt;
* Raid. Take the Weasel or a couple of Peewees on a cruise right by the enemy&#039;s mexes. Having trouble finding them? Type F4 to see where the ground is drained ^^ (Edit) This doesn&#039;t apply anymore, but you can still see where a mex is by trying to build one of your own there. If you can&#039;t, there&#039;s already one on the spot... A tip is to stay away from the Commander, or use buildings as hostages. Hide behind stuff and kill it off, then move on. If you&#039;re sure you&#039;re going to die, move close to a building and self-destruct.&lt;br /&gt;
* Just because Peewees are better then AK&#039;s doesn&#039;t meant that AK&#039;s are useless. (And so on). In fact, no unit is useless. If you believe it is, try it once. On what it was meant to do.&lt;br /&gt;
* Mix armies. Oh boy, if you haven&#039;t heard this one a thousand times before, you&#039;ve only been playing Half-Life for the last 7 years or something. Even if Goliaths rule, a couple of missile units will make them much more effective.&lt;br /&gt;
* FARKs are useful in combat. Always have 1-3 with you. Reclaiming DT, or doing what they were meant to do: Repair -.-* (also, when I say FARKs somewhere in the guide, construction units work too...)&lt;br /&gt;
* Ask questions. No one is going to laugh at you. And if someone does, he&#039;s an ass and should be paid no mind. No question is too stupid. Just don&#039;t expect anyone in the heat of a battle to answer to &#039;&#039;how do I play&#039;&#039;?.&lt;br /&gt;
* Learn! If you lost, use the replay to watch the game and try to understand why you died. What did the enemy do better than you? What was their key to winning? And so on.&lt;br /&gt;
* &#039;&#039;The Readme&#039;&#039; file has some cool keyboard shortcuts... check it out.&lt;br /&gt;
* Develop! Are you a programmer, a 3D design artist or just some regular Joe? Come to the forum for a talk, chatter, and make wacky suggestions! In the end you may contribute to making Spring better! Remember, Spring is open source. So just dive into there and replace AK&#039;s with flying monkeys in space suits or whatever.&lt;br /&gt;
* Try having some good music on when playing. It can give you a good kick. I recommend the War of the Worlds (the strategy game, yes it exists) Eve of the War Another great choice is, of course, [http://www.fileuniverse.com/?page=listing&amp;amp;ID=54 the original TA music]! You can choose whatever music you like, though.&lt;br /&gt;
* Need more strategy and tips? Since Spring is a remake of TA, any TA strategy guide works...&lt;br /&gt;
* LAST BUT NOT LEAST: &#039;&#039;&#039;Don&#039;t get too serious. It&#039;s only a game.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This guide was written by Kixxe before everyone edited (and improved!) it mercilessly.&lt;br /&gt;
&lt;br /&gt;
[[Category:Playing]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Springie&amp;diff=2675</id>
		<title>Springie</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Springie&amp;diff=2675"/>
		<updated>2026-03-05T01:50:53Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;Springie is now defunct.&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Springie is now defunct.&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=SPADS&amp;diff=2674</id>
		<title>SPADS</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=SPADS&amp;diff=2674"/>
		<updated>2026-03-05T01:50:23Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;== Support == *[https://github.com/Yaribz/SPADS/blob/master/INSTALL.md Installation] *[https://springrts.com/phpbb/viewtopic.php?f=1&amp;amp;t=17130 Discussion in Spring forums] *[https://github.com/Yaribz/SPADS/issues Issue tracker]  == Reference documents == *[http://planetspads.free.fr/spads/doc/spadsDoc.html Commands and settings reference guide] *[http://planetspads.free.fr/spads/conf/templates/ Configuration templates] *[https://github.com/Yaribz/SPADS/wiki/SPADS-official-...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Support ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/INSTALL.md Installation]&lt;br /&gt;
*[https://springrts.com/phpbb/viewtopic.php?f=1&amp;amp;t=17130 Discussion in Spring forums]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/issues Issue tracker]&lt;br /&gt;
&lt;br /&gt;
== Reference documents ==&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsDoc.html Commands and settings reference guide]&lt;br /&gt;
*[http://planetspads.free.fr/spads/conf/templates/ Configuration templates]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-official-plugins-list Official plugins list]&lt;br /&gt;
&lt;br /&gt;
== Memos ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-update-procedure-for-new-Spring-version SPADS update procedure for new Spring version]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/blob/master/UPDATE.md Manual procedure for major SPADS update]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-install-notes Plugin installation notes]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Balancing-teams-on-a-LAN-server Balancing teams on a LAN server]&lt;br /&gt;
&lt;br /&gt;
== Beyond All Reason specific ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-quick-install-guide-(Windows) Beyond All Reason LAN server quick install guide (Windows)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-quick-install-guide-(Linux) Beyond All Reason LAN server quick install guide (Linux)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-client-configuration-to-connect-to-a-LAN-server Beyond All Reason client configuration to connect to a LAN server]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-LAN-server-customization Beyond All Reason LAN server customization]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/Beyond-All-Reason-offline-LAN-server-guide Beyond All Reason offline LAN server guide]&lt;br /&gt;
&lt;br /&gt;
== Development ==&lt;br /&gt;
*[https://github.com/Yaribz/SPADS Source code]&lt;br /&gt;
*[http://planetspads.free.fr/spads/repository/CHANGELOG Detailed changelog]&lt;br /&gt;
*[http://planetspads.free.fr/spads/doc/spadsPluginApiDoc.html Plugin API documentation]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development Plugin development tutorials (Perl)]&lt;br /&gt;
*[https://github.com/Yaribz/SPADS/wiki/SPADS-plugin-development-(Python) Plugin development tutorials (Python)]&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;br /&gt;
[[Category:Autohosts]]&lt;br /&gt;
[[Category:SPADS]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Links&amp;diff=2673</id>
		<title>Links</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Links&amp;diff=2673"/>
		<updated>2026-03-05T01:50:02Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;Development &amp;lt; {{FULLPAGENAME}}  = Community Links = *Associated Development Groups - various forums etc *[http://springfiles.springrts.com/ SpringFiles] - download page for maps &amp;amp; games *[http://weblobby.springrts.com WebLobby] *mumble server *[http://feeds.feedburner.com/SpringCommunityHeadlines SpringInfo Community News] *[http://replays.springrts.com/ Spring Replays]  = Development Links&amp;lt;br/&amp;gt; =  *[htt...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Engine Development|Development]] &amp;lt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
= Community Links =&lt;br /&gt;
*[[Associated_Development_Groups|Associated Development Groups]] - various forums etc&lt;br /&gt;
*[http://springfiles.springrts.com/ SpringFiles] - download page for maps &amp;amp; games&lt;br /&gt;
*[http://weblobby.springrts.com WebLobby]&lt;br /&gt;
*[[mumble|mumble server]]&lt;br /&gt;
*[http://feeds.feedburner.com/SpringCommunityHeadlines SpringInfo Community News]&lt;br /&gt;
*[http://replays.springrts.com/ Spring Replays]&lt;br /&gt;
&lt;br /&gt;
= Development Links&amp;lt;br/&amp;gt; =&lt;br /&gt;
&lt;br /&gt;
*[http://stacktranslate.springrts.com Stacktrace Translator]&lt;br /&gt;
*[http://springrts.com/mantis/ Mantis] - place to report engine bugs, problems &amp;amp; (minor) feature requests&lt;br /&gt;
*[http://buildbot.springrts.com/waterfall?reload=30 buildbot waterfall]&lt;br /&gt;
*[http://springrts.com/dl/buildbot/default/ spring engine development builds]&lt;br /&gt;
*[[Rapid]] - the games download api&lt;br /&gt;
&lt;br /&gt;
= Repo. Links =&lt;br /&gt;
== Engine &amp;amp; Content ==&lt;br /&gt;
*[https://github.com/spring Spring github repo]&lt;br /&gt;
*[[Gamedev:PublicRepos | Game Repos]] - Publicly readable game repos&lt;br /&gt;
=== [https://github.com/jk3064/ jK&#039;s lua repos] ===&lt;br /&gt;
*[https://github.com/jk3064/Map-Blueprint Map Blueprint] - basic container to create new maps ([[MakingMapsWithBluePrintAndMapConv |see]])&lt;br /&gt;
*[https://github.com/jk3064/chiliui Chili] - a lua based gui framework&lt;br /&gt;
*[https://github.com/jk3064/chili_loadscreen Chili Loadscreen] - a chili based loadscreen&lt;br /&gt;
*[https://github.com/jk3064/Custom-Unit-Shader-Framework CustomUnitShaders] - a lua based per-unit shader framework (includes normalmap support)&lt;br /&gt;
&lt;br /&gt;
== Lobby Server &amp;amp; Clients ==&lt;br /&gt;
*[[uberserver]] - the lobby server&lt;br /&gt;
*[http://code.google.com/p/zero-k/ ZKLobby&#039;s google code page] - a .net lobby&lt;br /&gt;
*[[SpringLobby]] - a wxWidgets lobby&lt;br /&gt;
*[https://github.com/cleanrock/flobby flobby] - a FLTK based lobby (linux only)&lt;br /&gt;
&lt;br /&gt;
== AutoHosts (dedicated servers) ==&lt;br /&gt;
*[[SPADS]]&lt;br /&gt;
*[[Springie]] - mainly used by ZK&lt;br /&gt;
&lt;br /&gt;
== Content Downloaders == &lt;br /&gt;
*[[pr-downloader]] - an unified download app &amp;amp; lib (can dl from plasma, rapid, ...)&lt;br /&gt;
*[https://github.com/spring/RapidTools rapid server tools] - tools to create a rapid repository&lt;br /&gt;
*[[Plasma]] - a torrent based download system (mostly for maps, games should use rapid)&lt;br /&gt;
&lt;br /&gt;
== Tools ==&lt;br /&gt;
*[[Upspring]]&lt;br /&gt;
*[[MapConv]]&lt;br /&gt;
*[[MapConvNG]]&lt;br /&gt;
&lt;br /&gt;
= SpringFiles API =&lt;br /&gt;
*[https://springfiles.springrts.com/json.php json api]&lt;br /&gt;
*[https://github.com/springfiles/upq/blob/master/doc/exampleclient.py example client]&lt;br /&gt;
&lt;br /&gt;
= Miscellaneous =&lt;br /&gt;
&lt;br /&gt;
*[https://packages.springrts.com Rapid Upload Interface] - Mappers &amp;amp; GameDevs can upload their files via it&lt;br /&gt;
&lt;br /&gt;
= Statistics =&lt;br /&gt;
&lt;br /&gt;
*[http://zero-k.info/stats/ lobby stats]&lt;br /&gt;
&lt;br /&gt;
[[Category:Development]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Building_Spring_on_Buildbot&amp;diff=2672</id>
		<title>Building Spring on Buildbot</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Building_Spring_on_Buildbot&amp;diff=2672"/>
		<updated>2026-03-05T01:48:47Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;Development &amp;lt; Building Spring &amp;lt; {{FULLPAGENAME}}  == Current == See Buildbot:Gentoo or Buildbot:OSX for how to set it up.  ==New (since 0.82)==  Watch progress and errors of builds:  http://buildbot.springrts.com/waterfall   The outcome of builds can be found here:  http://springrts.com/dl/buildbot/default/  ==Old (until 0.81)==  The spring build-bot is a server that is setup to compile spring automatically, and is usabl...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Engine_Development|Development]] &amp;lt; [[Building_spring|Building Spring]] &amp;lt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
== Current ==&lt;br /&gt;
See [[Buildbot:Gentoo]] or [[Buildbot:OSX]] for how to set it up.&lt;br /&gt;
&lt;br /&gt;
==New (since 0.82)==&lt;br /&gt;
&lt;br /&gt;
Watch progress and errors of builds:&lt;br /&gt;
&lt;br /&gt;
http://buildbot.springrts.com/waterfall&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The outcome of builds can be found here:&lt;br /&gt;
&lt;br /&gt;
http://springrts.com/dl/buildbot/default/&lt;br /&gt;
&lt;br /&gt;
==Old (until 0.81)==&lt;br /&gt;
&lt;br /&gt;
The spring build-bot is a server that is setup to compile spring automatically, and is usable by everyone.&lt;br /&gt;
It currently supports compiling for:&lt;br /&gt;
* Windows 32bit with TDM-MinGW (default)&lt;br /&gt;
* Windows 32bit with MinGW&lt;br /&gt;
* Windows 64bit with MinGW&lt;br /&gt;
* Linux with GCC&lt;br /&gt;
&lt;br /&gt;
You may use the build-bot from within your Spring lobby client. Join channel &#039;&#039;&#039;#buildserv&#039;&#039;&#039;, type &#039;&#039;!help&#039;&#039; and follow the instructions. Most useful commands are &#039;&#039;!rebuild&#039;&#039; and &#039;&#039;!translate&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
It is maintained by bibim and mainly used by devs for testing if their changes would compile on other systems too, but as it also generates windows installers, that can be downloaded by everyone, it can be also useful for casual users willing to try the latest changes to spring.&lt;br /&gt;
&lt;br /&gt;
The outcome of builds can be found here:&lt;br /&gt;
[http://buildbot.eat-peet.net/spring/ http://buildbot.eat-peet.net/spring/]&lt;br /&gt;
&lt;br /&gt;
[[Category:Buildbot]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Building_Spring_Cross_Compiled&amp;diff=2671</id>
		<title>Building Spring Cross Compiled</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Building_Spring_Cross_Compiled&amp;diff=2671"/>
		<updated>2026-03-05T01:48:11Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Created page with &amp;quot;Development &amp;lt; Building Spring &amp;lt; {{FULLPAGENAME}}  __TOC__  Cross-compiling allows you to build for other platforms than the machine you are building on. You can, for example, compile Windows 32-bit binaries on a Linux 64-bit machine.  = Obtaining the Source =  {{Obtaining_The_Source_Code}}  = CMake cross compiling =  == Setting up the cross compiler ==  The compiler is called MinGW, which is a port of GCC to windows. The version...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Engine_Development|Development]] &amp;lt; [[Building_spring|Building Spring]] &amp;lt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
Cross-compiling allows you to build for other platforms than the machine you are building on. You can, for example, compile Windows 32-bit binaries on a Linux 64-bit machine.&lt;br /&gt;
&lt;br /&gt;
= Obtaining the Source =&lt;br /&gt;
&lt;br /&gt;
{{Obtaining_The_Source_Code}}&lt;br /&gt;
&lt;br /&gt;
= CMake cross compiling =&lt;br /&gt;
&lt;br /&gt;
== Setting up the cross compiler ==&lt;br /&gt;
&lt;br /&gt;
The compiler is called MinGW, which is a port of GCC to windows. The version are going to need is MinGW GCC 4.4.0 (which is MinGW 2.7) or later. Most Linux distributions come with MinGW-GCC 4.1.2 only in their standard repos (Summer 2009), so you have to do extra work to get 4.4.0+.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Gentoo===&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
emerge crossdev&lt;br /&gt;
EXTRA_ECONF=&amp;quot;--with-dwarf2 --disable-sjlj-exceptions&amp;quot; crossdev -t i686-mingw32&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
Additional instructions can be found [http://en.gentoo-wiki.com/wiki/Crossdev here] or [http://psas.pdx.edu/GentooCrossCompilerHowto/ here].&lt;br /&gt;
&lt;br /&gt;
===Ubuntu 10.04===&lt;br /&gt;
&lt;br /&gt;
Use the repository specified [https://launchpad.net/~tobydox/+archive/mingw mingw ppa], and then install the required packages:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
   sudo add-apt-repository ppa:tobydox/mingw&lt;br /&gt;
   sudo apt-get update&lt;br /&gt;
   sudo apt-get install mingw32-x-binutils mingw32-x-gcc mingw32-x-runtime mingw-x-w32api&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Ubuntu 11.10===&lt;br /&gt;
&lt;br /&gt;
see the section [https://github.com/spring/spring/blob/develop/buildbot/README.markdown Recompile MinGW package with dwarf2 exceptions instead of sjlj exceptions].&lt;br /&gt;
&lt;br /&gt;
===Other distros===&lt;br /&gt;
&lt;br /&gt;
You have to find a way yourself, or use [http://www.profv.de/mingw_cross_env/#tutorial these] instructions to get the sources and compile it yourself. When doing so, make sure to use these build flags for gcc:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
--with-dwarf2 --disable-sjlj-exceptions --enable-shared --enable-version-specific-runtime-libs&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
you can add them under &#039;&#039;src/gcc-core.mk&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note:&#039;&#039; This method will most likely not work anymore, as the checksum of the build files is checked by the makefile.&lt;br /&gt;
&lt;br /&gt;
Alternatively, use this line to compile all the deps (which will then be used instead of mingwlibs):&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;make zlib boost devil ogg vorbis openal freetype sdl glew gcc&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Getting Spring&#039;s windows dependencies ==&lt;br /&gt;
&lt;br /&gt;
These are called &#039;&#039;&#039;mingwlibs&#039;&#039;&#039;. How to get them is described below.&lt;br /&gt;
&lt;br /&gt;
=== official MinGW - dwarf2 (default) ===&lt;br /&gt;
You can get it here (&#039;&#039;&#039;Download Source&#039;&#039;&#039; button, or use &#039;&#039;git&#039;&#039;):&lt;br /&gt;
* [http://github.com/spring/mingwlibs &#039;&#039;&#039;mingwlibs&#039;&#039;&#039;]&lt;br /&gt;
&lt;br /&gt;
=== TDM MinGW - sjlj (alternative) ===&lt;br /&gt;
http://github.com/spring/spring/downloads&lt;br /&gt;
&lt;br /&gt;
== Setting up your directories ==&lt;br /&gt;
Here is my recommended layout&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
~/spring-xcompile/   (or wherever you want to put it all)&lt;br /&gt;
___ spring/   (the spring source)&lt;br /&gt;
___ win32/    (your target platform)&lt;br /&gt;
_______ build/ (the build files for your target will go here)&lt;br /&gt;
_______ final/ (the compiled binaries and libs will be installed here)&lt;br /&gt;
_______ libs/  (mingwlibs, the target libraries you just downloaded)&lt;br /&gt;
_______ win32.cmake (see below)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Create your cmake toolchain ==&lt;br /&gt;
Create the toolchain file:&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;pico &amp;quot;~/spring-xcompile/win32/win32.cmake&amp;quot;&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
=== Example toolchain file ===&lt;br /&gt;
An example should be self-explanatory:&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
# the name of the target operating system&lt;br /&gt;
SET(CMAKE_SYSTEM_NAME Windows)&lt;br /&gt;
&lt;br /&gt;
# which compilers to use for C and C++&lt;br /&gt;
SET(CMAKE_C_COMPILER i686-mingw32-gcc)&lt;br /&gt;
SET(CMAKE_CXX_COMPILER i686-mingw32-g++)&lt;br /&gt;
&lt;br /&gt;
# here is the target environment located&lt;br /&gt;
SET(CMAKE_FIND_ROOT_PATH /usr/i686-mingw32)&lt;br /&gt;
&lt;br /&gt;
# the spring mingw32 dependencies&lt;br /&gt;
SET(MINGWLIBS ~/spring-xcompile/win32/libs)&lt;br /&gt;
&lt;br /&gt;
# the path that make install will use to put the final binaries&lt;br /&gt;
SET(CMAKE_INSTALL_PREFIX ~/spring-xcompile/win32/final)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pass this with &amp;lt;code&amp;gt;-DCMAKE_TOOLCHAIN_FILE=~/spring-xcompile/win32/win32.cmake&amp;lt;/code&amp;gt; to cmake or select it in the gui to make a cross-compilation (see below).&lt;br /&gt;
&lt;br /&gt;
=== Some Options Explained ===&lt;br /&gt;
* &#039;&#039;&#039;MINGWLIBS&#039;&#039;&#039;: absolute path to the directory where [[Building_Spring_Cross_Compiled#Getting_Spring.27s_windows_dependencies |&#039;&#039;&#039;mingwlibs&#039;&#039;&#039;]] are located&lt;br /&gt;
* &#039;&#039;&#039;CMAKE_INSTALL_PREFIX&#039;&#039;&#039;: installation prefix (defaults to &#039;&#039;/usr/local&#039;&#039;) defines the base directory for installing&lt;br /&gt;
* &#039;&#039;&#039;DATADIR&#039;&#039;&#039;, &#039;&#039;&#039;LIBDIR&#039;&#039;&#039;, &#039;&#039;&#039;BINDIR&#039;&#039;&#039;: where to install data, libraries or binaries (relative paths are based on &#039;&#039;CMAKE_INSTALL_PREFIX&#039;&#039;, absolute paths are absolute (&#039;&#039;DESTDIR&#039;&#039; is still respected))&lt;br /&gt;
* &#039;&#039;&#039;APPLICATIONS_DIR&#039;&#039;&#039;, &#039;&#039;&#039;MIME_DIR&#039;&#039;&#039;, &#039;&#039;&#039;PIXMAPS_DIR&#039;&#039;&#039;: where to install freedesktop-files (icons, mime-types and application-description)&lt;br /&gt;
&lt;br /&gt;
=== Further reference ===&lt;br /&gt;
* [http://www.vtk.org/Wiki/CMake_FAQ CMake FAQ]&lt;br /&gt;
* &#039;&#039;man CMake&#039;&#039;&lt;br /&gt;
* [http://www.cmake.org/HTML/cmake-2.6.html CMake documentation] (long!)&lt;br /&gt;
&lt;br /&gt;
== Run CMake ==&lt;br /&gt;
&lt;br /&gt;
=== The GUI method ===&lt;br /&gt;
CMake offers a nice Qt4 gui to ease the following steps (to use the gui explained here, cmake must be at least version 2.6.1). Doing so should be straightforward:&lt;br /&gt;
* run &amp;lt;code&amp;gt;cmake-gui&amp;lt;/code&amp;gt;&lt;br /&gt;
* select source directory&lt;br /&gt;
* select (and/or create) build directory where temporary files are stored&lt;br /&gt;
* hit configure button&lt;br /&gt;
* choose your build type:&lt;br /&gt;
** Unix makefile if you are using linux and want to compile linux executables&lt;br /&gt;
** mingw makefiles if you are working on windows&lt;br /&gt;
** Unix makefile with cross compiler setup to cross-compile mingw32 executables, you have to select the toolchain file you want to use (see below)&lt;br /&gt;
* configure variables to your need (move the mouse over the options and read the tooltip for further informations on them and or see Section &amp;quot;Options&amp;quot;)&lt;br /&gt;
* push generate button&lt;br /&gt;
* open commandline and do &amp;quot;make&amp;quot; and &amp;quot;make install&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== The commandline method ===&lt;br /&gt;
&lt;br /&gt;
* cd to build directory&lt;br /&gt;
     &amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;cd ~user/spring-xcompile/win32/build&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;cmake &amp;lt;path to source tree&amp;gt; &amp;lt;options&amp;gt;&amp;lt;/code&amp;gt; where:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;lt;path to source tree&amp;gt;&amp;lt;/code&amp;gt; is the path to the spring source directory&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;lt;options&amp;gt;&amp;lt;/code&amp;gt; are defined like &amp;lt;code&amp;gt;-D&amp;lt;option name&amp;gt;=&amp;lt;value&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* IMPORTANT: depending on your settings you may need to run this command twice. It can&#039;t hurt so I recommend doing it anyway.&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;cmake &amp;quot;~user/spring-xcompile/spring&amp;quot; &amp;quot;-DCMAKE_TOOLCHAIN_FILE=~user/spring-xcompile/win32/win32.cmake&amp;quot;&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* run &amp;lt;code&amp;gt;make&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;make install&amp;lt;/code&amp;gt;&lt;br /&gt;
     &amp;lt;code&amp;gt;&amp;lt;pre&amp;gt;make spring &amp;amp;&amp;amp; make install-spring DESTDIR=~user/spring-xcompile/win32/final&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to show other build targets type: &amp;lt;code&amp;gt;make help&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Package Maintainers Note: ==&lt;br /&gt;
* doing &amp;lt;code&amp;gt;make install DESTDIR=/some/path&amp;lt;/code&amp;gt; will install spring in the specified place, putting files in subdirectories like configured. This may be helpful when making packages so Spring don&#039;t get installed on your system for real (see also CMake FAQ [[http://www.vtk.org/Wiki/CMake_FAQ#Does_CMake.27s_.22make_install.22_support_DESTDIR.3F]]).&lt;br /&gt;
* You need to update the mime database (&amp;lt;code&amp;gt;update-mime-database&amp;lt;/code&amp;gt;) and the kde database (&amp;lt;code&amp;gt;kbuildsycoca&amp;lt;/code&amp;gt;) after installing to make the system aware of the newly installed mime types and desktop shortcuts.&lt;br /&gt;
&lt;br /&gt;
==Buildbot==&lt;br /&gt;
For setting up a buildbot slave, see: {{sourcelink|file=buildbot/README.markdown}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Linux]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
	<entry>
		<id>https://www.fightorder.net/wiki/index.php?title=Create_map_texture_with_povray&amp;diff=2670</id>
		<title>Create map texture with povray</title>
		<link rel="alternate" type="text/html" href="https://www.fightorder.net/wiki/index.php?title=Create_map_texture_with_povray&amp;diff=2670"/>
		<updated>2026-03-04T02:12:03Z</updated>

		<summary type="html">&lt;p&gt;Qrow: Redirected page to Tutorial:MapWithPOVRay(Tinnut)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Tutorial:MapWithPOVRay(Tinnut)]]&lt;/div&gt;</summary>
		<author><name>Qrow</name></author>
	</entry>
</feed>