Skip to content

fix(persistence): keep UniverseConfig in sync so saving after a load doesn't drop the world - #5407

Open
soloturn wants to merge 1 commit into
developfrom
fix/world-generator-lost-on-save
Open

soloturn wants to merge 1 commit into
developfrom
fix/world-generator-lost-on-save

Conversation

@soloturn

Copy link
Copy Markdown
Contributor

AI-assisted change proposal. Filed by agent driven by @soloturn via GDD.

Summary

  • Fixes Saving a world clears worldGenerator in manifest.json, making the world unloadable #5395: saving a game after loading an existing one (not creating a new one) dropped the main world's worldGenerator - and the rest of its WorldInfo - from manifest.json, making the world unloadable afterwards.
  • Root cause: ReadWriteStorageManager.addGameManifestToSaveTransaction() rebuilds the manifest's world list purely from Config.getUniverseConfig() on every save. Only "Create New Game" (GameManifestProvider) and headless setup (StateHeadlessSetup) ever populated UniverseConfig. Every other way to start playing an existing save - SelectGameScreen's "Load Game" button, CLI --load-last-game/--create-last-game, multiplayer join - went straight to StateLoading without ever calling UniverseConfig.addWorldManager().
  • Fix: moved that population into InitialiseWorld.step(), the one load process every entry point (new or existing game) already runs through with both the GameManifest and Config in hand. Idempotent for the two paths that already did this themselves.
  • Checked all other open PRs for overlap with worldGenerator/UniverseConfig/InitialiseWorld - none touch this.

Test plan

  • engine:compileJava, engine:checkstyleMain - clean.
  • engine-tests:integrationTest --tests StorageManagerTest - all 11 existing tests still pass (no regression from the extra, idempotent UniverseConfig population).
  • No automated regression test added: the only existing harness exercising this load process (MTE's TestingStateHeadlessSetup) is itself modeled on StateHeadlessSetup, the one path that was never buggy, so it can't reproduce the missing-population precondition without new test infrastructure. Verified instead by tracing every StateLoading entry point, and empirically by temporarily reverting the fix and diagnostically printing UniverseConfig state under MTE (confirmed populated either way there, for the reason above - meaning the bug is real but only reachable through the GUI/CLI load paths, not the existing integration-test harness).

Related

…doesn't drop the world

Fixes #5395. ReadWriteStorageManager rebuilds the manifest's world list purely
from UniverseConfig on every save - but only "Create New Game"
(GameManifestProvider) and headless setup (StateHeadlessSetup) ever populated
it. Every other load path (SelectGameScreen's "Load Game", CLI
--load-last-game/--create-last-game, multiplayer join) went straight to
StateLoading without ever calling addWorldManager, so the first save after
loading an existing game silently dropped that world - including its
worldGenerator - from manifest.json, making it unloadable afterwards.

Moved the population into InitialiseWorld.step(), the one load process every
entry point (new game or existing) already runs through with both the
manifest and the Config in hand. Idempotent for the two paths that already
did this themselves.

No automated regression test: the only existing harness exercising this load
process (MTE's TestingStateHeadlessSetup) is itself modeled on the one path
that was never buggy, so it can't reproduce the missing-population precondition
without disproportionate new test-infrastructure work. Verified by tracing
every StateLoading entry point and confirming (via a temporary diagnostic
assertion, since removed) that UniverseConfig is empty at save time without
this fix and populated with it, on both the CLI/GUI-review-of-code path and
the MTE-bootstrapped integration tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the Type: Bug Issues reporting and PRs fixing problems label Sep 28, 2026
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 372c1ee6-b41e-4ce4-8dc7-68114ca7d754

📥 Commits

Reviewing files that changed from the base of the PR and between e110223 and 60201f7.

📒 Files selected for processing (1)
  • engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseWorld.java

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • World initialization now keeps the universe’s world information, spawn-world title, and seed in sync with the selected world and game manifest. This applies across all world initialization paths that reach this step.

Walkthrough

World initialization now adds the current WorldInfo to UniverseConfig, sets the spawn-world title from the world title, and copies the manifest seed into the universe configuration.

Changes

World initialization

Layer / File(s) Summary
Update universe configuration
engine/src/main/java/org/terasology/engine/core/modes/loadProcesses/InitialiseWorld.java
After ensuring the world has a seed, step() adds its WorldInfo to UniverseConfig, sets the spawn-world title from the world title, and copies the game manifest seed into the universe configuration.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 60201

No actionable merge-blocking issue is established. Preservation of externally populated additional worlds remains uncertain, but normal existing-game loading has not been shown to use them.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 60201

The change addresses a world-loss-on-save path, but it also allows joined-server world metadata to remain in the client’s local configuration after the session. The observed exposure is limited to gameplay metadata; no credential exposure or unauthorized game save was established.

Retained concerns

  • Low · security · inferred: A joined server’s WorldInfo now enters the client’s persistent universe configuration, even though the client is not authorized to save the game manifest. This crosses a session-to-local-configuration boundary; the observed impact is limited to world metadata, and a subsequent successful main-world initialization replaces that entry.
Security review details

Security Blast Radius

  • inferred — The demonstrated new remote-controlled persistence surface is a joined client’s local universe configuration, not an authority-controlled game manifest or another player’s save.

Trust Boundaries and Controls

  • observed — Initialization requires a main world with a valid generator identifier; normal save-on-exit requires network authority. Neither control prevents the newly added configuration assignment from occurring on a joined client.

Resilience and Maintainability Implications

  • inferred — Failure before generator registration limits direct game-save impact, but a later configuration shutdown can still record metadata from that unsuccessful load.

Hardening Proposals

  • proposed — Consider keeping joined-server metadata session-scoped and committing persistent world configuration only after successful initialization when local ownership is intended.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: keeping UniverseConfig synchronized so saves after loading a world preserve its data.
Description check ✅ Passed The description directly explains the affected save and load behavior, root cause, fix, validation, and test limitations.
Linked Issues check ✅ Passed The change addresses #5395. InitialiseWorld.step() now adds the loaded main-world WorldInfo to UniverseConfig before save services initialize. WorldInfo contains the valid worldGenerator, so…
Out of Scope Changes check ✅ Passed The diff changes only InitialiseWorld.java. It populates UniverseConfig with the loaded world data required to preserve worldGenerator, the spawn-world title, and the universe seed during saves.…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks the world’s new seed,
And adds its name with careful speed.
The spawn-world title finds its place,
While config keeps the world’s trace.
Then off I hop through fields of green!

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

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

Labels

Type: Bug Issues reporting and PRs fixing problems

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Saving a world clears worldGenerator in manifest.json, making the world unloadable

2 participants