Skip to content

Fix undo corruption when merged house becomes vacant - #795

Draft
csfreitas wants to merge 3 commits into
bvschaik:masterfrom
csfreitas:fix/775-undo-empty-merged-house
Draft

Fix undo corruption when merged house becomes vacant#795
csfreitas wants to merge 3 commits into
bvschaik:masterfrom
csfreitas:fix/775-undo-empty-merged-house

Conversation

@csfreitas

Copy link
Copy Markdown
Contributor

Summary

  • Disable Undo before a merged house is split into vacant lots.
  • Add a regression test using the existing brugle-lugdunum-native.sav fixture.

Root cause

When a merged house becomes empty, building_house_change_to_vacant_lot() keeps one building record and creates three additional vacant lots. This path did not invalidate an Undo operation that was still being prepared.

During a clear-land transaction, game_undo_contains_building() cannot protect the recorded building IDs while game_can_undo() is false (ready == 0). A newly created vacant lot can therefore reuse the ID of a demolished building. Performing Undo later restores the old building record over that ID, while the vacant-lot tile continues to reference it.

The corrupted save from #775 contains the resulting invariant violation: tile (50, 63) references building ID 414, but building 414 is a 2x2 villa at (43, 83).

The fix follows the existing policy used by other house merge and split paths: structural simulation changes that create building records invalidate the pending Undo operation.

Regression test

The test starts a clear-land Undo transaction, makes an existing merged house become vacant, finishes the transaction, and verifies that Undo is no longer available. Before the fix, Undo remains available and permits the unsafe ID reuse path.

Validation

  • CTest: 37/37 tests passed with Visual Studio 2022.
  • Fork CI passes on Windows MSVC, Windows 32/64-bit, Linux, macOS, Android, iOS, Nintendo Switch, and PS Vita.

CI note

The Emscripten job currently fails at the linker before tests run. The workflow installs emsdk latest, which now resolves to 6.0.9; the previous successful upstream run used 6.0.1. The failure is missing C++ RTTI symbols from libembind-rtti.a and is unrelated to this change.

Fixes #775

crudelios added a commit to Keriew/augustus that referenced this pull request Sep 5, 2026
@crudelios

Copy link
Copy Markdown
Collaborator

Good catch! I did disable undo on house evolution merge/split, but missed this one.

@bvschaik

bvschaik commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Could you detail the steps required to reproduce the bug so I can check whether the fix works?

I tried to reproduce but I could not. I built some statues (to be deleted later), then some houses. I caused the people to emigrate with high taxes and 0 wages. I deleted the statues just before a 2x2 merged house got vacated. The 3 vacant lots chose building ids outside the deleted statue range due to this bit of code in src/building/building.c:

building *building_create(building_type type, int x, int y)
{
building *b = 0;
for (int i = 1; i < MAX_BUILDINGS; i++) {
if (all_buildings[i].state == BUILDING_STATE_UNUSED && !game_undo_contains_building(i)) {
b = &all_buildings[i];
break;
}
}

The code for creating a building explicitly checks the undo list, so they are not reused.

Also, please don't bother with writing a test for this - the tests are only for integration testing the game logic, not these kind of bugs.

@csfreitas
csfreitas marked this pull request as draft September 5, 2026 19:59
@csfreitas

Copy link
Copy Markdown
Contributor Author

Thanks for checking this, Bianca. You are right about the building-ID protection, and I need to correct my earlier explanation.

My original diagnostic forced the house to become vacant between game_undo_start_build() and game_undo_finish_build(). During that interval, undo is not ready, so game_undo_contains_building() returns false. However, normal gameplay does not advance the simulation while building_construction_in_progress() is true. The diagnostic bypassed that restriction, so it was not a valid gameplay reproduction.

I then ran a separate local, headless diagnostic without this PR's fix, using the normal construction APIs and game_run(), with maximum taxes and zero wages. Across three existing saves, I observed 38 merged-to-vacant transitions. Four occurred while undo was available. In all four, the new lots avoided the protected statue ID, and undo restored the statue without duplicate map references to that ID. This was not a manual GUI reproduction and does not rule out other causes of #775.

The test in this PR only checks that undo gets disabled; it does not demonstrate the reported corruption. Understood about the integration suite: I will not pursue adding this diagnostic there.

I have moved this PR to draft. I do not currently have valid reproduction steps for the proposed cause, and the existing fix should not be treated as established. Sorry for presenting the earlier result too confidently, and thank you for catching this. The next step is to identify a sequence reachable through normal gameplay before proposing a correction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Houses or tiles seem to be linked

3 participants