diff --git a/CLAUDE.md b/CLAUDE.md index 313468d..8b2b909 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Rewild -Registered in the owner's ops process (`~/Code/ops`). Area: `minecraft-plugins` (lead: Minecraft: Plugins Lead). Added 2026-11-09 from the ops starter kit; the repo itself dates from 2026-11-07 (as WildernessRegen). +Registered in the owner's ops process (`~/Code/ops`). Area: `minecraft-plugins` (lead: Minecraft: Plugins Lead). Added 2026-11-09 from the ops starter kit; the repo itself dates from 2026-11-07. **Before doing anything here, run `~/Code/ops/scripts/repo-context`.** It shows who leads this repo, whether that session is active, what's pending, and which briefs apply. If it says the repo has no active owner, tell the director ("Helm") before starting work. diff --git a/README.md b/README.md index 4793e99..77d8a0d 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Before either kind of change, the plugin checks the bedrock pattern of the fresh If the server has no claim plugin at all, set `claims.plugin: none`. -**Upgrading from 0.2.x:** 0.2.x only recorded some placed blocks, so chunks it tracked stay unknown and never rewild. Only land generated after the upgrade can. The `intent` settings in config.yml do nothing now and can be deleted. `/wr`, `/wildernessregen` and the `wildernessregen.admin` permission are gone; use `/rewild` and `rewild.admin`. +**Upgrading from 0.2.x:** 0.2.x only recorded some placed blocks, so chunks it tracked stay unknown and never rewild. Only land generated after the upgrade can. The `intent` settings in config.yml do nothing now and can be deleted.admin` permission are gone; use `/rewild` and `rewild.admin`. ## Commands diff --git a/build.gradle.kts b/build.gradle.kts index 04315fb..e41dc72 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } group = "com.extantape" -version = "0.3.1" +version = "0.3.2" java { toolchain.languageVersion.set(JavaLanguageVersion.of(25)) diff --git a/docs/decisions.md b/docs/decisions.md index 9efd82b..3285e40 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -22,3 +22,7 @@ Decisions in the design brief that weren't reopened: off by default in every wor - Plugins report through `KeepProvider` (whole chunks, as before, and now block areas) or with `/rewild keep [] plugin: [note]` and `/rewild unkeep`. An admin's keeps always count; a plugin's count while it's respected. `--json` on `keep list` and `plugins`. - Player-placed blocks are found by Rewild's own tracking, which now records every placed block. CoreProtect stays optional and unused, so Rewild doesn't depend on it while it's off on 26.3. Chunks generated before 0.3.0's tracking began stay unknown, because 0.2.x recorded only some placed blocks. - `/wr`, `/wildernessregen` and `wildernessregen.admin` are removed, as announced for the release after 0.2.x. + +## 2026-11-14 + +- **No old names (owner, D196):** "Make sure none of the plugins retain old references or commands". The folder move from the plugin's earlier name and its tests are removed. Released as 0.3.2. diff --git a/docs/releases/v0.3.2.md b/docs/releases/v0.3.2.md new file mode 100644 index 0000000..6f78bd4 --- /dev/null +++ b/docs/releases/v0.3.2.md @@ -0,0 +1,9 @@ +No old names (owner, D196). Rewild no longer looks for the folder of the plugin it was renamed from, and its last mentions are gone from the docs and tests. Nothing changes for a server already on 0.3.x. + +## Upgrading + +Replace the jar. No config or data changes. + +## Tested + +Unit tests pass. diff --git a/docs/test-audit.md b/docs/test-audit.md index c60c196..28df0ab 100644 --- a/docs/test-audit.md +++ b/docs/test-audit.md @@ -48,11 +48,9 @@ Rewild changes world files, so its safety promises come first. "Acceptance" is ` | Crash-safe at every step, including undo | unit `crashAtEveryStepResumesToTheSameResult`, `crashDuringUndoResumes`; acceptance kill -9 | | Read-only world or unwritable trash: no change at all | unit `readOnlyWorldFolderFailsWithoutChangingAnything`, `trashThatCantBeWrittenStopsBeforeAnyChange` | | Below-ground repair only under the cut; refused if bedrock differs | unit `belowGroundRepairReplacesOnlySectionsUnderTheCut`, `referenceWithDifferentBedrockIsRefused` | -| Upgrade from WildernessRegen: folder moved, fails closed | unit `MigrationTest`; acceptance "Upgrade from WildernessRegen" | **Gaps added this audit:** the never-reset test in bold. -**Found and fixed:** 0.2.0 still told admins to use `/wr` in several messages and log lines (a miss in the rename). 0.2.1 names `/rewild` everywhere; the deprecation notice still names `/wr` on purpose. **Skipped, with reasons:** - How a reset area looks and blends in a real client: real client only, in the README's untested list. diff --git a/src/main/java/com/extantape/rewild/Migration.java b/src/main/java/com/extantape/rewild/Migration.java deleted file mode 100644 index b1b7953..0000000 --- a/src/main/java/com/extantape/rewild/Migration.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.extantape.rewild; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; - -/** - * Moves a server's files over from the old plugin folder after the rename from WildernessRegen (2026-11-09). - * It's a move, not a copy, because the folder holds undo backups of region files that can be large, and an - * approved run must be found in exactly one place. Paths inside are relative to the server, so they still work. - */ -final class Migration { - private Migration() {} - - /** What happened: a line for the log (null if nothing to do), and whether it failed. */ - record Result(String message, boolean failed) {} - - static Result moveIfMissing(Path old, Path now) { - if (!Files.isDirectory(old)) return new Result(null, false); - if (Files.exists(now)) return new Result("Both plugins/" + old.getFileName() + " and plugins/" + now.getFileName() - + " exist. Using plugins/" + now.getFileName() + "; the old folder is ignored. Delete it once you've checked nothing in it is needed.", false); - try { - Files.move(old, now, StandardCopyOption.ATOMIC_MOVE); - return new Result("Moved plugins/" + old.getFileName() + " to plugins/" + now.getFileName() - + " (WildernessRegen is now called Rewild). Runs, undo backups and settings came across unchanged.", false); - } catch (IOException e) { - return new Result("Couldn't move plugins/" + old.getFileName() + " to plugins/" + now.getFileName() + ": " + e - + ". Stop the server and rename the folder by hand.", true); - } - } -} diff --git a/src/main/java/com/extantape/rewild/RewildPlugin.java b/src/main/java/com/extantape/rewild/RewildPlugin.java index ab9fb5e..588a698 100644 --- a/src/main/java/com/extantape/rewild/RewildPlugin.java +++ b/src/main/java/com/extantape/rewild/RewildPlugin.java @@ -30,19 +30,9 @@ public final class RewildPlugin extends JavaPlugin { private RunService runs; private Preview preview; private MapLayer mapLayer = MapLayer.NONE; - /** Set when the move from plugins/WildernessRegen failed. Then nothing runs at all, so no data is stranded. */ - private String migrationProblem; - /** Runs before any world loads: apply or undo approved runs while no chunk is open. */ @Override public void onLoad() { - Migration.Result moved = Migration.moveIfMissing(getDataFolder().toPath().resolveSibling("WildernessRegen"), getDataFolder().toPath()); - if (moved.failed()) { - migrationProblem = moved.message(); - getLogger().severe(moved.message()); - return; - } - if (moved.message() != null) getLogger().warning(moved.message()); saveDefaultConfig(); reloadConfig(); config = new RegenConfig(getConfig()); @@ -81,11 +71,6 @@ public void onLoad() { @Override public void onEnable() { - if (migrationProblem != null) { - getLogger().severe("Rewild is turning itself off, so nothing resets until the folder is moved. " + migrationProblem); - getServer().getPluginManager().disablePlugin(this); - return; - } warnUnknownMaterials(); tracking = new Tracking(this, config); tracking.start(startupStores); @@ -102,10 +87,10 @@ public void onEnable() { getLogger().warning("BlueMap is installed but its API couldn't be loaded; no map layer: " + e); } } - RewildCommand wr = new RewildCommand(this); + RewildCommand command = new RewildCommand(this); getLifecycleManager().registerEventHandler(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, event -> { - event.registrar().register("rewild", "Rewild admin tools", wr); + event.registrar().register("rewild", "Rewild admin tools", command); }); if (config.dryRun) getLogger().info("Dry run is on: approved runs are checked at startup and nothing resets."); if (tracking.states().isEmpty()) getLogger().info("No world is enabled yet. Add one under worlds: in config.yml."); diff --git a/src/main/java/com/extantape/rewild/region/RegionFile.java b/src/main/java/com/extantape/rewild/region/RegionFile.java index aed8bd8..3494308 100644 --- a/src/main/java/com/extantape/rewild/region/RegionFile.java +++ b/src/main/java/com/extantape/rewild/region/RegionFile.java @@ -211,7 +211,7 @@ public void writeAtomically() throws IOException { } public static void writeFileAtomically(Path target, byte[] bytes) throws IOException { - Path tmp = target.resolveSibling(target.getFileName() + ".wr-tmp"); + Path tmp = target.resolveSibling(target.getFileName() + ".rewild-tmp"); try (FileChannel ch = FileChannel.open(tmp, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) { ByteBuffer buf = ByteBuffer.wrap(bytes); while (buf.hasRemaining()) ch.write(buf); diff --git a/src/main/java/com/extantape/rewild/run/Applier.java b/src/main/java/com/extantape/rewild/run/Applier.java index d08e4d3..3a7c3b1 100644 --- a/src/main/java/com/extantape/rewild/run/Applier.java +++ b/src/main/java/com/extantape/rewild/run/Applier.java @@ -370,7 +370,7 @@ private void backup(Map> files, Path serverRoot, Path tras static void copyDurably(Path from, Path to) throws IOException { Files.createDirectories(to.getParent()); - Path tmp = to.resolveSibling(to.getFileName() + ".wr-tmp"); + Path tmp = to.resolveSibling(to.getFileName() + ".rewild-tmp"); Files.copy(from, tmp, StandardCopyOption.REPLACE_EXISTING); try (var ch = java.nio.channels.FileChannel.open(tmp, java.nio.file.StandardOpenOption.WRITE)) { ch.force(true); diff --git a/src/main/java/com/extantape/rewild/store/WorldStore.java b/src/main/java/com/extantape/rewild/store/WorldStore.java index cf266bb..a98ff8f 100644 --- a/src/main/java/com/extantape/rewild/store/WorldStore.java +++ b/src/main/java/com/extantape/rewild/store/WorldStore.java @@ -276,7 +276,7 @@ public synchronized void compact() throws IOException { flush(); long next = generation + 1; Path snapshot = dir.resolve("chunks." + next + ".bin"); - Path tmp = dir.resolve("chunks." + next + ".bin.wr-tmp"); + Path tmp = dir.resolve("chunks." + next + ".bin.rewild-tmp"); try (FileChannel ch = FileChannel.open(tmp, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); DataOutputStream out = new DataOutputStream(new BufferedOutputStream(java.nio.channels.Channels.newOutputStream(ch), 1 << 16))) { out.writeInt(MAGIC); diff --git a/src/test/java/com/extantape/rewild/MigrationTest.java b/src/test/java/com/extantape/rewild/MigrationTest.java deleted file mode 100644 index 195548c..0000000 --- a/src/test/java/com/extantape/rewild/MigrationTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.extantape.rewild; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.*; - -class MigrationTest { - - @Test - void movesTheOldFolderWhenTheNewOneIsMissing(@TempDir Path plugins) throws Exception { - Path old = plugins.resolve("WildernessRegen"); - Files.createDirectories(old.resolve("runs/r1")); - Files.writeString(old.resolve("config.yml"), "worlds: {}\n"); - Files.writeString(old.resolve("runs/r1/run.properties"), "state=APPROVED\n"); - - Migration.Result r = Migration.moveIfMissing(old, plugins.resolve("Rewild")); - - assertFalse(r.failed()); - assertNotNull(r.message()); - assertEquals("state=APPROVED\n", Files.readString(plugins.resolve("Rewild/runs/r1/run.properties"))); - assertEquals("worlds: {}\n", Files.readString(plugins.resolve("Rewild/config.yml"))); - assertFalse(Files.exists(old), "an approved run must live in one place only"); - } - - @Test - void neverTouchesAnExistingNewFolder(@TempDir Path plugins) throws Exception { - Files.createDirectories(plugins.resolve("WildernessRegen")); - Files.writeString(plugins.resolve("WildernessRegen/config.yml"), "old"); - Files.createDirectories(plugins.resolve("Rewild")); - Files.writeString(plugins.resolve("Rewild/config.yml"), "new"); - - Migration.Result r = Migration.moveIfMissing(plugins.resolve("WildernessRegen"), plugins.resolve("Rewild")); - - assertFalse(r.failed()); - assertTrue(r.message().contains("ignored")); - assertEquals("new", Files.readString(plugins.resolve("Rewild/config.yml"))); - assertEquals("old", Files.readString(plugins.resolve("WildernessRegen/config.yml"))); - } - - @Test - void doesNothingOnAFreshServer(@TempDir Path plugins) { - Migration.Result r = Migration.moveIfMissing(plugins.resolve("WildernessRegen"), plugins.resolve("Rewild")); - assertNull(r.message()); - assertFalse(r.failed()); - } - - @Test - void reportsAFailedMoveSoNothingRuns(@TempDir Path plugins) throws Exception { - Files.createDirectories(plugins.resolve("WildernessRegen")); - Files.writeString(plugins.resolve("WildernessRegen/config.yml"), "x"); - Path blockedParent = plugins.resolve("not-a-folder"); - Files.writeString(blockedParent, "a file, so nothing can be moved under it"); - - Migration.Result r = Migration.moveIfMissing(plugins.resolve("WildernessRegen"), blockedParent.resolve("Rewild")); - - assertTrue(r.failed()); - assertTrue(Files.exists(plugins.resolve("WildernessRegen/config.yml"))); - } -} diff --git a/src/test/java/com/extantape/rewild/region/RegionTest.java b/src/test/java/com/extantape/rewild/region/RegionTest.java index 32d9a4d..c25ca9e 100644 --- a/src/test/java/com/extantape/rewild/region/RegionTest.java +++ b/src/test/java/com/extantape/rewild/region/RegionTest.java @@ -37,7 +37,7 @@ void chunksSurviveWriteAndRemoveLeavesNeighbours() throws Exception { assertEquals(1, again.count()); assertNull(again.get(0)); assertArrayEquals(before, again.get(RegionFile.index(1, 0)).data()); - assertFalse(Files.exists(p.resolveSibling("r.0.0.mca.wr-tmp"))); + assertFalse(Files.exists(p.resolveSibling("r.0.0.mca.rewild-tmp"))); } @Test diff --git a/test/acceptance.mjs b/test/acceptance.mjs index 23214be..10b37b5 100644 --- a/test/acceptance.mjs +++ b/test/acceptance.mjs @@ -526,19 +526,6 @@ const pWhy = await ps.cmd(`rewild why world ${px >> 4} ${pz >> 4}`, /Chunk -?\d+ check('fresh install on a played world: the mined chunk is unknown', /unknown/.test(pWhy)) await ps.stop() -console.log('\n## Upgrade from WildernessRegen') -{ - const { renameSync, readdirSync } = await import('node:fs') - const now = path.join(played, 'plugins/Rewild'), old = path.join(played, 'plugins/WildernessRegen') - const before = readdirSync(now).sort().join(',') - renameSync(now, old) - await ps.start() - check('plugins/WildernessRegen is moved to plugins/Rewild on first start', existsSync(path.join(now, 'config.yml')) && !existsSync(old) && readdirSync(now).sort().join(',') === before, readdirSync(now).sort().join(',')) - check('the log says what was moved', /Moved plugins\/WildernessRegen to plugins\/Rewild/.test(ps.log)) - const oldWhy = await ps.cmd(`wr why world ${px >> 4} ${pz >> 4}`, /Unknown or incomplete command|Chunk -?\d+/) - check('the old command /wr is gone in 0.3.0, as announced', !/Chunk -?\d+/.test(oldWhy), oldWhy.split('\n').slice(-2).join(' ').slice(0, 160)) - await ps.stop() -} ps.remove() process.exitCode = report(path.join(REPO, `build/acceptance-${VERSION}.json`)) ? 1 : 0