Civ1 exploit: design flaw in randomness

darkpanda

Dark Prince
Joined
Oct 28, 2007
Messages
823
Randomness in CIV

While looking at how CIV manages randomness (see this post), I discovered a design flaw that could be leveraged to bypass randomness.

To get started, let me re-state some initial findings on randomness, posted in the thread linked above:
  • CIV uses a very classic "seed"-based random algorithm:
    • The algorithm is initialized with a pair of seed values S1 and S2, which are computed from the system date and time at CIV startup
    • The random routine takes a single integer N as argument, and returns a value from 0 to N-1 included, computed using complicated mathematical formulas involving the input value N, and the seed values S1 and S2
    • At the same time, every call to the random routine also re-updates the seed values S1 and S2

If you are not familiar with such algorithms (which are very frequent in computer calculations), the basic idea you need to understand is that seed-based randomness is actually simulating randomness by selecting an initially hard to predict value - the seed - (system date and time, down to milliseconds if necessary), and then use complicated mathematical formulas applied to to this initial value to select the "next" random value.

The thing is, however complicated the mathematical formula can be, they are still deterministic, i.e. with the same input values, they will produce the same results. So, if the seed is initialized to the same value every time, then the sequence of "next" random values will always be the same!

Consequently, such algorithms are actually called pseudo-random in that they are good enough to look random, but they are essentially not random at all.

How can pseudo-randomness be exploited in CIV ?

In the case of CIV, the seeds are initialized based on system date&time during the opening credits. You would be right to think that, by controlling the system date and time when CIV starts, you may actually force the random algorithm to produce the same sequence of values every time. But a couple of issues come with this:
  • first, it is quite hard to control the date and time when CIV starts
  • second it is even harder to make sure that the date and time when CIV initializes the seeds is always the same, as your CPU make take more or less time between the launch of CIV.EXE and the instruction that compute the seed.

Fortunately, as part of the discussion in that previous thread, I discovered there is a place where CIV resets the seed during the course of game:
darkpanda said:
in addition, there are calls that, indeed, re-seed the randomness generator; typically, when displaying a City View, the random seed is re-initialized to the total sum of the city name's ASCII character codes (can you believe it! I just found this out 5 minutes ago...) while the 2nd variable is reset to 0; this ensures that a given city has always the same "random" - yet identical - streets and buildings layout

You can imagine how much this could simplify the control of randomness:
  • you could control when the seed is initialized by displaying a City view
  • moreover, you could control the value of the initial seed - and thus the sequence of random values! - by choosing a carefully crafted City (name, size, buildings)

Discovering this, I was impatient to put it to the test... My first experiment is as follows:
  • The aim is to verify that randomness can be manipulated by displaying City View
  • The initial condition: a newly started game, with just a 1-citizen capital city and a militia next to an unvisited tribal hut:
    civ_839.png civ_843.png
  • I performed 3 series of tests: in each series, the test is run 10 times, and the outcome of the tribal hut visit by the militia is recorded:
    • Series 1: the saved initial condition is re-loaded using the F10-hack, then the militia visits the tribal hut
    • Series 2: the saved initial condition is re-loaded manually, by exiting then restarting CIV, then the militia visits the tribal hut
    • Series 3: the saved initial condition is re-loaded using the F10-hack, then the City View is displayed, and finally the militia visits the tribal hut
  • Hereunder are the outcomes of the 3 series of tests:
    test #series 1series 2series 3
    1friendly tribe of skilled mercenaries (Cavalry)friendly tribe of skilled mercenaries (Cavalry)metal deposits of value 50
    2scrolls of ancient wisdom (Masonry)metal deposits of value 50metal deposits of value 50
    3scrolls of ancient wisdom (Alphabet)friendly tribe of skilled mercenaries (Cavalry)metal deposits of value 50
    4scrolls of ancient wisdom (Horseback Riding)scrolls of ancient wisdom (Alphabet)metal deposits of value 50
    5metal deposits of value 50metal deposits of value 50metal deposits of value 50
    6metal deposits of value 50friendly tribe of skilled mercenaries (Legion)metal deposits of value 50
    7metal deposits of value 50scrolls of ancient wisdom (Masonry)metal deposits of value 50
    8metal deposits of value 50scrolls of ancient wisdom (Alphabet)metal deposits of value 50
    9friendly tribe of skilled mercenaries (Legion)metal deposits of value 50metal deposits of value 50
    10friendly tribe of skilled mercenaries (Cavalry)scrolls of ancient wisdom (Alphabet)metal deposits of value 50

For me, the results above are clear: displaying the City View forced the outcome of the tribal hut to be "metal deposits".
civ_842.png

Since the new seed is computed using only the city name, I wanted to try changing the city name to control the outcome:
  • Initially, the city name is Ulundi (I skipped Zimbabwe by mistake on pressing ESC on the first turn...). According to the seed formula, its value is computed as the sum of the name's ASCII character codes:
    Code:
       U - 85
       l - 108
       u - 117
       n - 110
       d - 100
       i - 105
    ----------
    [B]seed = [COLOR="Blue"]625[/COLOR][/B]
  • I incremented the seed to 626 by changing the name to Uluodi
  • Then I re-ran the series 3 and obtained the following results:
    test #series 3
    1friendly tribe of skilled mercenaries (Cavalry)
    2friendly tribe of skilled mercenaries (Cavalry)
    3friendly tribe of skilled mercenaries (Cavalry)
    4friendly tribe of skilled mercenaries (Cavalry)
    5friendly tribe of skilled mercenaries (Cavalry)
    6friendly tribe of skilled mercenaries (Cavalry)
    7friendly tribe of skilled mercenaries (Cavalry)
    8friendly tribe of skilled mercenaries (Cavalry)
    9friendly tribe of skilled mercenaries (Cavalry)
    10friendly tribe of skilled mercenaries (Cavalry)

So the conclusion is that the outcome of the tribal hut visit can be controlled by the City name.

Now, looking at the City View code in CIV, there are plenty of calls to the random routine in order to select the positions of buildings. Obviously, the more citizens there are, the more houses to display, and also for every constructed improvement, there will be an additional random call to place it on the screen.
Those intermediate calls do not affect the seed itself, but it will affect the offset in the random sequence where CIV is when randomizing the tribal hut outcome.

I wanted to test this as well, so I re-did some series of test by modifying the city size and improvements"
  • Series 4: the city size is 2
    civ_848.png
  • Series 5: the city size is 2 and a Granary is built
    civ_849.png
  • Series 6: the city size is 3
    civ_850.png
  • Series 7: the city size is 3 and Barracks are built
    civ_851.png

Here are the results:
test #series 4 series 5series 6series 7
1metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
2metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
3metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
4metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
5metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
6metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
7metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
8metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
9metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)
10metal deposits of value 50friendly tribe of skilled mercenaries (Legion)friendly tribe of skilled mercenaries (Legion)scrolls of ancient wisdom (Masonry)

Moreover, if I sell the Barracks in the city size 3, then displaying the City View forces the hut outcome back to the previous situation (skilled mercenary - Legion).

Investigation is progressing but there are still more things to investigate:
  • Will it work for a series of huts ? i.e. if I have several huts that can be visisted, and I display the city before each hut, will I get the same outcomes every time ?
  • Can I force battles to be victorious, even if a militia attacks a fortified Mech. Inf. ?

TO BE CONTINUED...
 
Last edited:
I'd have to know more about exactly how the rand() function here works, but it should be possible to make a two-column table that shows what name to use for every kind of city, such that the next attacker is guaranteed to win, no matter what the units. I just reread Civ1 Combat Mechanics Explained, and it should work as long as the attack rand() call always returns positive, and the defense rand() always zero.

Ideally there would be a formula that showed how many rand() calls a City View will take (one per citizen and one per building?). Match that up to a city name that makes the attacker always win, maybe another name that makes triremes never sink. (The hut formula might be tougher.)

I love this discovery. You just took a couple useless, purely cosmetic features (change city name, city view) and made them into a game-changing strategy. Beautiful.
 
I'd have to know more about exactly how the rand() function here works, but it should be possible to make a two-column table that shows what name to use for every kind of city, such that the next attacker is guaranteed to win, no matter what the units. I just reread Civ1 Combat Mechanics Explained, and it should work as long as the attack rand() call always returns positive, and the defense rand() always zero.

I was looking at the exact same thread and started to think that it was impossible to control both attack and defense (specifically make attack > defense) values because they both depend on the units' strengths, status, terrain, etc.

But as a matter of fact, months ago I had fully ported the random routine from ASM to Java, in order to understand its inner workings, and by playing with it I can confirm that, while the returned values obviously depend on the input argument N (range of random value), the seed sequence does NOT depend on the input argument N, and even more, returning value 0 does NOT depend on the input argument N... -> This last statement is actually wrong: the returned value is proportional to the input argument N, but this can still be leveraged to force 0 or non-zero results (see further posts)

A world of possibilities is opening...... I lack the time to give more details about the random routine right now, but I'll come back with more as soon as possible.
 
Great and astonishing work! :goodjob:
I only feel a bit sad about how this classic game can be cheated to death... :sad:
 
I love this discovery. You just took a couple useless, purely cosmetic features (change city name, city view) and made them into a game-changing strategy. Beautiful.

By the way, just to clarify: I've only tested version EN 474.01, which doesn't have the "rename city" feature, so I used JCivED to rename the city. Other versions should be tested as well to confirm this was not fixed/changed.

Currently looking at "random" calls in the City View routine, there are more than 10 distinct calls, and some of them in loops...
 
Since you mentioned it, I did some testing of my own. I used English 474.05, and renamed with the rename city feature. I set up a quick test scenario with Rome on an Earth map, with a militia attacking Paris (which had a fortified militia, no veterans), and a settler popping a hut immediately after.

First 10 trials, I stuck with the original city name, Rome. All 10 times, my attacking militia lost, and the hut gave me $50.

I spent a bunch of time looking for a city name where both events happen differently. From Roma to Roml, I got a variety of different results. Romm finally gave me different results from Rome on both tests. My militia from Romm destroyed Paris this time, all 10 times. The hut gave barbarians, which moved the same way each time, destroying my settler in the process.

I probably should have checked for veteran status on my winning militia, but the thought didn't cross my mind. Oops.
 
I probably should have checked for veteran status on my winning militia, but the thought didn't cross my mind. Oops.

If you know your unit is going to win every time, do you really care anymore about being veteran ? :)

Your proof of concept makes it clear that the City View controls the outcome of all subsequent random events, provided they happen in the same sequence.

Now to progress on the city/size/buildings table you proposed, it's going to be very hard: in the City View routine, there are at least 2 complicated loops that contain calls to the Random routine. They are complicated in that their ending condition is not simple, and seems to depend a lot on the result of the previous iterations' random calls... -> see further below (edited 9/24/2013)

I'll try to investigate it further to see what's possible.

In the mean time, I am also including below the Java code for ported CIV random routine (it is just provided "as is" with no support for the moment, for the ones out there who want to put their hands on it quickly):

Code:
public class TestRandom2 {

	public static void main(String[] args) {
		TestRandom2 r0 = new TestRandom2(0x26A);
//		TestRandom2 r1 = new TestRandom2(10);
//		TestRandom2 r2 = new TestRandom2(10);
//		TestRandom2 r3 = new TestRandom2(10);
//		TestRandom2 r4 = new TestRandom2(10);

		int t = 50;
		
		int[][] tt = new int[5][t];
		
		for(int i=0;i<t;i++) {
			tt[0][i] = r0.next(2);   
//			tt[1][i] = r1.next(10);  
//			tt[2][i] = r2.next(20);  
//			tt[3][i] = r3.next(100); 
//			tt[4][i] = r4.next(250); 

			for(int j=0;j<1;j++) {
				System.out.print(""+tt[j][i]+(tt[j][i]<10?"   ":tt[j][i]<100?"  ":" ")+" seed1: "+r0.ds5BDA+" - seed2: "+r0.ds5BDC);
			}
			System.out.println();
		}
	}
	
	private short initialSeed;
	
	private long counter;

	private short _ax; // 16-bit AX register
	private short _bx; // 16-bit BX register
	private short _cx; // 16-bit CX register
	private short _dx; // 16-bit DX register

	boolean _zf = false; // ZERO flag
	boolean _cf = false; // CARRY flag
	boolean _of = false; // OVERFLOW flag

	java.util.Stack<Short> stack; 

	short ds5BDA; // seed 1, initially same as TerrainMasterWord
	short ds5BDC; // seed 2, initially 0

	public TestRandom2() {
		this((int)(System.currentTimeMillis()&0xFFFF));
	}
	
	public TestRandom2(int seed) {
		ds5BDA = (short) seed;
		initialSeed = (short) seed;
		ds5BDC = 0x0;
		stack = new java.util.Stack<Short>();
		counter = 0;
	}
	
	//Copy constructor
	public TestRandom2(TestRandom2 tr2) {
		ds5BDA = tr2.ds5BDA;
		initialSeed = tr2.initialSeed;
		ds5BDC = tr2.ds5BDC;
		stack = new java.util.Stack<Short>(); stack.addAll(tr2.stack);
		counter = tr2.counter;
	}

	public int next(int max) {
		_random((short) max);
		counter++;
		return _ax;
	}

	private void _asm_mul(short value) {
		int _eax = ((int)_ax)&0xFFFF;
		_eax *= value;
		_dx = (short) (_eax>>16);
		_ax = (short) (_eax);
		_cf = (_dx!=0x0);
		_of = _cf;
	}
	private void _asm_add_ax(short value) {
		int _eax = (((int)_ax)&0xFFFF) + (((int)value)&0xFFFF);
		_cf = ((_eax&0xFFFF0000) != 0);
		_ax = (short)(_eax);
	}
	private void _asm_add_dx(short value) {
		int _edx = (((int)_dx)&0xFFFF) + (((int)value)&0xFFFF);
		_cf = ((_edx&0xFFFF0000) != 0);
		_dx = (short)(_edx);
	}
	private void _asm_adc_dx(short value) {
		int _edx = (short) (_dx + value +(_cf?1:0));
		_cf = ((_edx&0xFFFF0000) != 0);
		_dx = (short)(_edx);
	}
	private void _asm_cwd() {
		_dx = (short) (_ax<0?-1:0);
	}
	void _asm_rcr_ax(int i) {
		boolean _tmpcf = _cf;
		_cf = ((_ax&0x1)==1);
		_ax >>= 1;
		if(_tmpcf) _ax |= 0x8000;
		else _ax &= 0x7FFF;
	}
	void _asm_sar_dx() {
		_cf = (_dx&0x1) == 0x1;
		_dx>>=1;
	}
	
	private void RandomPartFormula(short arg_0, short arg_2, short arg_4, short arg_6) {
		_ax = arg_2;
		_bx = arg_6;
		_bx |= _ax; _zf = (_bx==0);
		_bx = arg_4;
		if(_zf) {
			_ax = arg_0;
			_asm_mul(_bx);
		} else {
			_asm_mul(_bx);
			_cx = _ax;
			_ax = arg_0;
			_asm_mul(arg_6);
			_cx += _ax;
			_ax = arg_0;
			_asm_mul(_bx);
			//_dx += _cx; // TODO: actuall 'add' sets flags too
			_asm_add_dx(_cx);
		}
	}
	private void RandomSub1() {
		_ax = 0x43FD;
		_dx = 3;
		RandomPartFormula(ds5BDA,ds5BDC,_ax,_dx);
		_asm_add_ax((short) 0x9EC3);
		_asm_adc_dx((short) 0x26);
		ds5BDA = _ax;
		ds5BDC = _dx;
		_ax = _dx;
		_ax &= 0x7FFF;
	}
	
	private void RandomSub2() {
		_cx &= 0xFF; // xor ch,ch
		while(_cx!=0) {
			_asm_sar_dx();
			_asm_rcr_ax(1);
			_cx--;
		}
	}
	
	private void _random(short arg_0) {
		_ax = arg_0;
		_asm_cwd();
		stack.push(_dx);
		stack.push(_ax);
		
		RandomSub1();
		
		_asm_cwd();
		stack.push(_dx);
		stack.push(_ax);
		
		RandomPartFormula(stack.pop(),stack.pop(),stack.pop(),stack.pop());
		
		_cx = (short) ((_cx&0xFF00)|0x0F);
		
		RandomSub2();
	}

	public short getInitialSeed() {
		return initialSeed;
	}
	public long getCounter() {
		return counter;
	}

	public boolean equals(TestRandom2 tr2) {
		boolean eq = true;
		eq &= (initialSeed == tr2.initialSeed);
		eq &= (counter == tr2.counter);
		eq &= (ds5BDA == tr2.ds5BDA);
		eq &= (ds5BDC == tr2.ds5BDC);
		eq &= (stack.equals(tr2.stack));
		return eq;
	}
}
 
So, would it be possible to always discover a new city? It would be quite interesting to play a game and always get a new city when you find a hut.
 
or always discover a scroll... How many civ advancements could you acquire simply from huts?
 
A possibly useful resource:

http://tasvideos.org/EmulatorResources/JPC.html

It's a re-recording emulator used to make tool-assisted speedruns. I've never used this particular emulator, but everything else they put out operates at the frame level or better.

or always discover a scroll... How many civ advancements could you acquire simply from huts?

I start building a Trireme at my most productive coastal city as soon as it is available. By the time I have enough resources to complete it, a Frigate comes out.

So, would it be possible to always discover a new city? It would be quite interesting to play a game and always get a new city when you find a hut.

I'm not sure how "deep" the odds go, but it seems close to always possible to discover a city (or any other outcome). However, odds seem "locked" when using the save swap trick.
 
A little short on time but I wanted to mention a few things:
  • It should be possible to get new cities or advances whenever you want, theoretically
  • Correcting myself (1): return value '0' by the random routine does actually depend on input value N, but not in a problematic way, only partially
  • Correcting myself (2): after a few DosBox-Debug runs, I discovered that my ported Java routine has bugs; it updates the seed 1 properly, but not the seed 2, so the sequences of random values is not the same as CIV for the same seeds... Working on solving this at the moment -> Java code fixed directly in post above (edited 9/24/2013)

Cheers
 
Thanks for the update. All of us appreciate the amount of time and effort you have put into this game. Keep up the good work.
 
Ok, I think I finally nailed it, but it is really more arduous than expected...

First, I have fixed the Java-ported random routine so it now generates the exact same "random" seed sequence as CIV, and of course the same random results.

Second, I did a partial port of the City View algorithm, to Java as well, including all random calls, so it can correctly simulate all random calls as if CIV was executing them. This means I am now able to predict the status of the random generator after showing the city view for any city.

Now there are some tricky parts:
  • the first trick is that the number of calls to the random routine is very variable, and highly self-dependent: whether or not an additional random call is performed depends a lot on the result of the previous random calls; this is because CIV randomly selects buildings locations on the map, and does trial and error re-selecting random positions if the previous one is occupied
  • the second trick is that not only this number of calls depends on the city name, city size and existing buildings (and wonders), but it also depends on the number of techs the city owner possesses; this makes randomness control even more difficult, as even with stable non-changing cities, discovering new techs interfers with the random seed prediction -> experiments show that random results are actually independent from random results (edited 9/24/2013)
  • the third trick is that, upon exiting the city screen, coming back to the map screen may trigger an additional random call - or not !; this is less of burden and usually it can be detected by looking at the screen changing animation: fade out to black then fade in to map means there was no random call; re-displaying the map screen with "random" square re-tiling means there was a single random call -> exiting the city screen after city view always cross-fades, without map refresh; anyhow this trick becomes irrelevant as long as we now ho many random calls ahead is the "non-0 / 0" sequence (edited 9/24/2013)

Because of all the poins mentioned above, it is nearly impossible to make an exhaustive table of what city names will force the odds, as too many factors are involved. -> see further below

For the moment, I see 3 ways to progress on this:
  1. Establish a list of city names, for cities with 1 citizen and no building, for all possible tech counts, that will make you win combat for sure; the tech count is actually divided by 2, so a list of 36 names should be enough - except for those chasing Future Tech records; then all you need is to build a city, that you maintain at population 1 at all times, and without buildings, and rename it as necessary (CIV EN 474.05 only has the built-in "rename" feature)
  2. Write a small program (embedded in JCivED, for example) that will find out winning city names for any given city in any given savegame, as necessary
  3. Start an exhaustive, brute force research for unique names that may (possibly) make the odds independent from one or several factors (such as number of techs, buildings or population, etc.); including all impacting factors, this quickly goes into the 10 billion combinations to try out, very likely to take a long while...

In any case, you can see that it is really not that obvious to exploit this design flaw... So in the end, it may not be a critical flaw at all :)


Just before finishing, I'd like to share the proof of concept savegame that will show you that this can work:
View attachment CIVIL1.MAP
View attachment CIVIL1.SVE

In this savegame, a non-veteran English Militia is ready to attack the French city of Paris, protected by city walls and defended by a veteran fortified Mech. Inf.:

  • If you let the Militia attack directly, you're basically guaranteed to lose by a crushing defeat
  • Now re-load the savegame, click on the city e6 (previously London), display the city view, leave the city screen, DO NOT click anywhere on the map or minimap, and let the Militia attack Paris immediatly... I'll leave it to you to see what happens... It's always a weirdly great feeling to see a Mech. Inf. crushed by a Militia ;)

I tried the savegame above in CIV EN 474.01 and EN 474.05, and it works perfectly in both, so my guess is that it will work in all EN versions of CIV.
 

Attachments

  • civ_057.png
    civ_057.png
    5.9 KB · Views: 2,227
It seems that in this thread, I am going to keep going back and forth correcting myself again and again!

But this time it's for good news :) - I should probably edit the previous posts to strikethrough my earlier wrong statements...

So the good news come in several points, after playing around with the ported "CIV random simulator":
  1. Althought the CIV code uses the City Owner's "tech count", experiments show that in effect, tech count does not influence the total number of random calls... So we can basically ignore it for our purpose
  2. When trying out all possible combinations of city improvements and wonders, they produced highly redundant number of random calls, and analyzing it further, it seems the total number of random calls only depends on the number of buildings and/or wonders, but not on their actual type (some buildings and wonders should be ignored though: palace, city walls, aqueduct, mass transit, power plant, hydro plant, Pyramids, Great Wall, Colossus and Hoover Dam); this is quite logical if you consider that all improvements have the same land area usage in the city view, and same for wonders

The above results make it possible to slash through the possible combinations, reducing them by multiple orders of magnitude.

I am currently preparing a table as suggested by Urtica Dioica, that will list sample city names (and seeds) depending only on Population size and Number of buildings/wonders.

Previously, here was a draft table and game save illustrating the random hack for attack success; as a matter of fact, the table data was wrong and so I removed both the table and game save; hopefully you can properly experiment this hack using roper data form the next post's table
 

CIV RANDOM HACK - Attack table



How to read the table below?

The table below lists specific City names that will force the attack odds in your favour when displaying their City View.

Here is how to proceed:
  1. In your CIV game, select the city you wish to rename in order to force the odds; note: the city must NOT contain any Wonder (except for Pyramids, Colossus, Great Wall and Hoover Dam: those will not impact the process)
  2. Count the buildings in your city; note: do NOT include Palace, City walls, Aqueduct, Mass Transit, Power plant or Hydro plant in your count
  3. In the table below, locate the cells whose row number matches your city size, and whose column number matches the city's building count:
    • The first cell contains the city name to re-assign to your city
    • The second cell contains the number of additional random calls necessary before launching the attack
  4. Rename your city accordingly; note1: be very careful to follow the case, spacings, etc.; note2: in Civ EN v474.05, this can be done with the "Rename" button in the City Screen; in other versions, the only way is to hack your SVE file with JCivED, TerraForm, or a plain hex editor
  5. If the number of additional random calls is not '+0', press 'C' or click on the map as many times as required: each time it will redraw the map, increasing the random calls by 1 call; note: if the value is '+0', skip this step
  6. ATTACK!

Note: the city names were inferred from random seeds that result in a successful attack less than 10 calls after exiting the city screen, with the following priority:
  1. First, CIV City Names (EN versions)
  2. Second, real world city names (based on publicly available world cities databases), using the shortest one that matches a seed (max length <12)
  3. Third, generated artifical city names, as short as possible, that match the seeds

Attack table

Buildings ->00112233445566778899101011111212131314141515
Size 1 pross+4*o0+0Rouen+0Zunzu+02+1*W+0Byblos+8*Osovtsy+0Fo+0Khorasan+0zzzzzzzzzzzP+0Urutsu+0Ashrafkheyl+0d+1*Guzurnitsite+0Juynow+0
Size 2 orkenypuszta+0Rud'+0Zariqum+0Adhin+04+6*Tsyryn+0y+2*Ssuyutsun+0Bar+0Yurtsovo+0Moussoukoto+0Tszintszyyan+0Calcutta+0zX+0Q0+6*U+7*
Size 3 Umtata+0Atu+0Ashrafkheyl+0\+0dd0+0Montyonkuy+0Fi+0Wy+0Tharwaniyya+0Ma+0S+0Aixirivali+0L+0Dover+0Fayyadah+0Abas+0
Size 4 Q0+6*Aghorsang+0P0+0p+8*]+5*yyyyyyyyyyy6+0Lukhtowghay+0Reading+0Ajima+0k0+8*Kyumyurdzhii+0n+08+5*L+1*Macao+0zu+0
Size 5 p0+0noquis+0p+0Z2+5*Kottingbrunn+0Abgardu+0Untermixnitz+0Kul'+0Angu+3*cc1+2*Fi+0Kyzylly+0Acek+0Seleucia+0Syunnyulyar+0Pergamon+0
Size 6 Wurnitz+0Quimossoque+0Uj+0Kowtsay+0Muytu+0zz+0My+0Baltimore+0y+0Towszczyzna+0=+4*Samarkand+0vosspass+0wwwwwwwwwww3+0Abgardu+6*b0+0
Size 7 f+0Tun+0F-11+0g0+3*Zuygakhpyur+0A+2*Fahlayn+0Kyudyurlyu+0orkenypuszta+0K+3*M0+5*Baga+0Aa+6*Fo+7*hh0+0Cut+0
Size 8 isvor+0Llorts+0Aynukalay+0zzzzzzzzzzz8+0Uyutnyy+0Arzisk+04+8*zzzzzzzzzzz>+0ozum+0A'o+8*oby+4*Ostayu+0Kyzylly+0Qarovulustu+0Tsundzay+0Navurdzhoy+0
Size 9 Putuhuyu+0Uyts+0ose+0Berlin+0Awshu+8*zv+4*Zu+0Yuntsungssu+0Khorasan+0a0+0Amru+0uurunlu+0Ata+3*Angu+0ozyurt+0Vladivostok+0
Size 10Uo+0b0+0Berlin+0Ngome+0Be+7*]+8*Yuntsungssu+0Ataysu+0Z+4*l+3*K'uy+0etou+0To+2*Cambridge+0zd+0Vixesarri+0
Size 11zzzzzzzzzzzk+0Ngome+0Anjiristan+0Z4+5*Suzkyownsan+0Reading+0Bad+1*z^+5*cc0+1*zu+0uzor+7*m+6*Tlustovousy+0vvvvvvvvvvv4+0>+0zzzzzzzzzzzh+0
Size 12Gy+0N+0J+6*Ut+2*Jup+8*Fahlayn+0Gurguru+06+8*Urussuhy+0z+0V+0Ruwais+0Tur+2*zzzzzzzzzzzU+0Towourougou+0z+7*
Size 13xxxxxxxxxxx9+0Uo+4*Uyutnyy+0Bukyuvtsy+0Dekhtutkarez+0Akhtaray+0f+0Shuways+0Huttonmount+0Zzyzx+0<+0R0+1*d0+0Abrayjan+0Deira+0eye+0
Size 14Abeykheyl+0z+7*z+5*Youyupu+05+3*6+5*z+0S0+8*Urut+0Zzyzx+0strass+0P+0Alikozai+0Me+0Jup+0Kowtayzay+0
Size 15O0+0Zunzu+0Gyursuyu+0Dada+3*Acal+0Z1+0w+0Tyurkyany+0zf+0P0+0Z+0Kurusu+0oyi+0uzim+0h+8*Sutzuyu+0
Size 16Samarkand+0i+0Brighton+0Ajima+0Szwakszty+0Burhanuddin+0D+4*V+7*Rzy+0T+6*zP+3*Sur+0Aca+0Turin+0Ozurnovtsi+0New Orleans+0
Size 17Ai+0Quimossundo+0Cincinnati+0Tutumzar+0d0+8*Hail+0S0+0Mirfah+0Rzy+0Tyurkyany+0uzim+0@+0oyi+0Gyurdzhyulyu+0S+0Bapedi+0
Size 18Ajima+7*Frankfurt+0Ellipi+0zzzzzzzzzzzT+0V+8*Acal+0Ma+0Songyurlu+0Deba+0oby+0St.Louis+0Myusyusly+0Au+5*Q0+3*P+0Vladivostok+0
Size 19Da+5*Ma+0Laiyya+0Wuru+0Bed+0P0+5*Kaifeng+0O'o+3*Ashur+0K'uy+0z`+3*8+02+0zzzzzzzzzzzm+0S+8*z+5*
Size 20zi+0Rouen+0B+5*ozd+0Au+1*Ashur+0Vulturesti+0Cada+6*Athens+0Bowrgowch+08+0Qima+01+8*Yu+0Sowrosu+0=+6*
Size 21Qotur+0Toronto+0Strzyzyno+0oyi+0Bithnah+0Dzhoykhauz+0Y+0om+7*eoux+0ii1+0Hannover+0Syzez+0ee1+0Me+3*Fayzullakhan+0P+0
Size 22oby+2*zzzzzzzzzzzf+0Awtay+0zn+0Khosuy+0zi+0utue+0w+0Tsyryn+0Me+1*Bithnah+3*ee1+0Lors+0Y+3*Ado+0Bum+0
Size 23L+0Dacca+0Ruwais+0Uspu+0f+1*p+0Os+8*Kul'+0V+6*z^+4*t+6*e+0Me+3*F+0uru+0Sy+0
Size 24Oyun+0Ruwais+0J+2*J+0Z+0Ji+0Guzun+0Esturkhusp+0ozd+0U0+0F+6*Buffalo+0Ihaid+0O'o+0Dasa+0Boyuklukyuy+0
Size 25Dam+8*Bithnah+0Smolensk+0Durvu+0Abas+0Bigaverkarez+0Totyuvtsi+0Uyutnyy+0m0+0olyvostanya+0Ado+0Y+7*Cannae+0E+7*eze+0Kpoussoussou+0
Size 26Dam+0Ana+0Myusyusli+0Wullowitz+0Tun+0hh0+0Q+0I+1*J+0Coventry+0Awezrya+0Ust+0Boyuklukyuy+0Tatung+0Me+3*Rzy+0
Size 27Aqsu+0Bu+5*Wullowitz+0Ado+1*noquis+0Bohlulzay+0Bost+0Me+0hh0+0Cambridge+0Ogz+6*Coventry+0strosswitz+0zussdorf+0Dacca+0Achu+0
Size 28Wullowitz+0utz+0Koshtowz+0Dam+2*U+6*x+5*I+6*H+0Hail+0corenisht+0Qima+0zussdorf+0ypsos+5*d+4*Knossos+0Cannae+0
Size 29Santszyatszy+08+3*8+1*Bangalore+0q+0vvvvvvvvvvv:+0Arbela+0Q+0Szszutowo+0zzzzzzzzzzzP+0Cambridge+0zussdorf+0Knossos+0Ramla+0zp+0Ataysu+0
Size 30eye+8*Hyderabad+0Bajikoshteh+0Kyzylly+0Dupusta+0G+0Bozurgkhel+0Kabul+0Tsingtao+0Adozay+0zussdorf+0Knossos+0Ado+6*Guzun+6*Kyzylsay+0opitz+0
Size 31eye+0Kyzylly+0On+0q+0Dupusta+0corrushi+0Q+0Uyun+0m0+5*Cambridge+0Kti+1*Ru'+5*Zamua+0Ust+0Knossos+0Dadnah+0
Size 32q+0Dupusta+0Artaxata+0hh0+0Wuturu+0I+1*Or+3*Amru+0Uyun+0Z3+0Knossos+0Yu+0Cussolulovo+0Durup+0Dacca+0Baj+0
Size 33zzzzzzzzzzzh+0Artaxata+0otz+0zurstrasse+0G+0etou+4*etou+0Amiens+0ozum+0Z3+0Asya+0Zamua+0Sidon+0Sumbrair+1*zn+0m+3*
Size 34x+0G+5*hh0+0I+1*Wuturu+0Alikozai+0Amru+0Z3+0ozum+0Bordeaux+0Yu+0z^+0Cussolulovo+0Ko+3*Sidon+0Dadnah+0
Size 35hh0+0Q+2*Q+0Ashi+0Alikozai+0Coptos+4*O0+0ozum+0Asya+0olyvostanya+0Sevastopol+0uurunlu+0Sidon+0W+0Dacca+0K+7*
Size 36Q+0zzzzzzzzzzzP+0Coptos+0corenisht+0H+0ozum+0zussdorf+0uszog+0uzim+0Calcutta+0Asya+0IXL+2*E-mu+7*Abay+7*Os+2*zzzzzzzzzzzx+0
Size 37m0+7*Coptos+0Alikozai+0Ye+0H+0Amru+0z_+4*uzim+0ios+0Ko+0Knossos+0olyvostanya+0Y0+8*Baj+0Kyustyu+0Naukuzung+0
Size 38etou+0Ozurnovtsi+0Survapantoy+0Kti+0H+0Amiens+0Z3+0comaxtur+0Ko+0wwwwwwwwwww2+0g0+0olst+0Uspu+5*Amray+0Dadnah+0Towslahqowl+0
Size 39zzzzzzzzzzz6+0H+0Ye+0Or+3*Kti+0Boyuklukyuy+0comaxtur+0uzim+0ios+0oksuzkoy+0Afrij+0Yu+0Fara+1*Barishtugak+0Posnovishte+0h0+3*
Size 40H+0Plosnoutz+0Kti+0Au+5*uzim+0yyyyyyyyyyy1+0Ag+0comaxtur+0Pustu+6*ios+0h+3*Posnovishte+0Barikdzhoy+0Busiris+0ozum+0zn+0
 
Last edited:
See what I said about opening "pand...ora's box"? That table from darkpanda is the contents :p

Anyway, just a note that I confirmed the premise of this earlier as soon as Darkpanda mentioned that viewing the city view reseeds the random generator. However, I had some inconsistent results. Hearing that a map-click/redraw causes a rand() call to be made would explain it though.

I think it's hilarious and fantastic that something like this went unnoticed for 20+ years... and is only now made common knowledge by the work of one man. Thank you for sharing this with us, Darkpanda!
 
See what I said about opening "pand...ora's box"? That table from darkpanda is the contents :p

I should change my login to "darkpandora" ;)

However, I had some inconsistent results. Hearing that a map-click/redraw causes a rand() call to be made would explain it though.

I think it's hilarious and fantastic that something like this went unnoticed for 20+ years...

For me, the former explains the latter: even if someone noticed that viewing a city would result in similar odds outcomes for attack, AI moves, tribal huts rewards, etc., which already requires spending quite a lot of time playing CIV and display their city views quite often, then it would still be quite complex to make something useful out of it.

Indeed, in order to build this table, I searched for random sequences where first a "rand(500) = 0", then immediately after, a "rand(2) = 1". This is what I explained earlier about random results being proportional to input argument N:
  • if rand(500) returns 0, then rand(N) will also return 0 for any N<500
  • if rand(2) returns 1, then rand(N) will return values >= 1 for any N>2

If you just play CIV without those considerations, you could have random sequences that make your attacks win, but only under certain conditions, e.g. if the total attack is not too far below the total defense... Otherwise, your legions may be crushing every enemy until they meet their first veteran fortified musketeer or riflemen, which has a defense high enough so that the random trick no longer works.

In addition, the variability by city size and building count really makes it difficult to detect empirically, I believe.

Anyway, there's more in the pandora box, I am preparing a post about tribal hut rewards at the moment ;)
 
It doesn't seem work in WinCiv 1.2.0. Popping the hut after city-screen in the attached file gives different results:

View attachment 361503

Of course I am exclusively considering DOS versions of Civilization... All of them have a very similar binary structure, and very likely share a huge part of their code, which is MS-DOS 16-bit assembly, probably coded in C originally.

As soon as we move to other OSs or platforms, the code structure is more likely to change. Just by the look of it, I would say that the closest version to 16-bit MS-DOS is probably the Amiga version. I believe WinCiv is morbe different, just by looking at the contents of savegames and general UI.
 
Top Bottom