Changelog

Every release in plain English, newest first — generated from the project's living notes (notes/version/).

2026-08

2026-08-02 — v1.5.2 — full compliance sweep: gaps closed

Ran a full fairyfox compliance audit (report: notes/fairyfox-reports/2026-08-02-compliance-audit.md) — node compliant, Scorecard 7.9 (live) — and closed every gap it surfaced:

VERSION 1.5.1 → 1.5.2.

2026-08-02 — v1.5.1 — adopt hub 1.6.0: README structure & cross-linking standard

Ran the fairyfox check/adopt flow. Hub mirror fast-forwarded a6d7e68 → 8c6a50e (VERSION 1.5.1 → 1.6.0). The only project-facing change in the span is the new readme.md standard (worded README cross-links — the site chrome doesn't travel with the README to GitHub/registries, so it must carry its own mesh links); 1.5.2/1.5.3 are hub-side registry edits. Pre-authorized by the standing adopt-standards-by-default ledger grant → adopted by default. Applied to README.md:

## Verify run and recorded in adoption-manifest.md (readme → implemented, baseline → 1.6.0). check-links green (40 files). Report: notes/fairyfox-reports/2026-08-02-adopting-updates.md. Shipped as PATCH v1.5.1 (docs-only) via PR into main, full CI, tag, back-merge. VERSION 1.5.0 → 1.5.1.

2026-07

2026-07-24 — fix the Fabric client-mod build (green again) — Loom + yarn-mapping drift

The client/fabric companion mod's CI job (Build the Fabric client mod) had gone red from upstream drift — not from any change here. Fixed it end to end so the full CI suite is green:

2026-07-24 — docs-site chrome 2.2.1 → 2.3.0 (self-hosted fonts) + scorecard hygiene

Completing the fairyfox adoption's remaining items after the owner re-issued the "in full" mandate.

2026-07-24 — adopt fairyfox standards 0.20.2 → 1.5.1 (+ Docker local-first)

Ran the fairyfox check/adopt flow (pre-authorized by the standing adopt-standards-by-default grant). The hub moved three standards releases since this node's last anchor (0.20.2): 0.21.0 (checklists-are-contracts + mandate-ledger, the "done means verified" release), 1.4.0 (complete-by-default + phase-by-default + web-interface enforcement, incl. the 20-badge default set), and 1.5.0 (the new Docker local-first standard).

2026-07-22 — v1.5.0 (MINOR, in progress): per-target settings by command, and an optional client-mod protocol

The wand is gone. An earlier pass added a "despawn wand" — a tagged item that made right-click mean something special — plus a chest-inventory options menu. Both are deleted. The owner's objection is the right one: an item that pretends to be a tool, and a container that pretends to be a settings screen, are less predictable than a command, and a chat command is genuinely the more intuitive interface on a vanilla client. Server-side stays commands.

/despi target … is the new per-target surface, operating on the block you are looking at:

Your own targets by default; anyone's with despi.elevated. Every failure explains itself rather than doing nothing.

Optional client-mod protocol. A container screen has only real slots, so a plugin cannot put a button in one without replacing the whole screen and risking the player's items — but a client mod can, in a few lines. So the split is: the client draws and clicks, the server owns the truth. The bridge is now bidirectional with a handshake:

→ HELLO <version>              ← WELCOME <version> <capabilities…>  |  UNAVAILABLE <reason>
→ QUERY|MARK|UNMARK|TOGGLE|PRIORITY|CONTRABAND <world> <x> <y> <z> [value]
← TARGET … | ABSENT … | DENIED … <reason>

The server owner is in charge, as a first-class feature: targets.client-mod.enabled: false makes the plugin refuse the handshake, so a conforming mod hides its interface instead of showing dead controls — and /despi is completely unaffected. On servers that do allow client mods, the despi.client permission decides who gets one. The handshake reports the player's actual capabilities so the mod can grey out what the server would refuse.

Nothing the client sends is trusted: permission, reach, ownership and the location limit are all re-checked server-side, so a modified client can do nothing it could not already do by typing a command. Nothing is sent to a client that has not registered the channel, so vanilla players are unaffected. The protocol is plain text on a namespaced channel with no library to link against, so any third-party mod can interoperate.

Also: TargetActions split out of DespiActions (which had grown past its cohesion budget), and a read-only ContainerOpenListener that pushes state when a modded client opens a container so its button renders without a round trip.

2026-07-22 — v1.5.0 (MINOR, in progress): automated release screenshots · multi-platform publishing · README rewrite

Automated screenshots (scripts/screenshots.mjs). CI boots a real Paper 1.21.11 server with the freshly built plugin, joins it with a Mineflayer director bot, builds each scene from the server console, teleports a spectator camera into position and captures the frame at the exact tick the subject is on screen. Seven planned scenes: the despawn network, items waiting out their timer, a group relocating, the landing effect (wide + close), /recycle, the catch-all cluster, and the pipeline under load. Fully background — no window, no human, no GPU.

Two capture backends behind one interface:

Publishing (.github/workflows/screenshots.yml, docs.yml, ci.yml). The capture is a reusable workflow called by both CI (build artifacts on every push) and Docs (gallery on Pages), so the artifact images and the site images are produced by identical code. Non-blocking everywhere and deliberately not a required check on main: screenshots are a deliverable, not a correctness gate. New /screenshots.html docs page renders screenshots/manifest.json at runtime, so a newer capture set needs no page rebuild — and says so honestly when a build produced none. README image URLs point at the published paths and therefore refresh themselves.

Multi-platform publishing + artifact bundle (release.yml). Releases now carry the jar, that version's default config.yml, zipped API docs, a source archive, the screenshots, provenance, and SHA256SUMS.txt over the lot. A new publish job pushes to Modrinth and CurseForge (mc-publish, SHA pinned from the tag on the repo's new canonical owner) and Hangar, each gated on its token secret so the workflow stays green until the owner creates the projects. Declared loaders are paper · purpur · spigot · bukkit — deliberately not fabric/neoforge, because claiming a loader the jar cannot run on is a support trap.

Platform matrix (notes/plans/platform-targets.md). Answers the owner's survey target by target, sorted into: same jar (Paper/Purpur/Spigot/Bukkit), same codebase but real work (Folia — needs a PlatformScheduler and a shared-state audit that v1.5.0's throttle maps just made bigger), and separate implementation (Fabric, NeoForge, Sponge, Velocity, …). Hybrids (Arclight/Mohist/Magma) explicitly unsupported.

README rewritten end to end — opens on the actual problem ("Minecraft deletes your stuff"), then what it does, a dedicated why an admin would install this section, a why a player would use it section, the permission-driven per-rank tuning table, the honest supported-platform matrix, and auto-refreshing screenshots.

2026-07-22 — v1.5.0 (MINOR, in progress): per-user throttling · void chance · catch-all storage

Two new admin-facing systems, both off by default so upgrading changes no behaviour.

Per-user throttling (throttle:). The existing performance: budget answers "how much relocation work may the server do this tick". This answers the different question: "how much of that budget may one player take" — so a single player dumping a double chest of junk can no longer consume the whole pipeline while everyone else's items quietly expire. Four selectable strategies:

Allowances come from permissions, mirroring the precedence despi.limit.<n> already uses: despi.throttle.rate.<n> · .concurrent.<n> · .weight.<n> · despi.throttle.bypass (highest node wins; offline throwers fall back to the config defaults). Over-quota items are deferred (lossless, the default), dropped, or routed to the catch-all (void). Ownerless drops (mob loot, dispensers) are exempt unless throttle-unowned is set, so a mob farm can't eat the budget players are meant to share.

Void chance + catch-all (void:). A configurable probability (chance, default 0.0) that a despawning item is voided rather than relocated — rolled once at enqueue, not re-rolled per candidate location. An admin-extensible banned-materials list layers on top of the built-in contraband set. Crucially, both categories can now land somewhere instead of ceasing to exist: one or more catch-all containers (world;x;y;z, mode: first|random) receive banned and voided items, giving staff a server-wide lost-and-found and an audit trail. Catch-alls are config-driven and admin-owned by design — no storage-schema migration, and no way for a player to register one.

Testing (~60 new assertions across five new files, all layers): TokenBucketTest (pure, clock-injected — rate behaviour asserted deterministically, never by sleeping), ThrottleManagerTest (every strategy in isolation then combined, purge, reset, backwards clock), ThrottleQuotasTest (real MockBukkit permission attachments — precedence, highest-wins, junk nodes), CatchAllDeliveryTest (delivery, full-chest fallthrough, non-container, unknown world, malformed config), SchedulerVoidAndThrottleTest (seeded Random, so the probabilistic void path is asserted exactly rather than statistically), ThrottleAndVoidSettingsTest (clamping and graceful degradation — a server owner's typo must never abort the load). koverVerify ≥ 90 and detekt/ktlint stay green with no baseline and no suppression added.

2026-07-22 — Release v1.4.8 (PATCH): rolling latest-stable (hard) + latest-experimental (soft) Paper smoke

New CI job latest-paper proves the plugin still loads on Paper's moving frontier, resolved live from fill.papermc.io:

2026-07-22 — Release v1.4.7 (PATCH): uuid advisory cleared + optional local-playtest step

2026-07-22 — Release v1.4.6 (PATCH): npm lockfile → Scorecard Pinned-Dependencies 9→10

Closes the last agent-doable Scorecard lever from the ship mandate's remaining-headroom list.

2026-07-22 — Release v1.4.5 (PATCH): Scorecard 7.1, main required-status-checks (full-CI hard-enforced)

Closes the Scorecard clause of the 2026-07-21 ship mandate and records the result.

2026-07-21 — Release v1.4.4 (PATCH): ship-contract, phase-by-default, 8 Dependabot PRs handled, Scorecard hardening, tech debt cleared

Owner mandate: raise Scorecard into the 7.x range, remove tech debt, handle the 8 open PRs, and enforce all of that — plus phase-by-default — in CLAUDE.md and notes. Ledger: notes/plans/mandate-2026-07-21-ship-contract.md.

Owner-triggered retrospective on why the day's mandate took three prompts. New CLAUDE.md standing instruction "Owner Mandates Become Ledgers" (verbatim per-clause transcription, evidence-gated deferral, phase-end diff against the owner's words, auto-escalation on repetition); hub report notes/fairyfox-reports/2026-07-21-mandate-execution-failure-analysis.md with five proposed mesh-wide standard amendments. Docs-only PATCH — shipped to main as v1.4.3 (full gate: ./gradlew build + check-links.mjs green, PR CI + CodeQL green).

2026-07-21 — Release v1.4.2 (PATCH): exhaustive permission matrix, combined load + throughput proofs, Kotest/Pitest/JMH adopted, spark+JFR profiling, local Docker fixed

Third pass on the owner's mandate — every previously-deferred tool adopted, and the "does the job under load" dimension proven with numbers:

2026-07-21 — Release v1.4.1 (PATCH): server + client test layers automated in CI; Sonar scan wired; Testcontainers MariaDB

Second pass on the owner's testing mandate — the layers no mock can prove are now automated, closing the pyramid top to bottom (Kotlin → server → client):

2026-07-21 — Release v1.4.0 (MINOR): full-layer test suite (44%→~95%, gated ≥90%), 3 bug fixes, /despi recycle + renameable commands, CodeQL restored

Owner mandate: coverage near-100% (hard floor 90%), real tests on every feature at every layer, Scorecard at the repo's maximum, tech debt zero, full gates before release. Plan: notes/plans/coverage-max-2026-07-21.md; gap analysis for the hub: notes/fairyfox-reports/2026-07-21-testing-and-quality-gap-analysis.md.

2026-07-21 — Release v1.3.7 (PATCH): badge wall + supply-chain/quality tooling parity with random-ai-prompt

Owner asked to match the sibling project random-ai-prompt's README badge wall and security tooling, adapted from its JavaScript/Node stack to this Kotlin/Gradle/Paper one. Plan: notes/plans/badges-and-security-2026-07-21.md.

Needs owner-side external setup for the account-gated badges to go live (Codecov app + CODECOV_TOKEN; SonarCloud project import + SONAR_TOKEN; CodeFactor repo add; optional SCORECARD_TOKEN). ./gradlew build + koverXmlReport green; doc-link gate + workflow YAML validated. VERSION → 1.3.7.

2026-07-21 — Release v1.3.6 (PATCH): self-hosted legal pages + Legal subnav item

Fairy Fox caught the legal pages missing. Two findings: (1) the legal-docs standard is mandatory for every repo ("every repo ships the three pages, self-hosted") — the adoption manifest's "N/A — hub serves /legal/" was another wrong lenient read, and the chrome footer's …/legal/{privacy,terms,cookies}/ links had been 404ing on the live site since the chrome adoption; (2) the owner wants Legal reachable from the subnav.

VERSION → 1.3.6. Docs-site only.

2026-07-21 — Release v1.3.5 (PATCH): notes README pages rendered — live 404s fixed

Live-deploy verification of v1.3.4 (fetching the served pages, not trusting the build) found the notes landing linking sessions/README.html / fairyfox-reports/README.html — pages the renderer had excluded → 404 on the live site (a no-orphan/quality-bar violation). Section READMEs are now rendered (links resolve) while staying out of the sidebar (docs-site 06: the sidebar lists notes, not section overviews). VERSION → 1.3.5. Docs-build only.

2026-07-20 — Release v1.3.4 (PATCH): docs site rebuilt to the canonical shape — overview landing, Notes/Tutorials/Changelog pages, API boundaried under /api/

Fairy Fox caught that v1.3.3's subnav "fix" had satisfied the letter by shrinking the subnav to what existed instead of building what the standard expects — and that the site's default page was the raw Dokka module page. Rebuilt as docs-site Case A (hand-authored site, generator boundaried):

VERSION → 1.3.4. Docs/build/workflow only — no plugin behaviour change.

2026-07-20 — Release v1.3.3 (PATCH): full hub-standards audit — nav/subnav corrected, Downloads page, mandatory branch protection, provenance attestation

Ran the complete hub-standards audit (100+ items, notes/plans/standards-audit-2026-07-20.md) and applied every gap under the standing adopt-standards-by-default grant:

VERSION → 1.3.3. No plugin behaviour change — docs, CI/release workflow, and repo/notes meta only.

2026-07-20 — Release v1.3.2 (PATCH): docs site wears the shared fairyfox chrome (bundle v2.2.1)

The Dokka API docs now carry the shared fairyfox chrome bundle (VERSION 2.2.1) instead of being a bare generator island — the masthead (Home · Projects · Farms · Docs · Updates · About), this project's subnav (overview · Guide · Changelog · Download · Repository), the reader "Aa"/coins controls, and the shared footer, on every page.

Injected through Dokka's FreeMarker templatesDir (the Doxygen-style "full-page generator" adapter): docs-theme/dokka-templates/includes/{page_metadata,header,footer}.ftl paste the bundle's head/header/subnav/footer verbatim with only the FF_* slots filled. The four master assets (main.css, reader.js, nav.js, coins.js) are vendored under docs-theme/chrome/ and copied into the docs root at build time (vendorChromeAssets), referenced per-page via ${'$'}{pathToRoot} so they resolve at any depth — vendored, never hot-linked, renders with fairyfox.io offline. "Wear the chrome, boundary the reference": Dokka's API body stays Dokka, harmonised to the palette by dokka-fairyfox.css. Verified in-browser at root + a deep package page; full build green. Deviations (footer in the content column; Dokka's own bar kept as API controls) recorded in decisions/architecture.md.

VERSION → 1.3.2. No plugin behaviour/artifact change — docs-only.

2026-07-20 — Release v1.3.1 (PATCH): package namespace → io.fairyfox.papermc.despawneditems

Moved the Kotlin source out of com.popupmc.despawneditems into io.fairyfox.papermc.despawneditems and set the Gradle group to io.fairyfox to match. The plugin.yml main: now points at io.fairyfox.papermc.despawneditems.PaperMcDespawnedItems. ktlint import ordering re-sorted automatically (io.fairyfox sorts differently than com.popupmc did).

No behaviour, command, config, permission, artifact-name, plugin-id, or data-folder change — for a server admin the drop-in jar is identical (papermc-despawned-items-*.jar, plugin id papermc-despawned-items). This is purely the internal package/Maven namespace.

VERSION → 1.3.1. Full build green (ktlint + detekt + kover + tests).

2026-07-20 — Release v1.3.0: consistent naming + real static analysis & coverage

Addressed three follow-ups, none of which cut corners:

VERSION → 1.3.0. Ktlint + Detekt + Kover all gate the build and CI.

2026-07-20 — Release v1.2.0 (MINOR): Brigadier commands + quality gates

Milestone since 1.1.1: /despi and /recycle rewritten on Paper's Brigadier command API (typed args, permission-scoped visibility, native tab-completion; logic in testable DespiActions/RecycleAction) — verified under MockBukkit (dispatch) and enabling on real Paper 1.21.11. Added Ktlint and Detekt quality gates to the build (JDK-21 daemon pin so detekt runs; tuned config + baseline). Minor /despi syntax tweaks noted below. VERSION → 1.2.0.

2026-07-20 — Add Ktlint + Detekt quality gates; format the codebase

Adopted two static-quality tools and wired them into build (they run as part of check):

Detekt 1.23.8 can't run on a JDK 25 daemon, so gradle/gradle-daemon-jvm.properties pins the Gradle daemon to JDK 21 (foojay-provisioned) — which also makes the whole build run on the plugin's real target JVM. Kover (coverage) was attempted but is currently blocked (0.9.1 is Kotlin-2.4-incompatible; 0.9.8's plugin marker isn't on the portal) — deferred until a working release. Build green with both gates + tests + jar.

2026-07-20 — Modernize commands to Paper's Brigadier API

Replaced the legacy plugin.yml command + hand-rolled string parsing with Paper's modern Brigadier command API, registered via the lifecycle event system. /despi and /recycle are now proper command trees with typed arguments, permission-scoped visibility (.requires), native tab-completion, and client-side validation.

Structure: all behaviour lives in a testable DespiActions (and RecycleAction); DespiCommand builds the tree and registers it; CommandFeedback holds shared helpers. Removed the old framework (OnDespiCommand, AbstractDespiCommand, DespiSubcommandsA/B, OnRecycleCommand) and the plugin.yml commands: block. MockBukkit supports the lifecycle command API, so the enable test still passes and a new test dispatches /despi end-to-end through the registration. Minor syntax tweaks: clear player <name> (was clear <name>) and despawn create-material-amount <name> <amt> alongside create-material <names> (Brigadier can't follow a greedy string with an int).

2026-07-20 — Release v1.1.1 (PATCH): regression pins + 26.1 forward-compat verified

Post-1.1.0 hardening release. Pinned regression tests for the fixed bugs (recycle reward, particle data) by extracting RecycleProgress and ParticleData as pure units (behaviour unchanged), and verified forward-compat by booting the jar on a real Paper 26.1.2 server (enables cleanly, no registry-change breakage). No user-facing behaviour change. VERSION → 1.1.1.

2026-07-20 — Forward-compat verified on real Paper 26.1.2

Booted a headless Paper 26.1.2 server with the 1.1.0 jar (built against 1.21.11) and confirmed the forward-compat claim: the api-version: '1.21' descriptor is accepted, the libraries: (HikariCP + JDBC drivers) load, and onEnable completes cleanly — DespawnedItems is enabled, Done (10.3s)!, no exceptions despite the 26.x registry changes (Material iteration in RewardPool, Particle.valueOf in ParticleData, etc. all work). Closes the last verification gap; the README's "loads on 26.x" is now proven.

2026-07-20 — Regression tests for the fixed bugs (§87): recycle reward + particle data

Post-1.1.0 hardening. Per the testing standard, every fixed bug now has a permanent, pinned test. Extracted the two bug-prone pieces into pure, server-independent units and tested them: RecycleProgress (the "reward every 64 recycles" threshold that used to never fire) and ParticleData (resolves a particle + its required data safely, falling back with a warning instead of crashing for DUST/BLOCK/ITEM/unknown keys). 12 new pure tests. Also a small readability win — /recycle and FileConfig now delegate to these units instead of inlining the logic.

2026-07-20 — Release v1.1.0 (MINOR): full refactor to a modern, tested, scalable plugin

Cut the v1.1.0 milestone from the 2026-07-20 refactor work below. Highlights since 1.0.2: retargeted to Paper 1.21.x / Java 21; fixed real bugs (/recycle rewards, data-bearing particles, stale static strategy list, RemoveMaterials IOOBE, /despi reload); indexed LocationStore with O(1) lookups; pluggable YAML / SQLite / MySQL storage with incremental off-thread persistence and migration; throttled DespawnScheduler for large-server safety; per-user location limits via despi.limit.<n> group permissions; a JUnit 5 + MockBukkit test suite (~49 tests, CI green) plus a real-server smoke pass; rewritten README, filled context notes, and a GitHub Pages Dokka workflow. VERSION → 1.1.0; release.yml/ci.yml on Java 21.

2026-07-20 — Runtime-verified on real Paper 1.21.11 (headless smoke)

Booted a headless Paper 1.21.11 server with the built jar and confirmed a clean enable: Paper's library loader auto-downloaded and loaded HikariCP 5.1.0, sqlite-jdbc 3.49.1.0, and mariadb-java-client 3.5.3 from Maven Central, then DespawnedItems is enabled with no stack traces (Done (23.16s)!). The server logged it was "2 releases behind 26.1.2" — confirming the 1.21.11-vs-26.1 target. The smoke also caught (and corrected) that VERSION is 1.0.2 (status.md had said 1.0.0) and that the distributable jar is DespawnedItems-1.0.2.jar. Forward-compat load on an actual 26.1 server is still to verify.

2026-07-20 — Fill in the context notes (project/architecture/principles/overview)

Phase 4 docs. Filled the previously-templated notes/context/{project,architecture, principles}.md and notes/systems/overview.md to describe the current architecture (the location store/manager/repository seams, the throttled pipeline, limits), the repo layout, the data-integrity/scale/fail-safe principles, and the end-to-end system map. Refreshed notes/status.md to reflect the largely-complete refactor.

2026-07-20 — GitHub Pages docs workflow; CI on Java 21

Phase 4. Added .github/workflows/docs.yml — builds the Dokka API docs and deploys them to GitHub Pages on every release to main (SHA-pinned Pages actions; dokkaGenerate verified locally, output build/dokka/html). Enabled Pages with the GitHub-Actions build source. Also fixed ci.yml to use Java 21 (was still 25 after the retarget); the CI build already runs the full test suite.

2026-07-20 — Rename display name to "PaperMC Despawned Items"; rewrite README

Phase 4. Set the human-facing/listed name to PaperMC Despawned Items in the README title, Dokka moduleName, and CLAUDE.md heading. The repo slug (papermc-despawned-items) and the Bukkit plugin id (DespawnedItems) are unchanged — the latter can't contain spaces. Also set plugin.yml author to Fairy Fox. Rewrote the README to cover the 1.21.x target (loads on 26.x), the storage backends, per-user limits, performance/scaling, updated commands (dropped indexes), and the test suite.

2026-07-20 — Per-user location limits with permission/group tiers

Phase 3. Players self-manage their own despawn locations up to a cap. DespawnLimits resolves the cap as: limits.unlimited / despi.limit.bypass → unlimited; else the highest despi.limit.<n> permission the player holds (assign these to LuckPerms groups for per-rank caps); else limits.default (config). /despi add this enforces it for self-service adds, while admins adding for others (already behind despi.elevated) are uncapped. New limits: config section, LimitSettings, and the despi.limit.bypass permission node. Tests: settings parsing/boundary (3) + a full MockBukkit permission matrix (6). 49 tests green.

2026-07-20 — Throttled despawn scheduler (large-server safety)

Phase 1 scaling, final core piece. The old model started a chunk-loading DespawnProcess immediately for every despawning item with no upper bound — a flood of despawns could storm the server. Added DespawnScheduler: despawning items are enqueued and a once-per-tick drain starts at most performance.max-per-tick new relocations, never exceeding performance.max-concurrent in flight, with the queue capped at performance.max-queue and a configurable drop policy. New performance: config section and PerformanceSettings. The ItemDespawnEvent listener now enqueues; /despi despawn count-ongoing reports active + queued. Tests: config parsing/boundary (3) + MockBukkit enqueue/drain (2). 40 tests green.

2026-07-20 — Wire the new store + storage into the live plugin

Phase 1 integration. The plugin now actually uses the indexed store and pluggable storage instead of the old flat list: a new LocationManager (on plugin.locations) owns the LocationStore + configured LocationRepository, exposes all the read/mutate operations the pipeline and commands need, and persists incrementally and off-thread (a mutation marks its owner dirty and debounces a 5s async flush; disable//despi save flush synchronously). StorageFactory builds the yaml/sqlite/mysql backend from config and auto-migrates yaml → database on first switch. New storage: config section.

Rewired the despawn pipeline (DespawnProcess draws from the manager and uses an instance-scoped strategy list — fixing the old static list that captured the first plugin instance across reloads), decoupled DespawnEffect to take a plain Location, and updated RemoveMaterials (also fixed a latent index-out-of-bounds when a target owner had no locations). Updated every /despi subcommand to the manager; removed the now-obsolete indexes subcommand. Deleted FileLocations, LocationEntry, DespawnIndexes. Also fixed /despi reload to actually re-read config.yml from disk (it reused Bukkit's cached config before). Added a MockBukkit LocationManager integration test (add/remove/query + save-and-reload). 35 tests green; shaded jar builds.

2026-07-20 — Adopt the Kotlin-Paper testing checklist as the project standard

Added notes/plans/testing.md: adopts the comprehensive Kotlin-PaperMC testing checklist as this project's testing standard, maps its 95 sections onto this plugin (applicable / partial / N-A with status), sets the testing-pyramid targets and source organisation, and records the tool stack. A living document the suite is grown against. Current suite: 31 tests across unit, serialization/fuzz, database (SQLite), performance, and MockBukkit.

2026-07-20 — Pluggable storage backends (YAML + SQLite + MySQL/MariaDB) + perf tests

Phase 1 scaling. Turned persistence into a LocationRepository interface with two implementations: YamlLocationRepository (the flat userdata/<uuid>.yml files — default, zero-config, unchanged on-disk format, now writing incrementally per owner instead of rewriting every file on every change) and a dialect-agnostic JdbcLocationRepository for SQLite (embedded) and MySQL/MariaDB (network-shared), with transactional per-owner writes. JDBC drivers + HikariCP load at runtime via Paper's libraries: loader, so the jar stays small.

Tests: YAML roundtrip/resilience/incremental (5), JDBC-over-SQLite CRUD/replace/persist (4), and a performance-guardrail suite that fails the build if the store's inserts, lookups, random draw, or bulk removal regress from O(1)/O(log n) at 100k+ entries (3). 31 tests green.

2026-07-20 — Indexed location data model (DespawnLocation + LocationStore)

Phase 1 scaling foundation. Added an immutable, server-independent DespawnLocation value type (world name + block coords + owner; x;y;z;world serialisation kept byte-compatible with the old on-disk format) and a LocationStore that replaces the old flat ArrayList — the one that was copied and linearly scanned on every query and rewritten to disk in full on every change. The store gives O(1) add/remove/contains, a spatial index (block → owners), an owner index (owner → locations, for per-user limits and bulk ops), an O(1) swap-remove flat bag for uniform random draw, and dirty-owner tracking so persistence can write only what changed.

Both types are pure and fully unit-tested (roundtrip + fuzz for the value type; add/remove/index-integrity + a 5,000-op randomized integrity fuzz for the store). 19 tests green. Additive for now — wiring the store into the plugin comes next.

2026-07-20 — Stand up the test harness (JUnit 5 + MockBukkit), first tests green

Phase 2 foundation, brought online early so the refactor is tested as it lands. Added JUnit 5 + MockBukkit (mockbukkit-v1.21:4.110.0) to the build; test uses the JUnit platform and build runs it. First suite (PluginEnableTest) boots a mocked Paper 1.21 server, loads the plugin, and asserts it enables cleanly, both commands register, and the reward pool is populated/filtered — all green.

Two changes were needed to make the plugin testable, both improvements in their own right: DespawnedItems is now open (Kotlin classes are final; MockBukkit subclasses the main class via ByteBuddy), and RewardPool filters by stable enum name instead of calling server-backed Material.isAir() (which routed through the block registry — fragile and slower; MockBukkit can't mock the legacy path at all).

2026-07-20 — Fix fragile particle handling (data-bearing particles crashed)

Phase 1 correctness fix. The effect always called spawnParticle with no data, so any configured particle that requires data (DUST, BLOCK, ITEM, …) threw at spawn time. Now the particle's data requirement is resolved and validated once at config load: data-less particles pass null, DUST-style particles are built from new optional particles.color (#RRGGBB) and particles.dust-size keys, and any particle needing data the plugin doesn't model is swapped for the happy_villager default with a console warning instead of crashing. Unknown particle keys warn and fall back too. Build green on 1.21.11.

2026-07-20 — Fix silently-broken /recycle rewards; rename reward pool

Phase 1 (refactor) correctness fix. /recycle tracked its reward progress in three scoreboard objectives (recycleCountPart/recycleCount/recycleCountPaid) that the plugin never created, so on any normal server the reward path hit a null objective and silently granted nothing. Rewrote it to store progress in the player's PersistentDataContainer — persists across sessions, needs no scoreboard setup, and actually works. Behaviour preserved: one random reward every 64 recycles.

Also renamed the misnamed BlacklistedItems object to RewardPool (it is the allow-list of reward-eligible materials, not a blacklist) and tidied its filtering. Build green against Paper 1.21.11.

2026-07-20 — Retarget Paper 26.1 → 1.21.x (Java 21) + refactor plan

Kicked off a full refactor/modernisation/testing/scale-up effort (plan: notes/plans/refactor-2026-07.md). First step: retargeted the build from Paper 26.1 / Java 25 to Paper 1.21.11 / Java 21. Rationale — the MockBukkit test framework supports the 1.21.x line but not the newer 26.x line yet, so 1.21.x unlocks full integration/e2e testing; 1.21.11 is also the single most-installed version (~25% of tracked public servers, ~47% for the whole 1.21.x line) and a 1.21-built plugin still loads on 26.1 servers via Paper forward-compat (to verify). Bonus: Mineflayer supports 1.21.11, so in-game client automation is now possible.

Changes: build.gradle.kts Paper API → 1.21.11-R0.1-SNAPSHOT, Kotlin toolchain and jvmTarget → 21; settings.gradle.kts adds the foojay resolver (1.0.0, the Gradle-9-compatible release) to auto-provision JDK 21; plugin.yml api-version1.21; CLAUDE.md and notes/status.md updated. ./gradlew build is green against the real 1.21.11 API — the existing code compiled with zero API changes.

2026-07-20 — Project icon

Added the project icon (assets/icon.png, 1024×1024, transparent) to the repo: shown in the README header and wired as the Dokka docs header logo (via customAssets). Released as v1.0.2. The GitHub social-preview image still needs a one-time manual upload in repo Settings — gh/the API can't set it.

2026-07-19 — Make the Gradle wrapper executable for CI

Set the executable bit on gradlew so the Linux GitHub Actions runners can invoke it. Committed from Windows, the wrapper landed non-executable and CI failed with ./gradlew: Permission denied (exit 126). Released as v1.0.1.

2026-07-19 — Kotlin/Paper 26.1 rewrite + fairyfox mesh onboarding

Modernised the plugin and folded it into the fairyfox mesh in one pass.

Rewrote the entire plugin from Java 11 / Maven / Paper 1.16.5 to Kotlin / Gradle / Paper 26.1 on Java 25: all ~30 classes (the five-stage despawn pipeline, every /despi subcommand, /recycle, config and per-player location storage) ported to idiomatic Kotlin. Modernised churned APIs — ChatColor → Adventure components, Effect/playEffect → a sound key via playSound(String, …), Particle.VILLAGER_HAPPY → a resolved particle key, dropped removed MaterialData calls, and switched overloaded-setter properties to explicit setX(...) calls. Reconciled the plugin's own config holder to plugin.settings to avoid clashing with JavaPlugin.getConfig(), and reconciled plugin.yml permissions to match the code. The build shades the Kotlin stdlib into a single runnable jar and is wired for Dokka docs and Hangar publishing.

Verified ./gradlew build green against the real Paper 26.1.2 API, and confirmed the built jar loads and enables cleanly on a headless Paper 26.1.2 / Java 25 server.

Adopted the fairyfox standards: mesh-aware CLAUDE.md, the notes/ living-notes system, VERSION seeded at 1.0.0, LF .gitattributes, SECURITY.md, Dependabot, and SHA-pinned CI / branch-sync / release workflows, plus a reconciled README with the badge block.