Skip to content

Retype bastions to match their world block - #31

Open
grepsedawk wants to merge 1 commit into
EdenMinecraft:mainfrom
grepsedawk:bastion-sponge-reconcile
Open

Retype bastions to match their world block#31
grepsedawk wants to merge 1 commit into
EdenMinecraft:mainfrom
grepsedawk:bastion-sponge-reconcile

Conversation

@grepsedawk

Copy link
Copy Markdown

What

Heal bastions whose stored type no longer matches their world block.

Why

The legacy "old bastion" migration added the bastion_type column with
DEFAULT 'citybastion', so every pre-existing bastion was recorded as a
50-radius city bastion while its world block stayed SPONGE. Those bastions
enforce the wrong field and drop the wrong item when broken.

The correct type can only be recovered from the block itself — the lone type
column in the table is the corrupted one — and a block can only be read once
its chunk is loaded. So a SQL migration can't fix this, and a proactive
startup sweep would have to force-load the whole world and freeze the server.

How

A ChunkLoadEvent listener retypes any bastion whose stored type no longer
matches its block, treating the block as the source of truth. The world heals
gradually as chunks load during normal play, and the correction persists
across restarts.

It stays cheap on the hot path:

  • only inspects bastions the index already knows are in the chunk (one block
    read each — no block scanning);
  • retypes in place, so it never structurally mutates the bastion set that
    the async erosion/regen tasks iterate (avoids ConcurrentModificationException);
  • batches the DB writes off the main thread.

Notes

  • Pins JDK 21 via mise.toml so the build runs on the required toolchain.
  • Sponge-orphan recovery (a reinforced sponge with no row, left behind by the
    destroy-on-cache-miss bug) is intentionally out of scope here — it needs a
    block sweep or a Citadel per-chunk reinforcement enumeration, which is a
    cleaner follow-up.

Testing

./gradlew :plugins:bastion-paper:test — green (11 tests).

The legacy "old bastion" migration added the bastion_type column
with DEFAULT 'citybastion', so every pre-existing bastion was
recorded as a 50-radius city bastion while its world block stayed
SPONGE. Those bastions enforce the wrong field and drop the wrong
item when broken.

The correct type can only be recovered from the block itself --
the lone type column in the table is the corrupted one -- and a
block can only be read once its chunk is loaded. So a SQL
migration can't fix this, and a proactive startup sweep would have
to force-load the whole world and freeze the server. Instead, heal
lazily: when a chunk loads, retype any bastion whose stored type
no longer matches its block. Each block is read exactly when it is
already in memory, the work spreads across normal play, and the
correction persists across the daily restarts.

This stays cheap on the hot path -- it only inspects bastions the
index already knows are in the chunk, retypes in place (never
structurally mutating the set the async erosion tasks iterate),
and batches the DB writes off the main thread.

Pin JDK 21 via mise so the build runs on the required toolchain.
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