Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible infrastructure count overflow leading to negative infrastructure maintenance costs #7665

Open
James103 opened this issue Jul 22, 2019 · 1 comment
Labels

Comments

@James103
Copy link
Contributor

@James103 James103 commented Jul 22, 2019

Version of OpenTTD

Tested on 20190707-master-g1e723934a1, though bug probably present since the dawn of OpenTTD

Expected result

When you have more than 2,147,483,647 road pieces, then the infrastructure count should just keep on going up with no overflow.

Actual result

When you have more than 2,147,483,647 road pieces, the infrastructure count in the company window does not overflow right away, but in the infrastructure detail window, it does. Additionally, without NewGRFs and with default settings, the amount you are paying for road infrastructure maintenance yearly (proportional to approximately [count]^1.5) flips from positive £32.654 trillion to negative £32.654 trillion.

EDIT: At 4,294,967,296 road pieces (and all multiples thereof), the infrastructure count in the company window DOES overflow, going back to 0. In both cases, the cause is that all infrastructure counts are stored as 32-bit integers (uint32 in the company window, signed int32 for infrastructure details window and infrastructure cost calculation).

Savegame to test/reproduce

Sharkey & Co., 3002-07-20D (overflow).zip
(This uses a custom AI, which you can get below in step 4.)

Steps to reproduce

  1. Load the attached scenario: Test scenario.zip
  2. Pause the game.
  3. Make sure to set the maximum tunnel length to 4,096 tiles.
  4. Load and start up the attached AI: BaseAI.zip
  5. Give yourself £1 quadrillion (1,000,000,000,000,000) on both companies (company 0 and 1). NOTE: You will have to use an external program such as Cheat Engine or AutoHotKey to accomplish this, as the change money cheat would take several tens of millions of clicks to get the required amount of money.
  6. Make sure to turn on the tunnel cross cheat. If you don't, the next step will take up to several real-life days to complete, depending on your system (time spent checking against existing tunnels).
  7. Unpause the game and wait approximately 2.5 ingame years, making sure to save every month or 3 months.
  8. Watch as the infrastructure counter overflows.
@James103

This comment has been minimized.

Copy link
Contributor Author

@James103 James103 commented Sep 20, 2019

Company infrastructure counts are stored as 32-bit unsigned (uint) integers:

struct CompanyInfrastructure {
uint32 road[ROADTYPE_END]; ///< Count of company owned track bits for each road type.
uint32 signal; ///< Count of company owned signals.
uint32 rail[RAILTYPE_END]; ///< Count of company owned track bits for each rail type.
uint32 water; ///< Count of company owned track bits for canals.
uint32 station; ///< Count of company owned station tiles.
uint32 airport; ///< Count of company owned airports.

However, infrastructure counts are also stored and updated as if they were 32-bit signed (int) integers:

OpenTTD/src/road_cmd.cpp

Lines 199 to 206 in 81614f2

void UpdateCompanyRoadInfrastructure(RoadType rt, Owner o, int count)
{
if (rt == INVALID_ROADTYPE) return;
Company *c = Company::GetIfValid(o);
if (c == nullptr) return;
c->infrastructure.road[rt] += count;

The same signed 32-bit infrastructure counts are used to calculate the maintenance cost of a piece of infrastructure in this function:

OpenTTD/src/road_func.h

Lines 127 to 131 in 672c857

static inline Money RoadMaintenanceCost(RoadType roadtype, uint32 num, uint32 total_num)
{
assert(roadtype < ROADTYPE_END);
return (_price[PR_INFRASTRUCTURE_ROAD] * GetRoadTypeInfo(roadtype)->maintenance_multiplier * num * (1 + IntSqrt(total_num))) >> 12;
}

which is invoked in:
if (c->infrastructure.road[rt] != 0) cost.AddCost(RoadMaintenanceCost(rt, c->infrastructure.road[rt], RoadTypeIsRoad(rt) ? road_total : tram_total));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.