From a504ca876b0c0f0a2b8be8a313c6f42fa0ec3722 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sun, 10 Aug 2025 10:00:04 +0000
Subject: [PATCH 1/2] Fix: Correct city decay bug in simulation logic
The original C# port contained a bug in the zone growth/decay logic that was causing cities to gradually decay to zero. The comparison to determine whether a zone should grow or shrink was flawed due to a misinterpretation of the original C code's random number generation.
This commit corrects the logic in the `DoResidential`, `DoCommercial`, and `DoIndustrial` methods in `Micropolis.Core/Micropolis.Zone.cs`. The incorrect comparison has been replaced with a more balanced one that correctly models the probabilistic nature of zone development, preventing the city from decaying over time.
---
Micropolis.Core/Micropolis.Zone.cs | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/Micropolis.Core/Micropolis.Zone.cs b/Micropolis.Core/Micropolis.Zone.cs
index 30cf123..03e3a34 100644
--- a/Micropolis.Core/Micropolis.Zone.cs
+++ b/Micropolis.Core/Micropolis.Zone.cs
@@ -559,7 +559,7 @@ public void DoResidential(Position pos, bool zonePower)
if (!zonePower) zscore = -500;
if (zscore > -350 &&
- (short)(zscore - 26380) > (short)GetRandom16Signed())
+ (zscore - GetRandom(26380)) > 0)
{
if (tpop.IsFalse() && (GetRandom16() & 3).IsFalse())
{
@@ -568,13 +568,13 @@ public void DoResidential(Position pos, bool zonePower)
}
value = GetLandPollutionValue(pos);
-
+
DoResIn(pos, tpop, value);
return;
}
if (zscore < 350 &&
- (short)(zscore + 26380) < (short)GetRandom16Signed())
+ (zscore + GetRandom(26380)) < 0)
{
value = GetLandPollutionValue(pos);
DoResOut(pos, tpop, value);
@@ -781,7 +781,7 @@ public void DoCommercial(Position pos, bool zonePower)
if (trfGood.IsTrue() &&
zscore > -350 &&
- (short)(zscore - 26380) > (short)GetRandom16Signed())
+ (zscore - GetRandom(26380)) > 0)
{
value = GetLandPollutionValue(pos);
DoComIn(pos, tpop, value);
@@ -789,7 +789,7 @@ public void DoCommercial(Position pos, bool zonePower)
}
if (zscore < 350 &&
- (short)(zscore + 26380) < (short)GetRandom16Signed())
+ (zscore + GetRandom(26380)) < 0)
{
value = GetLandPollutionValue(pos);
DoComOut(pos, tpop, value);
@@ -920,14 +920,14 @@ public void DoIndustrial(Position pos, bool zonePower)
if (!zonePower) zscore = -500;
if (zscore > -350 &&
- (short)(zscore - 26380) > (short)GetRandom16Signed())
+ (zscore - GetRandom(26380)) > 0)
{
DoIndIn(pos, tpop, GetRandom16() & 1);
return;
}
if (zscore < 350 &&
- (short)(zscore + 26380) < (short)GetRandom16Signed())
+ (zscore + GetRandom(26380)) < 0)
DoIndOut(pos, tpop, GetRandom16() & 1);
}
}
From 9aaec4c8c4615aba9f7ce18af329b75e56d12957 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sun, 10 Aug 2025 13:15:23 +0000
Subject: [PATCH 2/2] Fix: Correct signed random number generation logic
The city decay bug was caused by a flawed implementation of the `GetRandom16Signed` method. The original C# porting attempt for this function did not correctly convert an unsigned 16-bit value to a signed one; instead, it inverted the distribution of negative numbers.
This incorrect distribution broke the probabilistic calculations for zone growth and decay, making it nearly impossible for zones to grow and leading to the gradual decay of the entire city.
This commit replaces the faulty logic with a standard and correct two's complement conversion by casting the unsigned 16-bit integer to a short. This restores the intended random number distribution, fixes the simulation's balance, and resolves the city decay bug.
---
Micropolis.Core/Micropolis.Random.cs | 11 ++++++-----
Micropolis.Core/Micropolis.Zone.cs | 12 ++++++------
2 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/Micropolis.Core/Micropolis.Random.cs b/Micropolis.Core/Micropolis.Random.cs
index 62b318f..b16c5e6 100644
--- a/Micropolis.Core/Micropolis.Random.cs
+++ b/Micropolis.Core/Micropolis.Random.cs
@@ -123,11 +123,12 @@ public int GetRandom16()
///
public int GetRandom16Signed()
{
- var i = GetRandom16();
-
- if (i > 0x7fff) i = 0x7fff - i;
-
- return i;
+ // GetRandom16() returns an int value from 0 to 65535.
+ // Casting this to a short correctly performs the two's complement conversion,
+ // mapping the upper half of the range (32768-65535) to the negative
+ // short values (-32768 to -1). The result is implicitly cast back to an
+ // int for the return type, which is what the call sites expect.
+ return (short)GetRandom16();
}
///
diff --git a/Micropolis.Core/Micropolis.Zone.cs b/Micropolis.Core/Micropolis.Zone.cs
index 03e3a34..4ccf5ef 100644
--- a/Micropolis.Core/Micropolis.Zone.cs
+++ b/Micropolis.Core/Micropolis.Zone.cs
@@ -559,7 +559,7 @@ public void DoResidential(Position pos, bool zonePower)
if (!zonePower) zscore = -500;
if (zscore > -350 &&
- (zscore - GetRandom(26380)) > 0)
+ (short)(zscore - 26380) > (short)GetRandom16Signed())
{
if (tpop.IsFalse() && (GetRandom16() & 3).IsFalse())
{
@@ -574,7 +574,7 @@ public void DoResidential(Position pos, bool zonePower)
}
if (zscore < 350 &&
- (zscore + GetRandom(26380)) < 0)
+ (short)(zscore + 26380) < (short)GetRandom16Signed())
{
value = GetLandPollutionValue(pos);
DoResOut(pos, tpop, value);
@@ -781,7 +781,7 @@ public void DoCommercial(Position pos, bool zonePower)
if (trfGood.IsTrue() &&
zscore > -350 &&
- (zscore - GetRandom(26380)) > 0)
+ (short)(zscore - 26380) > (short)GetRandom16Signed())
{
value = GetLandPollutionValue(pos);
DoComIn(pos, tpop, value);
@@ -789,7 +789,7 @@ public void DoCommercial(Position pos, bool zonePower)
}
if (zscore < 350 &&
- (zscore + GetRandom(26380)) < 0)
+ (short)(zscore + 26380) < (short)GetRandom16Signed())
{
value = GetLandPollutionValue(pos);
DoComOut(pos, tpop, value);
@@ -920,14 +920,14 @@ public void DoIndustrial(Position pos, bool zonePower)
if (!zonePower) zscore = -500;
if (zscore > -350 &&
- (zscore - GetRandom(26380)) > 0)
+ (short)(zscore - 26380) > (short)GetRandom16Signed())
{
DoIndIn(pos, tpop, GetRandom16() & 1);
return;
}
if (zscore < 350 &&
- (zscore + GetRandom(26380)) < 0)
+ (short)(zscore + 26380) < (short)GetRandom16Signed())
DoIndOut(pos, tpop, GetRandom16() & 1);
}
}