This is the description of map generation algorithm.

Parameters

The list of parameters the algorithm operates off.

Galaxy Size

The following parameters are associated with the galaxy size.

Galaxy Size n_stars size_x / size_y steps_x / steps_y cur_zoom / max_zoom cur_scale / max_scale

Small

20

506 / 400

5 / 4

0

10 / 10

Medium

36

759 / 600

6 / 6

1

15 / 15

Large

54

1012 / 800

9 / 6

2

20 / 20

Cluster

71

1011 / 800

9 / 6

3

15 / 20

Huge

71

1518 / 1200

9 / 8

3

30 / 30

  • Cluster is a special case added in 1.40, it is mostly a Large galaxy with count of stars from Huge.

  • size_x and size_y are specified in internal units, one parsec is 30 units. All ship and star coordinates are expressed in such units. The metric is Euclidean, the formula for distance is dist = isqrt(dx*dx + dy*dy) where isqrt(x) is the minimum positive integer y such that y*y >= x.

  • steps_x and steps_y define a number of windows the spaces is divided to for galaxy generation.

  • zooms and scales affect map generation since mapgen is concerned with star name labels visually overlapping neighboring stars, the name label is relatively the largest on maximum zoom.

Galaxy Age

Functions from this section are used in the the algorithm below.

  • Generate_Spectral_Class(gal_age) — randomize star color, parameters: galaxy age, star_class_chance

    #                                  average
    #                              young     |   old
    #                                  ▼     ▼     ▼
    star_class_chance       blue =    20    10     5;
    star_class_chance      white =    25    15     5;
    star_class_chance     yellow =    10    16    30;
    star_class_chance     orange =    10    16    21;
    star_class_chance        red =    32    37    30;
    star_class_chance      brown =     1     2     3;
    star_class_chance black_hole =     2     4     6;
        int Generate_Spectral_Class(int sid) {
            int w[7] = {};
            for (int i = 0; i < 7; ++i)
                w[i] = star_class_chance[i][gal_age];
            return weighted_roll(w)
        }
  • Generate_Number_Of_Satellites(sid), star color (id), class_to_num_satellites

    #                                              orange
    #                                         yellow    |
    #                                     white    |    |     brown
    #                                 blue    |    |    |  red    |
    #                                    ▼    ▼    ▼    ▼    ▼    ▼
    class_to_num_satellites random0 =    0    0    1    2    0    0;
    class_to_num_satellites random1 =    1    1    2    2    1    0;
    class_to_num_satellites random2 =    1    1    2    2    1    0;
    class_to_num_satellites random3 =    2    1    2    3    1    0;
    class_to_num_satellites random4 =    3    2    3    3    2    0;
    class_to_num_satellites random5 =    3    2    3    4    2    0;
    class_to_num_satellites random6 =    4    3    4    4    2    0;
    class_to_num_satellites random7 =    4    3    4    5    3    1;
    class_to_num_satellites random8 =    5    4    5    5    3    1;
    class_to_num_satellites random9 =    5    4    5    5    4    1;
        int Generate_Number_Of_Satellites(int sid) {
            int r = Random(10) - 1;
            int c = game.stars[sid].color;
            return is_bh(sid) ? 0 : return class_to_num_satellites[r][c];
        }
  • Planet_Generation(si) — create a single planet orbitin a star, the star may already have orbiting planets, parameters: sid, galaxy age, satellite_orbit_chance.

    #                                   average
    #                               young     |   old
    #                                   ▼     ▼     ▼
    satellite_orbit_chance orbit1 =    25    20    10;
    satellite_orbit_chance orbit2 =    18    20    22;
    satellite_orbit_chance orbit3 =    17    20    30;
    satellite_orbit_chance orbit4 =    15    20    33;
    satellite_orbit_chance orbit5 =    25    20     5;
        int Planet_Generation(int sid) {
            int w[5] = {};
            for (int i = 0; i < 5; ++i)
                w[i] = satellite_orbit_chance[i][gal_age];
    
            while(1) {
                int r = weighted_roll(w);
                if (game.stars[sid].orbits[r] == -1)
                    return r;
            }
        }

The Algorithm

The code below is a pseudocode with the same logic as that of the original executable, except that graphics and interrupt servicing bits are omitted.

  • Init_New_Game — the entry point, generates everything.

    void Init_New_Game() {
        // ... intialize various variables
        Set_Game_Random_Seed(); // set from command line parameter if given, otherwise randomize
        Universe_Generation(gal_size, gal_age, diff);
        while(1) {
            book ok = Generate_Home_Worlds();
            if (ok) {
                Enforce_Planet_Max();
                Set_Black_Hole_Visited_Flags(); // ?!
                Confirm_Planet_And_Star_Index_Link();
                if (settings.civ == ADVANCED_CIV) {
                    Advanced_Civilization_Colonies();
                    Assign_Advanced_Civilization_Starting_Ships();
                }
                break;
            }
        }
        Make_System_Monsters();
        Make_System_Monsters_Into_Ships();
        Generate_Wormhole_Links();
        if (settings.civ == ADVANCED_CIV) {
            Allocate_Adv_Civ_Officers();
        }
        Assign_Marooned_Heroes();
        Initialize_Black_Hole_Blocks();
        Initialize_Star_In_Nebula_Info();
        // ... named defaults, inits
        Update_Star_Owners();
        Twiddle_Initial_Homeworlds();
        // ... further inits
    }
  • Universe_Generation(…​) — creates stars and planets.

    void Universe_Generation(int gal_size, int gal_age, int diff) {
        Generate_Nebulas();
        int steps_x, steps_y <- from gal_size
        while(!gctx.f122) {
            Set_Star_XYs(steps_x, steps_y, gal_age, 1, 1);
            for (int si = 0; si < n_stars; ++si) {
                Star *star = &game.stars[si];
                star->owner = -1;
                star->size = weighted_roll(3, 4, 3); // cosmetic
                star->picture = Random(3) - 1; // cosmetic
                if (!is_bh(star)) {
                    int n_sat = Gnerate_Number_Of_Satellites(si);
                    while (n_sat--)
                        Planet_Generation(si);
                }
                if (!is_bh(star) && gctx.f2[si] && si != 0 /*Orion*/)
                    Generate_Star_Special(si, gctx.f110, a_mask12);
                star->wormhole = -1;
            }
            while(1) {
                if (n_nebulas)
                    Black_Hole_Fix(gctx.fm86); // black holes aren't allowed in nebulas
                Initialize_Black_Hole_Blocks();
                if (Map_Is_Connected())
                    break;
                Set_Star_XYs(gctx, 0, 0);
            }
            ...
        }
    }

    int weighted_roll(weight1, ..., weight_n) {
        roll 1 .. sum of weights, return index of appropriate interval 0..n-1
    }

    bool is_bh(Star *star) { return star->color == 6; } // check star is black hole

    void Set_Star_XYs(int steps_x, int steps_y, int gal_age, bool make_colors, bool make_names) {
        int dx = size_x / steps_x;
        int dy = size_y / steps_y;
        for (int si = 0; si < n_stars; ++si) {
            Star *star = &game.stars[si];
            if (make_colors)
                star->color = Generate_Spectral_Class(gal_age);
            if (make_names) {
                width = Get_String_Width(format);
            } else {
                width = Random(8) + Random(8) + Random(8) + 32; // wtf
            }
            int off_x = si % steps_x * step_x;
            int off_y = si * step_y / steps_x; // unusual sliding window, appears uniform

            ysz, yextra = cur_scale == 20 ? 36, 6
                        : cur_scale == 30 ? v7, 4
                                          : 50, 8;

            bool is_invalid = 0;
            int retries = 0;
            do {

                do
                    x = (Random(2 * step_x) + off_x) % size_x;
                while (scale(x) not in [24, scale(size_x) - star_width / 2); // not too close to sides

                do
                    y = (Random(2 * step_y) + off_y) % size_y;
                while (scale(y) not in [22, scale(size_y) - (star_height / 2 + yextra));

                star->x = x;
                star->y = y;
                is_invalid = Star_XY_Invalid(si, width, ysz, v32);
                int retries = 0;
                if (is_invalid == 1 && ++retries > 150) {
                    while(1) {
                        int rx = Get_Up_Scaled_Value(Random(393) + 20);
                        int ry = Get_Up_Scaled_Value(Random(341) + 20);
                        star->x = rx;
                        star->y = ry;
                        if (!Star_XY_Invalid(i, width, ysz, upscale(rx)))
                            break;
                        if (++retries > 150)
                            return;
                    }
                    is_invalid = 0;
                }
            } while (is_invalid);
        }
    }

    // Get_Scaled_Value
    int scale(int value) {
        return value * 10 / cur_scale;
    }

    // Get_Up_Scaled_Value
    int upscale(int value) {
        return value * cur_scale / 10;
    }

    // checks if star is too close to any other star with _smaller_ index
    bool Star_XY_Invalid(int si, int x_box, int y_box, int x_scaled) {
        Star *a = &game.stars[si];
        for (int i = 0; i < si; ++i) {
            Star *b = &game.stars[i];
            int sdx = scale(a->x - b->x);
            int sdy = scale(a->y - b->y);
            if (!is_bh(a) && !is_bh(b)) {
                bool too_close = sdx * sdx + sdy * sdy <= 800;
                bool in_the_box = abs(sdx) < x_box && abs(sdy) < y_box;
                bool in_fixed_box = abs(sdx) < 15 && abs(sdy) < scaled(60);
                bool too_far_right = x_scaled + 2 * x_box / 3 > scale(size_x); // wtf
                if (too_close || in_the_box || in_fixed_box || too_far_right)
                    return 1;
            } else if (Parsecs_Between_Stars(a, b) < 5) {
                return 1;
            }
        }
        return 0;
    }