ABOUT

Earth Estimator

Earth Estimator is a game about correctly estimating different statistics about different countries.

For more information about how to play, please have a read through the How To Play page.

This document explains why and how Earth Estimator was built.

01

WHY

Why build Earth Estimator?

The world is vast.

We, the affluent, statistically speaking, often live in parts of the world with clean air, access to clean water and electricity, high democracy indexes, and low corruption.

With high life satisfaction and higher life expectancy, it can be easy to forget that this is not the case everywhere in the world.

We forget about the parts of the world with higher poverty rates, lower life expectancies, less democracy, and fewer opportunities.

We sometimes even forget that not everyone in the developed world has it as easy as we do.

Sweden's homeless population: 27,377 in 2023

Sweden's homeless population: 33,250 in 2026

What does a 21.4% increase say?

Earth Estimator is fun, without a doubt, but Earth Estimator's aim is hopefully also to make you more aware of these differences in the world.

Statistics can feel distant when they are just numbers in a report.

Having to estimate them first, and then seeing how close or far away your assumption was, can make those numbers feel much more real.

Earth Estimator is a game.

But the numbers behind the game describe the real world.
02

HOW

How Earth Estimator works

2.1

The Technology

Earth Estimator is primarily a browser-based JavaScript application.

The interface is built with HTML, CSS and JavaScript. Firebase is used for hosting, authentication and cloud data storage.

The statistics themselves are stored in a large normalized JSON dataset containing approximately 361,117 lines and 20,991 statistical observations.

Although the visible website is relatively simple, most of the technical work happens in four areas:

Data acquisition

Collecting statistics from structured datasets, APIs and public statistical tables.

Data normalization

Converting different source formats into one predictable country and metric structure.

Game mathematics

Building question ranges, scoring estimates and adapting numerical precision.

Data visualization

Turning thousands of country values into searchable tables and adaptive map colours.

2.2

Data Acquisition

The statistics were gathered from public statistical databases and datasets.

Two main techniques were involved: pulling structured information from APIs or downloadable datasets, and scraping public statistical tables when the information was not conveniently exposed through an API.

A simplified API-pulling technique looks like this:

Python, API data pull
[CODE PLACEHOLDER]

World Bank API request using:

- requests
- country/all
- indicator/SP.POP.TOTL
- JSON response
- country
- value
- year extraction

This type of request returns already structured records, meaning the main job is extracting the country, value and year before converting them into Earth Estimator's common format.

Other statistics required table-oriented extraction.

Python, statistical table scraping
[CODE PLACEHOLDER]

Web scraping using:

- requests
- BeautifulSoup
- HTML table lookup
- <tr> rows
- <th> / <td> cells
- country
- value
- year extraction

The important distinction is that scraping returns presentation-oriented text, not necessarily clean statistical data.

Values therefore had to be converted from strings, footnote markers removed, missing values identified, years checked and country names normalized.

About these examples

The original acquisition scripts are not part of the current website repository. These examples are representative reconstructions of the techniques used rather than verbatim copies of those scripts.

2.3

Normalizing Different Sources

One of the largest problems was that statistical sources do not agree on naming conventions.

One source might use:

Different names for one country
United States
USA
United States of America

Another source may use:

Alternative country naming
Turkey
Türkiye

Earth Estimator therefore contains an explicit alias system.

JavaScript, country aliases
[CODE PLACEHOLDER]

COUNTRY_ALIASES example:

"United States of America"
    → "United States"
    → "USA"

"Turkey"
    → "Türkiye"

The lookup code then tries every accepted alias until it finds a matching country record.

JavaScript, alias lookup
[CODE PLACEHOLDER]

findCountryData(
    metricData,
    country
)

1. Get aliases
2. Loop through aliases
3. Check metricData[alias]
4. Validate country record
5. Return matching data
6. Otherwise return null

This separates the display name from the source name.

Once cleaned, statistics are stored in one predictable structure.

JSON, normalized metric structure
{
{
  "Population": {
    "unit": "people",
    "metric_explanation": "The total number of people living in the country at the stated time.",
    "unit_explanation": "People are counted as individual residents.",
    "Sweden": {
      "value": 10605529,
      "year": 2026,
      "source": [
        "https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/demo_gind?lang=en&geo=SE&indic_de=JAN&time=2026"
      ],
      "audit": {
        "status": "corrected",
        "checked_on": "2026-09-11",
        "data_kind": "reported_statistic",
        "dataset": "Eurostat demo_gind; refreshed 2026-07-21T23:00:00+0200",
        "indicator": "JAN",
        "reference_period": "2026",
        "year_basis": "observation_or_statistical_reference_year",
        "notes": "Population stock at 1 January. Eurostat status:",
        "latestness": "Latest eligible result in the checked structured/cached sources; years 2026 → 2025 → 2024 → 2023."
      }
    }
  }
}

The browser can therefore treat population, GDP, life expectancy or another statistic using essentially the same JavaScript.

2.4

Loading the Dataset

The data map loads the complete dataset asynchronously.

JavaScript, loading the JSON dataset
[CODE PLACEHOLDER]

await fetch(
    DATA_FILE,
    {
        cache: "no-cache"
    }
);

gameData =
    await response.json();

The program then discovers valid metrics dynamically instead of relying on one manually written question list.

JavaScript, dynamic metric discovery
[CODE PLACEHOLDER]

Object.entries(gameData)

    → filter valid metric objects

    → extract metric names

    → sort available questions

This means another properly formatted metric can be added to the JSON and become available to the program without rewriting the core rendering system.

2.5

Generating Questions

In Vanilla Mode, Earth Estimator first determines which countries contain enough usable statistics.

Every potential value is converted into a number and checked using Number.isFinite().

JavaScript, valid observation check
[CODE PLACEHOLDER]

const value =
    Number(
        countryData.value
    );

if (
    Number.isFinite(
        value
    )
) {
    countryMetricCounts[
        country
    ]++;
}

Only countries containing at least ten valid metrics become eligible for Vanilla Mode.

JavaScript, Vanilla eligibility
[CODE PLACEHOLDER]

if (
    countryMetricCounts[
        country
    ] >= 10
) {
    eligibleCountries.push(
        country
    );
}

After the player chooses a country, the available metrics are randomized and ten distinct questions are selected.

JavaScript, randomized question pool
[CODE PLACEHOLDER]

questionPool =
    shuffleArray(
        availableQuestions
    ).slice(
        0,
        10
    );

Custom Mode works differently. It begins with the player's selected metric and country identifiers and searches the dataset for combinations containing a finite numerical value.

2.6

The Accuracy Calculation

Earth Estimator deliberately does not use a binary correct-or-incorrect scoring system.

The first calculation is the absolute difference between the player's estimate and the correct answer.

JavaScript, absolute error
[CODE PLACEHOLDER]

const difference =
    Math.abs(
        playerAnswer -
        correctValue
    );

But an error of 10 cannot be judged without knowing the scale of the statistic.

The program therefore calculates the full available range:

JavaScript, metric range
[CODE PLACEHOLDER]

const valueRange =
    currentMaximumValue -
    currentMinimumValue;

The relative range error becomes:

e = d / r

where:

  • d = |guess - correct|
  • r = maximum - minimum
  • e = normalized range error
JavaScript, normalized error
[CODE PLACEHOLDER]

const rangeError =
    difference /
    valueRange;

Earth Estimator then applies its nonlinear accuracy curve.

JavaScript, accuracy curve
[CODE PLACEHOLDER]

const remainingAccuracy =
    Math.max(
        0,
        1 - (rangeError * 2)
    );

answerAccuracy =
    100 *
    Math.pow(
        remainingAccuracy,
        2
    );

Mathematically, this is:

A = 100 × max(0, 1 - 2e)²

Or, written using the original values:

Accuracy equation
                  |guess - correct|
A = 100 × max(0, 1 - 2 ─────────────────)²
                    maximum - minimum

The squaring is important. Without it, accuracy would fall linearly.

Squaring the remaining accuracy creates a stronger penalty as the estimate moves farther away from the real value.

The final game score is the arithmetic mean of all question accuracies.

JavaScript, final game accuracy
[CODE PLACEHOLDER]

finalAccuracy =
    totalAccuracy /
    totalQuestions;

2.7

Slider Precision

The slider is generated from the statistics instead of using one arbitrary fixed range.

For each metric, the smallest and largest available observations become the slider boundaries.

JavaScript, slider boundaries
[CODE PLACEHOLDER]

currentMinimumValue =
    Math.min(
        ...values
    );

currentMaximumValue =
    Math.max(
        ...values
    );

Earth Estimator also determines the smallest meaningful place value occurring in the source data.

Consider:

4,700,000 Example integer

The program repeatedly divides the number by ten while it remains divisible by ten.

JavaScript, place-value detection
[CODE PLACEHOLDER]

while (
    temporaryNumber % 10 === 0
) {
    placeValue *= 10;

    temporaryNumber /= 10;
}

The resulting place value becomes part of the slider's step calculation.

This lets the interface reflect the precision of the original statistics instead of inventing meaningless decimal places.

2.8

K, M and B Scaling

Large statistics are displayed using one common multiplier for the entire metric.

The program begins by finding the smallest non-zero observation.

JavaScript, smallest value
[CODE PLACEHOLDER]

const smallestValue =
    Math.min(
        ...nonZeroValues
    );

It then chooses whether the entire metric should use normal numbers, thousands, millions or billions.

JavaScript, display scale
[CODE PLACEHOLDER]

if smallestValue >= 1,000,000,000
    → B

else if smallestValue >= 1,000,000
    → M

else if smallestValue >= 1,000
    → K

else
    → normal number

If the smallest relevant observation is 4,000,000, the divisor becomes 1,000,000.

4,000,000 ÷ 1,000,000 = 4M
87,000,000 ÷ 1,000,000 = 87M
2,000,000,000 ÷ 1,000,000 = 2,000M

The important part is that the divisor is selected once per metric, not once per country.

Every displayed country value therefore remains directly comparable with every other country.

2.9

The Global Data Map

The world visualization is built with jsVectorMap.

Earth Estimator first translates its authoritative country names into two-letter geographical codes understood by the map.

JavaScript, map country codes
[CODE PLACEHOLDER]

const MAP_COUNTRY_CODES = {

    "Sweden": "SE",
    "Japan": "JP",
    "Brazil": "BR",
    "United States of America": "US"

};

The numeric observation can then be connected to the vector region representing that country.

Countries without numerical observations are initialized in grey.

jsVectorMap, no-data appearance
[CODE PLACEHOLDER]

regionStyle: {

    initial: {

        fill:
            "#c7c7c7",

        stroke:
            "#444444"
    }
}

The more complicated part is calculating the colour of countries that do contain data.

Sending the raw values through a simple linear gradient caused extreme observations to dominate the map.

If most countries lie between 10 and 100 but one country has a value of 5,000, almost every normal country would otherwise receive practically the same colour.

Earth Estimator instead analyzes the distribution first.

2.10

Detecting Outliers

The map calculates the first and third quartiles of the selected statistic.

JavaScript, quartiles
[CODE PLACEHOLDER]

const q1 =
    calculateQuantile(
        sortedValues,
        0.25
    );

const q3 =
    calculateQuantile(
        sortedValues,
        0.75
    );

The interquartile range is:

IQR = Q3 - Q1

Earth Estimator then uses Tukey's interquartile-range rule:

Upper boundary = Q3 + 1.5 × IQR
JavaScript, high outlier boundary
[CODE PLACEHOLDER]

const iqr =
    q3 -
    q1;

const highOutlierBoundary =
    q3 +
    (
        1.5 *
        iqr
    );

Only observations exceeding that boundary are treated as high outliers.

JavaScript, outlier classification
[CODE PLACEHOLDER]

if (
    row.value >
    highOutlierBoundary
) {
    outlierRows.push(
        row
    );
}

2.11

Percentile-Based Map Colours

If no statistically meaningful outlier exists, countries are coloured according to their percentile positions instead of their raw values.

For n unique ordered values, the program calculates:

p = rank / (n - 1)
JavaScript, percentile position
[CODE PLACEHOLDER]

const percentile =
    index /
    (
        uniqueValues.length -
        1
    );

Colour therefore represents a country's position in the distribution, rather than simply its raw numerical distance from zero.

When real high outliers exist, normal values occupy approximately:

0.00 → 0.70 Normal-value section of the colour scale

High outliers occupy:

0.88 → 1.00 Extreme-value section of the colour scale

The darkest part of the map can then extend all the way to black.

jsVectorMap, outlier colour scale
[CODE PLACEHOLDER]

scale: [
    "#d9e5f5",
    "#134287",
    "#06152b",
    "#000000"
]

As a result, an extreme country becomes nearly black only when it is actually extreme relative to the other countries.

2.12

Quantile Interpolation

Quartiles are not simply taken from one array position.

Earth Estimator first calculates a fractional position inside the ordered dataset.

JavaScript, quantile position
[CODE PLACEHOLDER]

const position =
    (
        sortedValues.length -
        1
    ) *
    percentile;

If that position falls between two observations, linear interpolation is used.

Q = x₀ + (x₁ - x₀)f

Here, f represents the fractional distance between the lower and upper observations.

JavaScript, linear interpolation
[CODE PLACEHOLDER]

return (
    lowerValue +
    (
        (
            upperValue -
            lowerValue
        ) *
        fraction
    )
);

2.13

Search Ranking

Even the Data Map's question search has a small ranking algorithm.

Matching results receive a priority based on how closely they match the search.

JavaScript, search ranking
[CODE PLACEHOLDER]

0 → exact match

1 → metric begins with search

2 → another word begins with search

3 → search appears somewhere else

For example, searching for:

GDP Search query

can prioritize:

GDP

GDP per capita

Military spending % GDP

before weaker substring matches.

2.14

Local and Cloud Persistence

Earth Estimator maintains separate persistence systems for guests and signed-in players.

Guest

Game results and settings are stored in the browser using localStorage.

Signed in

Game results and settings are stored using Firebase Authentication and Cloud Firestore.

Guest results can be stored using:

JavaScript, browser result storage
[CODE PLACEHOLDER]

gameHistory.push(
    safeAccuracy
);

localStorage.setItem(
    storageKey,
    JSON.stringify(
        gameHistory
    )
);

Authenticated results instead become Firestore documents below the player's Firebase UID.

JavaScript, Firestore result storage
[CODE PLACEHOLDER]

users
    / {uid}
        / gameResults
            / {resultId}

.add(
    resultData
)

A result document can contain:

Firestore, example result document
{
    accuracy: 84.7,
    gameMode: "vanilla",
    questionCount: 10,
    country: "Sweden",

    clientPlayedAt:
        Date.now(),

    playedAt:
        serverTimestamp()
}

Using both client and server timestamps gives the application an immediate local time while Firestore also supplies an authoritative server-side timestamp.

The account storage path is tied to the authenticated Firebase uid, keeping one player's cloud results separate from another player's.

03

THE SYSTEM

What happens behind the game

The visible game is therefore only the final layer of a longer processing chain.

Earth Estimator data flow
data acquisition
        ↓
cleaning
        ↓
country-name normalization
        ↓
common JSON schema
        ↓
question generation
        ↓
range + precision analysis
        ↓
accuracy mathematics
        ↓
percentile / IQR visualization
        ↓
local or Firestore persistence

Collecting, cleaning and restructuring the statistical data took the better part of a month.

Building the Earth Estimator platform itself took roughly three very intense weeks from the beginning of development to the first publishable version.

Development assistance

ChatGPT was used as a development tool for parts of the implementation, debugging and code revision, which considerably accelerated that process.

EARTH ESTIMATOR

A simple game built on a lot of data.

Earth Estimator combines public statistics, browser-side JavaScript, statistical normalization, adaptive visualization and a custom scoring system into one geography game.

The idea is simple: make an estimate, see the real number, and gradually build a better intuition for the world.

You do not need to know every statistic. You just need to make the best estimate you can.

Start a Game