+Written by: {{author.short_name}}
+
+{% if contributor != nil and contributor != false %}
+
+
+
+{% endif %}
+
+{% endif %}
diff --git a/_sass/content-nocharset.scss b/_sass/content-nocharset.scss
new file mode 100644
index 0000000000..032f8ef159
--- /dev/null
+++ b/_sass/content-nocharset.scss
@@ -0,0 +1,237 @@
+// Styles for rendered markdown in the .main-content container
+// stylelint-disable selector-no-type, max-nesting-depth, selector-max-compound-selectors, selector-max-type, selector-max-specificity, selector-max-id
+
+.main-content {
+ line-height: $content-line-height;
+
+ ol,
+ ul,
+ dl,
+ pre,
+ address,
+ blockquote,
+ .table-wrapper {
+ margin-top: 0.5em;
+ }
+
+ a {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ ul,
+ ol {
+ padding-left: 1.5em;
+ }
+
+ li {
+ .highlight {
+ margin-top: $sp-1;
+ }
+ }
+
+ ol {
+ list-style-type: none;
+ counter-reset: step-counter;
+
+ > li {
+ position: relative;
+
+ &::before {
+ position: absolute;
+ top: 0.2em;
+ left: -1.6em;
+ color: $grey-dk-000;
+ content: counter(step-counter);
+ counter-increment: step-counter;
+ @include fs-3;
+
+ @include mq(sm) {
+ top: 0.11em;
+ }
+ }
+
+ ol {
+ counter-reset: sub-counter;
+
+ > li {
+ &::before {
+ content: counter(sub-counter, lower-alpha);
+ counter-increment: sub-counter;
+ }
+ }
+ }
+ }
+ }
+
+ ul {
+ list-style: none;
+
+ > li {
+ &::before {
+ position: absolute;
+ margin-left: -1.4em;
+ color: $grey-dk-000;
+ content: "•";
+ }
+ }
+ }
+
+ .task-list-item {
+ &::before {
+ content: "";
+ }
+ }
+
+ .task-list-item-checkbox {
+ margin-right: 0.6em;
+ margin-left: -1.4em;
+
+ // The same margin-left is used above for ul > li::before
+ }
+
+ hr + * {
+ margin-top: 0;
+ }
+
+ h1:first-of-type {
+ margin-top: 0.5em;
+ }
+
+ dl {
+ display: grid;
+ grid-template: auto / 10em 1fr;
+ }
+
+ dt,
+ dd {
+ margin: 0.25em 0;
+ }
+
+ dt {
+ grid-column: 1;
+ font-weight: 500;
+ text-align: right;
+
+ &::after {
+ content: ":";
+ }
+ }
+
+ dd {
+ grid-column: 2;
+ margin-bottom: 0;
+ margin-left: 1em;
+
+ blockquote,
+ div,
+ dl,
+ dt,
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ li,
+ ol,
+ p,
+ pre,
+ table,
+ ul,
+ .table-wrapper {
+ &:first-child {
+ margin-top: 0;
+ }
+ }
+ }
+
+ dd,
+ ol,
+ ul {
+ dl:first-child {
+ dt:first-child,
+ dd:nth-child(2) {
+ margin-top: 0;
+ }
+ }
+ }
+
+ .anchor-heading {
+ position: absolute;
+ right: -$sp-4;
+ width: $sp-5;
+ height: 100%;
+ padding-right: $sp-1;
+ padding-left: $sp-1;
+ overflow: visible;
+
+ @include mq(md) {
+ right: auto;
+ left: -$sp-5;
+ }
+
+ svg {
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ color: $link-color;
+ visibility: hidden;
+ }
+ }
+
+ .anchor-heading:hover,
+ .anchor-heading:focus,
+ h1:hover > .anchor-heading,
+ h2:hover > .anchor-heading,
+ h3:hover > .anchor-heading,
+ h4:hover > .anchor-heading,
+ h5:hover > .anchor-heading,
+ h6:hover > .anchor-heading {
+ svg {
+ visibility: visible;
+ }
+ }
+
+ summary {
+ cursor: pointer;
+ }
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ #toctitle {
+ position: relative;
+ margin-top: 1.5em;
+ margin-bottom: 0.25em;
+
+ + table,
+ + .table-wrapper,
+ + .code-example,
+ + .highlighter-rouge,
+ + .sectionbody .listingblock {
+ margin-top: 1em;
+ }
+
+ + p:not(.label) {
+ margin-top: 0;
+ }
+ }
+
+ > h1:first-child,
+ > h2:first-child,
+ > h3:first-child,
+ > h4:first-child,
+ > h5:first-child,
+ > h6:first-child,
+ > .sect1:first-child > h2,
+ > .sect2:first-child > h3,
+ > .sect3:first-child > h4,
+ > .sect4:first-child > h5,
+ > .sect5:first-child > h6 {
+ margin-top: $sp-2;
+ }
+}
diff --git a/_sass/custom/custom.scss b/_sass/custom/custom.scss
new file mode 100644
index 0000000000..51766ac182
--- /dev/null
+++ b/_sass/custom/custom.scss
@@ -0,0 +1,226 @@
+/****************************/
+/***** Jekyll Glossary ******/
+/****************************/
+
+/* vendored from https://raw.githubusercontent.com/erikw/jekyll-glossary_tooltip/main/lib/jekyll-glossary_tooltip/jekyll-glossary_tooltip.css */
+
+.jekyll-glossary {
+ position: relative;
+ display: inline-block;
+ border-bottom: 2px dotted #0074bd;
+ cursor: help;
+}
+
+.jekyll-glossary .jekyll-glossary-tooltip {
+ visibility: hidden;
+ width: 120px;
+ background-color: black;
+ color: #fff;
+ text-align: center;
+ font-size: 0.5em;
+ padding: 5px;
+ border-radius: 6px;
+
+ /* Position the tooltip text - see examples below! */
+ position: absolute;
+ z-index: 1;
+
+ width: 160px;
+ bottom: 100%;
+ left: 50%;
+ margin-left: -80px; /* Use half of the width to center the tooltip */
+
+}
+
+/* Show the tooltip text when you mouse over the tooltip container */
+.jekyll-glossary:hover .jekyll-glossary-tooltip {
+ visibility: visible;
+}
+
+/* Style the source link (if there is one provided in the glossary entry). */
+.jekyll-glossary-source-link:before {
+ content: "[source]"; // "(reference)", "" or whatever you want.
+}
+
+/* Arrow created with borders. */
+.jekyll-glossary .jekyll-glossary-tooltip::after {
+ content: " ";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: black transparent transparent transparent;
+}
+
+/* Animation from invisible to visible on hover. */
+.jekyll-glossary .jekyll-glossary-tooltip {
+ opacity: 0;
+ transition: opacity 1s;
+}
+.jekyll-glossary:hover .jekyll-glossary-tooltip {
+ opacity: 1;
+}
+
+/***************************/
+/***** Theme switcher ******/
+/***************************/
+
+// vendored from https://github.com/argyleink/gui-challenges/blob/main/theme-switch
+
+@import"https://unpkg.com/open-props/easings.min.css";
+
+.sun-and-moon>:is(.moon,.sun,.sun-beams) {
+ transform-origin:center center
+}
+.sun-and-moon>:is(.moon,
+.sun) {
+ fill:var(--icon-fill)
+}
+.theme-toggle:is(:hover,
+:focus-visible)>.sun-and-moon>:is(.moon,
+.sun) {
+ fill:var(--icon-fill-hover)
+}
+.sun-and-moon>.sun-beams {
+ stroke:var(--icon-fill);
+ stroke-width:2px
+}
+.theme-toggle:is(:hover,
+:focus-visible) .sun-and-moon>.sun-beams {
+ stroke:var(--icon-fill-hover)
+}
+[data-theme=dark] .sun-and-moon>.sun {
+ transform:scale(1.75)
+}
+[data-theme=dark] .sun-and-moon>.sun-beams {
+ opacity:0
+}
+[data-theme=dark] .sun-and-moon>.moon>circle {
+ transform:translate(-7px)
+}
+@supports (cx: 1) {
+ [data-theme=dark] .sun-and-moon>.moon>circle {
+ transform:translate(0);
+ cx:17
+ }
+}
+@media (prefers-reduced-motion: no-preference) {
+ .sun-and-moon>.sun {
+ transition:transform .5s var(--ease-elastic-3)
+ }
+ .sun-and-moon>.sun-beams {
+ transition:transform .5s var(--ease-elastic-4),opacity .5s var(--ease-3)
+ }
+ .sun-and-moon .moon>circle {
+ transition:transform .25s var(--ease-out-5)
+ }
+ @supports (cx: 1) {
+ .sun-and-moon .moon>circle {
+ transition:cx .25s var(--ease-out-5)
+ }
+ }
+ [data-theme=dark] .sun-and-moon>.sun {
+ transform:scale(1.75);
+ transition-timing-function:var(--ease-3);
+ transition-duration:.25s
+ }
+ [data-theme=dark] .sun-and-moon>.sun-beams {
+ transform:rotate(-25deg);
+ transition-duration:.15s
+ }
+ [data-theme=dark] .sun-and-moon>.moon>circle {
+ transition-delay:.25s;
+ transition-duration:.5s
+ }
+}
+.theme-toggle {
+ --size: 2rem;
+ --icon-fill: hsl(210 10% 30%);
+ --icon-fill-hover: hsl(210 10% 15%);
+ background:none;
+ border:none;
+ padding:0;
+ inline-size:var(--size);
+ block-size:var(--size);
+ aspect-ratio:1;
+ border-radius:50%;
+ cursor:pointer;
+ touch-action:manipulation;
+ -webkit-tap-highlight-color:transparent;
+ outline-offset:5px
+}
+.theme-toggle>svg {
+ inline-size:100%;
+ block-size:100%;
+ stroke-linecap:round
+}
+[data-theme=dark] .theme-toggle {
+ --icon-fill: hsl(210 10% 70%);
+ --icon-fill-hover: hsl(210 15% 90%)
+}
+@media (hover: none) {
+ .theme-toggle {
+ --size: 48px
+ }
+}
+
+/***************************/
+/***** Custom Stuff ********/
+/***************************/
+
+// unvendored do not remove
+html[data-theme="dark"] button.theme-toggle, button.theme-toggle {
+ height: 100%;
+ margin: 0 0 0 1rem;
+}
+
+/* Enlarged dt */
+.main-content dl.big-dt, html[data-theme="dark"] dl.big-dt {
+ grid-template: auto / fit-content(20em) 1fr;
+}
+
+/* dt allowed to expand/contract */
+.main-content dl.dl-auto, html[data-theme="dark"] dl.dl-auto {
+ grid-template: auto / auto 1fr;
+}
+
+.aux-nav, html[data-theme="dark"] .aux-nav {
+ padding-right: 0;
+}
+
+.aux-nav-img img {
+ filter: invert(27%) sepia(14%) saturate(463%) hue-rotate(166deg) brightness(93%) contrast(85%);
+}
+
+.aux-nav-list-item:hover .aux-nav-img img {
+ filter: invert(13%) sepia(6%) saturate(958%) hue-rotate(169deg) brightness(96%) contrast(95%);
+}
+
+html:not(data-theme) .aux-nav-matrix img, html[data-theme="light"] .aux-nav-matrix img {
+ background-image: url("/spring/assets/matrix-logo.svg");
+}
+
+html[data-theme="dark"] .aux-nav-matrix img {
+ background-image: url("/spring/assets/matrix-logo-white.svg");
+}
+
+html:not(data-theme) .aux-nav-github img, html[data-theme="light"] .aux-nav-github img {
+ background-image: url("/spring/assets/github-mark.svg");
+}
+
+html[data-theme="dark"] .aux-nav-github img {
+ background-image: url("/spring/assets/github-mark-white.svg");
+}
+
+html .aux-nav-img img {
+ display: block;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+
+ background-repeat: no-repeat;
+ background-size: cover;
+ height: 32px;
+ padding-left: 100%; /* Equal to width of new image */
+}
diff --git a/_sass/modules-nocharset.scss b/_sass/modules-nocharset.scss
new file mode 100644
index 0000000000..cdf9509b4d
--- /dev/null
+++ b/_sass/modules-nocharset.scss
@@ -0,0 +1,16 @@
+// Import external dependencies
+@import "./vendor/normalize.scss/normalize";
+
+// Modules
+@import "./base";
+@import "./layout";
+@import "./content-nocharset";
+@import "./navigation";
+@import "./typography";
+@import "./labels";
+@import "./buttons";
+@import "./search";
+@import "./tables";
+@import "./code";
+@import "./utilities/utilities";
+@import "./print";
diff --git a/_scripts/get_engine_data.sh b/_scripts/get_engine_data.sh
new file mode 100755
index 0000000000..dc0b16d74f
--- /dev/null
+++ b/_scripts/get_engine_data.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+DATA_DIR="$SCRIPT_DIR/../_data"
+DATA_FILE="$DATA_DIR/latest_release.json"
+WORK_DIR="$SCRIPT_DIR/tmp"
+CONFIG_FILE="$DATA_DIR/configs.json"
+WDEFS_FILE="$DATA_DIR/weapondefs.json"
+COMMANDS_FILE="$DATA_DIR/unsynced_commands.json"
+SYNCED_COMMANDS_FILE="$DATA_DIR/synced_commands.json"
+
+DOWNLOAD_URL=$(jq -r '.assets[] | select(.name | contains("_linux-64-minimal-portable")).browser_download_url' $DATA_FILE)
+
+echo "> downloading latest engine release from $DOWNLOAD_URL"
+
+mkdir $WORK_DIR
+cd $WORK_DIR
+
+curl -L $DOWNLOAD_URL -o engine.7z
+7z -y e engine.7z spring
+
+echo "> writing $CONFIG_FILE"
+rm -f $CONFIG_FILE
+./spring --list-config-vars | grep -v "^\[t=" > $CONFIG_FILE
+
+echo "> writing $WDEFS_FILE"
+rm -f $WDEFS_FILE
+./spring --list-def-tags | grep -v "^\[t=" > $WDEFS_FILE
+
+echo "> writing $COMMANDS_FILE"
+rm -f $COMMANDS_FILE
+./spring --list-unsynced-commands | grep -v "^\[t=" > $COMMANDS_FILE
+
+echo "> writing $SYNCED_COMMANDS_FILE"
+rm -f $SYNCED_COMMANDS_FILE
+./spring --list-synced-commands | grep -v "^\[t=" > $SYNCED_COMMANDS_FILE
diff --git a/_scripts/get_release_data.sh b/_scripts/get_release_data.sh
new file mode 100755
index 0000000000..6199cd88c2
--- /dev/null
+++ b/_scripts/get_release_data.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+DATA_FILE="$SCRIPT_DIR/../_data/latest_release.json"
+
+echo "> writing $DATA_FILE"
+
+rm $DATA_FILE
+curl -s https://api.github.com/repos/beyond-all-reason/spring/releases/latest \
+ > $DATA_FILE
diff --git a/about.markdown b/about.markdown
new file mode 100644
index 0000000000..b6ac31f041
--- /dev/null
+++ b/about.markdown
@@ -0,0 +1,10 @@
+---
+layout: default
+title: About
+permalink: /about/
+---
+
+Recoil is an RTS engine.
+
+You can find the source code for Recoil at GitHub:
+[spring](https://github.com/beyond-all-reason/spring)
diff --git a/articles.markdown b/articles.markdown
new file mode 100644
index 0000000000..972cf8783e
--- /dev/null
+++ b/articles.markdown
@@ -0,0 +1,10 @@
+---
+layout: default
+title: Articles
+nav_order: 2
+has_children: true
+permalink: articles
+---
+
+# Articles
+{: .no_toc }
diff --git a/articles/ceg-operators.markdown b/articles/ceg-operators.markdown
new file mode 100644
index 0000000000..b7fa2132d5
--- /dev/null
+++ b/articles/ceg-operators.markdown
@@ -0,0 +1,218 @@
+---
+layout: post
+title: CEG operators
+parent: Articles
+permalink: articles/ceg-operators
+author: sprunk
+---
+
+## Basics
+
+You can customize some CEG entries by specifying a series of operators instead of just a number.
+This lets you apply limited logic to customize explosions.
+Each operation is specified by a single string.
+
+### Raw numbers
+
+A single number means just that number. So for example:
+```lua
+sizeGrowth = "3",
+```
+This sets `sizeGrowth` to 3. So far so simple.
+
+Some parameters, especially 3D vectors, accept multiple values. In that case, we separate then with the `,` (comma) operator.
+For example:
+```lua
+pos = "1, 2, 3",
+```
+This sets the position vector to x=1, y=2, and z=3.
+
+### Running value
+
+There is an implicit running value, which starts at 0 and on which all the operators work.
+A raw number is actually an "operatorless" operator that performs simple addition to that running value.
+So, one could think of the examples above as really being `0 +3` for `sizeGrowth` and `0 +1`, `0 +2`, and `0 +3` as the components of the `pos` vector respectively.
+
+
+To illustrate how this matters, consider this example. Note that there is no `,` between these!
+```lua
+sizeGrowth = "1 2 3",
+```
+The result of this is 6. This is because each of these is addition: `0 +1 +2 +3`, which nets 6.
+
+Similarly:
+```lua
+pos = "1 2, 3 4, 5 6",
+```
+This sets the components to 3 (0 +1 +2), 7 (0 +3 +4) and 11 (0 +5 +6) respectively.
+
+
+Putting a bunch of raw numbers next to each other doesn't make much sense, since we could have just written the sum directly, but the principle starts to matter when you mix operators.
+
+### Random (`r`)
+
+The `r` operator also works on the running value, but adds a random value between 0 and the operand.
+So, for example, `r4` gives a random value between 0 and 4. Practical hints:
+ * very useful to make explosions look less artificial.
+ * if you don't want to roll from 0, then just add the offset (remember a raw operatorless number performs addition).
+So, `3 r4` gives a value between 3 and 7.
+ * a common desire is to roll negative values, for example so that a directional particle can go either way.
+In that case, also use the offset. A common idiom is to use, for example, `-15 r30` to roll ±15.
+ * the value is distributed uniformly, but with some knowledge of statistics you could tweak it by stacking rolls.
+For example `r6` produces a flat uniform 0-6 distribution, `r3 r3` a sort of triangle where 3 is much more likely than 0 or 6, and `r2 r2 r2` something smoother still, more resembling a bell curve.
+In practice this seems very underused though, and you can't make the distribution asymmetrical via this basic method,
+though you can via the more advanced ones below.
+
+### Index (`i`)
+
+The `i` operator multiplies its operand by the index of the particle, and adds this to the running value.
+When a generator spawns multiple particles of the same kind, each one is assigned an increasing index: 0, 1, 2, etc.
+
+For example, if an explosion spawns 4 particles and `size = "3 i2"`, then they will have sizes of 3, 5, 7 and 9 respectively.
+
+ * useful for spawning stuff in something resembling a line or cone.
+ * useful for scaling explosions via particle count, since the low-index particles will behave the same as before.
+ * remember this doesn't multiply the running value, or anything other than the operand.
+
+### Damage (`d`)
+
+The `d` operator multiplies its operand by the "damage" of an explosion.
+For example `d0.1` will net 10 for a 100-damage explosion, and 50 for a 500-damage explosion.
+
+Some practical remarks:
+ * for regular weapons, this is the "default" damage. Beware if you treat it as the "features" armor class (since they can't have a real armor class)!
+ * for CEG trails, damage is the remaining TTL of the projectile. So you can for example make missile trails burn out.
+ * makes sure adjustments of damage (both ingame such as buffs or upgrades, or metagame changes such as balance changes after a patch) are subtly reflected in the visuals.
+ * also lets you reuse the same CEG for multiple weapons of a similar type, for great visual consistency.
+ * existing games prefer to have a separate effect for each similar weapon, so this is quite an uncommon operator as far as examples to look at.
+
+## Advanced
+
+In addition to the running value, you have an access to a buffer with 16 "slots" to which you can save values for later use.
+Other than allowing complex math, these let you reuse a value for multiple components of a vector (across the `,` boundary which normally resets the running value).
+There are also some operators that involve more complex values than just addition.
+
+### Yank (`y`), add (`a`), and multiply (`x`)
+
+The `y` operator saves ("yanks") the current running value to the buffer under given index, and resets the running value to 0.
+The `a` operator adds to the current running value from given buffer.
+The `x` operator multiplies the running value by the value of given buffer.
+
+Examples:
+ * `10 r20 y7 5 r10 x7`. Rolls a random value 10-30 (see earlier lesson), saves it to buffer #7 (which resets running value to 0), rolls a different one 5-15, then multiplies it by the value of the contents of buffer #7 (i.e. the previous roll). In general, the `foo yN bar xN` pattern is how you multiply `foo` and `bar`.
+ * `r10 y9 a9, 0, a9`. Rolls a value, saves it to buffer #9, loads it right back because of the reset. Reuses the value for the third component of the vector. This is how you can get diagonal vectors.
+
+### Sinus (`s`)
+
+The `s` operator treats its operand as an amplitude and the current running value as the phase, and _replaces_ the current running value with the result.
+For example `3 s2` is about 0.28, because that's `2 * sin(3 radians)`.
+
+ * only really makes sense with sources of unpredictability such as `r`, `i`, or `d`.
+ * there is no separate cosinus operator, but you can make a ghetto cosinus via `cos(x) = sin(π/2 + x)`, i.e. just do `1.57 sX` instead of just `sX`.
+ * good for making circular or spherical volumetric effects (for non-volumetric there's basic spread parameters like `emitRot`).
+
+### Sawtooth/modulo (`m`)
+
+Applies the modulo operator to the running value. So for example:
+ * `1 m7` is 1
+ * `6 m7` is 6
+ * `7 m7` is 0
+ * `8 m7` is 1
+ * `13 m7` is 6
+ * `14 m7` is 0
+
+In combination with the `i` operator, or the `d` operator for CEG trails, you can get periodic or "line stipple" style effects,
+since those are where multiple consecutive particles will be spawned with consecutive values for the sawtooth to work on.
+For example `numParticles = "d0.125 m1 0.125"` gets you one particle every 8 frames in a CEG trail.
+
+### Discretize (`k`)
+
+Truncates the running value to a multiple of the operand:
+ * `1 k7` is 0
+ * `6 k7` is 0
+ * `7 k7` is 7
+ * `8 k7` is 7
+ * `13 k7` is 7
+ * `14 k7` is 14
+
+You can use this to perform comparisons and have ghetto boolean logic, as long as you can provide some sort of upper bound on the running value.
+It's admittedly a pretty inane way to do it and if you're at that point consider whether it would not be better to just make particles via Lua though.
+* say you want to check "if x >= 123" and can assume that x < 10000.
+* do `(...) 9877 k10000 -9999`
+* now the running value is 0 or 1 depending on whether it was less or more than 123 before.
+* you can then use it to perform further operations.
+* in particular, multiplying/summing such "booleans" gives you the AND/OR logic operators respectively.
+
+Some obvious use cases achievable by conditionally setting `numParticles`:
+* you can make a CEG trail fizzle out a bit earlier than nominal projectile expiration by checking if the remaining TTL is low enough.
+* in a CEG used for multiple similar weapons via the damage operator, you can enable extra particle types for big enough explosions.
+* comparisons and boolean logic open up a lot of possibilities, making this perhaps the most powerful operator.
+
+### Power (`p`) and power buffer (`q`)
+
+The `p` operator raises the running value to the operandth power. The `q` operator is similar but takes the power from given buffer. The main use case is probably for getting x² or √x for making volumetric effects more or less center-heavy. Examples:
+ * `3 p4` is 81, since that's 3⁴.
+ * `4 y7 3 q7` is also 81 (and leaves the 7th buffer slot with the value of 4).
+
+## Table
+
+Notation: V is the running value, X is the operand, and B denotes the buffer.
+
+
+
+
operator
+
effect
+
+
+
(none)
+
V += X
+
+
+
r
+
V += random(0; X)
+
+
+
i
+
V += X * index
+
+
+
d
+
V += X * damage
+
+
+
y
+
B[X] = V V = 0
+
+
+
a
+
V += B[X]
+
+
+
x
+
V *= B[X]
+
+
+
s
+
V = X * sin(V)
+
+
+
m
+
V = V % X
+
+
+
k
+
V = floor(V / X) * X
+
+
+
p
+
V = VX
+
+
+
q
+
V = VB[X]
+
+
+
,
+
result = V V = 0
+
+
diff --git a/articles/modrules-and-others.markdown b/articles/modrules-and-others.markdown
new file mode 100644
index 0000000000..44334f25a8
--- /dev/null
+++ b/articles/modrules-and-others.markdown
@@ -0,0 +1,39 @@
+---
+layout: post
+title: Modrules and (un)related concepts
+parent: Articles
+permalink: articles/modrules-and-others
+author: sprunk
+---
+
+### Mod options, mod info, mod rules, rules params
+These concepts have similar names (which stems from how Recoil games used to be called "mods", even original/standalone ones)
+and all deal with customizability in some way, so often get confused with each other. But they are not directly related.
+This article will briefly describe all of them in a way that hopefully prevents any ambiguity.
+
+### Mod info
+* metadata about the game's archive. Things like name and version.
+* lives in `./modinfo.lua`, hence the name.
+* many mature games put `"$VERSION"` as the version. This is a magic value replaced by the Rapid distribution system with the actual version string. Check out Rapid's documentation for specifics.
+* this file is both necessary and sufficient for a Recoil game, and its contents can consist of just the `name` entry as well.
+* for engine devs: note that in engine internals, modinfo is **not** represented by the `CModInfo` class, that one is actually _mod rules_! Modinfo is a "class 1 meta file" only really referred to in the archive scanner.
+
+### Mod rules
+* a hardcoded set of knobs for tweaking engine behaviour.
+* some technical (like the choice of pathfinding system or allocation limits) and some gameplay related (like the resource cost scaling for repairing).
+* everything is a single constant global value. No per-unit rules, no changing at runtime, no rules outside the limited set exposed by the engine.
+* read from `./gamedata/modrules.lua`, hence the name. Internal engine C++ code stores them in the `CModInfo` class.
+* can depend on modoptions.
+
+### Mod options
+* per-match game setup.
+* a set of arbitrary key-value pairs supplied by the host; interpretation is solely up to the game.
+* usually live in `./modoptions.lua`, but this is just a convention for lobbies (this is where `unitsync` looks). The engine doesn't read this, nor enforce defaults etc.
+* usually used for things like enabling custom modes and game setups (add lava, disable units etc.), but also is the technical means for supplying singleplayer mission data.
+* maps can suggest and interpret modoptions too, these are often called mapoptions.
+
+### Rules params
+* dynamic per-unit/team or gamewide values.
+* arbitrary key-value pairs set and interpreted by the game.
+* usually used for storing attributes like "is burning" or "slowdown %" or such.
+* can depend on both modoptions and modrules.
diff --git a/articles/netcode-overview.markdown b/articles/netcode-overview.markdown
new file mode 100644
index 0000000000..bb935bcdc1
--- /dev/null
+++ b/articles/netcode-overview.markdown
@@ -0,0 +1,56 @@
+---
+layout: post
+title: Netcode overview
+parent: Articles
+permalink: articles/netcode-overview
+author: sprunk
+---
+
+Here's an overview of some aspects of Recoil's so-called netcode. The **tl;dr is that it uses lockstep simulation which is essentially the same as basically every classic RTS from the 90s and 00s**. Read on for a bit more detailed explanation.
+
+### What gets sent?
+
+**The Recoil network protocol does not involve sending parts of the simulation state**, such as unit positions or health. The **only thing sent over is player inputs**, such as a "move unit X to position Y" order.
+How does each client know the game state, then?
+The **inputs are sufficient to simulate the gamestate**: if unit X is ordered to move to position Y then its position will change according to its speed (speed being defined in a local file, so does not need to be sent),
+maybe it then finds enemy unit Z, which it can shoot according to its range (also defined in a local file) and then that unit's health will decrease by some number - again the calculations will look the same on each client.
+Also, keep in mind the **gamestate can easily get magnitudes larger than the size of the inputs** - for example the two orders "build unit, repeat" on a factory will produce a quickly growing gamestate.
+
+### Ping and the packet queue
+Of course, packets do not arrive at the server immediately, your ping is involved.
+But that is not all: **to guard against network instability**, they are not broadcast by the server to other clients immediately. Instead, **packets are queued for a bit later in a buffer**.
+By default this is about 3-6 simulation frames (100-200ms) on top of your "normal" ping, though **games can adjust it and the engine also adjusts it on its own if a match is "laggy"**.
+In interfaces such as the `/info` playerlist and `Spring.GetPlayerInfo`, the **ping is in terms of sim frames after taking the queue into account**.
+
+### Sync
+What makes the "simulate inputs" approach work is that **the engine takes utmost care to keep calculations identical** on each client.
+This is not trivial because you still have to work with **things that naturally differ on each client**, such as mouse position or which units are selected - this is **called the unsynced state**.
+On top of that, there can be **hardware differences** that have to be worked around to get identical results - the huge effort involved is one of the reasons why **Recoil is not available outside x86-64**.
+Finally, **desync can cascade in a sort of butterfly effect**.
+For example, maybe a unit deals a bit less damage than it should, which makes its target survive an otherwise lethal hit;
+that unit then bumps into a constructor, slightly delaying the construction of a resource collector.
+A difference in stored resources then stems from an incorrect damage calculation minutes earlier.
+The **huge effort of hunting down sources of desync** is one of the biggest drawbacks of the architecture.
+
+### Replays and saves
+A **replay is just a replication of the network queue from the live game**.
+Instead of receiving packets from the server, you read them from your local replay file. Otherwise **everything proceeds largely the same as a live game**.
+
+A save is an **imperfect snapshot** of the gamestate.
+While doable, a **perfect replication is difficult and not currently implemented**.
+Running identical inputs on a save will proceed differently compared to running them on the original state from which the save was produced.
+This means that **using the save mechanism is not currently possible to get mid-game joins or skipping backwards in replays** since it would desync, although could happen if somebody contributes the effort.
+
+### Tradeoffs involved
+The **benefits** of this setup are:
+* extremely small bandwidth use (remember Recoil has its roots in Total Annihilation back from 1997)
+* small replay size
+* you cannot easily cheat by modifying your own gamestate (cheat engine style), since there are no inputs that will replicate that on the server.
+* the server doesn't need to simulate a game, since clients already do. This makes hosting much easier. See [the dedicated server article]({{ site.baseurl }}{% link guides/headless-and-dedi.markdown %}).
+* for development: easy to reproduce bugs - running the same inputs will result in the same simulation, containing the bug.
+
+But there are also **drawbacks**:
+* you cannot jump to any specific time, either in a replay or e.g. to rejoin an ongoing game at its current state.
+The gamestate at any simulation frame has to be simulated from the previous frame, and that frame has to be simulated from the one before it, beginning all the way back at game start.
+* you also have to simulate the whole game state including enemy units, so maphack is possible.
+* making sure desync doesn't happen takes a huge effort.
diff --git a/articles/pathfinding.markdown b/articles/pathfinding.markdown
new file mode 100644
index 0000000000..4ee2abc927
--- /dev/null
+++ b/articles/pathfinding.markdown
@@ -0,0 +1,7 @@
+---
+layout: default
+title: Pathfinding
+parent: Articles
+permalink: articles/pathfinding
+published: false
+---
diff --git a/articles/select-command.markdown b/articles/select-command.markdown
new file mode 100644
index 0000000000..efb6938de9
--- /dev/null
+++ b/articles/select-command.markdown
@@ -0,0 +1,221 @@
+---
+layout: default
+title: The select command
+parent: Articles
+permalink: /articles/select-command/
+---
+
+# The `select` command
+
+You can bind the command `select` to a key, in order to define custom selection commands. After select, you can specify a **selector**, that defines *which* units will be selected.
+
+This selector consists of three parts: `SOURCE+FILTER+CONCLUSION+`
+
+- *SOURCE*: Which units to choose from.
+- *FILTER*: Narrow down the list of units.
+- *CONCLUSION*: What to do exactly with the units that were chosen and filtered.
+
+These three parts are all succeeded by a literal plus sign (`+`), so there is one `+` between *SOURCE* and *FILTER*, one between *FILTER* and *CONCLUSION*, and one after *CONCLUSION*. Do **not** use a space (` `) anywhere in the entire expression.
+
+For example, `AllMap+_Builder+_SelectAll+` is a valid selector, where:
+
+- `AllMap` is the *SOURCE*
+- `_Builder` is the *FILTER* and
+- `_SelectAll` is the *CONCLUSION*.
+
+Note that there is an **underscore** (`_`) between each element, even if there is already a plus sign (`+`), for hysterical raisins...
+
+## Source
+
+The *SOURCE* describes the set of units that you want to filter and pick a selection from. For players, these are restricted your team. It can be one of
+
+- `AllMap`: All active units on the entire map.
+- `Visible`: All active units that are currently visible.
+- `PrevSelection`: All units present in previous selection; that is, the one active before you hit the selection key.
+- `FromMouse_`: All units that are at most a distance of `` away from the mouse cursor.
+- `FromMouseC_`: Same as above, but using a vertical cylinder instead of a sphere. This is good for selecting airplanes or ships on deep water.
+
+## Filter
+
+The *FILTER* is an arbitrarily long list of filters.
+
+Here are the filters. Note that "units" generally means both buildings and mobile units. Typing both got old real quick.
+
+### `Not`
+
+ Every filter can be preceded by `Not` to negate it. You have to use a `_` to separate the `Not` from the filter, as in `Not_Builder`.
+
+### `AbsoluteHealth_`
+
+ Keep only units that have an absolute health greater than `` points.
+
+ - `AbsoluteHealth_100`: Keep only units that have **more** than 100 health points left.
+ - `Not_AbsoluteHealth_100`: Keep only units that have **less** than 101 health points left.
+
+### `Aircraft`
+
+ Keep only units that can fly.
+
+### `Builder`
+
+ Keep only units and buildings that can construct. This means Factories, Construction Turrets, Constructors, but not Rezzers.
+
+### `Buildoptions`
+
+ Keep only units that can build other units or buildings.
+
+### `Building`
+
+ Keep only buildings, not mobile units.
+
+### `Category_`
+
+ Keep only units of category ``
+
+### `Cloak`
+
+ Keep only units that can cloak.
+
+### `Cloaked`
+
+ Keep only units that are currently cloaked.
+
+### `Guarding`
+
+ Keep only units that currently have a **Guard** order.
+
+### `Patrolling`
+
+ Keep only units that have a **Patrol** order early in the queue (first 4 commands, including sub-orders spawned by Patrol).
+
+### `IdMatches_`
+
+ Keep only units whose internal name (unitDef name) matches `` **exactly**. Differently from other filters further invocations will match units matching one name **OR** another.
+
+ - `IdMatches_armcom`: keep only Armada Commanders (internally named `armcom`).
+ - `IdMatches_armcom_IdMatches_armflea`: keep only Armada Commanders or Fleas.
+ - `Not_IdMatches_armcom_Not_IdMatches_armflea`: keep all units that are not Armada Commanders or Fleas.
+
+### `Idle`
+
+ Keep only units that are currently idle, i.e. do not have any active order.
+
+### `InGroup_`
+
+ Keep only units that are in control group ``.
+
+ - `Not_InGroup_`: keep all units that are **not** currently in control group ``.
+
+### `InHotkeyGroup`
+
+ Keep only units that are in any control group.
+
+ - `Not_InHotkeyGroup`: keep all units that are **not** currently in any control group.
+
+### `InPrevSel`
+
+ Keep only units of the same type (unitDefID) as any unit in the selection before this `select` command was run.
+
+### `Jammer`
+
+ Keep only units that have a jammer radius greater than 0.
+
+### `ManualFireUnit`:
+
+ Keep only units that have a weapon that requires manual firing (currently only the commanders and Armada Thor).
+
+### `NameContain_`
+
+ Keep only units whose name contains the ``.
+
+### `Radar`
+
+ Keep only units that have a radar or sonar radius greater than 0.
+
+### `Resurrect`
+
+ Keep only units that can resurrect other units.
+
+### `RelativeHealth_`
+
+ Keep only units that have health greater than `` percent.
+
+ Example:
+ - `Not_RelativeHealth_10`: Keep only units that have less than 10% health.
+
+### `RulesParamEquals__`
+
+ Keep only units where the `` rules parameter has the exact value ``.
+
+### `Stealth`
+
+ Keep only units that are stealthy.
+
+### `Transport`
+
+ Keep only units that can transport other units.
+
+### `Waiting`
+
+ Keep only units that currently have a **Wait** order.
+
+### `WeaponRange_`
+
+ Keep only units that have a maximum weapon range greater than ``.
+
+### `Weapons`
+
+ Keep only units that have any weapons.
+
+
+## Conclusion
+
+The *CONCLUSION* specifies what to do with the units that are left from the source after running through all the filters.
+
+If the *CONCLUSION* starts with `_ClearSelection`, your new selection will replace the old one; otherwise, it will just add to it. It must be followed by exactly one of:
+
+- `SelectAll`: all units
+- `SelectOne`: one unit, will also center the camera on that unit. This command remembers which unit was selected, on repeating the selection command, the **next** unit will be selected, so you can cycle through all matching units.
+- `SelectClosestToCursor`: one unit, the one closest to the mouse cursor.
+- `SelectNum_`: `` units. Repeating this command adds `` more units.
+- `SelectPart_`: `` percent of the units.
+
+
+# Examples
+Recall that between every two tokens, there must be an underscore `_`, even if there is also a `+`. Another way to put it is that before every word in your selector except the *SOURCE*, there must be an underscore.
+
+Some examples. Again, "unit" also includes buildings.
+
+- `AllMap++_ClearSelection_SelectAll+`
+
+ Selects everything on the entire map.
+
+- `AllMap+_Builder_Idle+_ClearSelection_SelectOne+`
+
+ Selects any (one) idle builder (unit or building) on entire map. Repeatedly running this command will cycle through all idle builders.
+
+- `AllMap+_Buildoptions_Building+_ClearSelection_SelectNum_1+`
+
+ Selects any (one) building that can produce units (i.e. factories), map-wide. Repeatedly running this command will cycle through all factories. Unlike the above example, this selector will **not** snap the camera to the factory. Replace `SelectNum_1` with `SelectOne` in order to achieve this.
+
+- `AllMap+_Radar+_ClearSelection_SelectAll+`
+
+ Selects all units with radar/sonar/jammer.
+
+- `AllMap+_Not_Aircraft_Weapons+_ClearSelection_SelectAll+`
+
+ Selects all non-aircraft armed units
+
+- `AllMap+_InPrevSel+_ClearSelection_SelectAll+`
+
+ Selects all units of any type that was in your previous selection.
+
+ Note that up to now, all keys said `ClearSelection`, hence they replaced your old selection.
+
+- `AllMap+_InPrevSel_Not_InHotkeyGroup+_SelectAll+`
+
+ Selects all units of any type that was in your previous selection, unless they are already in a hotkey group.
+
+- `PrevSelection+_Not_Building_Not_RelativeHealth_30+_ClearSelection_SelectAll+`
+
+ From your previous selection, leaves everything that is below 30% health, and not a building. (Use this to quickly retreat damaged units.)
diff --git a/articles/team-terminology.markdown b/articles/team-terminology.markdown
new file mode 100644
index 0000000000..f2225763f6
--- /dev/null
+++ b/articles/team-terminology.markdown
@@ -0,0 +1,89 @@
+---
+layout: post
+title: Team terminology
+parent: Articles
+permalink: articles/team-terminology
+author: sprunk
+---
+
+## Overview
+
+Often players want to play as a team, or conduct diplomacy with each other.
+This article aims to explain Recoil's somewhat confusing terminology behind the various team-related entities and how they relate to each other.
+
+### Allyteam vs team
+
+What most people would naturally call a team is called an *"allyteam"* in Recoil terminology.
+A *"team"* is a single element of an *"allyteam"*. *Teams* are permanently bound to an *allyteam*.
+
+For example, NATO would be an *allyteam* while USA and UK would be *teams*, because they are separate entities but are in the same alliance, sharing goals and intel.
+For modders coming from Supreme Commander, the *team* is what SupCom calls an "army".
+
+*Teams* each have their own:
+ * units
+ * resources
+ * colour
+ * starting point
+ * faction (aka side)
+ * any other custom extra traits you add.
+
+*Teams* in the same *allyteam* always share some traits (meaning that, these operate on the level of *allyteams*):
+ * visibility (this includes sight, other sensors such as radar, but also reading of various unit traits that aren't readable by enemies even in sight, for example mana if a game wants to implement that)
+ * diplomacy towards other *allyteams* (in particular, all *teams* in an *allyteam* are never hostile to each other)
+ * victory goals
+
+### Team vs player
+
+A *team* is generally controlled by a *player* or an AI.
+An AI is not considered a *player* and the interfaces for dealing with AIs are separate to those for dealing with players,
+but generally they fill the same role of controlling a *team*.
+
+Control of a *team* is not exclusive.
+In particular, a *team* can be controlled by two or more *players* (or AIs, or a mix thereof) simultaneously (think SC2 Archon Mode).
+This is sometimes referred to as "comsharing" in the community for historical reasons.
+In that case all of them can exercise control over the team equally.
+
+Control of a *team* is also not permanent.
+*Players* can start controlling a different team (losing control of the previous *team*).
+In particular *players* can also control no team at all, in which case they're just spectators.
+It is up to the game to let *players* change their *team* to a different one (they cannot do so at will), though they can always become a spectator.
+
+To go with the analogy above, if the US Army is a *team* then Patton and Eisenhower are *players*: they both have control over the army (simultaneously with each other), and the army itself is generally unaffected by personal changes in the command staff: it is not bound to its generals, but the generals are bound to the army.
+
+A *team* can also have no controllers.
+This usually happens when somebody disconnects, but you can have teams that are uncontrolled by design (for example to have a perspective change in a singleplayer mission, or to have rescuable units).
+
+### Regular player vs spectator
+
+Spectators are *players* not controlling any *team*.
+They have two modes of spectating, one is a full-view mode where they can see everything, and the other is to spectate as if they were on a particular *team* (meaning their UI will display that team's resources, they will only see what that team sees, etc).
+Spectators can change what team they are spectating, and to and from the full-view mode, at will.
+The engine also has an option to allow new players to join mid-game (as opposed to regular players reconnecting); such added people always end up as spectators (in particular, this means they will have seen the full state of the game while simulating).
+
+### Player vs AI
+
+An AI fulfils a similar role to a *player*, being there to control a *team*.
+One difference is that an AI is permanently bound to a *team* and cannot spectate.
+The API to deal with AIs is also separate to *players* (so, for example, the function to get all *players* will not return them, and a *team* may look uncontrolled if care is not taken to handle both of its *player* and AI controllers).
+There are two main types of AI: Lua AI and Skirmish AI.
+
+### Lua AI vs Skirmish AI
+
+A "skirmish" AI is hosted by one of the players and generally acts very similar to a *player*.
+It can read the game state via AI interface and works by giving units commands.
+Strictly speaking, it is their hosting player relaying commands - this means that this type of AI is subject to lag and will drop if the hosting player quits.
+On the other hand, only the host player is taking on the burden of simulating the AI.
+There are currently Skirmish AI bindings for C and Java (though distributing the Java runtime environment for a Java skirmish AI is up to the game).
+A game does not need explicit support for this kind of AI (meaning for example, somebody can homebrew one), though it will likely want to handle distribution and infrastructure issues (for example to block homebrew AI).
+
+A Lua AI generally has two components: a piece of game mechanics, and the AI instance itself which is just a handle to tell game mechanics which teams are legal to control.
+Since game mechanics have full control over the game state, this type of AI can do things like spawn units on its own (for a sort of PvE experience) .
+It can also control teams that aren't explicitly marked for control by the LuaAI handle (for example a LuaAI can be made to automatically control AFK players' teams, or Gaia units - see below).
+This type of AI is written in Lua (like all game code), has to be included in the game itself, and the code runs for every player in the game.
+
+### Gaia
+
+Gaia is a special *team* that is always present, uncontrolled, and is always in its own *allyteam*.
+It is generally meant for ownership of map features such as trees and rocks, but it also can have resources and own units.
+This can be used for example for PvE enemies - note that game mechanics still have control an uncontrolled *team* so it is not necessary to have an explicit AI for it.
+At the moment there is only one Gaia.
diff --git a/articles/technicalities-of-starting-a-match.markdown b/articles/technicalities-of-starting-a-match.markdown
new file mode 100644
index 0000000000..02ed79cd00
--- /dev/null
+++ b/articles/technicalities-of-starting-a-match.markdown
@@ -0,0 +1,43 @@
+---
+layout: post
+title: Technicalities of starting a match
+parent: Articles
+permalink: articles/technicalities-of-starting-a-match
+author: sprunk
+---
+
+What does starting a game look like, from a technical point of view? What are the responsibilities of each component?
+
+### Engine
+
+The only thing that the engine needs to initiate a game is a _start script_: plaintext data containing the setup such as game, map, players, modoptions, etc. Alternatively, the engine can use match metadata (in particular, host IP) to connect to a match hosted elsewhere. Then it receives the startscript directly from the host, after connection.
+
+* in-engine Lua code can start a game via `Spring.Restart(string commandLineArgs, string startScript)`. This is available from LuaMenu (e.g. Chobby) but also regular ingame Lua instances.
+* external lobbies can run the engine and pass the startscript/metadata as standard input or command line args.
+* replays and save files generally work equivalently to a startscript (and contain the startscript for their game).
+* **practical tip**: during development it's convenient to have a `startscript.txt` file and drag-n-drop it to `spring.exe` (or equivalent). The engine leaves a `_script.txt` file containing the last used startscript so you can produce an intricate setup via a graphical lobby, or by watching a replay, and then reuse it.
+* **practical tip**: replays are associated with a specific engine version, so if you want to support watching old replays you'll need to handle it externally (it can be a simple script communicating with LuaMenu via a socket though, not necessarily a "full" lobby).
+
+Every other component's role is merely to facilitate the creation of startscripts and exchange of metadata.
+
+### Lobby client
+
+Either in-engine LuaMenu such as Chobby, or external programs.
+
+* for singleplayer, or multiplayer games where the player is the host, lobbies are responsible for generating the startscript based on game setup.
+* sometimes you already have a startscript somehow (e.g. maybe it comes from a mission archive, or you're just running a replay) and then the lobby is just responsible for passing it to the engine.
+* for multiplayer where the player is not a host, lobby clients handle receiving match metadata from the host, usually via a lobby server.
+
+### Autohost
+
+As far as the process of starting a game, autohost is just a stub lobby client.
+* it is practically always used as the host of a room on a lobbyserver, and thus needs to generate the startscript based on game setup.
+* **practical tip**: some lobby servers claim that a player "is the host" of a room where actually he is just the boss of a room where the actual host is a bot/autohost. In this case the autohost is responsible for generating the startscript. Don't confuse those.
+
+### Lobbyserver
+
+The lobbyserver is only responsible for transmitting metadata from the host to the other players so they can connect, but you can use any other alternative. For example one could use the Steam p2p direct connection for transmitting metadata to play co-op with a friend.
+
+### Startscript format
+
+See [here](https://github.com/beyond-all-reason/spring/blob/BAR105/doc/StartScriptFormat.txt).
diff --git a/articles/unit-defs.markdown b/articles/unit-defs.markdown
new file mode 100644
index 0000000000..d2507bafe4
--- /dev/null
+++ b/articles/unit-defs.markdown
@@ -0,0 +1,178 @@
+---
+layout: post
+title: Unit defs
+parent: Articles
+permalink: articles/unit-defs
+author: sprunk
+---
+
+# Unit types basics
+
+**Each unit in Spring/Recoil belongs to a single type**.
+By default, all units of a type are the same as far as most traits (for example health) go.
+Units don't necessarily need to fully conform to their type - **most traits can be changed per-unit**.
+
+This is **similar to other RTS engines**.
+For example, a default Starcraft2 marine has 45 health.
+Specific marines can actually have 55 health (after an upgrade), or 22 health (with 50% difficulty handicap), or 200 health (if they're the special campaign marine Jim Raynor).
+But marines, as a generalized type, have 45 health.
+It works **essentially the same way in Recoil**.
+Note that the **type itself cannot be modified at runtime**, though you can still easily apply a modified value to every unit of a type.
+
+The **set of unit types is static**.
+You **cannot dynamically add new types**, though you **can generate them beforehand**, including via code.
+
+The information about a unit type is usually called a **unit def** (from "definition"), and sometimes the type itself is referred to by "unit def" as well.
+This article will talk about and compare the two ways that unit defs are often dealt with that are often confused.
+
+As a general remark, the same notes apply not just to unit types, but also feature types and weapon types and can be applied there directly.
+
+## Unit def files
+
+At their simplest, games can provide **a set of simple Lua files with unit defs in the `./units` subfolder**, which **return a table** like this:
+```lua
+-- ./units/light_tank.lua
+return {
+ light_tank = {
+ health = 100,
+ maxVelocity = 1,
+ requiresSuperSecretResearch = false,
+ customParams = { flammable = 1, },
+ ...
+ },
+}
+```
+The above defines a unit def named "light_tank" with the appropriate stats.
+Note that each def **must have a unique name**.
+
+Note also the keys used inside: `health` and `maxVelocity` are standard and interpreted by the engine, you can find their meanings in the documentation.
+**The `customParams` table is somewhat special as it is itself standard but its contents are not**: anything inside (in this case `customParams.flammable`) needs to be handled by the game.
+Lastly, `requiresSuperSecretResearch` is non-standard.
+We will get back to the non-standard ones later.
+
+In the example above, the filename matches the single def inside and is basically just a static set of values.
+This **is generally the convention** for various reasons (for example it makes it easier for a mod to replace units one by one, and the files can get fairly long), but it **doesn't have to** be the case.
+In particular, you **can put multiple units there and have Lua code inside** to generate values, for example:
+```lua
+-- ./units/various_tanks.lua
+local base_tank_health = Shared.base_tank_health
+return {
+ light_tank = {
+ health = base_tank_health * 1,
+ maxVelocity = 1,
+ requiresSuperSecretResearch = false,
+ customParams = { flammable = 1, },
+ ...
+ },
+ heavy_tank = {
+ health = base_tank_health * 2.5,
+ maxVelocity = 0.3,
+ requiresSuperSecretResearch = true,
+ customParams = { flammable = 0, },
+ ...
+ }
+}
+```
+
+At some point you **might want to post-process** these values.
+For example you'd like to try out a sweeping design change, or maybe the match at hand is a custom scenario with different rules.
+For those cases, **default engine content lets you supply a file, `./gamedata/unitdefs_post.lua`**, which has access to a `UnitDefs` table with all the defs.
+For example, let's say you want to make all units slightly faster, but the healthiest ones more fragile using some arbitrary formula:
+```lua
+-- ./gamedata/unitdefs_post.lua
+for unitDefName, unitDef in pairs(UnitDefs) do
+ UnitDefs[unitDefName] = lowerkeys(unitDef)
+end
+
+for unitDefName, unitDef in pairs(UnitDefs) do
+ if unitDef.maxvelocity then
+ unitDef.maxvelocity = unitDef.maxvelocity * 1.2
+ end
+
+ if unitDef.health and unitDef.health > 300 then
+ unitDef.health = 300 + math.sqrt(unitDef.health - 300)
+ end
+
+ if unitDef.requiressupersecretresearch then
+ unitDef.customparams.tech_level_required = 4
+ end
+end
+```
+
+First, the `lowerkeys` function.
+Maybe some def files defined "maxVelocity" with an uppercase 'V' and some "maxvelocity" with a lowercase 'v', maybe even some used "MAXVELOCITY".
+In Lua, these are all different keys.
+The engine doesn't mind either way and accepts any casing, but Lua does, so calling lowerkeys **makes sure that handling those in post-processing does not become a hassle**.
+Some games put the lowerkeys call inside the individual def files as a convention; you also **don't have to do this at all** if you pay attention and don't expect this to be a problem.
+
+Note that **there are checks** so that calculation only happens if the value is defined, **even for the standard ones** (i.e. `if unitDef.health and...`).
+This is because **while the engine does fill in defaults** (so you don't need to define velocity for buildings, for instance) **it does so at a later point**: so far everything is still **just a regular Lua table**.
+In particular, you can make use of the fact that an entry is not defined and/or fill it with some default calculated by you.
+
+Another thing going on here is the handling non-standard entries in the table.
+Anything which is not a standard key **is going to be discarded by the engine after this point unless it's inside the `customparams` table**.
+This can be useful if you just want to define a helper for post-processing - in particular this can even be something like a function.
+`customParams` let you keep non-standard values, but **only allows strings as keys, and strings and numbers as values**.
+
+There is also a **pre-processing file, `./gamedata/unitdefs_pre.lua`**, which can prepare data **before any def files are read**.
+It exposes **a global `Shared` table**, which can be populated there and which can be seen used in one of the examples above.
+Use it to propagate reference values to multiple defs without having to redefine them.
+
+## The `UnitDefs` table inside wupgets
+
+This is where things get somewhat messy. The engine exposes a table also called `UnitDefs` to wupgets.
+However, **this is NOT the same table as the one above**.
+The table above gets parsed into internal engine structures and is gone.
+The engine exposes a new table with those parsed values. This means that, compared to unit def files:
+
+ * the overall table is **indexed by numerical IDs, not the name**.
+So it's (say) `UnitDefs[123]` instead of `UnitDefs["light_tank"]`.
+Default engine content provides **a `UnitDefNames` table indexed by the name** though.
+The internal name is also **provided in the table under the key `"name"`**.
+The majority of **wupget interfaces use the numerical ID**.
+
+ * keys are **not the same**.
+For example, metal cost is read as `buildCostMetal` from unit def files, but exposed as `metalCost` in `UnitDefs`.
+In particular, they often **have "proper" uppercase even if the post-processing file is all lowercase**.
+This may well be **the most common misconception** and mistake when it comes to defs!
+Remember, **don't copy-paste keys between unit def files and wupgets** or vice versa.
+
+ * unused values are discarded.
+In the example above, `UnitDefs[x].requireSuperSecretTech` (and the lowercase spelling) is `nil`.
+
+ * **values are not the same**.
+For example, speed is read as elmos/frame from unit def files, but exposed as elmos/second.
+Some values have caps or offsets applied. See the documentation for specifics.
+
+## Advanced technicalities
+
+Here's some looser remarks around these topics.
+
+### Unit def files
+
+In truth, the engine **only directly reads one file for all def file processing, which is `./gamedata/defs.lua`**.
+This file is **provided in basecontent** and for all the various def types (unit, weapon, etc) it loads a pre-processing file, individual legacy def files (TDF, FBI and other Total Annihilation formats), the individual Lua def files under `./units/`, and then the post-processing file.
+The practical effect is that you can **customize the loading process somewhat** (load things in a different order, or from elsewhere than `./units/`, etc.) if you don't like the default one.
+
+A limited number of **wupget interfaces are available** during def loading.
+This includes **getters involving the match** in general (map, player roster etc), of which most importantly **mod-options**.
+You can use them to customise a match (for example, maybe aircraft fly higher on maps with lava or something like that).
+
+VFS is also available, **as a game dev you can expose interfaces for modders** or mappers by attempting to load specific files.
+
+### UnitDefs
+
+A unitDef is a proxy table with the `__index` meta-method.
+**According to measurements** this makes it somewhat **slower than a plain Lua table**, so it might be worth **caching if a wupget mostly uses a single field** from it.
+
+There is a **defs-editing dev mode where you can edit defs**, toggled via `/editdefs` (requires cheats).
+In this mode, changes are done by just **assigning to a unitDef in Lua code**, which **isn't normally possible**.
+Keep in mind that there is **no standard widget** yet to allow easy editing, and that **editing the def files will do nothing**
+(of course unless you make your editing widget read them, but remember the caveat where the keys and values differ between
+unit defs and `UnitDefs`). This mode is **not usable for game logic** and will desync if used in multiplayer.
+
+There's three **minor differences between `WeaponDefs` and `UnitDefs`/`FeatureDefs`**:
+ * `WeaponDefs` are 0-indexed while the others are 1-indexed. Beware of `for i = 1, #WeaponDefs do`, this is incorrect!
+ * **negative weaponDefIDs are valid and mean things like lava or collisions**. Check the `Game.envDamageTypes` table.
+ * it is possible to iterate over all keys of a unitDef via `for key, value in unitDef:pairs() do`, but this is currently not possible for either weapon or feature defs.
+
diff --git a/articles/units-of-measurement.markdown b/articles/units-of-measurement.markdown
new file mode 100644
index 0000000000..5d8eea376c
--- /dev/null
+++ b/articles/units-of-measurement.markdown
@@ -0,0 +1,55 @@
+---
+layout: post
+title: Units of measurement
+parent: Articles
+permalink: articles/units-of-measurement
+author: sprunk
+---
+
+## Units of measurement
+
+In addition to standard units of measurement such as seconds or radians, Recoil uses some in-house units that warrant a bit of explanation.
+
+### Base
+
+ * **frames**. These represent the discrete, fundamental unit of time for simulation purposes.
+Everything in a simulation happens between two frames.
+Currently, a simulation at x1 speed runs frames at a constant 30 Hz (so a frame is 0.033s).
+On the other hand, it can run slower or faster depending on gamespeed (including at thousands Hz when catching up or skipping over a replay).
+Therefore, do not use frames to measure time for things that should use wall-clock time (for example interface animations), i.e. use `widget:Update(dt)` over `widget:GameFrame(f)`.
+The base frequency value in Hz is available to Lua as `Game.gameSpeed`.
+It is currently hardcoded by the engine and not configurable by games (despite being in the `Game` table and not `Engine`).
+
+* **elmos**. "Elmo" is the name for Recoil's arbitrary unit of distance. It is purposefully underdefined so that each game can have its own sense of scale.
+For example, maybe one game is some sort of galactic war and 1 elmo represents 1 parsec, while another game is about a war between bacteria and viruses and 1 elmo is 1 μm.
+Most existing content seems to assume it's 1 m or 12.5 cm, or within this order of magnitude, but there is nothing to enforce consistency.
+Almost all length/distance values are given in elmos (or derived values such as elmo/s for speed), unless otherwise noted.
+
+* **arbitrary**: many values, such as mass, are also in arbitrary units that a game could define on its own if it wanted to for world-building reasons.
+Unlike the elmo these aren't even named, so they're typically referred to as just, for example, "100 mass", "100 energy", or "100 map hardness".
+
+### Derived
+
+* **game square**. The map is divided into a grid of squares.
+The high-resolution yardmap grid is in squares, as is the heightmap (height is an interpolation between the corners of a square).
+The length of the edge of a game square in elmo is available as `Game.squareSize` to Lua.
+It is not configurable by games and is currently hard-coded to 8 (so a 1x1 square is 8x8 elmos).
+Note that for map creation, the supplied heightmap represents corners of squares (which is why its size has to be N/8 + 1).
+* **footprint square**. Footprints in unit defs are defined in squares of game squares (for historical reasons).
+Regular-resolution yardmap is defined per footprint square.
+The length of the edge of the footprint square in game squares is available to Lua as `Game.footprintScale` and is currently hard-coded to 2 (so a 1x1 footprint is 2x2 game squares).
+* **build square**. The grid to which construction of buildings is aligned.
+Similarly to footprints, it's in multiples of a regular game square.
+The length of the edge of a build square in elmos is available as `Game.buildSquareSize` to Lua and is currently hard-coded to 16 (which is the same as a footprint square, meaning that except for high-resolution yardmaps you can always fit buildings next to each other tightly).
+* **metalmap square**. The metal-map is divided into a grid which covers multiple game squares.
+Interfaces such as `Spring.GetMetalAmount` accept the co-ordinates in this unit.
+It is available to Lua as `Game.metalMapSquareSize` and its value is currently 16.
+Note that for map creation, the supplied metalmap represents the insides of metalmap squares (which is why its size has to be N/16).
+* **lobby map size**. This is the size of maps typically shown in lobbies and other such places.
+A 1x1 map is 512x512 elmos.
+This is merely a convention (though a strong one for historical reasons) and thus is not directly available to Lua (you can derive it via `Game.mapSizeX / Game.mapX` if you have a map loaded, though there isn't much point because `Game.mapX` is already about the only useful value in this unit).
+* **slow update**. Some performance-heavy things only happen to units once per slow-update.
+A slow-update happens once per 15 frames.
+It is a bit of an implementation detail, so it's not directly exposed, though some def entries related to allowing things to run more often are usually capped at the slow-update rate.
+* **TA angular unit**. A full circle is 65536 TA angular units.
+Used in some unit and weapon def entries - consult defs documentation for specifics.
diff --git a/articles/vfs-basics.markdown b/articles/vfs-basics.markdown
new file mode 100644
index 0000000000..78b09a059e
--- /dev/null
+++ b/articles/vfs-basics.markdown
@@ -0,0 +1,82 @@
+---
+layout: post
+title: VFS Basics
+parent: Articles
+permalink: articles/vfs-basics
+author: sprunk
+---
+
+## VFS basics
+
+### What is loaded?
+
+Recoil Engine **loads content from three main places** into its Virtual Filesystem (VFS): the game, the map, and the user's local files.
+The game and the map **are specified by the lobby** when launching an instance of Recoil.
+**Game content is primary** and it usually decides whether to even load content from the other two, with some exceptions (for example map layout is always taken from the map archive).
+
+### Archives
+
+Games and maps **typically come as archives** (a single compressed file).
+For development purposes, they can also be **regular folders as long as their name ends with ".SDD"**, though this is **not recommended for production** (for performance reasons).
+Archives **can specify dependency** upon other archives, so **if you want to build upon an existing game you don't need to copy-paste its whole contents** (and probably shouldn't).
+By default, games live in the `./games/` subfolder of your Recoil folder and maps live in `./maps/`, but a **lobby can specify arbitrary archives**.
+
+{: .warning }
+> At the moment, **files in dependencies are completely overridden** in the VFS and are not accessible.
+
+The user can also **specify local content folders** which are then loaded from, **if the game allows** that.
+By default, the Recoil folder is the read directory.
+In this case, there is usually **no archive - loose files are seen by the VFS**.
+Of course **an archive can be such loose file** and there are interfaces to load its contents.
+
+### Sync
+
+The **game and map are synced**, meaning their contents can be used as **authoritative data related to the simulation**.
+Local files are unsynced, so they **cannot even be accessed from synced contexts**.
+For example, the game can define that a tank has 100 health.
+The game **can give the map an opportunity to modify this**, so for example the map can say the tank has 200 health instead.
+But there is no way for local files to modify that further, unit health is part of the game mechanics simulation so the game cannot defer to local files here even if it wants to.
+What this means is that **local files are largely for local content like the UI**, and it is generally **safe to assume the VFS is under a game dev's control even if you don't pay attention**, as far as mechanics are concerned.
+
+### How do I defer to the other content?
+
+VFS interfaces tend to **expect a _mode_ parameter**, which lets you specify **where to look for and in what order**.
+The values are strings so **you can combine them** using the `..` operator, and they are read left-to-right.
+For example, if you want to include a file from the map but also have a game-side backup, you'd pass `VFS.MAP..VFS.GAME` to `VFS.Include`.
+Since `VFS.RAW` was not passed, any existing loose file with the appropriate name among the user's local files is ignored.
+See below for a listing of modes accessible to Lua.
+
+By paying attention to the VFS mode, you can **prevent loading unwanted content**.
+As mentioned above, requesting unsynced modes in synced contexts is also ignored.
+
+### Loose remarks
+
+There is a fourth place where content is loaded from, the **basecontent** archive.
+The engine **always loads** it and it contains various **bare necessities** for a functional game, such as default water texture or the basic wupget frameworks.
+Its content is **entirely optional** and can be avoided via the usual VFS mode interface, though even mature games will usually want to make use of its facilities.
+
+The engine **can also write files** in addition to reading them.
+Unlike multiple read-folders, there is **only a single write-folder** (defaults to the Recoil folder).
+Writing is done by general Lua interfaces such as `io` and `os`, not `VFS`.
+
+A somewhat unorthodox way to pass (synced) content is **via modoptions**.
+Modoptions can **contain data** that gameside code can act upon, and if you're brave enough you can even **pass Lua code** as a modoption to be excuted.
+This is one of the ways to let people **run their local files in a synced way**, by just forwarding them as modoptions.
+
+## VFS mode listing
+Here are the fundamental modes:
+
+* `VFS.GAME` - anything included by either a game OR its dependencies, as long as they are not basecontent. There is no way to tell where a file comes from, but dependencies override!
+* `VFS.MAP` - the map archive.
+* `VFS.BASE` - loaded basecontent archives. Some of those are always implicit dependencies. Any dependency with the appropriate archive type fits though.
+* `VFS.MENU` - the loaded menu (lobby) archive, i.e. in practice Chobby.
+* `VFS.RAW` - anything not in an archive, i.e. any loose files the client may have in his data folder, or even anywhere on the filesystem. Only unsynced content can access these.
+
+And here are the convenience/legacy ones:
+
+* `VFS.ZIP` = `VFS.GAME .. VFS.MAP .. VFS.BASE`. Synced content can only use subsets of this. Note that this doesn't mean actual `.zip` (aka `.sdz`) archives, `.sdd` and `.sd7` still apply.
+* `VFS.ZIP_FIRST` = `VFS.ZIP .. VFS.RAW`.
+* `VFS.RAW_FIRST` = `VFS.RAW .. VFS.ZIP`.
+* `VFS.MOD` = `VFS.GAME`. This is NOT for mods, they are indistinguishable from the main game from the VFS' point of view! It's just a legacy synonym.
+* `VFS.RAW_ONLY` = `VFS.RAW`.
+* `VFS.ZIP_ONLY` = `VFS.ZIP`.
diff --git a/articles/wupget-best-practices.markdown b/articles/wupget-best-practices.markdown
new file mode 100644
index 0000000000..2f4f55e38d
--- /dev/null
+++ b/articles/wupget-best-practices.markdown
@@ -0,0 +1,35 @@
+---
+layout: post
+title: Wupget best practices
+parent: Articles
+permalink: articles/wupget-best-practices
+author: sprunk
+---
+
+
+* `Spring.Echo` and similar debugging functions accept multiple args. When echoing variables, use `,` instead of `..` since more things can be printed standalone than natively glued together:
+
+```diff
+- Spring.Echo("foo is " .. foo) -- breaks when it is nil, table, function...
++ Spring.Echo("foo", foo) -- prints e.g. "foo, "
+```
+* for behaviours, use customparams instead of hardcoding unit def names in gadgets.
+* when hardcoding unit types for non-behaviour purposes (list of things to spawn in a mission etc.), use def names instead of numerical def IDs. Numerical ID can change between matches.
+* localize `UnitDefs` (and similar) accesses if you do it in an event that happens often, and you only want a limited number of traits.
+Avoid:
+
+```lua
+function wupget:UnitDamaged(unitID, unitDefID, ...) -- common event
+ if UnitDefs[unitDefID].health > 123 then
+```
+
+Prefer instead:
+```lua
+local healths = {}
+for unitDefID, unitDefID in pairs(UnitDefs) do
+ healths[unitDefID] = unitDef.health
+end
+
+function wupget:UnitDamaged(unitID, unitDefID, ...)
+ if healths[unitDefID] > 123 then
+```
diff --git a/articles/wupget-communication.markdown b/articles/wupget-communication.markdown
new file mode 100644
index 0000000000..709c2ea43d
--- /dev/null
+++ b/articles/wupget-communication.markdown
@@ -0,0 +1,88 @@
+---
+layout: post
+title: Wupget communication
+parent: Articles
+permalink: articles/wupget-communication
+author: sprunk
+---
+
+There are multiple Lua environments (see [old wiki](https://springrts.com/wiki/Lua:Environments)), and within an environment each wupget is also separated to some degree. How do these communicate?
+
+### WG and GG
+
+* the basic way to communicate - just tables putting globals inside, visible from wupgets in given environment.
+* are just regular tables exposed by the wupget handlers by convention.
+* in LuaUI it's called `WG` (widget global), elsewhere it's `GG` (gadget global)
+* keep in mind that the `GG` in unsynced LuaRules, the `GG` in synced LuaRules, and the `GG` in synced LuaGaia are all separate tables despite the same name! For communication between environments you'll have to use one of the methods below.
+
+### Rules params
+
+* synced LuaRules can set global key/value pairs as well as attach them to units, teams, players; all other environments can then read them
+* the standard way to expose new gameplay-relevant traits.
+* see also the article.
+* examples: "unit is on fire", "unit is slowed by 30%", "team 2 is cursed", "next night falls at 6:00", "there are 7 mex spots on the map at coordinates ..." .
+
+### `Script.LuaXYZ` events and "proper" `_G` globals
+
+* `_G` is the "real" global table of each environment (as opposed to `GG` and `WG` which are only "logically" global),
+i.e. it is the one accessible directly by the engine to be called from the outside of that environment (in particular, it is still per-environment)
+* wupget handlers also expose `wupgetHandler:RegisterGlobal('EventName', func)` which is essentially `_G.EventName = func` with some basic safety on top
+* you can then do `Script.LuaXYZ.EventName(args)` to call the given function
+* the two LuaRules environments can call `Script.LuaRules` to refer to themselves (but not each other!) and `Script.LuaGaia` (also only of the same syncedness), while unsynced LuaRules/LuaGaia and LuaUI can call `Script.LuaUI`
+* this only works if the global in question is a function, and you won't receive any return values
+* you could put "normal" vars there, but it is discouraged (use `GG`/`WG`)
+* the main use case for this is calling events "native style" where you do `Script.LuaXYZ.UnitDamaged` which is then handled by the wupget handler and distributed to individual wupgets
+* it is safe to call functions this way even if there is nothing on the other side.
+You can use the function notation to check whether anything is linked though, e.g. for optimisation:
+```lua
+if Script.LuaXYZ("Bla") then
+ local args = ExpensiveCalculation()
+ Script.LuaXYZ.Bla(args)
+end
+```
+* other examples of events you could add: nuclear launch detected, language changed in settings, metal spot added at runtime.
+
+### `SendToUnsynced` and `RecvFromSynced`
+* synced gadgets can pass data to unsynced gadgets by calling `SendToUnsynced(args)`
+* unsynced gadgets receive it via `function gadget:RecvFromSynced(args)`
+* usually the first arg is a magic value that lets the correct gadget consume the message
+* in unsynced, you can do `gadgetHandler:AddSyncAction("foobar", func)` which is generally equivalent to
+```lua
+function gadget:RecvFromSynced(magic, args)
+ if magic == "foobar" then
+ func(magic, args)
+ end
+end
+```
+* laundering via unsynced LuaRules is the way to call `Script.LuaUI` style events from synced LuaRules.
+
+### `Spring.SendLuaRulesMsg` and `gadget:RecvLuaMsg`
+* LuaUI can send messages to LuaRules
+* similar to `SendToUnsynced` in use
+* there's also `Spring.SendLuaGaiaMsg`
+* use to make UI to interact with mechanics where unit orders won't suffice
+
+### `Spring.SendLuaUIMsg` and `widget:RecvLuaMsg`
+* use these one to communicate with other players' widgets
+* lets you send to everyone, only allies, or only spectators
+* use for things like broadcasting cursor or selection, or UI interaction like custom markers
+
+### `SYNCED` proxy
+* synced LuaRules' global table (`_G`) can also be accessed from unsynced LuaRules
+* synced `_G.foo` can be accessed from unsynced as `SYNCED.foo`
+* note that this makes a copy on each access, so is very slow and will not reflect changes. Cache it, but remember to refresh
+* it only copies basic types (tables, strings, numbers, bools). No functions, metatables and whatnot
+* generally not recommended. Listen to the same events as synced and build the table in parallel
+
+### With the outside, unsynced
+* there's `/luaui foo` for a player to pass data to LuaUI.
+* LuaUI and LuaMenu have a socket API to communicate with the outside.
+
+### With the outside, synced
+* arbitrary setup data can be passed in the startscript as modoptions and player/team/AI custom keys. This would be done by the lobby or autohost.
+* players, incl. the autohost, can use `/luarules foo` commands to send arbitrary data to the synced state. Lua sees the autohost as playerID 255.
+* if the autohost uses [a dedicated instance (as opposed to headless)]({{ site.baseurl }}{% link guides/headless-and-dedi.markdown %}), players can send messages back to the autohost via whispers (`/wByNum 255 foo`) for non-sensitive data.
+
+### Others
+* a game can designate some other regular table for globals. For example many games put useful functions in `Spring.Utilities` which is not actually a native part of the `Spring` table.
+* two environments could include the same file to get the same data (in contents but not identity). LuaUI and LuaMenu can also read arbitrary files (outside the VFS).
diff --git a/articles/yardmaps.markdown b/articles/yardmaps.markdown
new file mode 100644
index 0000000000..db4b05a4ca
--- /dev/null
+++ b/articles/yardmaps.markdown
@@ -0,0 +1,73 @@
+---
+layout: post
+title: Yardmaps and map squares
+parent: Articles
+permalink: articles/yardmaps
+author: sprunk
+---
+
+### Squares
+The map is divided into a **discrete grid of 8x8 elmo squares**. Most terrain-related things (heightmap, parts of pathing, resource map aka metalmap, typemap, building masks, radar coverage...) work on these squares. Some of those are discretized further into 16x16 elmo squares, i.e. a 2x2 box of basic game squares. In particular, unit footprints are defined in big squares (also called a footprint square for that reason).
+
+### Footprint
+Buildings and mobile units have their footprint (in other words, size on the building grid) defined differently. **Mobile units define it in their move def**, and it has to be a square. In theory you can define a footprint in the unit def, but this is just used for construction.
+
+**Buildings define it in their unit def**. This can be a rectangle, and you can define parts of the footprint to allow pathing (useful for factory exit yards), buildable (for when a building doesn't occupy the whole square), change meaning when the unit activates (for some sort of opening animation) or require a geothermal vent underneath. Such definition is called a **yardmap, and consists of a string arranged visually to depict the building**, with characters conveying some meaning as listed in the table at the bottom.
+
+For example, consider the following yardmap. The `o` depicts the actual body area, while `y` is open space, and it corresponds to a diamond shape within the 5x5 footprint:
+```
+yyoyy
+yoooy
+ooooo
+yoooy
+yyoyy
+```
+
+And here's what happens if you try to place another of the same type on top. Notice how the `y` area does not produce conflicts.
+
+
+
+Here's how it looks if you try to place things on top:
+
+
+
+Essentially, the building is shaped like this (mock-up):
+
+
+
+### Yardmap characters
+
+Basic characters:
+
+| letter | buildable if active? | pathable if active? | buildable if INactive? | pathable if INactive? | stackable? | need geo? |
+|--------|----------------------|---------------------|------------------------|-----------------------|------------|-----------|
+| o | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| y | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
+| c | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
+| g | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
+| i | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
+| j | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+| s | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
+| b | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
+| u | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ |
+| e | ❌ | ⚠️ exit only | ❌ | ⚠️ exit only | ❌ | ❌ |
+
+Others:
+
+ * `h` has a special meaning - it **has to be the first letter and means a high resolution yardmap**,
+i.e. there have to be 4x more characters and each will correspond to a "regular" 8x8 elmo game square instead of the larger 16x16 elmo footprint square.
+ * `w`, `x`, `f`: deprecated, same as `o`.
+ * **whitespace is ignored**, which you can use to neatly **arrange the rectangle visually**.
+ * anything else is ignored as well but may be used in the future.
+
+### Typemaps and speed mod classes
+
+One of the metadata associated with game squares is terrain type. A map can define (and a game can in theory later tweak) up to 256 types of terrain with their own traits. The first built-in terrain trait is hardness, which is a multiplier on weapon terrain deformation (cratering). The other is speed multipliers for unit movement classes. There are four movement classes: "kbot", "tank", "hover" and "ship". Here are their characteristics:
+
+* **hovercraft** can move on the ground and on water surface.
+* **tank** and **kbot** can move on the ground and optionally underwater, on the seafloor. The only difference between them is which typemap speed multiplier affects them. Keep in mind they are essentially just two arbitrary classes, so you could for example replicate SC2 style creep movement speed bonuses by having the creep as its own terrain type that applies a multiplier for "tanks", then set Zerg units to the "tank" movetype and everything else as "kbots".
+* **ships** can only move in water. Ships that can "walk onto land", like SupCom Cybran Siren or RA3 Soviet Stingray have to be set as hovercraft!
+* aircraft, and units of the above classes being moved manually via Lua, do not use any terrain-based speed multipliers.
+* the engine pathfinder knows about these speed multipliers (including x0 multiplier, which is not passable) and will take them into account as appropriate.
+
+Lua can read terrain type, so you could use it to apply metadata to terrain (think healing ground, territory ownership or triggers), but keep in mind that for applying any sort of gameplay mechanics it's usually more performant to just manually poll a rectangular zone or such. Also there don't seem to be any conventions as to what ID represents what terrain type, so games and maps would need to pay a lot of attention to stay self-consistent and not break compatibility. It's probably best to avoid doing anything beyond the built-in engine typemap behaviour.
diff --git a/assets/css/just-the-docs-themes.scss b/assets/css/just-the-docs-themes.scss
new file mode 100644
index 0000000000..b6fd209f28
--- /dev/null
+++ b/assets/css/just-the-docs-themes.scss
@@ -0,0 +1,83 @@
+---
+---
+
+{% if site.logo %}
+$logo: "{{ site.logo | relative_url }}";
+{% endif %}
+@import "./support/support";
+@import "./custom/setup";
+
+@import "./color_schemes/light";
+@import "./modules";
+{% include css/callouts.scss.liquid color_scheme = "light" %}
+
+html[data-theme="dark"] {
+@import "./color_schemes/dark";
+@import "./modules-nocharset";
+{% include css/callouts.scss.liquid color_scheme = "dark" %}
+}
+
+// fix for search using class in html tag
+html[data-theme="dark"].search-active {
+ .search {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ }
+
+ .search-input-wrap {
+ height: $sp-10;
+ border-radius: 0;
+
+ @include mq(md) {
+ width: $search-results-width;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08);
+ }
+ }
+
+ .search-input {
+ background-color: $grey-dk-250;
+
+ @include mq(md) {
+ padding-left: 2.3rem;
+ }
+ }
+
+ .search-label {
+ @include mq(md) {
+ padding-left: 0.6rem;
+ }
+ }
+
+ .search-results {
+ display: block;
+ }
+
+ .search-overlay {
+ width: 100%;
+ height: 100%;
+ opacity: 1;
+ transition: opacity ease $transition-duration, width 0s, height 0s;
+ }
+
+ @include mq(md) {
+ .main {
+ position: fixed;
+ right: 0;
+ left: 0;
+ }
+ }
+
+ .main-header {
+ padding-top: $sp-10;
+
+ @include mq(md) {
+ padding-top: 0;
+ }
+ }
+}
+
+{% include css/custom.scss.liquid %}
diff --git a/assets/github-mark-white.svg b/assets/github-mark-white.svg
new file mode 100644
index 0000000000..d5e6491854
--- /dev/null
+++ b/assets/github-mark-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/github-mark.svg b/assets/github-mark.svg
new file mode 100644
index 0000000000..37fa923df3
--- /dev/null
+++ b/assets/github-mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/guides/article-yardmap-solars-1.png b/assets/guides/article-yardmap-solars-1.png
new file mode 100644
index 0000000000000000000000000000000000000000..e19e11195d5ff0ad527985a01fd6f22219ae28e3
GIT binary patch
literal 524153
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