Skip to content

perf+fix: parallel chunk fetches, lazy zero scan, fresh-island level=0#440

Merged
tastybento merged 3 commits into
developfrom
feat/lazy-zero-and-parallel-chunk-scan
May 17, 2026
Merged

perf+fix: parallel chunk fetches, lazy zero scan, fresh-island level=0#440
tastybento merged 3 commits into
developfrom
feat/lazy-zero-and-parallel-chunk-scan

Conversation

@tastybento
Copy link
Copy Markdown
Member

@tastybento tastybento commented May 17, 2026

Summary

  • Big-protection-range scans no longer time out. A 1000-range AcidIsland scan dropped from 160 s (most spent in sequential getChunkAtAsync round-trips for ungenerated chunks) to a few seconds. Two layered fixes: a sync World.isChunkGenerated fast-path for chunks that have never existed, and a 32-way parallel batch for the chunks that have.
  • No more forced generation during a level scan. gen=false always — NewChunkListener already keeps the initial-count handicap aligned with chunks as they generate during normal play, so the scan only ever needs to look at what's actually there.
  • NewChunkListener snapshots after a configurable delay and respects block-config limits, so paper ticket churn can't double-credit a chunk and cobblestone-heavy terrain can no longer inflate the handicap past anything the regular scan would credit.
  • /<gamemode> levelstatus now reports useful diagnostics. Per island: scan type (zero/regular), world + xyz, elapsed time, and a chunks scanned/total counter that now only counts chunks the scan actually read block data for. The level report adds a matching Chunks scanned = N/total line.
  • Fix fresh islands reading level 1 from missed handicap. The zero-island scan called setInitialIslandCount(totalPoints) which wiped any listener credits captured during the scan. Chunks generating mid-scan that the chunk-poll had already skipped were then in the next level scan's total with no matching handicap, producing a stable positive level. Now tracks per-island scan-visited chunks and listener-deferred credits, then folds in only the chunks the scan missed during the post-scan drain.
  • Stop the "Pending zero-scan for chunk …" log spam — it fired for every newly-generated chunk and flooded the console on island creation.
  • Raise zero-scan-delay-ticks default from 40 (2s) to 600 (30s) so slow-forming terrain like underwater obsidian (lava sources flowing into adjacent water) finishes before the listener snapshots, instead of showing up later in a level scan with no matching handicap.

Locale changes

Five new keys (admin.levelstatus.island-detail, island-queued, type-zero, type-regular, pending-zeros) translated into all 16 supported locales.

Test plan

  • mvn test — 229 tests pass, including new coverage:
    • IslandActivitiesListenersTest — drain folds in deferred listener credits after zero scan; no-op when drain is empty.
    • LevelsManagerTesttryDeferZeroScanCredit / recordScanVisitedChunk / drainZeroScanDeferred contract: visited chunks dropped, missed chunks summed and returned.
    • IslandLevelCalculatorTidyUpTest — pins down the level-0 progress/interval cases.
  • mvn clean package — clean build.
  • Live test on AcidIsland with protection range 1000:
    • /ai on a fresh island returns control immediately; console shows Initial zero scan complete for island at acidisland_world 0,60,0. Lazy zeroing will continue as new chunks generate.
    • /acid level <player> completes in well under the 5 min timeout (previously timed out).
    • /acid levelstatus during the scan shows increasing chunks-scanned count, world+xyz, elapsed time.
    • Level report shows Chunks scanned = N/N at completion.
  • Create a fresh AcidIsland island and immediately run /acid level <player> — expect level 0 (was level 1).
  • Fly around in creative for ~1 minute generating new chunks, run /acid level <player> again — expect level still 0 (was level 3 from missed underwater obsidian).
  • /acid admin levelstatus while a scan is in flight — expect X/Y to show only generated-and-scanned chunks.

🤖 Generated with Claude Code

tastybento and others added 3 commits May 16, 2026 17:15
A 1000-protection-range AcidIsland scan took 160s because loadChunks
fetched 15,876 chunk positions sequentially via getChunkAtAsync, and
each callback round-trip cost ~10ms even when the chunk was ungenerated
and returned null.

This change:
- Always skip generation during scans (gen=false). Lazy zeroing via
  NewChunkListener already keeps the handicap aligned with chunks as
  they generate, so forcing the generator for every position in a
  large protection range is unnecessary and times out.
- Fast-path ungenerated positions with World.isChunkGenerated, a
  synchronous region-file lookup that avoids the async-scheduler hop.
- Issue up to 32 async chunk fetches in parallel per batch instead
  of recursing one-at-a-time.
- Drop the raw Location.toString() from scan log lines in favour of
  "<world_name> x,y,z".
- Distinguish the zero-scan completion log from regular scans.

The /<gamemode> levelstatus command now reports, per island, the scan
type (zero vs regular), world + xyz, elapsed time, and a monotonically
increasing chunks-scanned/total counter. The level report adds a
matching "Chunks scanned = N/total" line under the initial-count line.

Locale keys added: admin.levelstatus.island-detail / island-queued /
type-zero / type-regular, translated into all 16 supported locales.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A 1000-protection-range new island reported level -44551 after a 1000-block
flight. The handicap was 4,455,925 but the regular scan only found 784
points of value across the same chunks — a 5,683x over-counting.

Two compounding causes:

- Paper can fire ChunkLoadEvent with isNewChunk=true more than once for a
  given chunk under heavy chunk activity (ticket churn, parallel level-scan
  loads, plugin-triggered reloads). Each duplicate event credited the
  chunk to initialCount again.

- The listener summed raw getValue per block while the regular scan
  applies per-material limits via limitCountAndValue. Limited high-value
  blocks could inflate the handicap past anything the scan would ever
  credit.

Fix:

- Track counted chunks per island in an in-memory Set<Long> keyed by
  packed (x,z). Skip if the chunk has already contributed during this
  server run. After a restart Paper reports isNewChunk=false for already-
  generated chunks, so prior-run chunks are not at risk on re-load.

- Apply per-material limits in valueAt, capping each material at its
  configured limit within each chunk. This bounds the handicap to what
  the regular scan would credit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The zero-island scan set initialCount via setInitialIslandCount(totalPoints),
which wiped any listener credits captured during the scan. Chunks that
generated mid-scan and were skipped by the chunk-poll (ungenerated at poll
time) then appeared in the next level scan with no matching handicap,
producing a stable positive level on a fresh island. Track per-island
scan-visited chunks and listener-deferred credits so the post-scan drain
folds in only the chunks the scan missed.

Also stop the console spam from logging every pending zero-scan, only
count actually-scanned chunks in the report's X/Y figure, and raise the
default zero-scan-delay-ticks from 40 (2s) to 600 (30s) so underwater
obsidian formation finishes before the listener snapshots.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

@tastybento tastybento changed the title perf: parallelize chunk fetches and add levelstatus diagnostics perf+fix: parallel chunk fetches, lazy zero scan, fresh-island level=0 May 17, 2026
@tastybento tastybento merged commit 0c75e7b into develop May 17, 2026
3 checks passed
@tastybento tastybento deleted the feat/lazy-zero-and-parallel-chunk-scan branch May 17, 2026 06:19
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