One weird trick to make game 500x fast

#rust #svelte #AI 2025/01/20

The first version of 5land was called visa.rsvp. It was far from rsvp and took 7 seconds to load. It was a simple multiplayer clicker game- a globe with 1000 tiles where you can upload memes. Reddit liked our game but we were unable to scale. It didn't take long before a bot took over the whole world with his frog and QR.

The tech was simple. We used globe-gl in tile mode to render memes on a sphere. We used Nextjs for full stack development.

Quick to get things off the ground but we soon hit cracks.

The rewrite

Over span of a month, we completely re-imagined and rebuilt our game's architecture.

Source code- https://github.com/5land/5land-client

3D to 2D

function (equirectangular projection, latitude of centre in radians, longitude of centre in radians) = orthographic projection
!pip install numpy
!pip install matplotlib
!pip install pillow

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import math

def create_orthographic_projection(image_path, centre_lat, centre_lon):
    """Create orthographic projection from equirectangular image."""
    # Read the image
    img = Image.open(image_path)
    width, height = img.size
    img_array = np.array(img)

    # Create output image (use square dimensions based on smaller side)
    output_size = min(width, height)
    output = np.zeros((output_size, output_size) + img_array.shape[2:], dtype=img_array.dtype)

    # Convert center coordinates to radians
    centre_lat_rad = np.radians(centre_lat)
    centre_lon_rad = np.radians(centre_lon)

    # Create coordinate meshgrid for output image
    y_coords, x_coords = np.mgrid[0:output_size, 0:output_size]

    # Normalize coordinates to [-1, 1] range for orthographic projection
    x_norm = 2 * (x_coords - output_size/2) / output_size
    y_norm = -2 * (y_coords - output_size/2) / output_size

    # Calculate radius for each point
    r = np.sqrt(x_norm**2 + y_norm**2)

    # Create mask for points inside the globe
    mask = r <= 1

    # Pre-compute some constants
    sin_centre_lat = np.sin(centre_lat_rad)
    cos_centre_lat = np.cos(centre_lat_rad)

    # For each point inside the globe, find corresponding input pixel
    for y in range(output_size):
        for x in range(output_size):
            if mask[y, x]:
                # Convert orthographic coordinates back to lat/lon
                if r[y, x] == 0:
                    lat = centre_lat_rad
                    lon = centre_lon_rad
                else:
                    rho = np.arcsin(r[y, x])  # Angular distance from center
                    sin_rho = np.sin(rho)
                    cos_rho = np.cos(rho)

                    # Calculate latitude
                    lat = np.arcsin(cos_rho * sin_centre_lat +
                                  (y_norm[y, x] * sin_rho * cos_centre_lat) / r[y, x])

                    # Calculate longitude
                    lon = centre_lon_rad + np.arctan2(
                        x_norm[y, x] * sin_rho,
                        r[y, x] * cos_centre_lat * cos_rho -
                        y_norm[y, x] * sin_centre_lat * sin_rho
                    )

                # Convert lat/lon to input image coordinates
                # For equirectangular projection, this is a direct linear mapping
                input_x = int(((np.degrees(lon) + 180) % 360) * width / 360)
                input_y = int((90 - np.degrees(lat)) * height / 180)

                if 0 <= input_x < width and 0 <= input_y < height:
                    output[y, x] = img_array[input_y, input_x]

    return output

def display_projection(image_path, centre_lat, centre_lon):
    """Display the original and orthographic projection side by side."""
    # Create orthographic projection
    orthographic = create_orthographic_projection(image_path, centre_lat, centre_lon)

    # Display results
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 7))

    # Show original image
    ax1.imshow(Image.open(image_path))
    ax1.set_title('Original Equirectangular Projection')
    ax1.axis('off')

    # Show orthographic projection
    ax2.imshow(orthographic)
    ax2.set_title(f'Orthographic Projection\nCenter: ({centre_lat}°, {centre_lon}°)')
    ax2.axis('off')

    plt.tight_layout()
    plt.show()

# Usage example:
display_projection('equirectangular.jpg', 76, -100)

Rewrite in Rust

So what next?

Frontend redux

Out of canvas

Broadly we are using two WebGL programs

  1. Space background: This is procedurally generated using a WebGL shader. We use latitude and longitude as uniforms to view the celestial sphere from different angles.
  2. The globe: Takes latitude, longitude and zoom level as uniforms. The lines on the grid and green color of the selected tile are handled by the shader.

Additionally we use an off-screen canvas in 2D mode to overlay tiles at specific coordinates on an equirectangular image. Since area shrinks near poles, the polar areas have lesser columns. We obtain the output and project it on the planet shader.

Svelte melt

tile.svelte.ts

let tile = $state<number | undefined>()

export function setTile(newTile: number | undefined) {
  tile = newTile
}

export function getTile(): number | undefined {
  return tile
}

// Magic!

No dependencies (ty AI)

Here is my Orbiter dictum. Frameworks and libraries were good at abtracting away code but came at the price of bloat. The time has come where AI can write bespoke components and bring santity to the space so accustomed to dependency hells. The future frameworks would not be libraries at all! Istead they will be a bunch of examples that AI can easily pick up for spitting out.

AI overload

Rust again (red dead redemption)

How to transport images fast?

(type) safety first

Challenge- write this in an ORM

INSERT INTO tiles (id, image, user_id, updated_at)
SELECT ?, ?, ?, ?
WHERE (SELECT free_coins + bonus_coins FROM users WHERE id = ?) >= ?
ON CONFLICT(id) DO UPDATE SET
    image = excluded.image,
    user_id = excluded.user_id,
    updated_at = excluded.updated_at;

UPDATE users
SET
    free_coins = MAX(0, free_coins - ?),
    bonus_coins = MAX(0, bonus_coins - MAX(0, ? - free_coins))
WHERE id = ? AND free_coins + bonus_coins >= ?
RETURNING id, free_coins, bonus_coins;

NixOS

{ config, pkgs, ... }:

let unstable = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz") {
  config = config.nixpkgs.config;
};

in {
  # systemd service
  systemd.services.visa-rsvp = {
    enable = true;
    after = ["network.target"];
    wantedBy = ["multi-user.target"];
    serviceConfig = {
      Type = "simple";
      WorkingDirectory = "/root/visa-rsvp-web2";
      ExecStart = ''
      ${pkgs.bash}/bin/bash -c '${unstable.nodejs_22}/bin/node node_modules/next/dist/bin/next start'
      '';
      Restart = "always";
      RestartSec = "5";
    };
  };

  security.acme = {
    acceptTerms = true;
    defaults.email = "[email protected]";
  };

  services.nginx = {
    enable = true;
    clientMaxBodySize = "10m";
    proxyTimeout = "3600s";

    recommendedGzipSettings = true;
    recommendedOptimisation = true;
    recommendedProxySettings = true;
    recommendedTlsSettings = true;

    # Add rate limiting configuration
    commonHttpConfig = ''
      limit_req_zone $binary_remote_addr zone=image_upload:10m rate=12r/m;
    '';

    virtualHosts."visa.rsvp" = {
      enableACME = true;
      forceSSL = true;

      # Handle SSE endpoint first
      # Fix for images not live loading
      locations."/api/events" = {
        proxyPass = "http://127.0.0.1:3000";
        extraConfig = ''
          proxy_buffering off;
          proxy_cache off;
          proxy_set_header Connection "keep-alive";
          proxy_http_version 1.1;
          proxy_read_timeout 24h;
          proxy_set_header Connection "";
        '';
      };

      # Add rate limited location for image uploads
      locations."/api/images" = {
        proxyPass = "http://127.0.0.1:3000";
        extraConfig = ''
          limit_req zone=image_upload burst=1 nodelay;
          proxy_read_timeout 3600s;
          proxy_send_timeout 3600s;
          proxy_connect_timeout 3600s;

          # Only apply rate limiting to POST requests
          if ($request_method != POST) {
            set $limit_req_enable 0;
          }
        '';
      };

      # Then handle everything else
      locations."/" = {
        proxyPass = "http://127.0.0.1:3000";
        proxyWebsockets = true;
        extraConfig = ''
          proxy_read_timeout 3600s;
          proxy_send_timeout 3600s;
          proxy_connect_timeout 3600s;
        '';
      };
    };
  };

  networking.firewall = {
    enable = true;
    allowedTCPPorts = [ 80 443 ];
  };
}

What's next Anon?

We're pleased to finally launch 5land! Thanks to Murphy's law, we could miraculously ship few hours before the Svelte hackathon deadline! Go post some memes on https://5land.org, or play around with our frontend code!