Skip to content

fix: reconcile the phase index with the phases folder on load#541

Merged
tastybento merged 3 commits into
developfrom
fix/phase-index-reconcile
Jul 21, 2026
Merged

fix: reconcile the phase index with the phases folder on load#541
tastybento merged 3 commits into
developfrom
fix/phase-index-reconcile

Conversation

@tastybento

Copy link
Copy Markdown
Member

Problem

The 1.26.0 phase index shipped as a preset. Upgrading servers received the stock phases_index.yml, which references the current jar's file names — but their phases/ folders hold older or customized layouts. Community feedback showed the fallout:

  • Phases whose file names differed from the stock index silently failed to load ("15 out of 20 phases").
  • Sulfur Caves could never appear on upgraded servers — phase files are only copied from the jar on fresh installs.
  • The admin phases GUI faithfully rendered the preset index, not the server's reality, with wrong starts/lengths and mostly stone icons.

Fix: reconcile the index with the folder on every load

The folder is the source of truth for which phases exist; the index owns order and enabled state:

  • An entry whose file and section exist on disk keeps its place.
  • An entry whose file is missing is re-pointed at a folder file holding a phase with the same name (follows shipped file renames like 11000_deep_dark10500_deep_dark); failing that, its file is restored from the addon jar (recovers phases added by upgrades, e.g. Sulfur Caves); failing that, the entry is removed with a warning.
  • Folder files unknown to the index are added: numeric section keys slot in by legacy start block with the implied length; non-numeric keys go to the end with the default length.
  • When repair was needed — or the index was freshly copied over an existing folder — lengths are refreshed from the gaps between the files' legacy keys, preserving the layout the server actually ran. A clean, matching index is never rewritten.

Also in this PR

  • Shift-left-click to set a phase length in /oba phases, via a chat prompt showing the current value (whole numbers > 0, cancel keeps it). The first edit writes adminLengths: true to the index, permanently stopping reconciliation from recomputing lengths, so admin values survive later file additions/renames.
  • Numeric names/keys are now optional for custom phase files — keys are only start blocks for the legacy non-index loader. Chest files pair by file name, so they work unchanged. A non-numeric section must look like a phase (name/blocks/fixedBlocks/mobs) to be picked up.
  • GUI polish from feedback: icons fall back to the phase's first block instead of stone, the how-to-use book wraps onto four lines, lore shortened for small screens, and the phases_index.yml header documents the new behavior.

Testing

  • 551 unit tests pass, including 7 new ones covering re-point by name, jar restore, unindexed adds (keyed and unkeyed with chests), dead-entry removal, no-op stability, and admin-length protection.
  • Verified end-to-end on a real Paper 26.2 server with a recreated legacy phases folder plus the stock index: all four renames re-pointed, Sulfur Caves restored and loaded, lengths matched the server's true pre-upgrade layout (e.g. Desert 1000, not the preset 500), and a second boot reconciled nothing. Simulated admin length edits survived reboots.

🤖 Generated with Claude Code

https://claude.ai/code/session_013C4BPNygFKjoQenDwTyMNY

tastybento and others added 3 commits July 20, 2026 18:14
The 1.26.0 phase index shipped as a preset: upgrading servers got the
stock phases_index.yml, which references the current jar's file names.
Servers with older or custom phase layouts silently lost every phase
whose file name differed, never received Sulfur Caves (phase files are
only copied on fresh installs), and the admin phases GUI showed the
preset instead of the server's reality.

The index is now reconciled with the phases folder on every load - the
folder is the source of truth for which phases exist:

- An entry whose file and section exist on disk keeps its place.
- An entry whose file is missing is re-pointed at a folder file holding
  a phase with the same name (follows shipped file renames), failing
  that its file is restored from the addon jar (recovers phases added
  by an upgrade), failing that it is removed.
- Folder files not in the index are added. Numeric section keys give a
  position and length from the legacy start-block layout; non-numeric
  keys - now fully supported for custom phases, chests pair by file
  name so they work too - go to the end with the default length for
  the admin to arrange in the GUI.
- When repair was needed, or the index was freshly copied over an
  existing folder, lengths are refreshed from the gaps between the
  files' legacy keys, preserving the layout the server actually ran.

The admin phases GUI also gains shift-left-click to set a phase's
length via a chat prompt that shows the current value. The first such
edit writes adminLengths: true to the index, which permanently stops
reconciliation from recomputing lengths, so admin values survive later
file additions or renames.

GUI polish from feedback: phase icons fall back to the phase's first
block instead of stone, the how-to-use book wraps onto four lines, and
the lore lines are shortened for small screens.

Verified end-to-end on a real 26.2 server with a recreated legacy
phases folder plus the stock index: all renames re-pointed, Sulfur
Caves restored, lengths matched the server's true layout, and a second
boot reconciled nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013C4BPNygFKjoQenDwTyMNY
- Replace the Bukkit conversation API (deprecated for removal in Paper)
  with a direct AsyncChatEvent listener for the phase length prompt.
  Same behaviour: prompt shows the current length, whole numbers above
  zero apply, invalid input re-prompts, the cancel word or a 60 second
  timeout keeps the length. Input is applied on the main thread.
- Cut cognitive complexity: loadPhases delegates to setUpNewFolder and
  loadUsingIndex; scanPhaseFolder delegates per file and per section;
  reconcileIndex is decomposed into reconcileEntry, matchOnDisk,
  addKeyedDiscoveries, addUnkeyedDiscoveries, and
  refreshLengthsFromFolder around a small Reconciliation state object.
  No behaviour change - all 552 tests pass unchanged.
- Use static imports for Mockito times in tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013C4BPNygFKjoQenDwTyMNY
@sonarqubecloud

Copy link
Copy Markdown

@tastybento
tastybento merged commit 3ed5424 into develop Jul 21, 2026
3 checks passed
@tastybento
tastybento deleted the fix/phase-index-reconcile branch July 21, 2026 04:27
@tastybento tastybento mentioned this pull request Jul 21, 2026
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.

1 participant