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
- Seed
5535345, new world, generate-structures=true.
- 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.
- 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.
Expected behavior
Identical structure layout to vanilla for the same seed.
Observed/Actual behavior
BridgeEndFiller, castle sections never start. The opposite corruption also occurs: the other fortress'StartPiececonstructor resets the shared counter, sohasAnyPiecesstays true and the fortress grows abnormally (up toMAX_DEPTH=30), producing bridge sections that do not exist in vanilla.StrongholdPiecescontains 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
5535345, new world,generate-structures=true.[-6,-5]and[-5,7], which land in different 16×16 scheduler cells and are generated concurrently by two worker threads.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):BridgeStraight@438835198on bothWorker #0andWorker #2), because eachStartPiececonstructor resets and shares the same static array elements.2,3,4,5,6,7,8,10,11,12,15skips values the other thread incremented.updatePieceWeightremoves weights as "filled" ahead of its own generation (totalWeight45 → 35 → -1); seal-off ratio is ~4× vanilla (8/19vs12/121BridgeEndFiller).[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_STARTSexecution 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.