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 destination | Function | |
|---|---|---|
| Raw LINK output | 2045:0442 | Runtime environment initialization, __setenvp |
| Final executable | 01a8:01c1 | empty_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.
| Area | Start | End | Role |
|---|---|---|---|
| Resident code | 00000 | 23740 | Main simulation and engine routines |
| Overlay code window | 23740 | 25CF0 | One active overlay body |
| Separate data storage | 25CF0 | 2B010 | Larger arrays and tables |
| Shared data and stack | 2B010 | 3A0E0 | DGROUP |
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.
| Offset | Array | Records | Record size |
|---|---|---|---|
0282 | terrains | 12 | 19 bytes |
112A | utypes | 28 | 34 bytes |
70EC | townbuf | 128 | 28 bytes |
81D4 | armydata | 8 × 128 | 12 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 ID | Module Source | Local Code Bytes | Example Shared Export | Functional Purpose |
|---|---|---|---|---|
| 1 | src/SETUP.C | 1,018 | setup_main() | Hardware detection, graphics mode & audio driver configuration. |
| 2 | src/INTRO.C | 6,588 | game_startup() | Title sequence, MicroProse logo animations, credits. |
| 3 | src/FAME.C | 3,192 | fame_load() | Hall of Fame score calculation and high score table display. |
| 4 | src/NOTES.C | 1,146 | o4_notes() | In-game instructional popups, game tips, and key summaries. |
| 5 | src/NEWGAME.C | 7,534 | new_game() | New game menu, difficulty, world customization, tribe selection. |
| 6 | src/AUDIENCE.C | 9,642 | leader_audience() | Diplomatic negotiations, treaty proposals, leader audience screens. |
| 7 | src/WORLDGEN.C | 7,512 | make_world() | Procedural map generation, land growth, climate, rivers. |
| 8 | src/PEDIA.C | 6,134 | civilopedia() | Civilopedia encyclopedia viewer (units, improvements, wonders). |
| 9 | src/REPLAY.C | 5,468 | o9_replay() | Historical timeline replay displayed upon game victory/defeat. |
| 10 | src/MAPVIEW.C | 1,702 | o10_mapview() | Full-world strategic minimap viewer and territory inspection. |
| 11 | src/SAVE.C | 3,994 | o11_save() | Save/load menus, disk validation, .SVE and .MAP serialization. |
| 12 | src/REPORTS.C | 7,000 | o12_reports() | Military, foreign affairs, commerce, and demographic status reports. |
| 13 | src/STATUS.C | 2,472 | o13_status() | World status screen, civilization ranking, Top 5 Cities display. |
| 14 | src/ADVISORS.C | 7,026 | advisor_open() | Graphic advisor consultations (Military, Domestic, Foreign, Science). |
| 15 | src/CIVWAR.C | 2,426 | civil_war() | Civil war partition logic triggered when an enemy capital falls. |
| 16 | src/QUIZ.C | 1,372 | o16_quiz() | Manual-lookup copy protection challenge questions. |
| 17 | src/PALACE.C | 4,246 | palace_view() | Palace upgrade view and graphic construction screen. |
| 18 | src/SPACESHP.C | 5,842 | spaceship_open() | Alpha Centauri spaceship component assembly, flight simulation, arrival. |
| 19 | src/CITYVIEW.C | 7,332 | city_scene() | Hand-drawn graphical city interior view with active improvements. |
| 20 | src/CITY.C | 5,188 | o20_city() | City founding ceremony, naming prompt, tile radius activation. |
| 21 | src/NEWS.C | 1,066 | news_box() | Newspaper banner popups announcing world wonders and disasters. |
| 22 | src/DIPLOMAT.C | 4,434 | dip_menu() | Diplomat operations (bribery, sabotage, theft). |
| 23 | src/NAMES.C | 1,926 | rename_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:
| Offset | Contents |
|---|---|
| 10h | Name and date, MGRAPHIC.EXE09-19-91 |
| 28h | Segment of the slot routines (relocated by DOS) |
| 2Ah | Data segment; the loader appends FONTS.CV after it |
| 2Ch | Bytes of data before the fonts (630h) |
| 2Eh | First slot (0) |
| 30h | Slot count (52) |
| 32h | Offset 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.
| Slot | Name | Effect |
|---|---|---|
| 0 | alloc_page_memory(n) | n = 0: the screen segment; otherwise a fresh 64,000-byte block (0 on failure) |
| 25 | set_page_memory(n, seg) | Use seg for page n |
| 8 | page_free(n) | Free page n’s block |
| 5 | page_clear(n, colour) | Fill page n |
| 31 | set_page(n) | Draw the mouse cursor on page n, then copy page n to the screen |
| 36 | page_swap(n) | Exchange page n with the screen |
| 10 | page_dissolve(n, time) | Copy page n to the screen in pseudo-random pixel order |
| 20 | blit(src, sx, sy, w, h, dst, dx, dy) | Copy a rectangle between pages, two bytes at a time |
| 3 | recolour_rect(pg, x, y, w, h, from, to) | Replace one colour with another in a rectangle (menu highlights) |
| 13, 19 | page_read_row, draw_scanline | Copy part of a row out of, or into, a page |
| 14 | pixel_at(n, x, y) | Read one pixel |
| 17 | page_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.
| Slot | Name | Effect |
|---|---|---|
| 32 | sprite_draw(pg, x, y, sprite) | Draw without clipping |
| 6 | sprite_draw_clipped(pg, x, y, sprite) | Draw clipped to the page record; return a far pointer to the rectangle drawn |
| 22 | sprite_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.
| Slot | Name | Effect |
|---|---|---|
| 28 | apply_palette(rec) | Load colours rec[4]..rec[5] from an "M0" palette record (RGB from byte 6) |
| 27 | load_ega_palette(pal) | Map 16 EGA colour numbers to default colours; byte 16 picks the border colour |
| 26 | palette_reset_colour(i, c) | DAC entry i = default colour c |
| 23, 24 | screen_off, screen_on | Blank the display through the sequencer’s clocking-mode register |
| 29, 51 | shake_start(n), shake_step | For 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) orraster_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:
| Choice | Driver |
|---|---|
| No sounds please | NSOUND.CVL |
| IBM sounds | ISOUND.CVL (PC speaker) |
| Tandy sounds | TSOUND.CVL |
| AdLib/Sound Blaster | ASOUND.CVL |
| Roland MT-32 MIDI board | RSOUND.CVL |
run_program copies the driver’s entry points into slots 100–110 of
driver_slots (DRIVER.INC). CIV then uses five calls:
| Call | Slot | When |
|---|---|---|
sound_init() | 100 | Start-up (INTRO.C) |
sound(n, variant) → sound_command | 101 | Every sound and tune; sound in GAME.C checks that sound is on (debug & 10h) |
sound_tick() | 103 | Timer interrupt, about 60 Hz; may ask for a faster or slower timer |
sound_fast_tick() | 104 | Timer interrupt at the fast rate |
sound_shutdown() | 102 | Exit |
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
| Slot | CIV name | Effect |
|---|---|---|
| 100 | sound_init(arg) | Detect the card and silence it (INTRO.C at start-up) |
| 101 | sound_command(n, variant) | Start sound command n; reached through sound() in GAME.C, which plays only while sound is on (debug & 10h) |
| 102 | sound_shutdown() | Silence the card; set the DOS time from the real-time clock (CIV’s fast timer made the DOS clock drift) |
| 103 | sound_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 |
| 104 | sound_fast_tick() | Advance the pitch sweeps (at the fast timer rate) |
| 105 | sound_quiet_ticks() | Ticks since the last command; CIV uses it as a source of random seeds |
| 106–110 | Return at once |
TIMER.INC calls slots 103 and 104 and changes the timer rate when
slot 103 asks for it.
Commands
| n | Use |
|---|---|
| 0 | Silence everything |
| 1 | End the music: every channel goes to the rest sequence |
| 2 | Nonzero while any channel plays |
| 3 | Title and intro music |
| 4 | World creation |
| 5–18 | Civilization 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–32 | Short motifs for the same civilizations, in the same order (civt[].f3) |
| 33 | Audience interlude: pause the channels, play one of seven phrases in turn, then resume |
| 34 | Celebration (We Love the King Day, Hall of Fame) |
| 35 | City grows in rapture (two tunes, alternately) |
| 36 | Bad news (famine, civil disorder, nuclear incident); also the theme of the two unused tribe slots |
| 37 | A land unit moves |
| 38, 39 | Battle won, battle lost: light units |
| 40, 41 | Battle won, battle lost: heavy units (attack above 4 or defence above 2) |
| 42 | Nuclear explosion |
| 43 | A bomber wins its attack |
| 44 | City-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:
| Mask | Meaning |
|---|---|
0x01 | City |
0x02 | Irrigation |
0x04 | Mine |
0x08 | Road |
0x10 | Railroad |
0x20 | Fortress |
0x40 | Pollution |
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:
- Irrigation and mining alter land yields using
work_rules. A road adds one trade on Desert, Plains and Grassland. - The coordinate pattern can remove shields from Grassland and River.
- The Colossus adds one to non-zero trade in its own city.
- Railroad adds half the current yield, rounded down, to each category.
- Anarchy and Despotism lose one from yields above two, unless the city is celebrating.
- Non-zero trade gains one under Republic and Democracy. Celebrating cities receive this bonus from Monarchy upwards.
- 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;
};
| Offset | Field | Use |
|---|---|---|
| 0 | status | Order and condition bits (below) |
| 1–2 | x, y | Map position |
| 3 | type | Index into utypes, or −1 |
| 4 | moves | Movement left this turn, in thirds |
| 5 | fuel | Turns of fuel for aircraft; work progress for Settlers |
| 6–7 | goto_x, goto_y | Destination; goto_x is −1 when there is none |
| 8 | f8 | Last direction moved (1–8), or −1 |
| 9 | visibility | One bit per civilization that can see the unit |
| 10 | next | Next unit in the same stack, or −1 |
| 11 | home_city | Supporting city, or −1 |
Status bits
| Bit | Meaning |
|---|---|
0x01 | Sentry. Also set on units carried by a ship. |
0x02 | Settlers building a road or railroad; aircraft returning to base |
0x04 | Fortifying. ai_turn() turns it into 0x08 at the start of the next turn. |
0x08 | Fortified (UNIT_FORTIFIED) |
0x10 | Busy: the AI has assigned the unit for this turn |
0x20 | Veteran (UNIT_VETERAN) |
0x40 | Settlers irrigating |
0x80 | Settlers 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
fuelone less than its type’sfuel; - 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.
The ring of next links
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:
| Function | Result |
|---|---|
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;
};
| Field | Meaning |
|---|---|
obsolete_by | Advance that makes the type obsolete; 127 for none |
domain | 0 land, 1 air, 2 sea |
moves | Moves per turn; ai_turn() multiplies by 3 |
fuel | Turns an aircraft may stay airborne (Fighter 1, Bomber 2, Nuclear 1) |
attack, defense | Combat factors |
cost | Production cost in tens of shields |
sight | Bit 1 (value 2) widens sight from the 3 × 3 to the 5 × 5 square |
capacity | Land units a ship carries |
role | Category used by the AI (roles) |
prereq | Advance 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:
| Type | Behaviour |
|---|---|
| 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:
| Step | Cost in thirds |
|---|---|
| Road to road | 1 |
| Road to road, from a railroad, moved by hand by the human | 0 |
| Otherwise | 3 * 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):
| Roll | Outcome |
|---|---|
| 0 | At 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. |
| 3 | At 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 tofuel - 1, and so does being carried by a moving Carrier.- When the aircraft has used all its movement,
ai_turn()refills it tofuelif 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:
- Barbarian attackers. Against the human,
a = a * (difficulty + 1) / 4; against a computer player,a /= 2. Against a city,abecomes 0 if it is the defender’s only city and is halved if it holds the Palace. - Veterans. Each side’s veteran status adds half.
- 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.
- 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():
- reports “… use nuclear weapons!” unless the human launched it;
- plays the explosion (
nuke_animation()inDIPLOMAT.C); - 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;
- sets
0x088intreaties[victim][attacker]for each civilization that loses units: a grievance and the nuclear flag. The flag allows later AI nuclear strikes against the attacker; - 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 - severityocean 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
| Offset | Field | Use |
|---|---|---|
0x00 | buildings | 32-bit mask; improvement n uses bit n−1 |
0x04–05 | x, y | Map position |
0x06 | status | State flags; −1 means unused |
0x07 | size | Population size |
0x08 | visible_size | Last reported size for the map display |
0x09 | production | Unit ID, or negative improvement ID |
0x0a | trade | Trade used in route calculations |
0x0b | owner | Civilization slot |
0x0c | food_box | Stored food, a 16-bit integer |
0x0e | shield_box | Stored production, a 16-bit integer |
0x10 | workers | Worked tiles in bits 0–20; specialist count in bits 26–31 |
0x14 | specialists | Eight two-bit specialist types |
0x16 | name_id | Unsigned index into place_name[256][13], accessed with a cast |
0x17–19 | routes | Up to three destination cities; −1 means empty |
0x1a–1b | garrison | Two 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.
| Entry | Leader | Tribe | f0 | f1 | f2 |
|---|---|---|---|---|---|
| 1 | Caesar | Romans | 0 | 1 | 1 |
| 2 | Hammurabi | Babylonians | −1 | −1 | 1 |
| 3 | Frederick | Germans | 1 | −1 | 1 |
| 4 | Ramesses | Egyptians | 0 | 0 | 1 |
| 5 | Abe Lincoln | Americans | −1 | 0 | 1 |
| 6 | Alexander | Greeks | 0 | 1 | −1 |
| 7 | M.Gandhi | Indians | −1 | −1 | 0 |
| 9 | Stalin | Russians | 1 | 0 | −1 |
| 10 | Shaka | Zulus | 1 | 0 | 0 |
| 11 | Napoleon | French | 1 | 1 | 1 |
| 12 | Montezuma | Aztecs | 0 | −1 | 1 |
| 13 | Mao Tse Tung | Chinese | 0 | 0 | 1 |
| 14 | Elizabeth I | English | 0 | 1 | 0 |
| 15 | Genghis Khan | Mongols | 1 | 1 | −1 |
Trait labels
The foreign-affairs dossier in ADVISORS.C prints a label for each non-zero
trait:
| Field | 1 | −1 |
|---|---|---|
f0 | Aggressive | Friendly |
f1 | Expansionistic | Perfectionist |
f2 | Civilized | Militaristic |
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()inNEWGAME.Csets the starting science rate tof2 + 3, andECONOMY.Caddsf2when 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) or6 - f2as its factor, so Militaristic tribes build fewer settlers. advance_value()returnsa + f2 * bfrom the advance’sadvance_attrsentry. 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 negativeb, such as Bronze Working or Gunpowder, appeal more to Militaristic tribes.- During the AI’s city pass,
CITYMGT.Csubtracts frommargin_democracy7 - f2(or5 - f2when the city has improvement bit0x10) for each unit counted as abroad.unit_phase()adopts the Republic only whilemargin_democracyis 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.
| Slot | Colour index | First tribe (civt[slot]) | Alternate (civt[slot + 8]) |
|---|---|---|---|
| 0 | 12 | Barbarians | — |
| 1 | 15 | Romans | Russians |
| 2 | 10 | Babylonians | Zulus |
| 3 | 9 | Germans | French |
| 4 | 14 | Egyptians | Aztecs |
| 5 | 11 | Americans | Chinese |
| 6 | 13 | Greeks | English |
| 7 | 7 | Indians | Mongols |
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:
| Government | Index | Title |
|---|---|---|
| Anarchy | 0 | Mr. |
| Despotism | 1 | Emperor |
| Monarchy | 2 | King |
| Communism | 3 | Comrade |
| Republic | 4 | President |
| Democracy | 5 | President |
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.
| Bit | Meaning |
|---|---|
0x001 | Contact. Without peace ((t & 3) == 1) the pair is at war. |
0x002 | Peace treaty. |
0x004 | Alliance. Reports print “ALLIED” for peace plus this bit, and cancelling peace clears it, but no recovered code sets it. |
0x008 | Grievance: a regards b as untrustworthy. |
0x010 | Contact during the current sixteen-turn period. |
0x020 | Fighting during the current period. |
0x040 | a has an embassy with b. |
0x080 | Nuclear weapons have entered the relationship. |
0x100 | a intends to cancel its treaty with b. |
0x200 | a 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()sets0x088); - 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 withmode = 0when units of the human andcivmeet for the first time, or meet at least sixteen turns after the previous audience.- A human Diplomat’s “Meet with King” order in
DIPLOMAT.Ccalls it withmode = 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.
| Variable | Meaning |
|---|---|
k | Fear. 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. |
b | Tribute. Summed attack-minus-defence margins over the other continents. |
y | Hostility. Set when both have units on a continent the AI does not fear. |
c | Willingness to ask for anything: 0, 1, or 2 when hostile. |
The values are then adjusted:
- 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.
- Nuclear weapons. An AI with nuclear weapons sets
0x080and, 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. - Fear wins. Any fear clears hostility and
c. - Wonders. If the human owns the Great Wall or the United Nations, the AI is not hostile.
- 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. - 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:
- 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. - Demand tribute (
*TRIBUTE) when hostile and the human can pay. - 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:
| Section | When |
|---|---|
*PROVOKE | The two were at peace and the tribute was over 50. |
*REJECT | A demand was refused. |
*RID | First 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:
*GIVECIV: one of its advances, when its fear is at least 4.*GIVECASH:clamp(2k − 4, 0, treasury / 50) * 50gold.*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) * 50gold (*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 changetreaties. 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:
-
If
ais at war with the human, it leaves alone abweaker than the human, unlessbis allied with the human. The same rule applies withaandbreversed. -
adoes not attack a civilization with more nuclear weapons. -
On every continent where
ahas towns, half ofa’s attack plus its defence must exceedb’s attack there. -
acompares the attack it can bring againstb’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 offersclamp(treasury / 100, 1, 10) * 50gold. - If
iis 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:
- Republic, if it knows The Republic and
margin_democracyis not negative (see Leaders and Personalities); - Communism, if it knows Communism and has more than ten cities;
- 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.
| Role | Meaning | Unit types |
|---|---|---|
| 0 | Settle | Settlers |
| 1 | Attack | Legion, Cavalry, Knights, Catapult, Cannon, Chariot, Armor, Artillery, Bomber, Nuclear |
| 2 | Defend | Militia, Phalanx, Musketeers, Riflemen, Mech. Inf. |
| 3 | Naval combat | Ironclad, Cruiser, Battleship, Submarine, Carrier |
| 4 | Air superiority | Fighter |
| 5 | Transport | Trireme, Sail, Frigate, Transport |
| 6 | Special | Diplomat, Caravan |
Strength tables
unit_phase() first rebuilds per-continent tables for the civilization.
Continents are the region numbers 1–14 from world generation.
| Table | Contents |
|---|---|
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]:
| Plan | Meaning | Condition |
|---|---|---|
| 0 | Develop | None of the conditions below |
| 5 | Expand overseas | The 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 |
| 1 | Attack | An 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. |
| 2 | Defend | An 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 |
| 1 | Attack | The 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 eachunit_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.
| Result | Command |
|---|---|
0 | Continue towards goto_x, goto_y |
1–8 | Step 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):
- 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;
- failing that, a distant city of its own on another continent, preferring one whose plan matches the units’ roles;
- 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 isrand_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, wheredis 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_gapof 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.
Header
The first eight two-byte values are:
| Offset | Variable | Meaning |
|---|---|---|
0x00 | turn_tick | Turn number |
0x02 | civnow | Human civilization slot |
0x04 | human_set | Human-controlled civilization mask |
0x06 | map_rng_state | Map seed |
0x08 | year | Calendar year |
0x0a | cur_tier | Difficulty, 0–4 |
0x0c | players | Living civilization mask |
0x0e | selected_index | Human 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).
| Offset | Bytes | Variable | Transfer | Use |
|---|---|---|---|---|
0x0000 | 2 | turn_tick | near | Turn number |
0x0002 | 2 | civnow | near | Active player index |
0x0004 | 2 | human_set | near | Civilizations under human control |
0x0006 | 2 | map_rng_state | near | RNG seed |
0x0008 | 2 | year | near | Calendar year |
0x000a | 2 | cur_tier | near | Difficulty level (0..4) |
0x000c | 2 | players | near | Living civilization bitmask |
0x000e | 2 | selected_index | near | Advance being researched by the player |
0x0010 | 112 | leader_names | near | Leader names (8 × 14) |
0x0080 | 96 | civ_names | near | Civilization names (8 × 12) |
0x00e0 | 88 | civ_adjectives | near | Adjectives (8 × 11) |
0x0138 | 16 | money | near | Gold reserves (8 × int16) |
0x0148 | 16 | civ_research_tally | near | Research accumulated toward the next advance (8 × int16) |
0x0158 | 448 | forces_present | near | Units in play per civilization and type (8 × 28 × int16) |
0x0318 | 448 | civ_units_ordered | near | Cities building each unit type (8 × 28 × int16) |
0x04d8 | 16 | tech_count | near | Advances known per civilization (8 × int16) |
0x04e8 | 80 | advances_known | near | Known-advance bitmasks (8 × 5 × int16) |
0x0538 | 16 | civ_gov | near | Government type (8 × int16) |
0x0548 | 256 | civ_area_plan | near | AI policy per civilization and continent (8 × 16 × int16) |
0x0648 | 128 | treaties | near | Bilateral relations matrix (8 × 8 × int16) |
0x06c8 | 16 | city_count | near | Cities per civilization |
0x06d8 | 16 | troops_total | near | Non-settler units per civilization |
0x06e8 | 16 | civ_held_cells | near | Coarse 4 × 4 map cells holding own units or cities |
0x06f8 | 16 | civ_settlers_n | near | Settlers per civilization |
0x0708 | 16 | citizens | near | Total city sizes per civilization |
0x0718 | 16 | military_table | near | Military strength: attack plus defence of all units |
0x0728 | 16 | nation_ratings | near | Civilization power ranking |
0x0738 | 16 | civ_toll | near | Tax rate in tenths (8 × int16) |
0x0748 | 16 | civ_ratings | near | Historical victory score |
0x0758 | 16 | last_met_turn | near | Turn of the last audience with each civ (+16 after a refusal, -2 when destroyed) |
0x0768 | 16 | x_cap | near | X of the capital; centres map views |
0x0778 | 16 | civ_tribe_of | near | Tribe template type |
0x0788 | 256 | defenders_island | near | Defence strength per civilization and continent |
0x0888 | 256 | civ_cont_attackers | near | Attack strength per civilization and continent |
0x0988 | 256 | ai_continent_towns | near | Cities per civilization and continent |
0x0a88 | 256 | continent_span | near | Tiles per land region, then per sea region (2 × 64 × int16) |
0x0b88 | 32 | ai_zone_places | near | Good city sites per continent (16 × int16) |
0x0ba8 | 1,200 | power_history | far | Civilization power history (150 samples × 8) |
0x1058 | 1,200 | war_record | far | Civilizations each civ was at war with, sampled every 4 turns (150 × 8 masks) |
0x1508 | 3,584 | townbuf | near | The 128 City Records (128 × 28 bytes) |
0x2308 | 952 | utypes | near | The 28 Unit Types (28 × 34 bytes) |
0x26c0 | 12,288 | armydata | near | The 1,024 Active Units (8 × 128 × 12 bytes) |
0x56c0 | 4,000 | map_visible_to | far | Per-civilization tile visibility bits (80 × 50) |
0x6660 | 128 | spot_classes | near | AI site kind per civilization and slot |
0x66e0 | 128 | ai_camp_site_weights | near | AI site weight per slot |
0x6760 | 128 | camp_xpos | near | AI site X per slot |
0x67e0 | 128 | civ_lot_y | near | AI site Y per slot |
0x6860 | 144 | advance_owners | far | First discoverer of each advance |
0x68f0 | 128 | killed | near | Bilateral casualty counters (8 × 8 × int16) |
0x6970 | 3,328 | place_name | far | City Name Pool (256 × 13 bytes null-padded) |
0x7670 | 2 | replay_length | near | Bytes used in replay_log |
0x7672 | 4,096 | replay_log | far | Replay event log |
0x8672 | 44 | marvel_site | near | The 22 Wonders (22 × int16 city indices) |
0x869e | 448 | unit_losses | far | Units lost per civilization and type (8 × 28 × int16) |
0x885e | 576 | adv_source | far | Technology discovery tracking |
0x8a9e | 2 | polluted | near | Global pollution tile counter |
0x8aa0 | 2 | warm_levels | near | Global warming risk (0..99; warming strikes above 16) |
0x8aa2 | 2 | warming | near | Global warming event stage |
0x8aa4 | 2 | debug | near | Debug cheat flag |
0x8aa6 | 260 | block_ground_way | near | Land connectivity of coarse 4 × 4 map cells (20 × 13) |
0x8baa | 2 | max_rating | near | All-time highest score |
0x8bac | 2 | advances_future | near | Future Technology advances discovered |
0x8bae | 2 | rule_options | near | Game rule flags (all set at the start) |
0x8bb0 | 16 | sci | near | Science slider rate (8 × int16) |
0x8bc0 | 2 | next_event_turn | near | Scheduled historical event turn |
0x8bc2 | 16 | history_rating | near | Historian ranking points; [0] counts reports |
0x8bd2 | 1,440 | spaceships | far | Spaceship layouts (8 × 15 × 12) |
0x9172 | 2 | game_flags | near | Spaceship flags: bits 0–7 launched, 8–15 built |
0x9174 | 2 | ship_success | near | Human spaceship success chance, percent |
0x9176 | 2 | ai_ship_success | near | AI spaceship success chance, percent |
0x9178 | 16 | spaceship_arrive_dates | near | Spaceship arrival year (8 × int16, -1 none) |
0x9188 | 24 | floors_palace | near | Palace architectural room heights |
0x91a0 | 24 | section_grades | near | Palace architectural styles (Classical, etc.) |
0x91b8 | 256 | city_site_x | far | Proposed city site X |
0x92b8 | 256 | city_site_y | far | Proposed city site Y |
0x93b8 | 2 | palace_stage | near | Palace upgrade completion stage |
0x93ba | 2 | player_peace_run | near | Peaceful turns since the last non-barbarian battle (counted in AD) |
0x93bc | 2 | civ_total | near | Total living civilizations count |
0x93be | 16 | space_ship_habitation | near | Habitation modules per civilization |
0x93ce | 16 | liftoff_when | near | Spaceship launch year per civilization |
0x93de | 2 | civ_flags_bits | near | Master 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
0x90is written as0x90, 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 ID | Government |
|---|---|
| 0 | Anarchy |
| 1 | Despotism |
| 2 | Monarchy |
| 3 | Communism |
| 4 | Republic |
| 5 | Democracy |
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.
| ID | Unit |
|---|---|
| 0 | Settlers |
| 1 | Militia |
| 2 | Phalanx |
| 3 | Legion |
| 4 | Musketeers |
| 5 | Riflemen |
| 6 | Cavalry |
| 7 | Knights |
| 8 | Catapult |
| 9 | Cannon |
| 10 | Chariot |
| 11 | Armor |
| 12 | Mech. Inf. |
| 13 | Artillery |
| 14 | Fighter |
| 15 | Bomber |
| 16 | Trireme |
| 17 | Sail |
| 18 | Frigate |
| 19 | Ironclad |
| 20 | Cruiser |
| 21 | Battleship |
| 22 | Submarine |
| 23 | Carrier |
| 24 | Transport |
| 25 | Nuclear |
| 26 | Diplomat |
| 27 | Caravan |
Terrain IDs follow terrains table order.
| ID | Terrain |
|---|---|
| 0 | Desert |
| 1 | Plains |
| 2 | Grassland |
| 3 | Forest |
| 4 | Hills |
| 5 | Mountains |
| 6 | Tundra |
| 7 | Arctic |
| 8 | Swamp |
| 9 | Jungle |
| 10 | Ocean |
| 11 | River |
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);