Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reading the source

This book describes the MS-DOS version of Sid Meier’s Civilization, version 474.05. It follows the C and assembly in src/, from DOS drivers and map storage to city simulation, unit movement and diplomacy.

The code is reverse engineered. Names are inferred, and some modules still use local record declarations or casts that differ from the shared headers. Descriptions here follow the operations and stored values, including quirks that may differ from a player’s expectations. The build notes explain what the executable comparison establishes.

For a guided reading of a few complete files, see the Docco story in story/. This book groups explanations by game system and ends with source tables and shared declarations.

Entry point

main() in GAME.C starts MISC.EXE, runs setup, installs break handlers and calls game_startup(). It checks available memory, loads cursor images, starts the resident timer routines and initializes the mouse.

After game_menu() and start_game(), it calls game_turn() until leave is set. On exit it closes the mouse, stops sound, restores timer and break handlers, and requests BIOS text mode 3.

int main(void)
{
    int j, k, n, memory, i;

    run_program(load_program("misc.exe", 0));
    setup_main();
    *(char far *) 0x00400017L &= 0xdf;
    install_break_handlers();
    game_startup();
    disk_insert(0, 1L);
    memory = free_paragraphs();
    if (small_space_check)
        memory = 0;
    if ((vga_modes ? 8000 : 4000) > memory) {
        rect_fill(scrbuf, 0, 0, 320, 200, 12);
        msgtext[0] = 0;
        strcat(msgtext, itoa(memory, nstr, 10));
        strcpy(message_slot, msgtext);
        msgtext[0] = 0;
        named_msg("*LOMEM");
        show_menu(msgtext, 64, 49);
    }
    if (vga_modes && memory < 9150)
        low_memory = 1;
    sprite_arena_init();
    mouse_cursor = load_cursor("torch.pic");
    sprite_arena_release();
    if (has_mouse)
        has_mouse = init_mouse();
    if (has_mouse) {
        set_mousecur(0, 0, mouse_cursor);
        mouse_on();
    }
    game_menu();
    start_game();
    if (main_option_result == 1) {
        map_x0 = x_cap[civnow] - 7;
        map_y0 = 19;
        sel_player();
    }
    leave = 0;
    do
        game_turn();
    while (!leave);
    if (has_mouse)
        mouse_close();
    sound(0);
    sound_shutdown();
    restore_timer();
    restore_break_handlers();
    bios_call_reg_block_io.h.ah = 0;
    bios_call_reg_block_io.h.al = 3;
    int86(0x10, &bios_call_reg_block_io, &bios_call_reg_block_io);
}

GAME.C also defines the terrain, technology, improvement, unit and tribe tables. The memory chapter explains how shared state remains available while screens and other operations use overlays.

Building the same executable

The sources build with Microsoft C 5.1, MASM 5.10 and LINK 5.01 under Microsoft MAKE 4.07. The compiler options, segment names, declarations and link order matter: changing apparently equivalent C can change the resulting machine code or data layout.

Names are inferred unless preserved in the executable. They are not claimed to be MicroProse’s original names. Under this toolchain, identifier spelling can affect local-variable allocation and the order of uninitialized global storage. Some awkward names and repeated declarations remain to keep the compiled layout unchanged.

Compiler memory

The rules for CONTROL.C and CITYMGT.C use loadfix -192 to limit the memory available to the compiler. Available memory can affect the code Microsoft C 5.1 emits for these large modules. The checked build includes that condition; the original developers’ machine configuration is unknown.

Startup call

The build keeps raw linker output as CIVRAW.EXE. A small program built from CRTEDIT.C then changes the destination of one far call, leaving four bytes different in CIV.EXE.

Call destinationFunction
Raw LINK output2045:0442Runtime environment initialization, __setenvp
Final executable01a8:01c1empty_hook, which immediately returns

The call opcode is at file offset 0x204c9; its four destination bytes occupy 0x204ca–0x204cd. The editor checks length, CRC and expected bytes before making the change, then checks the result. The reason the original executable bypasses environment initialization is unknown.

just verify builds from scratch and checks SHA-256 hashes for both the raw and final executables, plus the separate graphics driver, sound driver and MISC.EXE. Matching those bytes establishes the build result. It does not establish the original spelling of symbols or the historical build procedure.

Game Memory Layout

Civilization keeps the main game loop, unit operations, city simulation and tactical AI resident in memory. Screens and less frequently used operations occupy 23 overlays, which share a 9,648-byte code window. Loading another overlay replaces the code in that window.

Shared state remains available across overlay changes. City and unit records, civilization finances, diplomacy and rule tables use the common data area, DGROUP. Larger arrays such as map_visible_to, power_history and place_name occupy separate data storage. The execution stack is 2,048 bytes.

Tile attributes also use the page 2 map layout, accessed through the game’s graphics and map routines. This storage links the map engine to the screen-page abstraction.

Executable Layout

These addresses are relative to the executable’s unrelocated load image.

AreaStartEndRole
Resident code0000023740Main simulation and engine routines
Overlay code window2374025CF0One active overlay body
Separate data storage25CF02B010Larger arrays and tables
Shared data and stack2B0103A0E0DGROUP

The main module initializes the engine and enters the turn loop. The overlay catalog identifies the modules loaded for menus, reports, world generation and other operations.

Shared Game Data

Civilization’s resident modules and overlays share a common data area called DGROUP. It holds rule tables, civilization state, entity arrays, strings, scratch buffers and the execution stack. Changing the active overlay leaves this state available.

Principal Record Arrays

Offsets below are relative to DGROUP.

OffsetArrayRecordsRecord size
0282terrains1219 bytes
112Autypes2834 bytes
70ECtownbuf12828 bytes
81D4armydata8 × 12812 bytes

The record declarations show their fields. src/GAME.C also defines the 46-entry production table, containing city improvements, spaceship parts and wonders.

Civilization-wide variables track treasury, science and tax rates, technological knowledge and diplomatic relations. The save transfer sequence shows which state is written to disk and in what order.

Larger arrays, including map visibility, power history and the city-name pool, use separate storage. Tile attributes are accessed through screen page 2.

Overlays

To run within the 640 KiB conventional memory ceiling of MS-DOS, Civilization uses Microsoft LINK’s dynamic overlay manager.

All 23 overlays share a single memory window of 9,648 bytes located at resident linear address 0x23740 (frame 0x2374). When an overlay routine is invoked, the overlay manager loads the requested overlay from disk (CIV.EXE) into this shared window before executing it.

       Resident Code Area                     Shared Overlay Swap Window
┌───────────────────────────────┐          ┌──────────────────────────────┐
│ Resident Modules (0000..23740)│ ──Call──>│ 2374:0000                    │
│                               │          │ Active Overlay Code (Max 9KB)│
│  [GAME.C, TURN.C, CITYMGT.C]  │<──Ret────│                              │
└───────────────────────────────┘          └──────────────────────────────┘
                                                          ▲
                                                          │ Swapped from disk
                                           ┌──────────────┴──────────────┐
                                           │ CIVILIZATION EXE File       │
                                           │  - Overlay 1  (SETUP)       │
                                           │  - Overlay 6  (AUDIENCE)    │
                                           │  - Overlay 7  (WORLDGEN)    │
                                           │  - Overlay 11 (SAVE)        │
                                           │  - Overlay 18 (SPACESHP)    │
                                           │  ... (23 Total Overlays)    │
                                           └─────────────────────────────┘

Each module can expose several entry points; the table shows one current shared declaration.

Complete Catalog of the 23 Overlays

Overlay IDModule SourceLocal Code BytesExample Shared ExportFunctional Purpose
1src/SETUP.C1,018setup_main()Hardware detection, graphics mode & audio driver configuration.
2src/INTRO.C6,588game_startup()Title sequence, MicroProse logo animations, credits.
3src/FAME.C3,192fame_load()Hall of Fame score calculation and high score table display.
4src/NOTES.C1,146o4_notes()In-game instructional popups, game tips, and key summaries.
5src/NEWGAME.C7,534new_game()New game menu, difficulty, world customization, tribe selection.
6src/AUDIENCE.C9,642leader_audience()Diplomatic negotiations, treaty proposals, leader audience screens.
7src/WORLDGEN.C7,512make_world()Procedural map generation, land growth, climate, rivers.
8src/PEDIA.C6,134civilopedia()Civilopedia encyclopedia viewer (units, improvements, wonders).
9src/REPLAY.C5,468o9_replay()Historical timeline replay displayed upon game victory/defeat.
10src/MAPVIEW.C1,702o10_mapview()Full-world strategic minimap viewer and territory inspection.
11src/SAVE.C3,994o11_save()Save/load menus, disk validation, .SVE and .MAP serialization.
12src/REPORTS.C7,000o12_reports()Military, foreign affairs, commerce, and demographic status reports.
13src/STATUS.C2,472o13_status()World status screen, civilization ranking, Top 5 Cities display.
14src/ADVISORS.C7,026advisor_open()Graphic advisor consultations (Military, Domestic, Foreign, Science).
15src/CIVWAR.C2,426civil_war()Civil war partition logic triggered when an enemy capital falls.
16src/QUIZ.C1,372o16_quiz()Manual-lookup copy protection challenge questions.
17src/PALACE.C4,246palace_view()Palace upgrade view and graphic construction screen.
18src/SPACESHP.C5,842spaceship_open()Alpha Centauri spaceship component assembly, flight simulation, arrival.
19src/CITYVIEW.C7,332city_scene()Hand-drawn graphical city interior view with active improvements.
20src/CITY.C5,188o20_city()City founding ceremony, naming prompt, tile radius activation.
21src/NEWS.C1,066news_box()Newspaper banner popups announcing world wonders and disasters.
22src/DIPLOMAT.C4,434dip_menu()Diplomat operations (bribery, sabotage, theft).
23src/NAMES.C1,926rename_leader()Procedural city name lists and tribal leader nomenclature.

Overlay Sizing Constraint

The overlay swap window is determined by the largest single overlay linked into the binary: $$\text{Max Overlay Size} = \text{Overlay 6 (\texttt{AUDIENCE.C})} = 9,642\text{ bytes}$$

Because LINK allocates 9,648 bytes (rounded up to a paragraph boundary) at [0x23740, 0x25CF0), any individual overlay is guaranteed to fit into the swap window without corrupting adjacent far data segments.

Overlay Lifecycle

All 23 overlays share one code window. When the game invokes an overlay function, the overlay runtime loads the corresponding body from CIV.EXE if needed, then transfers control to that function. A module can expose several entry points; see the overlay interfaces.

Only overlay code is replaced. Module-owned tables, strings and writable data remain in shared or separate resident data storage. Diplomatic and world-generation state therefore survives a change of overlay.

flowchart LR
    Caller[Resident game code] --> Request[Request overlay function]
    Request --> Check{Required overlay loaded?}
    Check -->|Yes| Run[Execute overlay function]
    Check -->|No| Load[Load code from CIV.EXE]
    Load --> Run
    Run --> Return[Return to caller]

Graphics

Game drawing code calls a table of driver entry points. MGRAPHIC.EXE implements MCGA/VGA drawing; EGA and Tandy have separate drivers. The loader installs far jumps in driver_slots from each driver’s header. The MCGA driver chapter describes its slots and data.

Pages

The MCGA driver uses 320-by-200 byte-per-pixel pages. Page 0 is the screen; allocated pages provide offscreen storage. Game code passes a struct page with the page number, origin, clipping bounds and text settings:

struct page {
    int number;
    int left, top, right, bottom;
    int active, colour;
    int text_colour;
    int font;
};

blit() copies rectangles between pages. sprite_draw() and its variants draw stored sprites. Game code assembles map tiles, cities, units and menus from those operations. Mouse visibility is managed around drawing through mouse_off() and mouse_on().

Page 2 also holds the map database. A page number therefore identifies storage, not necessarily a picture intended for display.

Video selection

SETUP.C offers VGA, MCGA, EGA and Tandy 1000. VGA and MCGA both select driver M. MGRAPHIC.ASM initializes BIOS mode 13h, a 320-by-200 display with 256 palette indices. This repository builds the M driver; the setup choices alone do not establish how the other drivers implement drawing.

Palette animation

PALETTE.INC implements set_cycle(), start_cycle() and stop_cycle(). FADE.INC implements fades. The MCGA driver writes palette values to the VGA DAC. Changing a palette entry changes the displayed colour of every pixel using that index without rewriting the pixels themselves.

Palette asset loading uses load_palette() in FILEIO.C, through the picture-loading path. A file named as a palette must not be assumed to be a headerless array of RGB values.

The MCGA Graphics Driver (MGRAPHIC.EXE)

CIV.EXE draws nothing itself. Every pixel goes through a graphics driver, a separate program loaded at start-up: MGRAPHIC.EXE for MCGA/VGA, EGRAPHIC.EXE for EGA and TGRAPHIC.EXE for Tandy. src/MGRAPHIC.ASM is the recovered MCGA driver. just verify rebuilds it with MASM 5.10 and LINK 5.01 and checks it against the original byte for byte.

Loading and the slot table

load_program (LOADER.INC) loads the driver with DOS EXEC function 4B03h (“load overlay”). run_program (LOADTAIL.INC) then copies the driver’s entry points into CIV’s jump table, driver_slots in DRIVER.INC. CIV calls each slot by its C name, a far call to a far jump that run_program has patched.

The driver begins with a header that run_program reads:

OffsetContents
10hName and date, MGRAPHIC.EXE09-19-91
28hSegment of the slot routines (relocated by DOS)
2AhData segment; the loader appends FONTS.CV after it
2ChBytes of data before the fonts (630h)
2EhFirst slot (0)
30hSlot count (52)
32hOffset of each slot routine

The sound drivers (*.CVL, slots 100–110) and MISC.EXE (slots 90–97) use the same header.

Pages

A page is a 320×200 byte-per-pixel buffer in its own segment. page_segs holds eight entries. All of them start as the screen (A000h); CIV then gives pages 1 and 2 their own memory with alloc_page_memory and set_page_memory. Drawing calls take a page record (struct page in GRAPHICS.H), whose page number and origin the driver adds to the coordinates. Calls that work on a whole page take its number.

SlotNameEffect
0alloc_page_memory(n)n = 0: the screen segment; otherwise a fresh 64,000-byte block (0 on failure)
25set_page_memory(n, seg)Use seg for page n
8page_free(n)Free page n’s block
5page_clear(n, colour)Fill page n
31set_page(n)Draw the mouse cursor on page n, then copy page n to the screen
36page_swap(n)Exchange page n with the screen
10page_dissolve(n, time)Copy page n to the screen in pseudo-random pixel order
20blit(src, sx, sy, w, h, dst, dx, dy)Copy a rectangle between pages, two bytes at a time
3recolour_rect(pg, x, y, w, h, from, to)Replace one colour with another in a rectangle (menu highlights)
13, 19page_read_row, draw_scanlineCopy part of a row out of, or into, a page
14pixel_at(n, x, y)Read one pixel
17page_ready(n)Status word; always 0 on MCGA

The dissolve. page_dissolve first waits for a BIOS timer tick. It sets the diskette motor counter at 0040:0040 to 1 and waits for the BIOS to count it down. It then visits all 64,000 pixels in an order given by a 16-bit Galois LFSR: shift right, then XOR B400h if a bit fell out. Values above 64,000 are skipped, and value v copies pixel v − 1. Between pixels it waits in a busy loop. On first use it measures how many steps fit in one video frame, then sizes the delay so the whole dissolve takes roughly the requested time. A port must use the same LFSR to show the same sequence.

Sprites

Every tile, unit, face and cursor is a sprite: run-length rows in a paragraph-aligned block. CIV refers to a sprite by its segment.

word  width
word  height
then per row:
  word  n      pixels stored for the row
  word  lead   transparent pixels before them
  byte  n pixels (0 is transparent)

The encoder trims transparent pixels at both ends of each row. CIV builds sprites in an arena: the largest free DOS block, taken by sprite_arena_init (slot 18) and shrunk back to what was used by sprite_arena_release (slot 7). cut_tile(page, x, y, w, h) (slot 2) runs sprite_begin, sprite_encode_row and sprite_end (slots 35, 34, 33) over a page rectangle and returns the new sprite’s segment. A sprite may not exceed 65,520 bytes.

SlotNameEffect
32sprite_draw(pg, x, y, sprite)Draw without clipping
6sprite_draw_clipped(pg, x, y, sprite)Draw clipped to the page record; return a far pointer to the rectangle drawn
22sprite_draw_scaled(pg, x, y, w, h, sprite, c)Draw stretched to w×h; a negative size mirrors that axis (it patches the inner loop’s CLD/INC DI into STD/DEC DI)

Mouse cursor

The cursor is a sprite drawn into whichever page is about to be shown. cursor_set(x, y, sprite) (slot 30) records the sprite and position and clips the area underneath to the screen. That area is limited to 512 bytes, so a wide cursor saves fewer rows. cursor_draw (slot 9, page in AX) saves what is underneath and draws the cursor. cursor_erase (slot 15) puts the saved pixels back. MOUSE.INC calls these from the mouse handler.

Palette

The data segment holds 128 default colours in 6-bit RGB. The first 16 are the EGA colours. Writes to the DAC (dac_write) wait for vertical retrace and then send colours until the retrace ends. The number of colours that fit in one retrace is learnt as it goes.

SlotNameEffect
28apply_palette(rec)Load colours rec[4]..rec[5] from an "M0" palette record (RGB from byte 6)
27load_ega_palette(pal)Map 16 EGA colour numbers to default colours; byte 16 picks the border colour
26palette_reset_colour(i, c)DAC entry i = default colour c
23, 24screen_off, screen_onBlank the display through the sequencer’s clocking-mode register
29, 51shake_start(n), shake_stepFor n frames, write a pseudo-random 0–3 (LCG ×5+1) into CRTC register 0Dh (start address low)

Raster primitives

RASTER.INC draws lines and filled shapes through register-call slots. The first two choose the target and the colour:

  • raster_page (48) or raster_target (49) selects the target page and origin.
  • raster_colour (41) sets AL as the colour and AH as the operation: 0 set, 1 AND, 2 OR, 3 XOR. It works by patching the opcode of the store instruction (plot_store, line_store, hline_store, text_store).

The drawing slots:

  • raster_plot (38) plots one point.
  • raster_line (42) draws a Bresenham line.
  • raster_spans (39) fills rows between two edge tables: left x at [BX + 2y], right x at [BX + 400 + 2y].
  • raster_copy_spans (50) copies the same spans from one page to another.
  • raster_flush (40) does nothing on MCGA.

Text

FONTS.CV is appended to the driver at data offset 630h (fonts). It starts with a font count and an offset for each font. Nine bytes before each font’s glyph data hold its metrics: first and last character, the row width as a shift, the fixed width (0 means proportional, using the width table just before the metrics), the height, the character spacing and extra rows. Each glyph row is a big-endian bit row, most significant bit first.

page_string (slot 21) is the C entry. It calls the clipping variant (slots 43–47) that fits, which leave out characters and glyph columns beyond the edges. In strings, a byte of 80h or more changes the colour to its low seven bits. char_width (4) and font_height (12) report metrics. font_address (11) and font_copy (16) find and fill a font’s glyph block. font_address checks the font number against the word at data offset 20h, not 630h. That puzzle is recorded but not explained.

Video mode

video_init(flags) (slot 37) sets BIOS mode 13h; bit 7 of flags keeps video memory. It then reads the mode back. If the mode did not take, it prints “MCGA is not supported on your machine” and exits to DOS.

Audio Subsystem & Driver Dispatch

CIV.EXE contains no sound code. The player picks a sound card in the setup menu (sound_names in SETUP.C), and CIV loads the matching driver as an overlay at start-up, just as it loads the graphics driver:

ChoiceDriver
No sounds pleaseNSOUND.CVL
IBM soundsISOUND.CVL (PC speaker)
Tandy soundsTSOUND.CVL
AdLib/Sound BlasterASOUND.CVL
Roland MT-32 MIDI boardRSOUND.CVL

run_program copies the driver’s entry points into slots 100–110 of driver_slots (DRIVER.INC). CIV then uses five calls:

CallSlotWhen
sound_init()100Start-up (INTRO.C)
sound(n, variant) → sound_command101Every sound and tune; sound in GAME.C checks that sound is on (debug & 10h)
sound_tick()103Timer interrupt, about 60 Hz; may ask for a faster or slower timer
sound_fast_tick()104Timer interrupt at the fast rate
sound_shutdown()102Exit

sound_quiet_ticks() (slot 105) counts timer ticks since the last command, and CIV also uses it as a source of random numbers.

The commands and the driver’s internals are described in The AdLib Sound Driver; src/ASOUND.ASM is its recovered source.

The AdLib Sound Driver (ASOUND.CVL)

Sound, like graphics, lives in a driver loaded at start-up. CIV has NSOUND.CVL (none), ISOUND.CVL (PC speaker), TSOUND.CVL (Tandy), RSOUND.CVL (Roland MT-32) and ASOUND.CVL. src/ASOUND.ASM is the recovered AdLib driver. It also drives a Sound Blaster Pro’s two OPL chips and a Pro AudioSpectrum, and just verify rebuilds it byte for byte. ASOUND.CVL and RSOUND.CVL carry the same identification (RLND Cvlzatn12-03-91): they share the sequencer and differ in output.

The driver has the same header as the graphics driver (see the MCGA driver) and fills slots 100–110.

Slots

SlotCIV nameEffect
100sound_init(arg)Detect the card and silence it (INTRO.C at start-up)
101sound_command(n, variant)Start sound command n; reached through sound() in GAME.C, which plays only while sound is on (debug & 10h)
102sound_shutdown()Silence the card; set the DOS time from the real-time clock (CIV’s fast timer made the DOS clock drift)
103sound_tick()One music step. Called from the timer interrupt at about 60 Hz; returns a timer request: 1 for fast while a pitch sweep runs, −1 for slow again, 0 for no change
104sound_fast_tick()Advance the pitch sweeps (at the fast timer rate)
105sound_quiet_ticks()Ticks since the last command; CIV uses it as a source of random seeds
106–110Return at once

TIMER.INC calls slots 103 and 104 and changes the timer rate when slot 103 asks for it.

Commands

nUse
0Silence everything
1End the music: every channel goes to the rest sequence
2Nonzero while any channel plays
3Title and intro music
4World creation
5–18Civilization anthems (civt[].f4): American, Aztec, Egyptian, Zulu, French, Roman, Russian, Greek, English, Babylonian, Chinese, Mongol, Indian, German. The second argument (0–3) chooses how many parts play; 3 is the full arrangement
19–32Short motifs for the same civilizations, in the same order (civt[].f3)
33Audience interlude: pause the channels, play one of seven phrases in turn, then resume
34Celebration (We Love the King Day, Hall of Fame)
35City grows in rapture (two tunes, alternately)
36Bad news (famine, civil disorder, nuclear incident); also the theme of the two unused tribe slots
37A land unit moves
38, 39Battle won, battle lost: light units
40, 41Battle won, battle lost: heavy units (attack above 4 or defence above 2)
42Nuclear explosion
43A bomber wins its attack
44City-view street sounds (random notes)

Sequencer

Music is a byte code in the data segment, about 29 KB in all. Each command starts a sequence on some of the nine channels, one for each OPL2 voice. The recovered source labels every sequence after the command that plays it, for example roman_anthem_part1.

Every tick, channels_step runs channel_step once for each channel. This part of the driver was written in C and compiled without optimisation; the reload of channel_now before each field access gives it away. A channel record (30 bytes) holds:

  • the duration counter and the note-off timer;
  • the sequence pointer, two loop points and their repeat counts;
  • the volume and instrument.

Bytes F3h–FFh are commands (loops, repeats, instrument, volume); the rest encode notes and durations. Two extra voices can sweep their pitch; they run from the fast timer through slot 104.

The driver keeps a copy of every OPL register it writes (opl_shadow). Pitches come from a table of the twelve semitones’ F-numbers. Volumes go through a 128-step attenuation table. sound_random is the driver’s own generator, x = (x + 9248h) rotated right by 3, starting from 1234. Command 44 uses it for the city sounds.

Hardware

card_detect first runs the AdLib timer test on port 388h. It then looks for a Sound Blaster Pro at 220h by writing and reading back mixer register 26h (FM volume). Last it probes the Pro AudioSpectrum’s registers at B8Ah and B8Bh. On a Sound Blaster Pro, each register write goes to both OPL chips (left and right).

Input and interrupts

The game combines keyboard polling, mouse callbacks and a hardware timer. key_cmd() and menu routines wait for commands; CONTROL.C interprets commands during unit movement. Keyboard helpers include entries loaded from MISC.EXE, whose source is MISC.ASM.

The numeric keypad supplies eight movement directions. Letter commands include B to found a city, F to fortify, S for sentry, R for roads, I for irrigation, M for mining and G for a destination. Context determines which commands are available. The tests and resulting orders are in ai_turn().

Mouse

MOUSE.INC talks to the installed DOS mouse driver through INT 33h. The game uses its own sprite cursor through graphics-driver calls. mouse_on() and mouse_off() in GAME.C keep a nesting count, so paired calls preserve the visibility state across nested drawing operations. Cursor drawing saves the pixels underneath and restores them on erasure. The MCGA driver limits that saved area to 512 bytes.

Timer

TIMER.INC, included by RESIDENT.ASM, installs an INT 08h handler and programs the hardware timer. It uses a private interrupt stack, calibrates against vertical retrace, and chains to the saved handler as accumulated time requires. The sound driver can request a faster rate for pitch sweeps. Normal timer processing services sound and palette animation.

restore_timer() restores the saved vector and timer programming. This is a direct IRQ0 hook, rather than an INT 1Ch callback running only at the standard DOS timer rate.

Break handling

install_break_handlers() in GAME.C saves the INT 1Bh and INT 23h vectors and replaces them with an empty handler. restore_break_handlers() restores them at exit. Disk operations have separate error paths in FILEIO.C and SAVE.C; they should be read separately from break handling.

Map coordinates

The world is 80 tiles wide and 50 tiles high. Coordinates run from 0–79 and 0–49. Horizontal movement wraps; vertical movement does not. wrap_x() in PATH.C adds or subtracts 80 once, so callers must keep coordinates within one map width of the valid range. map_in_bounds() in MAP.C checks both axes.

Distance

map_dist(x1, x2, y1, y2) in CORE.C takes the shortest horizontal difference across the wrap seam. It then calls half_dist():

dx = min(abs(x1 - x2), 80 - abs(x1 - x2))
dy = abs(y1 - y2)
distance = max(dx, dy) + min(dx, dy) / 2

Division truncates. An orthogonal or diagonal neighbour is at distance 1; two diagonal steps give distance 3. This is a scoring measure, not a count of movement points. The separate distance_twice() helper returns 2 * max(dx, dy) + min(dx, dy) without the division.

Neighbours and city radius

spiral_x and spiral_y provide offsets for searches around a tile. Directions 1–8 start north and proceed clockwise. City workers use the separate radius_dx[21] and radius_dy[21] tables. Their footprint is a five-by-five square without its four corners. Tile 20 is the centre.

Display

reveal() in MAP.C draws a 15-by-12 tile viewport, with 16-by-16 pixel tiles beginning at screen position (80, 8). The left panel includes an 80-by-50 overview. Display coordinates and world coordinates are separate; map_x0 and map_y0 place the viewport on the world.

Map storage

The game uses graphics page 2 as a map database. Each map tile has a position in several 80-by-50 areas of the 320-by-200 page. Values are read with help_probe() and written with map_claim().

Page coordinates for tile (x, y)Contents
(x, y)Encoded terrain, translated through terrain_map[16]
(x, y + 50)Ranked land or sea region
(x + 80, y)Site score or territory marker
(x + 80, y + 50)Saved site score
(x + 160, y)Civilization in low three bits; bit 3 means units present
(x + 240, y)Overview-map colour
(x, y + 100)Low nibble of current tile flags
(x, y + 150)High nibble of current tile flags
(x + 80, y + 100)Low nibble of remembered flags
(x + 80, y + 150)High nibble of remembered flags

terrain_at() translates the encoded terrain rather than returning the page byte directly. map_owner() returns −1 if the unit-present bit is clear; map_city() reads the low three bits regardless. map_stack() then searches the owner’s unit records for a unit at that location.

Tile flags

map_flags() combines the nibbles at y+100 and y+150. These describe improvements and tile state:

MaskMeaning
0x01City
0x02Irrigation
0x04Mine
0x08Road
0x10Railroad
0x20Fortress
0x40Pollution

flag_tile() sets flags; passing zero clears them all. unflag() clears selected bits. known_flags() reads the remembered copy, and map_mark() refreshes that copy from the current flags.

Exploration

Exploration uses the separate far array map_visible_to[4000], indexed as x * 50 + y. Bit n marks exploration by civilization n. It is separate from tile flags and the unit-present bit. The Barbarian bit also records visited huts. Unit visibility has another mask in each unit record.

Terrain tables

GAME.C defines twelve base terrains and twelve corresponding special resource records. Each record is 19 bytes. The special record supplies a replacement yield, rather than a bonus to add to the base terrain.

struct terrain_type {
    char name[12];
    unsigned char move_cost;
    signed char defense;        /* defense multiplier, in halves */
    unsigned char food, shields, trade, f5, f6;
};
struct terrain_rec terrains[24] = {
    { "Desert", 1, 2, 0, 1, 0, 1, 14 },
    { "Plains", 1, 2, 1, 1, 0, 1, 6 },
    { "Grassland", 1, 2, 2, 1, 0, 1, 10 },
    { "Forest", 2, 3, 1, 2, 0, 2, 2 },
    { "Hills", 2, 4, 1, 0, 0, 2, 12 },
    { "Mountains", 3, 6, 0, 1, 0, 3, 13 },
    { "Tundra", 1, 2, 1, 0, 0, 0, 7 },
    { "Arctic", 2, 2, 0, 0, 0, 0, 15 },
    { "Swamp", 2, 3, 1, 0, 0, 0, 3 },
    { "Jungle", 2, 3, 1, 0, 0, 0, 11 },
    { "Ocean", 1, 2, 1, 0, 2, 0, 1 },
    { "River", 1, 3, 2, 1, 1, 2, 9 },
    { "Oasis", 1, 2, 3, 1, 0, 1, 14 },
    { "Horses", 1, 2, 1, 3, 0, 1, 6 },
    { "Grassland", 1, 2, 2, 1, 0, 1, 10 },
    { "Game", 2, 3, 3, 2, 0, 2, 2 },
    { "Coal", 2, 4, 1, 2, 0, 2, 12 },
    { "Gold", 3, 6, 0, 1, 6, 3, 13 },
    { "Game", 1, 2, 3, 0, 0, 0, 7 },
    { "Seals", 2, 2, 2, 0, 0, 0, 15 },
    { "Oil", 2, 3, 1, 4, 0, 0, 3 },
    { "Gems", 2, 3, 1, 0, 4, 0, 11 },
    { "Fish", 1, 2, 3, 0, 2, 0, 1 },
    { "River", 1, 3, 2, 1, 1, 2, 9 },
};

The fields after the name are movement cost, defence in halves, food, shields, trade, and two further bytes. A defence value of 2 means the normal factor; 3 means one and a half, 4 means double and 6 means triple. Ships and aircraft have separate combat rules; see Combat.

Special resources

patterned_cell() in MAP.C derives resource positions from coordinates and map_rng_state. It selects one position per four-by-four block, except on the two rows nearest each pole. The terrain at that position determines which special record is used: Desert gives Oasis, Hills Coal, Mountains Gold, Ocean Fish, and so on.

Grassland and River shields also depend on (7 * x + 11 * y) & 2 in tile_yield(). The base table alone therefore does not describe every tile.

Tile yields and improvements

tile_yield(x, y, kind) in CITYMGT.C returns food (kind 0), shields (1) or trade (2). It first selects the base or special-resource record. It then applies these rules in order:

  1. Irrigation and mining alter land yields using work_rules. A road adds one trade on Desert, Plains and Grassland.
  2. The coordinate pattern can remove shields from Grassland and River.
  3. The Colossus adds one to non-zero trade in its own city.
  4. Railroad adds half the current yield, rounded down, to each category.
  5. Anarchy and Despotism lose one from yields above two, unless the city is celebrating.
  6. Non-zero trade gains one under Republic and Democracy. Celebrating cities receive this bonus from Monarchy upwards.
  7. Pollution halves the result, rounded up. Negative results become zero.

The order matters: a railroad bonus is applied before the Despotism penalty and the final pollution reduction. Pollution reduces yields; it does not necessarily remove them.

Settler work

CONTROL.C handles road, railroad, irrigation, mining, fortress and pollution-cleaning commands. work_rules supplies terrain-specific work and transformation values. Some orders change the terrain type instead of simply setting an improvement bit. Work progress shares the unit’s fuel byte with aircraft range tracking.

Road and railroad movement rules are described in Movement. Rivers do not give road movement costs. A fortress doubles ordinary terrain defence; it does not add to a unit’s fortification bonus.

World generation

make_world() in WORLDGEN.C is overlay 7. It prepares terrain, regions, connections and potential city sites while advancing the creation pictures and text through calls to story_step().

custom_params[0..3] hold land mass, temperature, climate and age. If beginner_help selects Earth, the routine loads map.pic instead of generating terrain, then runs the common region and connection stages.

Land and climate

grow_land() and walk_land() build land through bounded random walks. The initial growth stage stops at 640 + 320 * custom_params[0] land cells. Border cleanup and later terrain changes follow, so this threshold is not a promise about the final land count.

A pass removes diagonal checkerboard contacts. Land is then assigned terrain using abs(29 - (rand_scaled(8) + y)) + 1 - custom_params[1]. Dividing this value by six selects Desert, Plains, Tundra or Arctic.

Rainfall passes scan each row in both directions. Ocean cells build a rain counter; land consumes it and can change terrain. Mountains consume extra rain. The climate parameter affects both the amount collected and random depletion. Age controls a further 800 + 800 * custom_params[3] terrain-change iterations.

Rivers

make_rivers() saves a working area before attempting a river. It traces a route and tests the endpoint and length. Accepted routes remain; rejected ones restore the saved area. The code uses terrain tests and random choices, rather than an elevation field with a general downhill-flow calculation.

Regions

number_regions() labels connected land and sea separately, merging labels as the scan finds connections. It then ranks regions by size. The fourteen largest receive IDs 1–14, with 1 the largest; remaining regions share ID 15. continent_span[0] holds land sizes and [1] sea sizes. Entry 15 is set to 1, rather than a combined area for all remaining regions.

link_land() and link_seas() build connections on a coarser 20-by-13 grid for pathfinding.

City sites

score_sites() scores Plains, Grassland and River locations. It combines food, shields and trade across the city radius, with extra weight for nearby tiles and adjustments for resources and potential improvements. The result is reduced to a small value stored at x+80 in the working page. ai_zone_places counts candidate sites by region.

This supplies later settlement decisions. It does not directly choose and found eight starting cities.

Exploration and visibility

Exploration and visible units are tracked separately. map_visible_to remembers explored terrain for each civilization; each unit’s visibility byte records which civilizations can see it. Cities retain a visible_size for the human’s map display. Remembered improvement flags live on page 2.

reveal() in MAP.C fills unexplored tiles with black. Explored tiles are drawn through map_note(), which checks unit visibility before drawing foreign stacks. Debug mode xray_on bypasses these display checks.

Unit sight

unit_sight() in UNITS.C examines a unit’s neighbours, updates exploration and unit visibility, and handles contact. Ordinary sight covers the adjacent eight tiles. Unit types with sight bit 2 also examine the outer ring of a five-by-five square. For ships, these extra tiles must be Ocean; Submarines have additional visibility exceptions.

The same routine updates remembered city sizes and improvement flags for the human, cancels some destinations when units encounter one another, and adds AI targets. Sight is therefore part of movement and diplomacy as well as drawing.

Map sharing

Audience map exchanges combine exploration masks. Other events can reveal cities or terrain; their effects must be read at the relevant call site. The Great Library’s technology-sharing effect does not imply a general map-revelation effect.

Unit Records (struct unit)

Every unit in the world is a 12-byte record in struct unit armydata[8][128] (GLOBALS.C, DGROUP 81D4). The first index is the civilization slot (0 for the Barbarians), the second the unit number. A record whose type is −1 is free.

struct unit {
    char status;                /* 0x08 fortified, 0x20 veteran */
    unsigned char x, y;
    signed char type;
    signed char moves;          /* remaining moves, in thirds */
    char fuel;
    char goto_x;
    char goto_y;
    char f8;
    signed char visibility;     /* one bit per civilization */
    signed char next;           /* next unit in the same stack, or -1 */
    char home_city;
};
OffsetFieldUse
0statusOrder and condition bits (below)
1–2x, yMap position
3typeIndex into utypes, or −1
4movesMovement left this turn, in thirds
5fuelTurns of fuel for aircraft; work progress for Settlers
6–7goto_x, goto_yDestination; goto_x is −1 when there is none
8f8Last direction moved (1–8), or −1
9visibilityOne bit per civilization that can see the unit
10nextNext unit in the same stack, or −1
11home_citySupporting city, or −1

Status bits

BitMeaning
0x01Sentry. Also set on units carried by a ship.
0x02Settlers building a road or railroad; aircraft returning to base
0x04Fortifying. ai_turn() turns it into 0x08 at the start of the next turn.
0x08Fortified (UNIT_FORTIFIED)
0x10Busy: the AI has assigned the unit for this turn
0x20Veteran (UNIT_VETERAN)
0x40Settlers irrigating
0x80Settlers mining

Settler work combines bits: 0x40 | 0x80 builds a fortress and 0x80 | 0x02 cleans pollution. While work continues, fuel counts the turns spent.

Creating units

create_unit(owner, type, x, y) in UNITS.C takes the first free record among numbers 0–126. Number 127 is never allocated; the pathfinder uses armydata[0][127] as a probe (see Path Finding). When the human’s records are full, the game shows the *UMAX message once.

The new unit:

  • is placed on its tile’s stack and made visible to its owner;
  • has no orders, destination or last direction;
  • receives fuel one less than its type’s fuel;
  • is homed to the nearest city if the owner holds it, otherwise to none;
  • increments forces_present[owner][type].

It then calls unit_sight() (see Visibility). The human’s first two units each trigger an introductory notice.

drop_u_stack(civ, unit) removes a unit. Despite its name it removes one unit. A ship at sea that is removed takes its land cargo with it, and a Carrier at sea takes its aircraft. forces_present is decremented, and, during a battle, the loss is counted in unit_losses for the reports.

Movement allowance

At the start of a civilization’s movement phase, ai_turn() sets every unit’s moves to three times its type’s moves. Ships get three more thirds while their owner has the Lighthouse or Magellan’s Expedition. See Movement.

Stacks and City Garrisons

Units of one civilization on the same tile form a stack. Only one civilization can occupy a tile.

Finding the stack on a tile

Page 2 of the map storage holds an occupancy byte for every tile at (x + 160, y). Bit 3 means units are present and bits 0–2 give their owner. map_owner(x, y) returns the owner or −1. map_stack(x, y) then scans that civilization’s 128 records for the first unit at (x, y), records the owner in the_enemy, and returns the unit number. If none is found, it clears the stale occupancy byte.

put_unit() and lift_unit() in MAP.C keep the occupancy byte and the stack links up to date as units move.

A stack is a circular list. A lone unit has next = −1. stack_add() in BATTLE.C makes two units point at each other, and inserts further units after the head. stack_remove() finds the predecessor, bypasses the removed unit, and sets a unit left alone back to −1.

each_stack(civ, unit, callback) in UNITS.C calls a function for each unit in the ring, stopping when it returns to the starting unit. It stops after ten units, so every stack operation built on it (counts, strengths, and the destruction of a defeated stack) sees at most ten units. The helpers built on it are:

FunctionResult
stack_len(civ, unit, mode)Sum of cost (0), defence (1), count (2) or attack (3); mode 4 counts defenders before unit
count_u(civ, unit, type)Units of one type
stack_strength(civ, unit, role)Units of one role
stack_cap(civ, unit)Free transport capacity: ship capacity minus land units

Choosing the defender

When a stack is attacked, stack_other() picks the defender with the highest score from score_group_record():

land unit: (fortified ? 3 : 2) * defence * terrain defence * 8
other:     defence * 16
veteran:   + score / 2

Land units at sea are skipped. City Walls and fortresses do not enter the choice, although they affect the battle.

Packing garrisons into cities

Unit records are limited, so the human’s cities store part of their garrison in the city record. When a unit finishes fortifying in its home city, ai_turn() calls garrison_in() for that city. It keeps the first fortified unit homed to the city on the map and packs up to two more into townbuf[city].garrison[]. Each byte holds the unit type in bits 0–5 and the veteran flag in bit 6, and the packed units are removed from armydata. garrison_in() clears status bit 0x04 of the city, and unpacking sets it again.

When another civilization’s unit comes next to the city, unit_sight() calls unpack_garrison(). It recreates the stored units as fortified units (veterans keep their status) and clears the bytes. Both functions return immediately for cities not owned by the human. Even so, the AI’s strength tables count any garrison bytes they find.

Unit Types (struct unit_type)

The 28 unit types are 34-byte records in utypes[28] (GAME.C, DGROUP 112A).

struct unit_type {
    char name[12];
    int obsolete_by;
    int domain;                 /* 0 land, 1 air, 2 sea */
    int moves;
    int fuel;
    int attack;
    int defense;
    int cost;
    int sight;
    int capacity;
    int role;
    int prereq;
};
FieldMeaning
obsolete_byAdvance that makes the type obsolete; 127 for none
domain0 land, 1 air, 2 sea
movesMoves per turn; ai_turn() multiplies by 3
fuelTurns an aircraft may stay airborne (Fighter 1, Bomber 2, Nuclear 1)
attack, defenseCombat factors
costProduction cost in tens of shields
sightBit 1 (value 2) widens sight from the 3 × 3 to the 5 × 5 square
capacityLand units a ship carries
roleCategory used by the AI (roles)
prereqAdvance needed to build the type; −1 for none

GAME.C declares its own copy of the structure with fields w2–w10 in the same positions. The table, in ID order:

struct unit_type utypes[28] = {
    { "Settlers", 127, 0, 1, 0, 0, 1, 4, 0, 0, 0, -1 },
    { "Militia", 34, 0, 1, 0, 1, 1, 1, 0, 0, 2, -1 },
    { "Phalanx", 34, 0, 1, 0, 1, 2, 2, 0, 0, 2, 17 },
    { "Legion", 64, 0, 1, 0, 3, 1, 2, 0, 0, 1, 18 },
    { "Musketeers", 64, 0, 1, 0, 2, 3, 3, 0, 0, 2, 34 },
    { "Riflemen", 127, 0, 1, 0, 3, 5, 3, 0, 0, 2, 64 },
    { "Cavalry", 64, 0, 2, 0, 2, 1, 2, 0, 0, 1, 31 },
    { "Knights", 58, 0, 2, 0, 4, 2, 4, 0, 0, 1, 62 },
    { "Catapult", 48, 0, 1, 0, 6, 1, 4, 0, 0, 1, 9 },
    { "Cannon", 63, 0, 1, 0, 8, 1, 4, 0, 0, 1, 48 },
    { "Chariot", 62, 0, 2, 0, 4, 1, 4, 0, 0, 1, 33 },
    { "Armor", 127, 0, 3, 0, 10, 5, 8, 0, 0, 1, 58 },
    { "Mech. Inf.", 127, 0, 3, 0, 6, 6, 5, 0, 0, 2, 65 },
    { "Artillery", 127, 0, 2, 0, 12, 2, 6, 0, 0, 1, 63 },
    { "Fighter", 127, 1, 10, 1, 4, 2, 6, 2, 0, 4, 38 },
    { "Bomber", 127, 1, 8, 2, 12, 1, 12, 2, 0, 1, 39 },
    { "Trireme", 8, 2, 3, 0, 1, 0, 4, 0, 2, 5, 7 },
    { "Sail", 14, 2, 3, 0, 1, 1, 4, 0, 3, 5, 8 },
    { "Frigate", 35, 2, 3, 0, 2, 2, 4, 0, 4, 5, 14 },
    { "Ironclad", 37, 2, 4, 0, 4, 4, 6, 0, 0, 3, 23 },
    { "Cruiser", 127, 2, 6, 0, 6, 6, 8, 3, 0, 3, 37 },
    { "Battleship", 127, 2, 4, 0, 18, 12, 16, 3, 0, 3, 52 },
    { "Submarine", 127, 2, 3, 0, 8, 2, 5, 3, 0, 3, 41 },
    { "Carrier", 127, 2, 5, 0, 1, 12, 16, 3, 0, 3, 39 },
    { "Transport", 127, 2, 4, 0, 0, 3, 5, 0, 8, 5, 35 },
    { "Nuclear", 127, 1, 16, 1, 99, 0, 16, 0, 0, 1, 46 },
    { "Diplomat", 127, 0, 2, 0, 0, 0, 3, 0, 0, 6, 22 },
    { "Caravan", 127, 0, 1, 0, 0, 1, 5, 0, 0, 6, 24 },
};

Building a type

find_slot() in ECONOMY.C offers a unit type when the civilization knows its prerequisite and does not know its obsolescence advance. Ships need a coastal city (status bit 0x02). Nuclear units also need the Manhattan Project to have been built by someone and the owner to know Nuclear Fission.

The AI disbands obsolete units over time (see Unit AI). The game does not upgrade them.

Special abilities

The table gives few abilities directly; most are tests on type numbers in the code:

TypeBehaviour
Settlers (0)Only type that builds cities, joins them and changes terrain
Fighter (14)Only type that may attack aircraft outside a city
Bomber (15)Its movement ends after an attack
Trireme (16)A human Trireme that ends its move with no land next to it is lost half the time (*TRIREME)
Submarine (22)Cannot attack land. Not seen beyond adjacent tiles, and does not see beyond them itself
Carrier (23)Carries any number of aircraft (its capacity is 0), and refuels them
Nuclear (25)Entering a foreign city detonates it (atom())
Diplomat (26)Enters foreign cities for the Diplomat menu and bribes units by moving onto them. Ignores zones of control. The Barbarians’ leader is a Diplomat.
Caravan (27)Establishes trade routes or helps build a wonder. Ignores zones of control.

Attack factor 12 (Artillery and Bomber) ignores City Walls; see Combat.

Capacities are Trireme 2, Sail 3, Frigate 4 and Transport 8. Fighters, Bombers and the Cruiser, Battleship, Submarine and Carrier have sight bit 1; aboard ships, the wider sight covers only ocean.

Movement

ai_turn(civ) in CONTROL.C moves a civilization’s units. It is used for every civilization. For the human it reads commands from the keyboard, mouse or a unit’s standing orders. For the computer it takes them from ai_order(). Directions 1–8 index the first ring of spiral_x and spiral_y, starting north and turning clockwise.

Movement points

Movement is counted in thirds. At the start of the phase every unit gets 3 * utypes[type].moves thirds, plus three for ships while the owner has the Lighthouse or Magellan’s Expedition. A unit that was fortifying becomes fortified and does not move that turn. Sentried and fortified units are skipped.

A land unit’s step costs:

StepCost in thirds
Road to road1
Road to road, from a railroad, moved by hand by the human0
Otherwise3 * terrains[destination].move_cost

Air and sea units always pay 3 per step.

The railroad rule is narrow. Only the human’s manual moves are free, and only the starting tile needs a railroad (both need a road). A unit on a go-to order, and any computer unit, pays one third. A river is ordinary terrain with a move cost of 1; it gives no road-like bonus.

Moving with too few points

When a land unit with anything other than exactly three thirds left enters terrain that costs more than it has, rand_scaled(3 * move_cost) > moves decides whether it fails. On failure it loses its remaining points and stays put. A unit with exactly one full move (three thirds) always succeeds; so does a unit whose remaining points cover the cost. After a successful step the cost is subtracted and clamped at 0.

Zones of control

A land unit (other than a Diplomat or Caravan) cannot move from a tile next to a foreign unit to another tile next to a foreign unit, unless the destination holds units. if_open() treats a city tile as never blocked, so moving into or out of a city is always allowed. Units at sea are exempt. The human hears a sound; with debug bit 0 set (as at Chieftain) the *ZOC help text also appears.

unit_blocked() performs the test. When the human is the one tested, it also reveals the foreign units and cities that are doing the blocking.

Entering a tile

Moving onto a tile occupied by another civilization’s units is an attack; see Combat. Other rules, in the order the code applies them:

  • Diplomats entering a foreign city open the Diplomat menu, and moving onto foreign units tries to bribe them (buy_u()). Neither Diplomats nor Caravans may enter a foreign city from the sea.
  • Caravans entering a foreign city establish a trade route. Entering one of their own cities, they offer “Keep moving”, “Establish trade route” and, when the city is building a wonder, “Help build WONDER”, which adds the Caravan’s cost to the city’s shields. At a map_dist() below nine from home the trade route is dimmed, and a city on the home continent, at a distance below nine and not building a wonder, is simply entered.
  • Boarding. A land unit can enter an ocean tile only if its own ships there have free capacity (stack_cap()). It is sentried, and boarding uses up its movement.
  • Ships cannot enter land other than a city.
  • Foreign cities can be entered by land units only, which captures them (capunit()). A Nuclear unit entering one detonates it.
  • Leaving a ship onto land ends the unit’s movement.
  • AI stacks. A computer unit outside a city will not join a stack of four or more of its own units (two or more when it has no movement left).
  • Turning back. A computer unit that would step straight back the way it came, in a stack of at most two, stops and drops its destination.

Territory

When a city is founded, city_create() writes the owner’s number into the site layer of page 2 at (x + 80, y) for its 21 working tiles. It also lowers the site values of the next ring (to 8). A land unit stepping onto an improved tile (with a road, irrigation or mine) owned by another of the civilizations 1–7 can cause a diplomatic incident:

  • The human is asked to confirm breaking the treaty (attack_ok()), and confirming cancels peace.
  • A computer unit at peace with the human will not enter the human’s territory.

The test applies only between the human and others. Units on improved tiles of foreign territory become visible to its owner, and any other civilization’s step clears the ownership mark.

Carrying units

A ship with capacity, or a Carrier, moves its cargo with it. Land units travel if they are sentried, if the ship starts at sea, or (for computer players) if they have no fortify orders. A Carrier moves aircraft instead. At most capacity units go with a ship, and Barbarian ships take everything. Carried units are sentried and aircraft are refuelled.

Huts

has_hut() derives huts from the map seed: at most one per 4 × 4 block, at a position chosen by map_rng_state, about half the blocks, not on ocean, cities or the polar rows. Visiting a hut sets bit 0 (the Barbarians’ bit) in map_visible_to for the tile, which removes it.

enter_hut() in UNITS.C ignores Barbarians and aircraft and draws rand_scaled(4):

RollOutcome
0At town_gap <= 3 from a city: mercenaries. Otherwise, with a site value of 13 or more, “an advanced tribe” founds a city for the visitor; with a lower one, 50 gold.
1“Scrolls of ancient wisdom”: a random advance whose prerequisites are known. On the first turn or after 1000 AD, 50 gold instead.
2“Valuable metal deposits”: 50 gold.
3At town_gap <= 3 from a city, or with no cities: mercenaries. Otherwise a horde of barbarians appears around the hut: Legions on mountains, Cavalry elsewhere, up to eight units for a civilization with three or more cities.

Mercenaries are a Legion or Cavalry unit for the visitor. Barbarian units from a hut are visible to the visitor.

Coarse and Local Pathfinding (PATH.C)

src/PATH.C contains six functions: unit_goal, path_coarse, path_select, path_search, path_probe, and wrap_x. Its code combines coarse connectivity, a cached local cost search, and directional fallback scoring.

path_coarse traverses 20×13 land or sea connectivity cells using a circular queue. It selects an intermediate target near a 4×4 cell center. path_select tries nearby connected cells when the initial coarse cell is unavailable.

path_search clears a 256-byte local cost grid and propagates costs outward from the target using the shared queue. It restricts coordinates to a window within eight tiles of the target, relaxes cheaper arrivals, and limits processing with tail6 < 225. The next direction is selected from neighboring cells using stored costs, with stack and distance tie breakers. Queue indices wrap through unsigned-byte conversion. This is not an unbounded optimal-path guarantee.

unit_goal tries local and coarse searches before scoring adjacent directions. The fallback includes distance, movement cost and a penalty for changing direction. path_probe reuses the local search with temporary probe state. wrap_x adds or subtracts 80 once, preserving the original behavior rather than providing arbitrary modulo normalization.

The source declarations list shared entry points. Shared scratch storage remains external to this translation unit.

Aircraft and Fuel

Fighters, Bombers and Nuclear units have domain 1. They pay three thirds per step on any terrain, ignore zones of control, and cannot enter a foreign city (*OCCUPY), except a Nuclear unit, which explodes there.

Fuel

utypes[type].fuel is the number of turns an aircraft may end away from a base: 1 for the Fighter and Nuclear unit, 2 for the Bomber. The unit’s own fuel byte counts down:

  • create_unit() sets it to fuel - 1, and so does being carried by a moving Carrier.
  • When the aircraft has used all its movement, ai_turn() refills it to fuel if the aircraft is in a city or with a Carrier, then subtracts one. A result below 0 destroys the aircraft (*FUEL).
  • Ending a move in a city, or on a Carrier, stops the aircraft for the turn; on a Carrier it is also refuelled.

A Fighter therefore has to finish every turn at a base. A Bomber may spend one turn in the air between bases, and the AI sends it home ('h') when its fuel is spent. A Bomber’s movement ends after it attacks.

Returning to base

The human’s 'h' order on an aircraft away from a city calls near_base() in UNITS.C. It finds the nearest of the owner’s cities and Carriers by the larger of the two coordinate distances, then picks the neighbouring step that most reduces distance_twice() to it, avoiding foreign units and cities. While the order lasts the unit carries status 0x02. If no step helps, a computer aircraft is disbanded.

Carriers

A Carrier (type 23) has capacity 0 in utypes, but the movement code treats it specially: when it moves, aircraft in its stack move with it, with no limit except the ten-unit cap of stack walks. Aircraft carried this way are refuelled to fuel - 1. A Carrier destroyed at sea takes its aircraft with it.

Air combat

Only a Fighter may attack an aircraft outside a city. Defending aircraft and ships use defense * 8, without terrain or fortification. A city’s walls do not help an aircraft. See Combat.

Combat (BATTLE.C)

A unit attacks by moving onto a tile held by another civilization. ai_turn() checks that the attack is allowed, chooses the defender with stack_other() (see Stacks) and calls battle(attacker_civ, attacker, defender_civ, defender, 1). One random comparison decides the outcome, and the loser is destroyed.

Who may attack

ai_turn() refuses:

  • a land-unit attack from the sea (*AMPHIB);
  • a Submarine attacking land;
  • a computer unit with no attack factor, and any Diplomat;
  • an attack on aircraft outside a city by anything except a Fighter (*FIGHTER).

A human unit with less than a full move asks “Attack at n/3 strength?”; a computer unit needs at least two thirds. If the human is at peace with the defender, attack_ok() asks whether to break the treaty, and under a Republic or Democracy the Senate cancels the attack.

Strengths

battle() computes an attack value a and a defence value d. The terrain factor t is terrains[type].defense for the defender’s tile, in halves: 2 for open terrain, 3 for Forest, Swamp, Jungle and River, 4 for Hills and 6 for Mountains.

a = attack * 8

d = t * defense * 8     on a fortress tile (map flag 0x20)
d = t * defense * 6     fortified (12 / 2)
d = t * defense * 4     otherwise
d = defense * 8         defender is an aircraft or ship (replaces the above)
d = t * defense * 12    in a city with City Walls, unless the attacker's
                        attack factor is 12 or the defender is an aircraft

Then, in order:

  1. Barbarian attackers. Against the human, a = a * (difficulty + 1) / 4; against a computer player, a /= 2. Against a city, a becomes 0 if it is the defender’s only city and is halved if it holds the Palace.
  2. Veterans. Each side’s veteran status adds half.
  3. Partial moves. An attacker with fewer than three thirds left has a = moves * a / 3.

With flag = 0, battle() stops here and returns a * 8 / (d + 1). The AI uses this to evaluate attacks.

  1. Difficulty. A non-barbarian attack on the human at Chieftain or Warlord is halved. At Chieftain, the human’s attacks are doubled.

The walls test compares attack factors, so it exempts both Artillery and Bombers. A fortified unit in a fortress gets the fortress factor only, and City Walls replace fortification rather than adding to it. Ships in a walled city do receive the walls factor.

Resolution

attacker_wins = rand_scaled(a) > rand_scaled(d);

A barbarian attack on a city faces a second roll after winning the first. A second roll, rand_scaled(a) < rand_scaled(d), can still give the city the victory. A tie on this second comparison leaves the attacker winning.

If the human is involved or can see either unit, the attack is animated with sounds chosen by the winner’s strength. The battle then sets the fighting bit (0x020) in treaties in both directions, reports a sneak attack if one applies, and cancels any peace between the two.

A Nuclear unit does not fight: battle() calls atom() and returns. See Nuclear Weapons.

Outcome

When the attacker wins:

  • A captured barbarian leader (a Diplomat) pays 100 gold (*LEADER).
  • Outside cities and fortresses, the whole defending stack is destroyed (“n units destroyed.”). Because each_stack() visits at most ten units, at most ten are lost.
  • In a city or fortress, only the defender dies. When the last defender of a city falls, nearby computer units have their orders cleared. If the city is the human’s, nearby AI transports are sent towards it (city_captured()).
  • The attacker becomes a veteran half the time.

When the defender wins, the attacker is destroyed and the defender becomes a veteran half the time. killed[winner][loser] counts losses for the reports in both cases.

Back in ai_turn(), a surviving attacker loses one full move (a Bomber loses all its movement) and never advances into the tile. An attack on a city that the attacker survives reduces its size by one, and marks a computer player’s city as attacked (status 0x10). The size is not reduced if the city has City Walls, if the attack came from the sea, or at Chieftain when the city is the human’s. A city reduced to size 0 is destroyed, and its owner is removed if it has nothing left.

Example

A veteran Phalanx (defence 2) fortified on Mountains has d = 6 * 2 * 6 * 1.5 = 108. A Battleship (attack 18) with a full move attacks with a = 144, and wins if rand_scaled(144) > rand_scaled(108), about 62% of the time. With one third of a move left, a = 48 and its chance falls to about 22%.

Nuclear Weapons

A Nuclear unit (type 25) can be built once anyone has completed the Manhattan Project and its owner knows Nuclear Fission. It is an aircraft with a fuel of one turn and 16 moves. It explodes when it enters a foreign city (the move handler removes it and calls atom(owner, x, y) in BATTLE.C) or attacks a unit (battle() calls atom() on the defender’s tile).

The blast

atom():

  1. reports “… use nuclear weapons!” unless the human launched it;
  2. plays the explosion (nuke_animation() in DIPLOMAT.C);
  3. visits the target tile and its eight neighbours. On each tile with units, all of them (up to ten, see Stacks) are destroyed and counted as losses. This includes the attacker’s own units, so a Nuclear unit attacking from a neighbouring tile destroys itself. A city tile with SDI Defense reports “SDI protects …” and keeps its units;
  4. sets 0x088 in treaties[victim][attacker] for each civilization that loses units: a grievance and the nuclear flag. The flag allows later AI nuclear strikes against the attacker;
  5. calls nuke_fallout().

nuke_fallout() visits the same nine tiles. Every city loses half its size, rounded down (size -= size / 2). Each land tile without a city is polluted two times in three. SDI Defense does not prevent either effect.

Pollution and global warming

add_pollution() in CITYMGT.C sets map flag 0x40 and increments the global count polluted. Each polluted tile costs ten points of score, and a polluted tile’s yields are halved, rounded up (Tile Yields). Settlers clean a tile in four turns.

Each turn ai_event() in TURN.C moves warm_levels one step towards polluted * 2 - warming * 4. When it reaches 12 with more than six polluted tiles, a news item reports “Scientists alarmed by rising temperatures. Global Warming feared!” Above 16, global_warming(warming++) in WORLDGEN.C changes the climate and the level returns to 0. Each previous warming lowers the target by four, so the next one needs two more polluted tiles.

global_warming(severity) considers every Desert, Plains, Grassland and Forest tile:

  • A tile with at least 7 - severity ocean neighbours floods. Forest becomes Jungle and the others Swamp, and irrigation and mines are lost.
  • Otherwise, one tile in eight (chosen by (x * 11 + y * 13) & 7 == severity) dries. Desert and Plains become Desert, Grassland and Forest become Plains.

The message reads “Global temperature rises! Icecaps melt. Severe Drought.”

City records

The world has room for 128 cities, shared by all eight civilization slots. townbuf[128] starts at DGROUP offset 0x70ec. Each record is 28 bytes, so the array occupies 3,584 bytes. A status of −1 marks a free record.

struct city {
    long buildings;             /* bit (n-1) set: improvement n is built */
    unsigned char x, y;
    char status;
    signed char size;
    char visible_size;
    char production;
    char trade;
    signed char owner;
    int food_box;               /* accumulated food */
    int shield_box;             /* accumulated production */
    long workers;               /* worked tiles 0..20, specialist count 26..31 */
    int specialists;            /* eight two-bit specialist types */
    char name_id;               /* unsigned index into place_name */
    char routes[3];             /* destination city indices, -1 if empty */
    char garrison[2];           /* unit type plus veteran bit 0x40, -1 if empty */
};

o20_city() in CITY.C finds a free record when a Settler founds a city. city_create() sets up its position, owner, name, workers and reserves. vanish() in ECONOMY.C removes a city, clears its map flags and removes references to it from trade routes and supported units.

Record layout

OffsetFieldUse
0x00buildings32-bit mask; improvement n uses bit n−1
0x04–05x, yMap position
0x06statusState flags; −1 means unused
0x07sizePopulation size
0x08visible_sizeLast reported size for the map display
0x09productionUnit ID, or negative improvement ID
0x0atradeTrade used in route calculations
0x0bownerCivilization slot
0x0cfood_boxStored food, a 16-bit integer
0x0eshield_boxStored production, a 16-bit integer
0x10workersWorked tiles in bits 0–20; specialist count in bits 26–31
0x14specialistsEight two-bit specialist types
0x16name_idUnsigned index into place_name[256][13], accessed with a cast
0x17–19routesUp to three destination cities; −1 means empty
0x1a–1bgarrisonTwo stored unit types, with veteran bit 0x40; −1 means empty

Workers and specialists

radius_dx and radius_dy give the 21 tile offsets around a city. The centre is tile 20 and is worked free of charge. Other worked tiles use citizens. city_process() reserves neighbouring cities’ worked tiles and centres before assigning workers, so two cities cannot work the same tile.

The specialist count and specialist types are stored separately. set_specialist() and get_specialist() use 0 for an unused slot, 1 for a taxman, 2 for a scientist and 3 for an entertainer. Indices of eight or more read back as taxmen. Each specialist adds two points to taxes, science or luxuries, respectively, before building multipliers.

Stored garrisons

garrison_in() in UNITS.C can store two fortified units in the city record, keeping the first qualifying unit in the ordinary unit array. The stored bytes hold types and veteran flags. unpack_garrison() recreates those units with their fortified and veteran status.

Production and growth

civ_economy() in ECONOMY.C calls city_process(city, 0L) for each city. CITYMGT.C contains both this turn processing and the interactive city screen. The mode argument chooses which work to perform.

Food and growth

The turn pass first resolves the food box left by the previous turn. If it is negative, a supported Settler is removed if one can be found. Otherwise the city loses one population point. A city reduced to zero is removed. The food box is then set to zero.

The growth threshold is (size + 1) * cost_scale. For the human, cost_scale is 10. For other owners it starts at 16 - 2 * difficulty, with a further reduction of two from AD 1 when the human leads the power ranking and difficulty exceeds Warlord. The same scale applies to production.

At the threshold the city grows. Its food box becomes zero without a Granary, or new_size * 5 + 5 with one. If growth would take it above size 10 without an Aqueduct, and rule bit 4 is enabled, the size increase is cancelled after resetting the food box.

Later in the pass, tile food is added and consumption subtracted: two per citizen, plus one per supported Settler under Anarchy or Despotism, or two under later governments. That balance is resolved on the next city pass.

Shields and completion

Worked tiles supply shields. Factory and power bonuses are applied, then unit support is subtracted. Existing disorder sets the shield contribution to zero. The remainder goes into shield_box.

A non-negative production selects a unit in utypes[28]; a negative value selects improvements[-production]. Completion requires the table cost times cost_scale.

Unit completion subtracts the cost and normally leaves the excess in the box. Settlers can reduce population; a size-one city on Chieftain postpones Settler completion. Buildings set their improvement bit, wonders record their city in marvel_site, and spaceship parts update the ship. The human completion path clears the remaining shields for these projects. AI building completion can retain the excess and choose another project.

Buying production

For the human, let r be missing shields, clamped to 0–999. Unit purchases cost r * r / 20 + 2 * r. Improvements cost 2 * r, doubled when the improvement ID exceeds 21, which includes spaceship parts and wonders. An empty shield box doubles the price again. Integer division truncates.

For example, a 40-shield unit with one shield stored costs 39 * 39 / 20 + 78 = 154 gold. With none stored it costs 320 gold.

AI purchases follow separate conditions. They add a chosen number of shields and charge two gold per shield; treasury fractions limit how much is bought. They do not use the human unit-price formula.

Taxes and science

Each civilization has a treasury (money), a science rate (sci) and a tax rate (civ_toll). Rates are in tenths. Luxury spending is the remainder, 10 - sci - civ_toll. civ_research_tally stores research progress; spaceship_arrive_dates stores spaceship arrival years.

CITYMGT.C calculates city trade, corruption, route income, taxes, luxuries and research. ECONOMY.C processes the cities and adjusts AI rates, chooses research and records discoveries.

Corruption

The city pass finds distance to an owned Palace, starting with a maximum of 32. A city with a Palace starts at distance zero. Communism replaces the distance with 10. For government ID g, the loss is:

loss = trade * palace_distance * 3 / (g * 20 + 80)

A Courthouse halves it; Democracy sets it to zero. The calculation is run before and after route income is added. This is a trade deduction; the source does not apply this formula to shields.

Splitting trade

After corruption and route income, luxuries are rounded with +5 before dividing by ten. Taxes are rounded the same way and limited to the trade left over. Science receives the remainder. Each specialist then adds two to its chosen category.

A Marketplace adds half the current taxes and luxuries. A Bank then adds half of those updated amounts, so the bonuses compound. A Library and a University each add half the original science amount. Wonders can modify science further.

During turn processing, a city in order adds its taxes unless its government is Anarchy. Building maintenance is charged later in city_process(). Military support is deducted from city shields, not paid as a general one-gold-per-unit treasury charge.

Research

give_advance() records discoveries in advances_known, updates discovery counts and sources, and handles effects such as obsolete Barracks and Great Library transfers. has_tech() tests the packed bits. Technology prerequisites and names come from advances[72] in GAME.C.

Research thresholds depend on advances already known, difficulty, the calendar and other modifiers in city_process(). The human research selection is selected_index; the AI chooses with weights from advance_value(), including the leader’s personality.

Happiness and disorder

city_process() in CITYMGT.C counts happy and unhappy citizens. The remaining non-specialists are content. Luxury spending, buildings, troops and wonders alter those counts before the turn pass compares them.

Difficulty and city count

For the human, the initial unhappy count is computed with:

n = (government / 2 + 2) * ((14 - 2 * difficulty) / 2)
unhappy = size + (city_index % n + city_count - n) / n + difficulty - 6

These are integer operations in the order shown. City index as well as city count matters. Without the empire adjustment, the population thresholds are six content citizens at Chieftain down to two at Emperor. For AI cities, the initial unhappy count is size - 3. fit_citizens() adjusts the counts to fit the population and specialist total.

Luxuries initially supply one happy citizen per two luxury points. Temples, Cathedrals and Colosseums reduce unhappiness; their effects depend on technology and wonders. Wonders can also add happiness or remove unrest.

Troops

Under governments below Republic, up to three units with a non-zero attack factor suppress one unhappy citizen each. Stored garrisons count too.

Republic and Democracy instead penalize supported units counted as abroad. The base penalty per unit is one under Republic and two under Democracy; Women’s Suffrage reduces it by one. This uses the unit-support tests in the city pass, rather than a general test for crossing a national border.

Disorder

When unhappy citizens exceed happy citizens, the turn pass sets city status bit 0x01. A city already in disorder can collapse a Democracy into Anarchy on a later pass. Restoring order clears that bit. Disorder stops shield contributions and tax collection in the relevant branches; research has its own government checks.

Celebrations

A city of at least size three celebrates when it has no unhappy citizens, at least half its population is happy (rounded up), and its government is not Anarchy. Status bit 0x40 records the celebration. On a later celebrating pass, Republic and Democracy can grow by one if food exceeds consumption and the Aqueduct requirement is met. Celebration also changes tile-yield rules; see Tile yields.

Caravan trade

Caravans are unit type 27. CONTROL.C handles their arrival and offers trade or help with a wonder. rpath() in CAPTURE.C calculates the delivery reward and updates route records.

Each city has three route slots in routes. A slot stores a destination city index, with −1 for empty. Destroying a city clears routes that refer to it. Routes link cities, rather than storing a fixed income in the save.

During city_process(), the city first records its trade after an initial corruption calculation. It then visits its route slots in order. A foreign route adds (destination_trade + current_trade + 4) / 8; a domestic route uses a divisor of 16. Each addition changes current_trade for the next slot. Corruption is recalculated before the national spending split.

For the one-time delivery reward, rpath() first calculates:

reward = (map_dist(source, destination) + 10) * (source_trade + destination_trade) / 24

It halves the result when a region comparison succeeds, and halves it again for a domestic destination. It then reduces the reward by a third for each of Railroad and Flight known by the destination’s owner. The result is added to both treasury and research, and the Caravan is removed.

The region comparison contains a quirk: the source lookup uses the source city’s x coordinate for both x and y. It therefore does not reliably test whether the two cities share a continent.

Buildings and wonders

improvements[46] in GAME.C contains a sentinel, 21 city buildings, three spaceship parts and 21 wonders. Each record holds a 24-byte name and three 16-bit integers: cost, maintenance and prerequisite. Costs are in tens of shields for the human player.

Buildings use bits in the city’s buildings field. Improvement n uses bit n−1. CIV.H names the Palace, City Walls and SDI masks. Spaceship parts and wonders have separate state; they are not extra city-building bits.

A wonder can be built once. Improvement IDs 25–45 map to wonder IDs 1–21 in marvel_site, which records the city that built each wonder. Effects are tested at their use sites, so their scope varies: some affect one city, some its continent, and some the owning civilization.

civ_wonder() and active_wonder_city() in CITYMGT.C check obsolescence against improvement_words[22]. A wonder expires when any non-barbarian civilization knows its obsoleting technology. The sentinel 127 means no obsoleting technology.

Production table

struct improvement improvements[46] = {
    { "NONE", 8, 0, -1 },
    { "Palace", 20, 5, 16 },
    { "Barracks", 4, 0, -1 },
    { "Granary", 6, 1, 42 },
    { "Temple", 4, 1, 25 },
    { "MarketPlace", 8, 1, 2 },
    { "Library", 8, 1, 22 },
    { "Courthouse", 8, 1, 1 },
    { "City Walls", 12, 2, 16 },
    { "Aqueduct", 12, 2, 45 },
    { "Bank", 12, 3, 53 },
    { "Cathedral", 16, 3, 29 },
    { "University", 16, 3, 13 },
    { "Mass Transit", 16, 4, 41 },
    { "Colosseum", 10, 4, 45 },
    { "Factory", 20, 4, 35 },
    { "Mfg. Plant", 32, 6, 63 },
    { "SDI Defense", 20, 4, 57 },
    { "Recycling Cntr.", 20, 2, 61 },
    { "Power Plant", 16, 4, 55 },
    { "Hydro Plant", 24, 4, 15 },
    { "Nuclear Plant", 16, 2, 50 },
    { "SS Structural", 8, 0, 40 },
    { "SS Component", 16, 0, 60 },
    { "SS Module", 32, 0, 63 },
    { "Pyramids", 30, 0, 16 },
    { "Hanging Gardens", 30, 0, 42 },
    { "Colossus", 20, 0, 17 },
    { "Lighthouse", 20, 0, 7 },
    { "Great Library", 30, 0, 30 },
    { "Oracle", 30, 0, 26 },
    { "Great Wall", 30, 0, 16 },
    { "Magellan's Expedition", 40, 0, 8 },
    { "Michelangelo's Chapel", 30, 0, 29 },
    { "Copernicus' Observatory", 30, 0, 6 },
    { "Shakespeare's Theatre", 40, 0, 10 },
    { "Isaac Newton's College", 40, 0, 51 },
    { "J.S.Bach's Cathedral", 40, 0, 29 },
    { "Darwin's Voyage", 30, 0, 49 },
    { "Hoover Dam", 60, 0, 15 },
    { "Women's Suffrage", 60, 0, 35 },
    { "Manhattan Project", 60, 0, 27 },
    { "United Nations", 60, 0, 43 },
    { "Apollo Program", 60, 0, 40 },
    { "SETI Program", 60, 0, 21 },
    { "Cure for Cancer", 60, 0, 59 }
};

Obsolescence table

Values are technology IDs; see the technology table.

int improvement_words[22] = {
    -1, 43, 20, 54, 14, 13, 29, 34, 127, 43, 127, 15, 27, 127, 127, 127, 127, 127, 127, 127, 127, 127
};

Leaders and Personalities

Each of the fourteen playable tribes has a 58-byte record in civt[16] (src/GAME.C). Entries 1–7 are the first tribe of each colour slot and entries 9–15 the alternate tribe; entries 0 and 8 are empty.

struct civ_type {
    char leader[16];
    char plural[16];      /* empty: the adjective plus "s" */
    char adjective[16];
    int f0, f1, f2, f3, f4;
};

f0, f1 and f2 are the personality. Each is −1, 0 or 1. f3 and f4 are tune numbers passed to sound(); f4 plays when the leader’s portrait appears and f3 when its emissary arrives, when it captures a city and when it discovers an advance.

EntryLeaderTribef0f1f2
1CaesarRomans011
2HammurabiBabylonians−1−11
3FrederickGermans1−11
4RamessesEgyptians001
5Abe LincolnAmericans−101
6AlexanderGreeks01−1
7M.GandhiIndians−1−10
9StalinRussians10−1
10ShakaZulus100
11NapoleonFrench111
12MontezumaAztecs0−11
13Mao Tse TungChinese001
14Elizabeth IEnglish010
15Genghis KhanMongols11−1

Trait labels

The foreign-affairs dossier in ADVISORS.C prints a label for each non-zero trait:

Field1−1
f0AggressiveFriendly
f1ExpansionisticPerfectionist
f2CivilizedMilitaristic

A debug key in CONTROL.C replaces all three traits of every tribe with rand_scaled(3) - 1 and reports “Leaders randomized.”

Where the traits are read

Aggression (f0). In a leader audience, leader_audience() counts the AI’s reasons to be conciliatory, such as wars with stronger rivals, subtracts f0, and drops its hostility if the count is high enough. An Aggressive leader needs one more reason and a Friendly leader one fewer. In AI-to-AI diplomacy, alliance_ok() subtracts f0 from its count of the civilization’s wars before comparing military strength. See Leader Audiences and AI Diplomacy Between Rivals.

Expansion (f1). ai_order() in UNITAI.C lets a settler with a destination keep travelling while (f1 + 1) * ai_zone_places[region] > towns_on_region * 16, where ai_zone_places counts the continent’s possible city sites. A Perfectionist (f1 = −1) therefore stops at once and turns to terrain work; an Expansionistic tribe keeps founding cities on a continent until it holds about one town for every eight sites.

Civilization (f2). This trait affects several decisions:

  • init_civ() in NEWGAME.C sets the starting science rate to f2 + 3, and ECONOMY.C adds f2 when the AI resets its science rate.
  • The AI production chooser divides each improvement’s rank by f2 + 3 (rank = 3 * rank / (f2 + 3)). A lower rank wins, so a Civilized tribe favours improvements and a Militaristic tribe favours units.
  • The settler rank uses 4 - f2 (with a Granary) or 6 - f2 as its factor, so Militaristic tribes build fewer settlers.
  • advance_value() returns a + f2 * b from the advance’s advance_attrs entry. It weights the AI’s random research choice, the advance taken when a city is captured and the advance an AI demands in an audience. Advances with a negative b, such as Bronze Working or Gunpowder, appeal more to Militaristic tribes.
  • During the AI’s city pass, CITYMGT.C subtracts from margin_democracy 7 - f2 (or 5 - f2 when the city has improvement bit 0x10) for each unit counted as abroad. unit_phase() adopts the Republic only while margin_democracy is not negative, so Militaristic tribes with armies abroad keep a Monarchy or Communism.

The “Nuclear Gandhi” story

A widely repeated story holds that Gandhi’s aggression was stored as 1, that adopting Democracy subtracted 2, and that an unsigned byte wrapped to 255. The recovered code does not support it. Gandhi’s f0 is −1 (Friendly), the field is a signed int, and no code adjusts it when a government changes. The traits change only in the debug randomizer.

Tribes, Colours and Titles

A game has eight civilization slots. Slot 0 is always the Barbarians; slots 1–7 are colour slots, and each can hold one of two tribes. civ_tribe_of[slot] is an index into civt: the slot number selects the first tribe and the slot number plus 8 the alternate.

SlotColour indexFirst tribe (civt[slot])Alternate (civt[slot + 8])
012Barbarians—
115RomansRussians
210BabyloniansZulus
39GermansFrench
414EgyptiansAztecs
511AmericansChinese
613GreeksEnglish
77IndiansMongols

The colour index comes from civ_colours[8] in GAME.C; civ_shadows[8] holds a darker companion for each. Because a slot can hold only one tribe, the Romans and Russians never meet in the same world.

Choosing tribes

new_game() in NEWGAME.C offers the tribes of slots 1 to civ_total + 1, first tribes then alternates, so a game with fewer rivals offers fewer tribes. Leaving the menu without a choice picks a random slot and calls rename_tribe() in NAMES.C, which stores the typed plural and derives the adjective by dropping a final “s”.

For each rival, init_civ() draws rand_scaled(16) until the result belongs to that slot ((tribe & 7) == slot) and is not already in use. When a slot is reused later in the game, the tribe is toggled with civ_tribe_of[civ] ^= 8, so a destroyed tribe returns as its partner. civil_war() in CIVWAR.C usually does the same for the breakaway civilization; for a slot beyond the game’s original rivals it picks either tribe at random.

The tables civ_names, civ_adjectives and leader_names in GAME.C are working copies. Their initial contents (including leaders such as Xerxes and Saladin, who do not appear in civt) are replaced from civt as each slot is filled.

Titles

leader_titles[civ_gov[civ]] gives the title used in audiences and reports:

GovernmentIndexTitle
Anarchy0Mr.
Despotism1Emperor
Monarchy2King
Communism3Comrade
Republic4President
Democracy5President

While an audience with Elizabeth I (civt[14]) is open, leader_audience() replaces entries 1 and 2 with “Empress” and “Queen”. On closing it restores them from rank_title in INTRO.C, whose five entries (“Chief”, “Lord”, “Prince”, “King”, “Emperor”) otherwise name the five difficulty levels in the Hall of Fame and save-game lists.

Diplomatic Relations

Relations between civilizations are bit flags in int treaties[8][8] (GLOBALS.C). treaties[a][b] describes how civilization a regards b. Contact and peace are always set and cleared in both directions; most other bits are one-sided.

BitMeaning
0x001Contact. Without peace ((t & 3) == 1) the pair is at war.
0x002Peace treaty.
0x004Alliance. Reports print “ALLIED” for peace plus this bit, and cancelling peace clears it, but no recovered code sets it.
0x008Grievance: a regards b as untrustworthy.
0x010Contact during the current sixteen-turn period.
0x020Fighting during the current period.
0x040a has an embassy with b.
0x080Nuclear weapons have entered the relationship.
0x100a intends to cancel its treaty with b.
0x200a has cancelled the treaty and its next attack is a sneak attack.

Row 0 is used differently. Bit 0x004 of treaties[civ][0] marks a government that has collapsed into anarchy; city_phase() and set_government() in DIPLOMAC.C use it to time the revolution and its announcement.

Contact and the sixteen-turn period

When units of two civilizations meet, UNITS.C calls diplomacy_event(), which sets 0x011 in both directions. If the human is one of the pair, a first contact, or a meeting at least sixteen turns after the last audience, opens a leader audience.

Every sixteenth turn, city_phase() checks each pair. A pair without 0x010 loses contact; if it was at war, a civilization with whom the human has an embassy reports “The war between … has ended.” The 0x010 bits are then cleared for the next period. War therefore lapses after sixteen turns without contact.

Setting and breaking peace

Two helpers in DIPLOMAC.C change the relationship in both directions:

  • relation_set(a, b, 2) signs peace, posts a replay event and clears the fighting bits.
  • accord(a, b, 2) cancels peace. It posts a replay event when a treaty existed and also clears the alliance bit.

BATTLE.C sets 0x020 in both directions after every combat. If the two civilizations were at peace, it reports “… cancel peace treaty with …” and calls accord(). When the human is the attacker, the victim also records a grievance (0x008). Before an attack, attack_ok() asks the human to confirm breaking a treaty; under a Republic or Democracy the Senate refuses.

Grievances

0x008 makes an AI less forgiving: it greets the human with “O most untrustworthy leader of the infidels” and doubles the tribute it demands. The bit is set when:

  • the human attacks a civilization at peace with it;
  • the human agrees to an AI’s request to cancel a treaty with a third civilization, or joins an AI’s war against one (the third civilization records the grievance);
  • a nuclear weapon strikes the civilization (atom() sets 0x088);
  • a civil war creates a new civilization (treaties[new][old] |= 9).

set_government() clears all of a civilization’s grievances when its government changes.

Planned cancellation and sneak attacks

An AI at peace with the human that is hostile in an audience, but weaker than the human or holding a grievance, makes no demand. It sets 0x100 instead, deciding to end the treaty later. On each later turn, city_phase() has a one-in-three chance of clearing 0x100 and peace and setting 0x200. The next battle in which that civilization attacks is announced as “Sneak attack by … forces!”, and the bit is cleared. The same message appears whenever a civilization attacks the human while the human still regards it as at peace. unit_phase() treats a civilization with 0x100 set as an enemy when it plans its continents.

Embassies

Bit 0x040 in treaties[civnow][civ] gives the human intelligence about civ. A Diplomat sets it (DIPLOMAT.C). It enables the “Check Intelligence” option when an emissary arrives, the dossier in the foreign affairs report, and messages about that civilization’s discoveries, wars, treaties and conquests.

give_advance() in ECONOMY.C was meant to exchange ambassadors automatically at Chieftain level when a rival learns Writing. Its test is (!treaties[civnow][civ] & 0x40): ! binds first and yields 0 or 1, so the test is always false. The recovered source keeps that expression because the original binary contains it.

Leader Audiences (AUDIENCE.C)

A leader audience is a conversation between the human and one AI ruler. leader_audience(civ, x, y, mode) runs it. The function is in overlay 6, the largest overlay at 9,642 bytes. It opens in two ways:

  • diplomacy_event() calls it with mode = 0 when units of the human and civ meet for the first time, or meet at least sixteen turns after the previous audience.
  • A human Diplomat’s “Meet with King” order in DIPLOMAT.C calls it with mode = 1.

The ruler’s text comes from keyed sections of the game’s KING.TXT, which named_msg() loads (*TRIBUTE, *PEACE and so on). The data file is not part of this repository, so this chapter names the sections rather than quoting them.

Receiving the emissary

audience_open() shows “An emissary from the … wishes to speak with you. Will you receive him?” with Yes and No, plus “Check Intelligence” if the human has an embassy. Intelligence opens the dossier and asks again. Refusing does not end the decisions: the AI still makes them, and a declaration of war is still shown as a plain message.

Once the emissary is received, the portrait loads, f4 plays and leader_greeting() chooses one of four greetings. A ruler with a grievance begins “O most untrustworthy leader of the infidels, hear now…”, and one with nuclear weapons adds “Our words are backed with NUCLEAR WEAPONS!”

The AI’s assessment

Before the first line of dialogue, the AI computes four values from per-continent strength tables built by unit_phase(). Here “attack” and “defence” are the summed attack and defence factors of a civilization’s units on a continent, and the military total is the sum over all its units.

VariableMeaning
kFear. On each continent where the AI has towns and the human’s attack exceeds the AI’s defence, k grows by attack ÷ (defence + 1), times 4 at Chieftain or 2 otherwise.
bTribute. Summed attack-minus-defence margins over the other continents.
yHostility. Set when both have units on a continent the AI does not fear.
cWillingness to ask for anything: 0, 1, or 2 when hostile.

The values are then adjusted:

  1. The leader is the target. The human may rank first, with more than four cities and no nuclear weapons, after turn 200, while the AI has more than one city. Or the AI may hold a grievance and have a military total more than a third of the human’s. In either case, if the human has no nuclear weapons, the AI becomes hostile, forgets its fear and asks at least ten times its own rank in tribute.
  2. Nuclear weapons. An AI with nuclear weapons sets 0x080 and, if the human has none, becomes hostile with a tribute of at least 100. If only the human has them, the AI’s fear is set to 4. If both do, the tribute is scaled by the ratio of their arsenals.
  3. Fear wins. Any fear clears hostility and c.
  4. Wonders. If the human owns the Great Wall or the United Nations, the AI is not hostile.
  5. Price. A grievance doubles the tribute. The tribute then becomes clamp((difficulty + 1) * b / 32, 0, 20) * 50, so 0 to 1,000 gold. If that exceeds the human’s treasury but not twice it, the demand is cut to the treasury, rounded down to fifty.
  6. Weakness. An AI with no tribute to ask, or whose military total is under a third of the human’s, is not hostile.

Next, the AI counts reasons to be conciliatory. It starts at −2 with a grievance, adds one for each war in which it is not overwhelmingly stronger and one for each war against a stronger rival, subtracts its aggression trait f0, and subtracts one if the human is weaker than it. If the count exceeds 0 (at peace) or 1 (otherwise), and the leader is not the target, the AI drops its hostility, and half the time its tribute.

A hostile AI under a Republic or Democracy without a grievance backs down when the human’s military total exceeds two thirds of its own. Otherwise it changes its government to Monarchy and carries on.

Finally, an AI at peace with the human that is still hostile, but weaker or holding a grievance, makes no demand now. It marks the treaty for later cancellation (0x100, see relations).

Technology exchange

The AI looks for an advance it knows and the human lacks, and an advance the human knows that it values most (advance_value()). If it is not hostile and has not fought the human this period, or if it knows six advances or fewer, and the human knows at least as many advances as it does, it offers an exchange (*EXCHANGE) naming one of its advances.

On acceptance each side takes one advance from the other through steal_advance(): the AI takes the one it values most, and the human chooses from a menu of the AI’s advances. The offer then repeats until it is refused or nothing is left to trade.

Demands

In order, a willing AI may:

  1. Ask the human to cancel a treaty (*CANCEL) with a third civilization that the AI is at war with. It “requests” or, when hostile, “demands”. Agreeing cancels that treaty, gives the third civilization a grievance, ends the AI’s hostility, and half the time its tribute.
  2. Demand tribute (*TRIBUTE) when hostile and the human can pay.
  3. Demand an advance (*TAKECIV) if it made no tribute demand.

Paying or handing over the advance ends the hostility.

War

If the AI is still hostile, the audience ends. If one of these applies, it first declares war:

SectionWhen
*PROVOKEThe two were at peace and the tribute was over 50.
*REJECTA demand was refused.
*RIDFirst contact, or an audience called by a Diplomat.

Each calls accord(civnow, civ, 2). A hostile AI already at war says nothing more.

Peace

An AI that is not hostile, and not already at peace with the human (or meeting a Diplomat), proposes peace (*PEACE). Accepting shows the treaty text (*TREATY) and calls make_peace(), which signs the treaty and delays the next audience by sixteen turns. At Chieftain level make_peace() also disbands the AI’s units whose nearest city is human-owned and at town_gap <= 2.

A human under a Republic or Democracy who refuses is told “You are overruled by the Senate. Treaty signed.” A refusal under another government ends an existing treaty.

A frightened AI that was refused then raises its offer, while there is no treaty or grievance:

  1. *GIVECIV: one of its advances, when its fear is at least 4.
  2. *GIVECASH: clamp(2k − 4, 0, treasury / 50) * 50 gold.
  3. *GROVEL: when it has at most one city, all its gold and every advance the human lacks.

If no peace results, *MOBILIZE closes the audience at war.

Closing at peace

A friendly AI at peace either states its attitude (*ATTITUDE) or replies “We welcome the friendship of the … people and their most wise and munificent leader”. The human then gets a menu (*THREAT):

  • Threaten. A frightened AI pays clamp(k, 0, treasury / 50) * 50 gold (*GIVEMORE). One that was hostile at the start declares war (*PROVOKE). Otherwise: “We ignore your feeble threats.” The option is dimmed under a Republic or Democracy, and within sixteen turns of the previous audience or of a gift in this one.
  • Ask for an attack on a third civilization. The AI refuses without contact (*NOCONTACT) or because it is already at war (*ATWAR). Otherwise it names a price (*MERCENARY) based on the target’s military total and its own treasury. The recovered code moves the gold and reports “… declare war on the …”, but does not change treaties. The option is dimmed when only the two civilizations remain.

The audience then sets 0x100 if cancellation was planned, clears the fighting bits (0x020) in both directions and closes the portrait.

AI Diplomacy Between Rivals

Rivals make treaties and wars among themselves without the human, through diplomacy_event() and city_phase() in DIPLOMAC.C. The same war test also decides whether two rivals stay at peace.

Meetings

UNITS.C calls diplomacy_event(a, b, x, y) when units of two civilizations meet. Contact (0x011) is set in both directions. If neither is the human:

  • A repeated meeting is ignored three times in four ((a + b + turn_tick) & 3).
  • A pair in which either side holds a grievance does nothing.
  • Otherwise each side runs the war test against the other. A side that passes does not go to war if the target owns the Great Wall or if its own government is a Republic or Democracy.

If neither side goes to war, trade_advances() swaps one advance each way when both have something the other lacks, and a pair not already at peace signs a treaty. If either side goes to war, a pair at peace cancels its treaty and a pair meeting for the first time declares war; a pair already at war carries on. Messages (“… sign a peace treaty”, “… cancel peace treaty”, “… declare war on each other”) appear only if the human has an embassy with one of them.

The war test

alliance_ok(a, b) (the name is provisional) returns 1 when a is willing to fight b:

  1. If a is at war with the human, it leaves alone a b weaker than the human, unless b is allied with the human. The same rule applies with a and b reversed.

  2. a does not attack a civilization with more nuclear weapons.

  3. On every continent where a has towns, half of a’s attack plus its defence must exceed b’s attack there.

  4. a compares the attack it can bring against b’s strength on the continents where both have units:

    wc   = a's other wars + (1 if a and b are at peace) - a's aggression f0
    x    = sum of a's attack on continents where b has defenders
    den  = 1 + sum of (b's attack / 2 + b's defence) on those continents
    pass = x * 4 / den >= wc + 4, or a has no towns on those continents
    

Other wars and an existing treaty raise the bar; an Aggressive leader lowers it by one. Clearing bit 1 of rule_options would make the test always pass, but new_game() sets all four rule bits.

Requests to the human

Once every 32 turns (on the turn where turn_tick & 31 equals four times its slot number), each AI checks its recent fighting. For each civilization i that it fought this period, while it has not fought the human and the human is at peace with i:

  • If i’s military total is more than twice its own, it asks for help (*HELPME) and offers clamp(treasury / 100, 1, 10) * 50 gold.
  • If i is stronger, but no more than twice as strong, it proposes a joint war (*CRUSADE), worded “agree to a” or “extend our” depending on whether it already has a treaty with the human.

Either way, accepting gives the AI a treaty with the human, cancels the human’s treaty with i, and gives i a grievance against the human. In the *HELPME case the code adds the gold to both treasuries rather than moving it from the AI to the human.

Ganging up on the leader

rate_civs() in TURN.C ranks the civilizations each turn by

treasury / 32 + citizens * 8 + advances + sum of unit cost over all units

and stores the rank (7 for the leader) in nation_ratings. When the human leads, has more than four cities and no nuclear weapons, and more than 200 turns have passed, it sets gangmode. Every AI with more than one city that is at peace with the human marks its treaty for cancellation (0x100); the others are put in contact with the human, and so at war. The same condition makes AIs hostile in audiences. While gangmode is set, AI cities on continents planned for expansion (plan 5) choose production as if they were at war.

Government

unit_phase() reconsiders an AI’s government every eighth turn (staggered by slot). It chooses Despotism while its troops exceed what its cities can support. Otherwise it prefers, in order:

  1. Republic, if it knows The Republic and margin_democracy is not negative (see Leaders and Personalities);
  2. Communism, if it knows Communism and has more than ten cities;
  3. Monarchy, if it knows Monarchy.

An AI does not choose Democracy, although a hostile Republic or Democracy in an audience may revert to Monarchy. After a revolution, city_phase() moves an AI from Anarchy to Despotism on the next turn divisible by four, or at once with the Pyramids.

Unit AI (UNITAI.C)

The computer players’ unit planning is in src/UNITAI.C, which compiles to the resident segment F15FB (16,266 bytes). It has two halves:

  • unit_phase(player) runs once per civilization per turn. It measures strength on every continent, decides a plan for each continent, chooses the government and sends idle units towards targets.
  • ai_order(player, unit) runs for each unit as it moves. It returns the key the unit would press: see Unit Orders.

game_turn() in TURN.C processes each civilization in turn: civ_economy() for its cities, then city_phase(), unit_phase(), unit_cleanup() and finally ai_turn() in CONTROL.C, which moves the units. ai_turn() is used for the human too; for a computer player it calls ai_order() instead of reading the keyboard.

Unit roles

Each unit type has a role in utypes (GAME.C). The AI matches units to targets by role.

RoleMeaningUnit types
0SettleSettlers
1AttackLegion, Cavalry, Knights, Catapult, Cannon, Chariot, Armor, Artillery, Bomber, Nuclear
2DefendMilitia, Phalanx, Musketeers, Riflemen, Mech. Inf.
3Naval combatIronclad, Cruiser, Battleship, Submarine, Carrier
4Air superiorityFighter
5TransportTrireme, Sail, Frigate, Transport
6SpecialDiplomat, Caravan

Strength tables

unit_phase() first rebuilds per-continent tables for the civilization. Continents are the region numbers 1–14 from world generation.

TableContents
defenders_island[civ][region]Sum of the defence factors of land-based units and city garrisons
civ_cont_attackers[civ][region]Sum of the attack factors of the same units
ai_continent_towns[civ][region]Number of cities
military_table[civ]Sum of attack plus defence over all units except Nuclear
troops_total[civ], civ_settlers_n[civ]Unit counts

A land unit at sea is marked busy (status bit 0x10) and left out of the continent tables. The pass also builds fog_map, a 20 × 13 grid of 4 × 4 tile blocks. Bit 0 marks a block containing one of the civilization’s cities and bit 1 << (role + 1) a block containing a unit of that role. civ_held_cells counts the occupied blocks.

The same loop over cities adds targets. Each foreign city that the civilization may attack becomes an attack target (kind 1). Its priority is 3 with City Walls, otherwise 5. A city with no units on its tile and no garrison becomes a defence target (kind 2): priority 4 for the civilization’s own cities, 2 for others. A foreign city qualifies as an attack target if it is not covered by peace, and if it is visible, belongs to another AI, or turn_tick * difficulty exceeds 200. One city in four is skipped on each turn.

Continent plans

For each continent, unit_phase() sets civ_area_plan[civ][region]:

PlanMeaningCondition
0DevelopNone of the conditions below
5Expand overseasThe civilization knows Map Making, and the continent is crowded: total defence is more than half its land area, or towns × 6 + 2 exceed its city sites
1AttackAn enemy (at war, or with cancellation planned) is present that the civilization can outfight: its defence is below the civilization’s attack, or its attack is below the civilization’s defence. Barbarian cities also count.
2DefendAn enemy is present that the civilization cannot outfight, or the human, although at peace, has more attack there than half the civilization’s attack plus its defence
1AttackThe civilization has no units or cities on the continent, and does not share it only with civilizations at peace with it

The rules are applied in this order, and a later rule overrides an earlier one. Enemies count only if they have defenders on the continent and either cities there or more than a quarter of the civilization’s attack. When a plan changes, retarget() makes every city of the civilization on that continent choose its production again.

The tables also set near_human when the civilization has defenders on a continent where the human has more than one city.

After the plans, Barbarians stop here, and so do human players. A computer civilization with no cities or settlers is removed with kill_civ(). Government is then reconsidered every eighth turn (see AI Diplomacy).

Targets and sites

Each civilization has two lists:

  • Targets: 32 entries of position, kind (a unit role) and priority (civ_targets_x, civ_targets_y, objectives_type, aim_count). add_target() keeps them sorted by priority and ignores a duplicate of equal or lower priority. Units add targets as they move.
  • Sites: 16 remembered entries (camp_xpos, civ_lot_y, spot_classes, ai_camp_site_weights), mostly city sites found by settlers. unit_cleanup() clears the targets at the end of each unit_phase() and re-adds every site of kind 0–2 as a target.

Sending units to targets

unit_phase() then looks at each idle unit. Units that are not busy, and not Nuclear, are considered.

First, a unit may be disbanded. Every sixteenth turn for a given unit ((unit + turn_tick) & 15), the AI disbands it when its type is obsolete for the civilization, or when it is Militia and the civilization is beyond Despotism or the continent’s plan is 5. A unit is kept if it is blocked by enemies, is alone in a city, is at sea, or is on a continent planned for defence.

Otherwise, the unit considers every target of its role on its own continent. Bombers consider attack and naval targets on any continent, but only odd-priority targets on alternating turns, with a cost that doubles when map_dist() exceeds the unit’s movement allowance. For other units the cost of a target is

cost = slots[target] * distance / (priority + 1)

where slots starts at max(3, troops / 8) for every target and grows by one each time a unit is sent. Units that are fortifying or fortified leave only for targets with a priority of at least two that are not already well served, and never leave a city. A target is skipped when four times its priority is less than its cost divided by the starting slot value. The unit takes the cheapest target as its destination (goto_x, goto_y) and is marked busy.

Unit Orders and Attack Evaluation

When ai_turn() in CONTROL.C reaches a computer player’s unit, it calls ai_order(player, unit). The result is the command a human would type, so the AI’s units go through the same command handlers as the human’s. Any non-zero result also clears the unit’s destination. After five orders in one turn the unit is made to wait.

ResultCommand
0Continue towards goto_x, goto_y
1–8Step in direction n of spiral_x / spiral_y
' 'Wait for the next turn
'b'Found a city, or join the city the unit is in
'f'Fortify
'h'Return to base (aircraft)
'i', 'm', 'r'Irrigate, mine, build a road or railroad
'p'Clean up pollution
'P'Pillage
'u'Unload a ship

Barbarians (civilization 0) use a separate routine, unit_ai_move(), described at the end of this chapter.

Distances quoted as map_dist() or town_gap use the game’s distance score, which includes half the shorter coordinate difference. They are not exact travel costs.

Special units

Bombers return to base ('h') when out of fuel. Fighters without a destination also return when they have used half their movement. Otherwise they chase the nearest human Bomber they can see. If the Bomber is out of reach, the Fighter flies to the nearest of its own cities within range of it.

Nuclear units pick a city to destroy. A candidate city has no SDI Defense and is larger than size 4. Its owner must not be at peace with the AI and must have been marked for nuclear exchange (0x080). In addition, the AI must be weaker than the owner, hold a grievance, or face an owner with no nuclear weapons that it does not outclass two to one. The score is half the city’s size, plus one for each tile of its 3 × 3 block occupied by units of such an enemy, minus two for each tile with the AI’s own units and 99 for each tile with anyone else’s. The AI needs one of its own cities at map_dist() <= 16 from the target. If the best score is at least 10 and the AI has more than one Nuclear unit, the unit is moved directly onto a free tile beside the target and attacks it. A strike on the human sets last_met_turn to −2, which lowers the price of the next audience.

Diplomats without a destination head for the nearest city if it is foreign, and are disbanded inside the AI’s own territory.

City sites and huts

World generation stores a site value of 8–15 for every possible city tile in page 2 at (x + 80, y) (0 elsewhere). Settlers, attackers and defenders on a tile with a site value look at the eight neighbours. A neighbour with a hut attracts any unit except Settlers. If the best value is at least 8, at least 15 - distance to the nearest city, and no enemy is adjacent, then:

  • Settlers found a city ('b'), or step towards a better neighbour first;
  • other units record the site (site_add(), kind 0, priority 2) for settlers to find later.

On the first turn of an EARTH game, the threshold is treated as met, so settlers placed at their tribe’s historical start found a city at once. (The flag for an EARTH game is named beginner_help in the source.) Above Chieftain, an AI settler near a human city may also found a city there when the AI knows fewer advances than the human. This uses the unmodified copy of the site value at (x + 80, y + 50), with a threshold of 9 and 14 - distance.

Defenders

A defender alone in a city fortifies. In a larger stack, the strongest defender also fortifies. The city needs size / divisor + 1 defence, where the divisor is 4 (3 if enemies are adjacent), one less for the capital and one more on a continent in plan 0. While the stack’s defence is no more than that, every defender fortifies, and while it is less, the city is listed as a defence target. Every eighth turn a surplus defender’s fortification is cleared so that it can be sent elsewhere.

Away from a city, a defender with no destination fortifies when it is within three tiles of one of its own cities with no other unit beside it, or when enemies block it. An attacker alone in a city waits there and lists the city as a defence target.

Ships

A transport (role 5) or warship (role 3) first counts the land units it carries. A transport with too little cargo heads for one of the AI’s coastal cities on a continent with at least 16 defence. It prefers continents planned for expansion and avoids ports where it is not needed. Arriving, it adds settle, defend and attack targets there so that units come to it.

A loaded transport looks for an adjacent landing site: land with no foreign units, on a continent where its cargo is useful (one with little defence and no expansion plan, a continent planned for the cargo’s role, or the destination). The continent must have at least five city sites. If possible it first moves along the coast to the beach with the most unclaimed land (ocean_edge()), then unloads ('u'). On a continent planned for attack it also lists the landing site as a naval target.

A ship with no destination then chooses one (Triremes excepted):

  1. an enemy city, taking the nearest of every eighth city (an offset that changes each turn). While the human ranks first, the only enemy cities considered are the human’s;
  2. failing that, a distant city of its own on another continent, preferring one whose plan matches the units’ roles;
  3. failing that, a random land tile on a continent with none of its cities.

When the last defender of a human city is destroyed, city_captured() sends every loaded transport at sea at a map_dist() less than three times the ship’s movement allowance, belonging to a civilization without a treaty with the human, towards that city.

Settlers at work

A settler outside its city’s square, at map_dist() <= 2 from the AI’s own city, improves terrain when the rule option allows (always, in practice):

  • work_tile() chooses irrigation ('i') or a mine ('m') when the terrain rule gives a worthwhile bonus and water is available;
  • road ('r') on irrigated or mined grassland, plains or desert;
  • railroad, once the AI knows the RailRoad advance, on a tile yielding at least two shields (one if it already has a road).

A settler on polluted land cleans it ('p'). Next comes the expansion test from the leader’s f1 trait (see Leaders and Personalities): a settler with a destination keeps going while the continent has room.

A settler beside its own city then looks for a neighbouring tile to work, and otherwise builds roads that join existing ones. Rivers need Bridge Building. With nothing to improve, it explores a random nearby 4 × 4 block holding none of its cities or settlers. If it finds none and the continent already has more than one town per eight sites, a settler in a city of size under 10 joins it ('b'), and one elsewhere goes home.

Pillage

A slow unit (fewer than two moves) near a human city (town_gap <= 3), when the AI has no treaty with the human, pillages an irrigated or mined tile it stands on ('P').

Choosing a step

When no rule above has given an order, ai_order() scores the eight neighbouring tiles and takes the best:

  • A base of rand_scaled(5). For a unit on land that no one can see, the base is rand_scaled(3) minus twice the move cost (attackers) or minus the terrain defence (others).
  • A tile of a friendly stack scores by the stack’s mix: attackers like stacks that need attack, defenders stacks that need defence. Other tiles add four times the terrain defence.
  • An undefended foreign city scores 999 and a hut adds 20.
  • When the unit is free to move, it gains 8 for an unexplored 4 × 4 block in that direction, 2 for each unseen tile around the point four steps away, and loses 2 for each occupied one. Settlers add the food of those tiles.
  • Turning is penalized by 2 * d * d, where d is the change of direction from the last step (f8).

Attack odds

A neighbouring enemy stack at war (Settlers never attack) is scored with battle(..., 0), which returns the attack-to-defence ratio without fighting (see Combat):

odds = battle() * (defenders in the stack + 1) / attacker cost
odds *= 3   against a city
odds *= 3   for an attacker on a continent planned for attack
odds *= 2   for an attacker in a stack with more than twice its attack

The unit attacks if odds >= 6, adding 4 * odds to the tile’s score. Otherwise the tile scores −999. Then, a land attacker on an attack continent facing a lone fortified or veteran defender on hills or mountains outside a city may, one time in four, call buy_u(), the Diplomat’s bribery routine. With fewer than three movement thirds left, the unit does not attack at all.

Stacks of a civilization at peace are not attacked. Units other than Fighters ignore enemy aircraft outside cities.

Barbarians

unit_ai_move() handles civilization 0:

  • Barbarian units in the polar rows are disbanded.
  • An empty barbarian ship is disbanded. A loaded ship attacks an adjacent unit of another civilization. Otherwise it lands beside a free, unclaimed land tile within a town_gap of 8 from a city. If the human can see the landing near one of its cities, it reports “Barbarian raiding party lands near …! Citizens are alarmed.” Otherwise the ship sails for the city with the highest (unrest_chance + 50) / (distance + 1).
  • A land unit alone in a city, or a defender there, fortifies. Every fourth turn a unit is disbanded if it is more than eight tiles from the nearest city or on another continent.
  • Units march towards the nearest human city on their continent, unless that civilization has fewer than two cities and less than 100 gold. They attack any adjacent city that is not theirs.
  • The barbarian leader (a Diplomat) stays with its troops, keeps away from enemies and is disbanded when left alone too far from a city. Capturing it pays a ransom of 100 gold (BATTLE.C).

Saved games

SAVE.C writes two files per save: CIVIL?.SVE for variables and arrays, and CIVIL?.MAP for the map page. Both are needed to restore a game.

State transfer

transfer_game() uses the same list of addresses and byte counts to read and write the SVE file. Values are stored in the DOS program’s representation: 16-bit little-endian integers, four-byte longs and packed record bytes. The file has no separate field-name or type metadata.

The transfer table gives exact offsets and lengths. It includes the city, unit-type and unit arrays, exploration masks, relations, replay history, spaceship layouts and palace design.

transfer_near() transfers shared-data buffers directly. transfer_far() copies through msgtext in chunks of at most 512 bytes to handle data in other segments.

The first eight two-byte values are:

OffsetVariableMeaning
0x00turn_tickTurn number
0x02civnowHuman civilization slot
0x04human_setHuman-controlled civilization mask
0x06map_rng_stateMap seed
0x08yearCalendar year
0x0acur_tierDifficulty, 0–4
0x0cplayersLiving civilization mask
0x0eselected_indexHuman research selection

Leader names occupy 0x10–0x7f, civilization names 0x80–0xdf, and adjectives 0xe0–0x137. The preview path in read_save_header() seeks to selected fields to build menu entries without loading the full state.

The map seed controls resource and hut patterns. Its presence in a save does not establish that the C runtime’s complete random-generator state has been saved.

Treaties are bit flags, not a numbered list of mutually exclusive diplomatic states.

State transfer table

transfer_game() in SAVE.C transfers the following fields in order. Offsets are measured from the start of the SVE file. Near and far describe the source address space; both write the listed number of bytes.

The total length is 37,856 bytes (0x93e0).

OffsetBytesVariableTransferUse
0x00002turn_ticknearTurn number
0x00022civnownearActive player index
0x00042human_setnearCivilizations under human control
0x00062map_rng_statenearRNG seed
0x00082yearnearCalendar year
0x000a2cur_tiernearDifficulty level (0..4)
0x000c2playersnearLiving civilization bitmask
0x000e2selected_indexnearAdvance being researched by the player
0x0010112leader_namesnearLeader names (8 × 14)
0x008096civ_namesnearCivilization names (8 × 12)
0x00e088civ_adjectivesnearAdjectives (8 × 11)
0x013816moneynearGold reserves (8 × int16)
0x014816civ_research_tallynearResearch accumulated toward the next advance (8 × int16)
0x0158448forces_presentnearUnits in play per civilization and type (8 × 28 × int16)
0x0318448civ_units_orderednearCities building each unit type (8 × 28 × int16)
0x04d816tech_countnearAdvances known per civilization (8 × int16)
0x04e880advances_knownnearKnown-advance bitmasks (8 × 5 × int16)
0x053816civ_govnearGovernment type (8 × int16)
0x0548256civ_area_plannearAI policy per civilization and continent (8 × 16 × int16)
0x0648128treatiesnearBilateral relations matrix (8 × 8 × int16)
0x06c816city_countnearCities per civilization
0x06d816troops_totalnearNon-settler units per civilization
0x06e816civ_held_cellsnearCoarse 4 × 4 map cells holding own units or cities
0x06f816civ_settlers_nnearSettlers per civilization
0x070816citizensnearTotal city sizes per civilization
0x071816military_tablenearMilitary strength: attack plus defence of all units
0x072816nation_ratingsnearCivilization power ranking
0x073816civ_tollnearTax rate in tenths (8 × int16)
0x074816civ_ratingsnearHistorical victory score
0x075816last_met_turnnearTurn of the last audience with each civ (+16 after a refusal, -2 when destroyed)
0x076816x_capnearX of the capital; centres map views
0x077816civ_tribe_ofnearTribe template type
0x0788256defenders_islandnearDefence strength per civilization and continent
0x0888256civ_cont_attackersnearAttack strength per civilization and continent
0x0988256ai_continent_townsnearCities per civilization and continent
0x0a88256continent_spannearTiles per land region, then per sea region (2 × 64 × int16)
0x0b8832ai_zone_placesnearGood city sites per continent (16 × int16)
0x0ba81,200power_historyfarCivilization power history (150 samples × 8)
0x10581,200war_recordfarCivilizations each civ was at war with, sampled every 4 turns (150 × 8 masks)
0x15083,584townbufnearThe 128 City Records (128 × 28 bytes)
0x2308952utypesnearThe 28 Unit Types (28 × 34 bytes)
0x26c012,288armydatanearThe 1,024 Active Units (8 × 128 × 12 bytes)
0x56c04,000map_visible_tofarPer-civilization tile visibility bits (80 × 50)
0x6660128spot_classesnearAI site kind per civilization and slot
0x66e0128ai_camp_site_weightsnearAI site weight per slot
0x6760128camp_xposnearAI site X per slot
0x67e0128civ_lot_ynearAI site Y per slot
0x6860144advance_ownersfarFirst discoverer of each advance
0x68f0128killednearBilateral casualty counters (8 × 8 × int16)
0x69703,328place_namefarCity Name Pool (256 × 13 bytes null-padded)
0x76702replay_lengthnearBytes used in replay_log
0x76724,096replay_logfarReplay event log
0x867244marvel_sitenearThe 22 Wonders (22 × int16 city indices)
0x869e448unit_lossesfarUnits lost per civilization and type (8 × 28 × int16)
0x885e576adv_sourcefarTechnology discovery tracking
0x8a9e2pollutednearGlobal pollution tile counter
0x8aa02warm_levelsnearGlobal warming risk (0..99; warming strikes above 16)
0x8aa22warmingnearGlobal warming event stage
0x8aa42debugnearDebug cheat flag
0x8aa6260block_ground_waynearLand connectivity of coarse 4 × 4 map cells (20 × 13)
0x8baa2max_ratingnearAll-time highest score
0x8bac2advances_futurenearFuture Technology advances discovered
0x8bae2rule_optionsnearGame rule flags (all set at the start)
0x8bb016scinearScience slider rate (8 × int16)
0x8bc02next_event_turnnearScheduled historical event turn
0x8bc216history_ratingnearHistorian ranking points; [0] counts reports
0x8bd21,440spaceshipsfarSpaceship layouts (8 × 15 × 12)
0x91722game_flagsnearSpaceship flags: bits 0–7 launched, 8–15 built
0x91742ship_successnearHuman spaceship success chance, percent
0x91762ai_ship_successnearAI spaceship success chance, percent
0x917816spaceship_arrive_datesnearSpaceship arrival year (8 × int16, -1 none)
0x918824floors_palacenearPalace architectural room heights
0x91a024section_gradesnearPalace architectural styles (Classical, etc.)
0x91b8256city_site_xfarProposed city site X
0x92b8256city_site_yfarProposed city site Y
0x93b82palace_stagenearPalace upgrade completion stage
0x93ba2player_peace_runnearPeaceful turns since the last non-barbarian battle (counted in AD)
0x93bc2civ_totalnearTotal living civilizations count
0x93be16space_ship_habitationnearHabitation modules per civilization
0x93ce16liftoff_whennearSpaceship launch year per civilization
0x93de2civ_flags_bitsnearMaster civilization bitflags

The map save file

The companion CIVIL?.MAP saves graphics page 2, which holds the map database. read_save() calls image_screen(2, file); load_save() restores it through load_screen(). Despite its name, read_save() is the writing routine.

The decoded page has 320 columns and 200 rows. At that level, byte position y * 320 + x corresponds to a page coordinate. The MAP file itself uses the screen-image encoder, including framing and compression. It is not a fixed 64,000-byte raw dump, and page offsets are not file offsets.

Terrain codes, region numbers, site data, occupancy and improvement nibbles occupy separate areas of the decoded page. The per-civilization exploration array is stored in the SVE file, separately from the remembered improvement flags on this page.

Pictures, palettes and fonts

FILEIO.C loads resources either from individual files or through an archive directory. Picture and palette loading share LOAD_PIC(). PICTURE.INC handles picture chunks and decoding; the graphics driver supplies scanline drawing and palette application.

File extensions alone do not identify the byte layout. Palette data can travel through the picture loader, and FONTS.CV contains the fonts used by the MCGA driver.

Screen-image encoding

IMAGE.C writes a rectangular page area through image_begin(), image_byte() and image_finish() in PICTURE.INC. Its run encoder uses escape byte 0x90:

  • A literal 0x90 is written as 0x90, 0.
  • A run of at least four equal non-escape bytes is written as value, 0x90, count, with count at most 255.
  • Short remaining runs are written literally.

This describes the run layer. The assembly routines handle the surrounding stream encoding and framing, so these rules alone are not a complete PIC or MAP decoder.

Sprites and fonts

Pictures provide source pixels from which the game cuts sprites. In the MCGA driver a sprite stores width, height and rows trimmed of leading and trailing transparency. A row has a stored-pixel count, a leading transparent count, and its pixel bytes. Zero pixels are transparent when drawn.

FONTS.CV is appended to the driver’s data. The font table supplies offsets, metrics and glyph data, with a width table for proportional fonts. See the MCGA driver for the rendering calls.

Constants and Domain Values

The following pages describe civilization slots, governments, unit and terrain IDs, improvements, wonders and technology indices. Numeric IDs follow the game’s source tables.

Record declarations are in src/CIV.H; global tables are defined in src/GAME.C. Enum names used in explanations are notation rather than additional source declarations.

Governments and civilization slots

Civilization slot 0 is the Barbarians; slots 1–7 hold the human and computer players. Slot numbers are separate from tribe-table indices. The same colour slot can hold one of two tribes; see Tribes.

Government IDGovernment
0Anarchy
1Despotism
2Monarchy
3Communism
4Republic
5Democracy

Difficulty ranges from 0 (Chieftain) to 4 (Emperor). Diplomatic relations use bit flags rather than government-style numbered states.

Unit and Terrain IDs

Unit IDs follow utypes table order. These names are source display strings, not proposed enum declarations.

IDUnit
0Settlers
1Militia
2Phalanx
3Legion
4Musketeers
5Riflemen
6Cavalry
7Knights
8Catapult
9Cannon
10Chariot
11Armor
12Mech. Inf.
13Artillery
14Fighter
15Bomber
16Trireme
17Sail
18Frigate
19Ironclad
20Cruiser
21Battleship
22Submarine
23Carrier
24Transport
25Nuclear
26Diplomat
27Caravan

Terrain IDs follow terrains table order.

IDTerrain
0Desert
1Plains
2Grassland
3Forest
4Hills
5Mountains
6Tundra
7Arctic
8Swamp
9Jungle
10Ocean
11River

Unit domains are 0 (land), 1 (air) and 2 (sea).

Improvement and Wonder Values

Improvement numbers follow the source improvement table; the bit for improvement n in a city’s buildings word is n - 1. CIV.H defines HAS_PALACE, HAS_CITY_WALLS and HAS_SDI.

Wonder names and ordering are documented in the wonder catalog.

Technology Indices

Technology IDs follow table order, not alphabetical order. Entries 67–70 are numbered placeholders; Future Tech is index 71. Each record has a 20-byte name and two byte-sized prerequisites.

struct advance advances[72] = {
    { "Alphabet", -1, -1 },
    { "Code of Laws", 0, -1 },
    { "Currency", 17, -1 },
    { "Atomic Theory", 51, 11 },
    { "Democracy", 28, 30 },
    { "Monarchy", 25, 1 },
    { "Astronomy", 26, 9 },
    { "MapMaking", 0, -1 },
    { "Navigation", 7, 6 },
    { "Mathematics", 0, 16 },
    { "Medicine", 28, 24 },
    { "Physics", 9, 8 },
    { "Engineering", 33, 45 },
    { "University", 9, 28 },
    { "Magnetism", 8, 11 },
    { "Electronics", 12, 54 },
    { "Masonry", -1, -1 },
    { "Bronze Working", -1, -1 },
    { "Iron Working", 17, -1 },
    { "Bridge Building", 18, 45 },
    { "Invention", 12, 30 },
    { "Computers", 9, 15 },
    { "Writing", 0, -1 },
    { "Steam Engine", 11, 20 },
    { "Trade", 2, 1 },
    { "Ceremonial Burial", -1, -1 },
    { "Mysticism", 25, -1 },
    { "Nuclear Fission", 41, 3 },
    { "Philosophy", 26, 30 },
    { "Religion", 28, 22 },
    { "Literacy", 22, 1 },
    { "Horseback Riding", -1, -1 },
    { "Feudalism", 16, 5 },
    { "The Wheel", -1, -1 },
    { "Gunpowder", 20, 18 },
    { "Industrialization", 49, 53 },
    { "Chemistry", 13, 10 },
    { "Combustion", 55, 56 },
    { "Flight", 37, 11 },
    { "Advanced Flight", 38, 54 },
    { "Space Flight", 21, 46 },
    { "Mass Production", 58, 47 },
    { "Pottery", -1, -1 },
    { "Communism", 28, 35 },
    { "The Republic", 1, 30 },
    { "Construction", 16, 2 },
    { "Rocketry", 39, 15 },
    { "The Corporation", 53, 35 },
    { "Metallurgy", 34, 13 },
    { "RailRoad", 23, 19 },
    { "Nuclear Power", 27, 15 },
    { "Theory of Gravity", 6, 13 },
    { "Steel", 48, 35 },
    { "Banking", 24, 44 },
    { "Electricity", 48, 14 },
    { "Refining", 36, 47 },
    { "Explosives", 34, 36 },
    { "SuperConductor", 60, 41 },
    { "Automobile", 37, 52 },
    { "Genetic Engineering", 10, 47 },
    { "Plastics", 55, 40 },
    { "Recycling", 41, 4 },
    { "Chivalry", 32, 31 },
    { "Robotics", 60, 21 },
    { "Conscription", 44, 56 },
    { "Labor Union", 41, 43 },
    { "Fusion Power", 50, 57 },
    { "1", 127, 127 },
    { "2", 127, 127 },
    { "3", 127, 127 },
    { "4", 127, 127 },
    { "Future Tech.", 127, 127 }
};

Game Record Declarations

src/CIV.H defines the shared city, unit, unit-type and terrain records shown in the following pages. In this DOS game, int occupies two bytes and long occupies four.

Cities and units use compact records with byte-sized coordinates and status fields. Cities name their food and shield reserves, worked-tile mask, specialist types, name index, trade routes and stored garrison units directly. Unit f8 retains its provisional name and has uses including pathfinding direction.

Shared declarations are organized in headers named after the module defining the symbols. The function reference groups them by subsystem.

struct city

The declaration below is included directly from src/CIV.H.

struct city {
    long buildings;             /* bit (n-1) set: improvement n is built */
    unsigned char x, y;
    char status;
    signed char size;
    char visible_size;
    char production;
    char trade;
    signed char owner;
    int food_box;               /* accumulated food */
    int shield_box;             /* accumulated production */
    long workers;               /* worked tiles 0..20, specialist count 26..31 */
    int specialists;            /* eight two-bit specialist types */
    char name_id;               /* unsigned index into place_name */
    char routes[3];             /* destination city indices, -1 if empty */
    char garrison[2];           /* unit type plus veteran bit 0x40, -1 if empty */
};

struct unit

The declaration below is included directly from src/CIV.H.

struct unit {
    char status;                /* 0x08 fortified, 0x20 veteran */
    unsigned char x, y;
    signed char type;
    signed char moves;          /* remaining moves, in thirds */
    char fuel;
    char goto_x;
    char goto_y;
    char f8;
    signed char visibility;     /* one bit per civilization */
    signed char next;           /* next unit in the same stack, or -1 */
    char home_city;
};

struct unit_type

The declaration below is included directly from src/CIV.H.

struct unit_type {
    char name[12];
    int obsolete_by;
    int domain;                 /* 0 land, 1 air, 2 sea */
    int moves;
    int fuel;
    int attack;
    int defense;
    int cost;
    int sight;
    int capacity;
    int role;
    int prereq;
};

struct terrain_type

The declaration below is included directly from src/CIV.H.

struct terrain_type {
    char name[12];
    unsigned char move_cost;
    signed char defense;        /* defense multiplier, in halves */
    unsigned char food, shields, trade, f5, f6;
};

Global Game State

Game state is distributed across shared globals and larger arrays. It includes the current turn and year, active player, civilization finances and research, diplomatic relations, cities, units, wonders and map visibility.

src/GAME.C defines the main rule tables and civilization variables. Subsystem headers identify other shared globals. The save transfer sequence shows the serialized state and its ordering.

Source Declarations and Interfaces

The following pages include shared headers for resident subsystems and overlays. They list functions and variables used across modules; module-local helpers are not included.

Many declarations have empty argument lists. Consult the function definitions and callers in src/ for parameter names, order and types.

Resident Owner Headers

These are shared declarations, not an exhaustive list of module-local functions. A module may expose multiple overlay entry points.

MAP.H

/* MAP.H: shared declarations of symbols defined in MAP.C. */

extern int terrain_map[];
extern int redraw_stack(int, int);
extern int map_in_bounds(int, int);
extern int map_city(int, int);
extern int has_road(int, int);
extern int known_flags(int, int);
extern int get_city(int, int);
extern int map_random(int);
extern int patterned_cell(int, int, int);
extern int has_hut(int, int, int);
extern int region_key(int, int);
extern int map_region(int, int);
extern int draw_unit(int, int, int, int);
extern int flag_tile(int, int, int);
extern int map_refresh(int, int, int);
extern int occupy(int, int);
extern int unflag(int, int, int);
extern int map_claim(int, int, int, int);
extern int plot_pixel(int, int, int);
extern int put_unit(int, int, int, int);

UNITS.H

/* UNITS.H: shared declarations of symbols defined in UNITS.C. */

extern int quiz_busy;
extern void unpack_garrison(int);
extern void garrison_in(int);
/* Callers pass long arguments to int parameters; preserve their stack layout. */
extern void unit_sight();
extern void blink_unit(int, int, int, int);
extern int select_location_record(int, int);
extern int stack_other(int, int);
extern void score_group_record(int, int);
extern int stack_len(int, int, int);
extern void add_group_value(int, int);
extern int count_u(int, int, int);
extern void count_matching_record(int, int);
extern int stack_strength(int, int, int);
extern void match_type_category(int, int);
extern int stack_cap(int, int);
extern void count_weighted_record(int, int);
extern void validate_record(int, int);
extern void remove_reset_kind_zero(int, int);
extern void kill_stack_cb(int, int);
extern void remove_reset_kind_one(int, int);
extern int if_open(int, int, int);
extern int unit_blocked(int, int, int);
/* Calls use differing argument counts; retain the unprototyped interface. */
extern int adj_grp();
extern void enter_hut(int, int);
extern void anim_unit(int, int, int);
extern int pick_unit(int, int);
extern int near_base(int, int);
/* Calls use differing argument counts; retain the unprototyped interface. */
extern void post_event();
extern void menu_begin(void);
extern void menu_end(void);
extern int help_probe(int, int, int);
extern void menu_clear(void);
extern int create_unit(int, int, int, int);
extern void reset_state(int, int);
extern void unit_reset(int, int);
extern void drop_u_stack(int, int);
extern void kill_cb(int, int);
extern void map_center(int, int, int);

BATTLE.H

/* BATTLE.H: shared declarations of symbols defined in BATTLE.C. */

/* Callers pass long arguments to int parameters; preserve their stack layout. */
extern int battle();
extern int stack_add(int, int, int);
extern int stack_remove(int, int);
extern int attack_ok(int);
extern int atom(int, int, int);
extern int nuke_fallout(int, int);

CITYMGT.H

/* CITYMGT.H: shared declarations of symbols defined in CITYMGT.C. */

extern int working_tiles;
extern int city_view_mode;
extern int radius_dx[21];
extern int radius_dy[21];
extern int radius_index(int, int);
extern void add_tile_yield(int, int, int);
extern int tile_yield(int, int, int);
extern int civ_wonder(int, int);
extern int active_wonder_city(int);
extern void set_specialist(int, int);
extern int get_specialist(int);
extern int count_specialists(int);
extern void fit_citizens(int, int);
extern void draw_icon(int, int, int);
extern void framed_panel(int, int, int, int);
extern void tile_panel(int, int, int, int);
extern void draw_button(int, int, int, int, char *, int);
extern void draw_minimap(void);
extern void minimap_dot(int, int, int);
extern void specialist_warning(void);
extern void add_pollution(int, int);
extern int draw_city_view(int, int, int, int, int);

ECONOMY.H

/* ECONOMY.H: shared declarations of symbols defined in ECONOMY.C. */

extern void civ_economy(int);
extern void vanish(int, int, int);
extern void backdrop_save(void);
extern void backdrop_load(void);
extern void kill(int, int);
extern int find_slot(int, int);
extern void turns_text(int, int);
extern void give_advance(int, int, int);
extern int has_tech(int, int);
extern int advance_value(int, int);
extern int reduce_attitude(int, int);

PATH.H

/* PATH.H: shared declarations of symbols defined in PATH.C. */

extern int unit_goal(int, int);
extern int path_coarse(int, int);
extern int path_select(int, int, int);
extern int path_search(int, int, int);
extern int path_probe(int, int, int, int, int, int);

Overlay Owner Headers

These are shared declarations, not an exhaustive list of module-local functions. A module may expose multiple overlay entry points.

SETUP.H

/* SETUP.H: shared declarations of symbols defined in SETUP.C. */

extern char *gfx_names[];
extern char *sound_names[];
extern char sound_codes[];
extern char gfx_codes[];
extern char *clear_line;
extern void setup_main(void);

INTRO.H

/* INTRO.H: shared declarations of symbols defined in INTRO.C. */

extern void game_startup(void);
extern void spaceship_arrival(int);
extern void civ_destroyed(void);
extern void show_start(void);
extern char *rank_title[];

FAME.H

/* FAME.H: shared declarations of symbols defined in FAME.C. */

extern void fame_load(void);
extern void fame_save(void);
extern int fame_slot;
extern void fame_header(int, int);
extern void top_five(void);
extern int show_fame(int);

NEWGAME.H

/* NEWGAME.H: shared declarations of symbols defined in NEWGAME.C. */

extern void new_game(void);
extern int init_civ(int);
extern void kill_civ(int, int);
extern int rate_starts(void);
extern void setup_art(void);
extern void cabinet(void);
extern void new_government(void);
extern void reset_spaceship(int);

AUDIENCE.H

/* AUDIENCE.H: shared declarations of symbols defined in AUDIENCE.C. */

/* Calls use differing argument counts; retain the unprototyped interface. */
extern void leader_audience();
extern int mouse_mode;
extern int style;
extern char leader_portrait[];
extern void audience_close(void);
extern int audience_open(int, int, int);
extern void leader_greeting(int);
extern int treaty_prompt(int);
extern void make_peace(int);
extern char face_eyes[];
extern char face_mouths[];
extern char face_next[];
extern void leader_animate(void);
extern int advisor_x[];
extern void load_leader(int, int, long);
extern void leader_backdrop(int);
extern void free_leader(void);
extern void draw_leader_face(int, int, int);
extern int leader_menu(char *, int, int);

WORLDGEN.H

/* WORLDGEN.H: shared declarations of symbols defined in WORLDGEN.C. */

extern void make_world(void);
extern int land_corner(int, int);
extern void make_rivers(void);
extern void grow_land(void);
extern void walk_land(int, int);
extern void number_regions(void);
extern void score_sites(void);
/* Callers pass long arguments to int parameters; preserve their stack layout. */
extern void link_land();
/* Calls use differing argument counts; retain the unprototyped interface. */
extern void link_seas();
extern int gen_terrain_at(int, int);
extern int gen_region_at(int, int);
extern int story_mode;
extern int story_step(void);
extern void global_warming(int);

PEDIA.H

/* PEDIA.H: shared declarations of symbols defined in PEDIA.C. */

extern int no_pictures;
extern void civilopedia(int, int);
extern int pedia_list(int, int);
extern void pedia_show(int, int);
extern int pedia_advance_index(int);
extern void blit_centred(int, int, int, int, int, int);
extern int pedia_unit_index(int);
extern int pedia_terrain_index(int);

ADVISORS.H

/* ADVISORS.H: shared declarations of symbols defined in ADVISORS.C. */

extern void advisor_open(char *, int);
extern void advisor_header(char *, int);
extern void advisor_close(void);
extern void sci_rpt(int);
extern void mil_rpt(int);
extern void military_losses(int);
extern void trade_rpt(int);
/* Calls use differing argument counts; retain the unprototyped interface. */
extern void spy_rpt();
extern void civ_dossier(int);
extern void mood_rpt(int);
extern void append_percent(int, int);
extern void city_rpt(int);

CIVWAR.H

/* CIVWAR.H: shared declarations of symbols defined in CIVWAR.C. */

extern int civil_war(int);
extern void transfer_city(int, int, int);
extern int central_city(int);

PALACE.H

/* PALACE.H: shared declarations of symbols defined in PALACE.C. */

extern void palace_view(int);
/* Calls use differing argument counts; retain the unprototyped interface. */
extern void palpic();
/* Callers pass long arguments to int parameters; preserve their stack layout. */
extern void calc_section_type();
extern void draw_section(int, int, int, int, int);
/* Callers pass long arguments to int parameters; preserve their stack layout. */
extern void load_castle_tiles();

SPACESHP.H

/* SPACESHP.H: shared declarations of symbols defined in SPACESHP.C. */

extern int game_flags;
extern void spaceship_open(void);
extern void spaceship_close(void);
/* Calls use differing argument counts; retain the unprototyped interface. */
extern int spaceship_view();
extern int ship_connected(int, int, int);
extern void spaceship_stars(void);
extern int spaceship_ai_build(int, int);
extern void spaceship_build(int, int);
extern void spaceship_tile(int, int, int);
extern int spaceship_fits(int, int, int, int);
extern int spaceship_free(int, int, int);
extern void spaceship_inspect(void);
extern void spaceship_launch(int);

CITYVIEW.H

/* CITYVIEW.H: shared declarations of symbols defined in CITYVIEW.C. */

extern void city_scene(int, int);
extern void city_pop(int, int, int);
extern void city_house(int, int, int, int);
extern void city_building(int, int, int);
extern void city_citizens(int, int, int, int);
extern void city_load(int, int, int);
extern int wonder_tile(int);
extern void invasion_scene(int);
extern void riot_anim(void);
extern void love_anim(void);

NEWS.H

/* NEWS.H: shared declarations of symbols defined in NEWS.C. */

extern void news_box(int);

DIPLOMAT.H

/* DIPLOMAT.H: shared declarations of symbols defined in DIPLOMAT.C. */

extern void dip_menu(int, int, int);
extern void buy_u(int, int, int);
extern void city_revolt(int, int);
extern void city_founded(int);
extern void nuke_animation(int, int);

NAMES.H

/* NAMES.H: shared declarations of symbols defined in NAMES.C. */

extern void rename_leader(void);
extern void rename_tribe(void);
extern void goto_city(void);
extern int find_city(char *);
extern int edit_line(int, int, int);
extern int rename_city(int);