Asinine Curio Chest

One who says little is one who speaks two syllables.

Posts RSS About

Zero-K: How effective is wind, really?

This post expands upon my forum post here, which did a deep dive into the energy efficiency of wind power in Zero-K. Zero-K is a free and open source RTS game, following the Total Annihilation style. Conventional wisdom was that Wind was “worth it”, when the minimum was at least +0.2e/s (energy per second).

An expanding brain meme, beginning and ending with: Windgens are more effective than Solars or Fusions. The intermediate steps list increasingly complex equations simulating wind payoffs.

A diminishing returns economy

One of the more interesting features in Zero-K compared to its peers is the diminishing “overdrive” economy, rather than the exponential metal-maker economy of most TA-likes. The Zero-K wiki has a detailed guide on the economy, but the short version is: Excess energy is converted to metal at a rate which reduces in effectiveness the more energy is converted. This means that in the early game, when energy can be converted to metal at a very high rate, the choice of economy matters a lot.

The diminishing returns of the overdrive economy are implemented as a square root. Specifically, where \(E\) is the amount of overdrive energy allocated to that specific mex, a mex produces metal at its face value multiplied by:

\[ 1 + E^{0.5} / 4 \]

So if your mexes produce a total of +100m/s (metal per second), and there is 100e/s excess energy for overdrive, the amount of metal you get depends on how that is divided:

Basic energy structure efficiency

In the early game, you have two options for cheap energy generation: Wind and Solar. Wind is cheap, random, and fragile, but is generally more cost effective, producing between a map and height based minimum, generally up to +1.0e/s, and a maximum of +2.5e/s. Solars have a slightly higher fixed cost, but fold up, turning themselves off, and becoming extremely durable when raided. Solars produce a consistent +2e/s.

In-game, Zero-K does a pretty fair job of estimating how long energy structures will take to pay for themselves. If a smaller energy structure would pay for itself in the time it would take for a larger energy structure to complete, you’d be better off building the smaller energy structure first, since you’d finish the larger structure in the same time, plus you also have the smaller structure.

Expressed in terms of metal-seconds per energy output, the structures have the following cost efficiency, least to most efficient, picking specific lower-bound examples for wind generators:

Structure Cost (m) Energy output (e/s) Cost efficiency (\(mse^{-1}\))
Solar Collector 70 2 35
Tidal 35 1.2 \(29.166\dots\)
Fusion Reactor 1000 35 \(28.5714\dots\)
Wind (0.0 lower bound) 35 1.25 28
Wind (0.5 lower bound) 35 1.5 \(23.3\dots\)
Geothermal Generator 500 25 20
Wind (1.0 lower bound) 35 1.75 20
Singularity Reactor 4000 225 \(17.78\dots\)
Advanced Geothermal Generator 1500 100 15

Wind efficiency is a very deep rabbit hole. Even at ground level, Wind always beats Solar, and barely requires anything (either in terms of height, or other factors like wind grids finishing incrementally) to beat basic Fusion, assuming that all your stable energy needs are met and you’re only concerned with overdrive output.

Wind efficiency

The above table is correct for metal seconds per energy, but when thinking about overdrive you want to be thinking about the overdrive multiplier. Overdrive has diminishing returns over the squareroot function, so Wind is less effective because running at +.5e/s below average, even if you run at +.5e/s above average for the same amount of time later, is worse than just running at average.

Interactive Desmos graph where you can experiment with the parameters here. This interactive graph exposes two simplified equations for wind efficiency.

This model simply assumes that a Wind Generator will always produce its mean energy value as output:

\[ y=1+\sqrt{\frac{\left(2.5-\frac{\left(2.5-l\right)}{2}\right)x}{35\cdot16n}} \]

This model assumes that a Wind Generator will produce a linearly random amount of energy, between its lower bound and +2.5e/s. This means its average output is slightly lower:

\[ y=1+\frac{\int_{l}^{2.5}\sqrt{\frac{p\cdot x}{35\cdot16n}}dp}{2.5-l} \]

While ground level wind still produces more raw energy than Fusion on average, they only start producing more metal (from energy via overdrive) than Fusions on average once they reach a lower bound of +0.2e/s, since the diminishing returns of overdrive makes Fusion stability better, even if all of your stable energy needs are already met! You still probably don’t want to be going mostly wind until you reach a lower bound of about +0.4e/s, though.

Going even further beyond

However, this doesn’t completely model the wind periodic cycle. Wind speed is not a truly linear distribution: Every 10 seconds out of 32, it is moving linearly between two linearly random values, then stays stable for a 22 seconds, then repeats. This means that Wind is more likely to produce values near the middle of its range, and so provides power more stably than the linear model above would predict.

The following formula will fully model wind overdrive efficiency over the wind periodic cycle. However, this is about the point that Desmos starts sobbing uncontrollably and asking what it ever did to deserve this, as it fails to render, and only comes back to render the other functions after about a minute of busy processing that appears to involve webworkers in ways that I am not interested in tracing:

\[ y=1+\frac{\left(\int_{l}^{2.5}\sqrt{\frac{\left(p\cdot x\right)}{35\cdot16n}}dp\right)\cdot\frac{22}{32}+\left(\int_{l}^{2.5}\left(\frac{\int_{l}^{2.5}\left(\frac{\int_{s}^{f}\left(\sqrt{\frac{\left(p\cdot x\right)}{35\cdot16n}}\right)dp}{f-s}\right)df}{2.5-l}\right)ds\right)\cdot\frac{10}{32}}{2.5-l} \]

Not to be defeated, you can always throw numpy+scipy at the problem to get an idea of how much the divergence is:

import numpy as np
from scipy.integrate import quad


def linear_model(n,l,x):
  return 1 + quad(lambda p:np.sqrt(p*x/(35*16*n)), l, 2.5)[0] / (2.5 - l)

def full_model(n,l,x):
  return 1 + (
    quad(
      lambda p: np.sqrt(p*x/(35*16*n)),
      l,
      2.5
    )[0] * 22/32
    +
    quad(
      lambda s:
        quad(
          lambda f:
            quad(
              lambda p: np.sqrt(p*x/(35*16*n)),
              s,
              f
            )[0] / (f - s) if f != s else 0,
          l,
          2.5
        )[0] / (2.5 - l),
      l,
      2.5
    )[0] * 10/32
  ) / (2.5 - l)
display([full_model(2,0.02,x) - linear_model(2,0.02,x) for x in range(0,10)])
display([full_model(2,0.02,x) - linear_model(2,0.02,x) for x in range(10,20)])
[0.0,
 0.0002224839773998788,
 0.0003146398586824084,
 0.00038535355328073173,
 0.00044496795545656553,
 0.0004974892981144396,
 0.0005449722214081465,
 0.0005886372758061587,
 0.0006292797174807241,
 0.0006674519332559026]



[0.0007035561125969814,
 0.0007378958760879684,
 0.000770707106698687,
 0.0008021773897883833,
 0.0008324588188410864,
 0.0008616767406925963,
 0.0008899359110712268,
 0.0009173249403600092,
 0.0009439195763258912,
 0.0009697851756813591]
import matplotlib.pyplot as plt
from IPython.display import display

xs = list(range(0, 5000, 50))
deltas = [full_model(2, 0.02, x) - linear_model(2, 0.02, x) for x in xs]

fig, ax = plt.subplots(figsize=(8, 4.5))
ax.plot(xs, deltas, marker="o", linewidth=1.5)
title = "Absolute difference: full_model - linear_model (n=2, l=0.02)"
ax.set_title(title)
ax.set_xlabel("Metal investment")
ax.set_ylabel("Metal overdrive multiplier delta")
ax.grid(True, alpha=0.3)
fig.tight_layout()

display(fig, metadata={"caption": title})
plt.close(fig)
Absolute difference: full_model - linear_model (n=2, l=0.02)
from IPython.display import display

deltas = [full_model(2, 0.02, x) / linear_model(2, 0.02, x) for x in xs]

fig, ax = plt.subplots(figsize=(8, 4.5))
ax.plot(xs, deltas, marker="o", linewidth=1.5)
title = "Relative difference: full_model / linear_model (n=2, l=0.02)"
ax.set_title(title)
ax.set_xlabel("Metal investment")
ax.set_ylabel("Metal overdrive multiplier ratio")
ax.grid(True, alpha=0.3)
fig.tight_layout()

display(fig, metadata={"caption": title})
plt.close(fig)
Relative difference: full_model / linear_model (n=2, l=0.02)

So, you’re fine to just use the linear model for all practical cases.