PaperMC / PaperMC/Paper

Parallel structure generation corrupts nether fortresses / strongholds

Open
#14,176 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: needs triage
Dominant language
Java
Stars
12.7k
Forks
3.5k
Avg merge
3d 13h
Merged PRs (30d)
11

Description

Expected behavior

Identical structure layout to vanilla for the same seed.

Observed/Actual behavior
  • Nether fortress starts that are generated concurrently (e.g. during the spawn pre-generation at world load) end up ~1/4 to 1/5 of the vanilla size: the shared weight list is exhausted early, corridors get sealed with BridgeEndFiller, castle sections never start. The opposite corruption also occurs: the other fortress' StartPiece constructor resets the shared counter, so hasAnyPieces stays true and the fortress grows abnormally (up to MAX_DEPTH=30), producing bridge sections that do not exist in vanilla.
  • StrongholdPieces contains the same shared mutable state and therefore the same race carrier. We could not observe it in normal play — stronghold starts are sparse (concentric-ring placement; adjacent starts ≥ ~1800 blocks apart), so two are never generated concurrently in practice — and we have not attempted to force it. The stronghold fix is therefore preventive, based on the identical shared-state pattern rather than a reproduced failure.
Steps/models to reproduce
  1. Seed 5535345, new world, generate-structures=true.
  2. Paper pre-generates the nether spawn area (~10 chunk radius) at startup. For this seed it contains two fortress start chunks [-6,-5] and [-5,7], which land in different 16×16 scheduler cells and are generated concurrently by two worker threads.
  3. Compare with vanilla: piece counts are 23 / 19 instead of 100 / 121.

Control: the fortress at [18,17], generated alone, matches vanilla piece-for-piece (37 / 37) — the same code produces identical output without concurrency, isolating the race.

Plugin and Datapack List

N/A — reproduced on a clean server with default datapacks, no plugins involved.

Paper version

Paper version 26.2-111-main@f570646 (2026-08-07T16:16:19Z) (Implementing API version 26.2.build.111-stable)

Other

Evidence it is a shared-state race (instrumented runs, before the fix, seed 5535345, nether spawn area):

  • Shared state across threads: both worker threads observe the same weight object identities (e.g. BridgeStraight@438835198 on both Worker #0 and Worker #2), because each StartPiece constructor resets and shares the same static array elements.
  • Interleaved increments: the shared counter is incremented by both threads — one thread's observed sequence 2,3,4,5,6,7,8,10,11,12,15 skips values the other thread incremented.
  • Early weight removal: the affected fortress's updatePieceWeight removes weights as "filled" ahead of its own generation (totalWeight 45 → 35 → -1); seal-off ratio is ~4× vanilla (8/19 vs 12/121 BridgeEndFiller).
  • Control: the fortress generated alone ([18,17]) matches vanilla piece-for-piece in every run.

The analysis above is based on two real machine runs. Both Paper and Vanilla were instrumented at the same point to print detailed structure generation behavior to the console. The original logs are available here (It seems log file is too large and can't be uploaded to GitHub gist; I've hosted it on my own Cloudflare R2 bucket):

Cause: The parallel STRUCTURE_STARTS execution originates from Moonrise's chunk scheduler (parallelCapableStatus), while vanilla structure generation assumes serialized execution, which is why the shared static state has never been a problem upstream. The comment "CB has fixed the concurrency issue with stronghold generations" predates the 1.18 structure rewrite and does not appear to apply to the current structure implementation.

Related:

About AI Assistance

An AI agent helped with most of the coding work and explaining the code logic, and the issue/PR were polished with LLM assistance.

I personally confirmed the bug, decided the fix plan, and carried out the practical testing.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the linked draft Paper PR and the upstream Moonrise issue and PR, then reproduce seed 5535345 with concurrent spawn-area generation and compare the fortress piece counts with vanilla. Done means fortress and stronghold generation no longer share mutable state across concurrent starts and the generated layouts match vanilla.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
distributed-systems, game-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.