diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..9e40a20
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,23 @@
+## Summary
+
+Describe the problem and the approach taken.
+
+## Validation
+
+- [ ] `./gradlew clean build` passes on Java 21.
+- [ ] New or changed behavior has automated tests where practical.
+- [ ] Paper API access remains on the server thread.
+- [ ] New queues, scans, or I/O paths have explicit bounds.
+- [ ] Commands and state-changing actions enforce permissions.
+- [ ] Configuration changes are documented and validated.
+- [ ] User-facing changes are reflected in the changelog or wiki.
+
+## Manual testing
+
+List the Paper version, commands exercised, and observed result. Include only
+sanitized logs or exports; remove server addresses, player data, world names,
+and sensitive coordinates.
+
+## Compatibility
+
+State whether this changes the supported Paper, Minecraft, or Java versions.
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ab05433..db9e3e1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -42,6 +42,12 @@ jobs:
fi
./gradlew clean build --no-daemon --warning-mode all
+ - name: Publish to Modrinth
+ env:
+ MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
+ MODRINTH_PROJECT_ID: ${{ vars.MODRINTH_PROJECT_ID }}
+ run: ./gradlew modrinth --no-daemon --warning-mode all
+
- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml
new file mode 100644
index 0000000..88d6d66
--- /dev/null
+++ b/.github/workflows/wiki.yml
@@ -0,0 +1,58 @@
+name: Publish Wiki
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "wiki/**"
+ - ".github/workflows/wiki.yml"
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+concurrency:
+ group: publish-wiki
+ cancel-in-progress: false
+
+jobs:
+ publish:
+ name: Sync wiki pages
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Publish wiki
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ WIKI_REPOSITORY: ${{ github.repository }}.wiki
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ wiki_url="https://x-access-token:${GITHUB_TOKEN}@github.com/${WIKI_REPOSITORY}.git"
+ if ! git ls-remote "$wiki_url" >/dev/null 2>&1; then
+ echo "::error::Initialize the GitHub Wiki by creating its first Home page, then rerun this workflow."
+ exit 1
+ fi
+
+ wiki_checkout="$RUNNER_TEMP/chunkdoctor-wiki"
+ git clone --depth 1 "$wiki_url" "$wiki_checkout"
+ rsync -a --delete --exclude ".git/" wiki/ "$wiki_checkout/"
+
+ cd "$wiki_checkout"
+ wiki_branch="$(git branch --show-current)"
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add --all
+
+ if git diff --cached --quiet; then
+ echo "Wiki is already up to date."
+ exit 0
+ fi
+
+ git commit -m "docs: sync project wiki"
+ git push origin "HEAD:$wiki_branch"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc1560d..353b43c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ All notable changes to ChunkDoctor are documented here. The project follows
- Interactive Adventure and MiniMessage command interface.
- Administrator notifications with cooldown and persistent opt-out.
- Safe asynchronous JSON report exports.
+- Anonymous, globally opt-out bStats usage metrics.
- Unit tests and checksum-pinned Gradle builds.
[Unreleased]: https://github.com/denfry/ChunkDoctor/compare/v1.0.0...HEAD
diff --git a/README.md b/README.md
index 3a954bf..5524b56 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@
+
+
@@ -21,6 +23,8 @@
Installation ·
Commands ·
Configuration ·
+ Wiki ·
+ Gallery ·
Русская документация
@@ -67,6 +71,16 @@ to profile execution time.
- **Permission-gated notifications** with cooldowns and persistent player opt-out.
- **No NMS, reflection, database, or mandatory plugin dependencies.**
+## Gallery
+
+| Explainable analysis | Highest-risk chunks | Bounded deep scan |
+|---|---|---|
+| [](docs/assets/gallery/chunkdoctor-risk-analysis.png) | [](docs/assets/gallery/chunkdoctor-risk-ranking.png) | [](docs/assets/gallery/chunkdoctor-deep-scan.png) |
+
+These are representative gameplay mockups based on the real commands, default
+budgets, and output model. They are not captures from a particular production
+server.
+
## Performance model
ChunkDoctor follows one rule: a lag diagnostic must not become the source of lag.
@@ -180,7 +194,19 @@ disabling the plugin.
The complete scoring weights, world filters, notification settings, message
templates, JSON schema, operational guidance, and troubleshooting are documented
in the [Russian reference](docs/README_RU.md). An English configuration reference
-will expand as the public API stabilizes.
+is available in the [project wiki](https://github.com/denfry/ChunkDoctor/wiki).
+
+## Documentation
+
+| Guide | Contents |
+|---|---|
+| [Installation](https://github.com/denfry/ChunkDoctor/wiki/Installation) | Requirements, upgrade procedure, first-run checks |
+| [Commands and permissions](https://github.com/denfry/ChunkDoctor/wiki/Commands-and-Permissions) | Complete command and permission reference |
+| [Configuration](https://github.com/denfry/ChunkDoctor/wiki/Configuration) | Monitoring, scanning, worlds, notifications, exports |
+| [Risk scoring](https://github.com/denfry/ChunkDoctor/wiki/Risk-Scoring) | Score inputs, nonlinear penalties, levels, confidence |
+| [Performance and safety](https://github.com/denfry/ChunkDoctor/wiki/Performance-and-Safety) | Thread boundaries, budgets, overload behavior |
+| [Troubleshooting](https://github.com/denfry/ChunkDoctor/wiki/Troubleshooting) | Common symptoms and diagnostic steps |
+| [Building and releasing](https://github.com/denfry/ChunkDoctor/wiki/Building-and-Releasing) | Java 21 build, tags, Modrinth and GitHub releases |
## Building from source
@@ -222,6 +248,14 @@ Please do not publish exploitable issues, path escapes, permission bypasses, or
denial-of-service findings in a public issue. Follow [SECURITY.md](SECURITY.md)
for private reporting.
+## Anonymous metrics
+
+ChunkDoctor uses [bStats](https://bstats.org/plugin/bukkit/ChunkDoctor/32969) to
+collect anonymous, aggregated usage statistics. It does not collect player
+names, chat, world data, coordinates, scan results, configuration values, or
+report contents. Server owners can disable metrics globally in
+`plugins/bStats/config.yml`.
+
## License
ChunkDoctor is available under the [MIT License](LICENSE).
diff --git a/build.gradle.kts b/build.gradle.kts
index 9a49780..a839c9a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,11 +1,27 @@
plugins {
java
id("com.gradleup.shadow") version "8.3.10"
+ id("com.modrinth.minotaur") version "2.9.0"
}
group = "dev.chunkdoctor"
version = "1.0.0"
val pluginVersionForResources = version.toString()
+val bStatsPluginId = providers.gradleProperty("bstats_plugin_id").orElse("0")
+
+fun releaseNotesFor(version: String): String {
+ val changelog = rootProject.file("CHANGELOG.md").readText()
+ val versionHeader = Regex("""(?m)^## \[${Regex.escape(version)}]\s+-\s+.+$""")
+ val match = versionHeader.find(changelog)
+ ?: throw GradleException("CHANGELOG.md has no release section for version $version")
+ val nextHeader = Regex("""(?m)^## \[""").find(changelog, match.range.last + 1)
+ return changelog
+ .substring(match.range.last + 1, nextHeader?.range?.first ?: changelog.length)
+ .trim()
+ .ifEmpty {
+ throw GradleException("CHANGELOG.md release section for version $version is empty")
+ }
+}
repositories {
mavenCentral()
@@ -15,6 +31,7 @@ repositories {
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT")
implementation("com.google.code.gson:gson:2.13.1")
+ implementation("org.bstats:bstats-bukkit:3.2.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.13.4")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
@@ -26,8 +43,11 @@ java {
tasks {
processResources {
- filesMatching("plugin.yml") {
- expand("version" to pluginVersionForResources)
+ filesMatching(listOf("plugin.yml", "bstats.properties")) {
+ expand(
+ "version" to pluginVersionForResources,
+ "bstatsPluginId" to bStatsPluginId.get()
+ )
}
}
@@ -38,6 +58,7 @@ tasks {
shadowJar {
archiveClassifier.set("")
relocate("com.google.gson", "dev.chunkdoctor.lib.gson")
+ relocate("org.bstats", "dev.chunkdoctor.lib.bstats")
}
jar {
@@ -48,3 +69,21 @@ tasks {
dependsOn(shadowJar)
}
}
+
+modrinth {
+ token.set(providers.environmentVariable("MODRINTH_TOKEN"))
+ projectId.set(providers.environmentVariable("MODRINTH_PROJECT_ID"))
+ versionNumber.set(project.version.toString())
+ versionName.set("ChunkDoctor ${project.version}")
+ versionType.set(
+ if (project.version.toString().contains(Regex("""(?i)(alpha|beta|rc)"""))) {
+ "beta"
+ } else {
+ "release"
+ }
+ )
+ uploadFile.set(tasks.shadowJar)
+ gameVersions.add("1.21.8")
+ loaders.add("paper")
+ changelog.set(providers.provider { releaseNotesFor(project.version.toString()) })
+}
diff --git a/docs/MODRINTH.md b/docs/MODRINTH.md
new file mode 100644
index 0000000..8901c08
--- /dev/null
+++ b/docs/MODRINTH.md
@@ -0,0 +1,175 @@
+# Modrinth publication guide
+
+This file contains the exact values and copy needed to publish ChunkDoctor on
+Modrinth. The public project description is maintained separately in
+[`modrinth-description.md`](modrinth-description.md).
+
+## Project form
+
+| Field | Value |
+|---|---|
+| Project type | Plugin |
+| Name | ChunkDoctor |
+| Suggested slug | `chunkdoctor` |
+| Summary | Find suspicious loaded chunks with bounded Paper monitoring, explainable risk scores, deep diagnostics, and actionable optimization advice. |
+| Client-side | Unsupported |
+| Server-side | Required |
+| Primary categories | Management, Optimization, Utility |
+| License | MIT |
+| Source | `https://github.com/denfry/ChunkDoctor` |
+| Issues | `https://github.com/denfry/ChunkDoctor/issues` |
+| Documentation | `https://github.com/denfry/ChunkDoctor/blob/main/docs/README_RU.md` |
+| Discord | Leave empty until an official support server exists |
+| Donations | Leave empty until an official donation page exists |
+
+If `chunkdoctor` is unavailable, choose a stable slug such as
+`chunkdoctor-paper`. Do not change the Gradle configuration: release automation
+uses the immutable project ID stored in GitHub rather than assuming a slug.
+
+Paste the complete contents of `docs/modrinth-description.md` into the project
+description.
+
+The description uses only GitHub-flavored Markdown plus a small centered header,
+provides descriptive alt text for every image, and does not claim compatibility
+that has not been tested.
+
+## Branding and gallery
+
+- Icon: `docs/assets/chunkdoctor-icon.png`.
+- Featured gallery image: `docs/assets/chunkdoctor-hero.png`.
+- Gallery title: `ChunkDoctor risk analysis`.
+- Gallery description: `ChunkDoctor highlights a suspicious loaded chunk and explains the factors contributing to its risk score.`
+
+Upload these representative gameplay mockups after the featured image:
+
+| Order | File | Gallery title | Gallery description |
+|---:|---|---|---|
+| 1 | `docs/assets/gallery/chunkdoctor-risk-analysis.png` | Explainable chunk analysis | Representative gameplay mockup showing a CRITICAL loaded chunk, its 84/100 risk score, confidence, and the observed factors behind the result. |
+| 2 | `docs/assets/gallery/chunkdoctor-risk-ranking.png` | Highest-risk loaded chunks | Representative gameplay mockup of `/cd top`, with ranked LOW-to-CRITICAL results and administrator actions for details and safe teleportation. |
+| 3 | `docs/assets/gallery/chunkdoctor-deep-scan.png` | Bounded deep scan | Representative gameplay mockup showing a manual deep scan split across ticks with visible block and time budgets. |
+
+The three files are promotional UI mockups based on real ChunkDoctor commands
+and defaults. Keep the word **representative** in their gallery descriptions so
+they are not presented as captures from a specific production server.
+
+The artwork is original project branding. Do not add screenshots that claim
+support for server software or Minecraft versions that have not been tested.
+
+## Initial version
+
+| Field | Value |
+|---|---|
+| Version name | ChunkDoctor 1.0.0 |
+| Version number | `1.0.0` |
+| Release channel | Release |
+| Minecraft version | `1.21.8` |
+| Platform | Paper |
+| File | `build/libs/ChunkDoctor-1.0.0.jar` |
+| Dependencies | None |
+
+Do not upload `ChunkDoctor-1.0.0-plain.jar`; it does not contain the relocated
+Gson dependency and is not the production artifact.
+
+### Initial release changelog
+
+```markdown
+### Added
+
+- Fair passive monitoring of loaded chunks with TPS pause/resume hysteresis.
+- Tick-sliced quick radius scans and manual deep block scans.
+- Configurable nonlinear risk scoring with confidence levels.
+- Entity, villager, item, vehicle, hopper, furnace, redstone, spawner,
+ container, and block-entity metrics.
+- Context-aware explanations and optimization recommendations.
+- Interactive Adventure and MiniMessage command interface.
+- Administrator notifications with cooldown and persistent opt-out.
+- Safe asynchronous JSON report exports.
+- Anonymous, globally opt-out bStats usage metrics.
+- Unit tests and strict runtime safety limits.
+```
+
+## Moderation notes
+
+Use this only if the review form provides a private moderation field:
+
+```text
+ChunkDoctor is a server-side Paper plugin. It does not require a client mod or
+another plugin. The production JAR is built from the linked public source under
+the MIT License. Gson is embedded and relocated under
+dev.chunkdoctor.lib.gson. The plugin only analyzes already-loaded chunks;
+manual deep scans are bounded and split across ticks.
+```
+
+Before submitting for review:
+
+1. Build with Java 21 using `./gradlew clean build`.
+2. Confirm the production JAR starts on a staging Paper 1.21.8 server.
+3. Upload the icon, featured gallery image, project description, and version.
+4. Verify client-side is `Unsupported` and server-side is `Required`.
+5. Verify only `Paper` and Minecraft `1.21.8` are selected.
+6. Confirm the MIT license, source URL, issue tracker, and installation steps
+ are visible.
+7. Submit the project for moderation.
+
+## Automated releases
+
+The Gradle `modrinth` task uploads the shaded production JAR and extracts the
+matching version section from `CHANGELOG.md`. The tag release workflow publishes
+to Modrinth before creating the GitHub release.
+
+Configure the GitHub repository:
+
+1. Create a Modrinth personal access token with the `CREATE_VERSION` scope.
+2. In GitHub, open **Settings → Secrets and variables → Actions**.
+3. Add repository secret `MODRINTH_TOKEN`.
+4. Add repository variable `MODRINTH_PROJECT_ID` containing the Modrinth
+ project's immutable ID.
+5. Never put the token in `gradle.properties`, workflow YAML, logs, issues, or
+ release notes.
+
+For each release:
+
+1. Set the Gradle `version`.
+2. Move release notes from `[Unreleased]` into a matching
+ `## [x.y.z] - YYYY-MM-DD` section in `CHANGELOG.md`.
+3. Commit and push the release changes.
+4. Create and push the exact tag `vx.y.z`.
+5. The workflow builds and tests, uploads the production JAR to Modrinth, then
+ creates the GitHub release.
+
+Use `x.y.z-alpha.n`, `x.y.z-beta.n`, or `x.y.z-rc.n` for prereleases. The
+Gradle configuration publishes those versions to Modrinth as `beta`; stable
+versions are published as `release`.
+
+Run a local metadata-only check without uploading:
+
+```powershell
+$env:MODRINTH_PROJECT_ID = "your-project-id"
+$env:MODRINTH_TOKEN = "temporary-token"
+.\gradlew.bat modrinth --dry-run
+```
+
+`--dry-run` verifies Gradle task wiring but does not contact Modrinth. Remove
+the temporary token from the shell after testing:
+
+```powershell
+Remove-Item Env:MODRINTH_TOKEN
+```
+
+## bStats registration
+
+bStats uses public plugin ID `32969`, registered for **ChunkDoctor** as
+Bukkit / Spigot software:
+
+`https://bstats.org/plugin/bukkit/ChunkDoctor/32969`
+
+The build-time property is `bstats_plugin_id=32969` in `gradle.properties`.
+Never reuse another project's ID.
+
+The live badge in `docs/modrinth-description.md` uses:
+
+```markdown
+[](https://bstats.org/plugin/bukkit/ChunkDoctor/32969)
+```
+
+bStats has no secret token to commit; its plugin ID is intentionally public.
diff --git a/docs/assets/gallery/chunkdoctor-deep-scan.png b/docs/assets/gallery/chunkdoctor-deep-scan.png
new file mode 100644
index 0000000..0411948
Binary files /dev/null and b/docs/assets/gallery/chunkdoctor-deep-scan.png differ
diff --git a/docs/assets/gallery/chunkdoctor-risk-analysis.png b/docs/assets/gallery/chunkdoctor-risk-analysis.png
new file mode 100644
index 0000000..b5bfe3e
Binary files /dev/null and b/docs/assets/gallery/chunkdoctor-risk-analysis.png differ
diff --git a/docs/assets/gallery/chunkdoctor-risk-ranking.png b/docs/assets/gallery/chunkdoctor-risk-ranking.png
new file mode 100644
index 0000000..8f6d064
Binary files /dev/null and b/docs/assets/gallery/chunkdoctor-risk-ranking.png differ
diff --git a/docs/modrinth-description.md b/docs/modrinth-description.md
new file mode 100644
index 0000000..0922475
--- /dev/null
+++ b/docs/modrinth-description.md
@@ -0,0 +1,174 @@
+
+
+

+
+# ChunkDoctor
+
+**Find suspicious loaded chunks before they become a server-wide problem.**
+
+[](https://github.com/denfry/ChunkDoctor/actions/workflows/build.yml)
+[](https://github.com/denfry/ChunkDoctor/releases)
+[](https://papermc.io/)
+[](https://adoptium.net/)
+[](https://github.com/denfry/ChunkDoctor/blob/main/LICENSE)
+[](https://bstats.org/plugin/bukkit/ChunkDoctor/32969)
+
+
+
+
+
+ChunkDoctor is a performance-conscious Paper plugin that monitors already-loaded
+chunks, assigns an explainable risk score, and tells administrators what is
+worth inspecting.
+
+Minecraft servers rarely struggle because of one obvious block. The expensive
+area is often a dense combination of entities, villagers, dropped items,
+minecarts, hoppers, furnaces, redstone, spawners, and block entities.
+ChunkDoctor turns those observations into a clear score from 0 to 100.
+
+> **ChunkDoctor estimates potential load; it does not pretend to measure the
+> exact TPS cost of a chunk.** Use it to prioritize an investigation, then use
+> [spark](https://spark.lucko.me/) when you need execution-time profiling.
+
+## Why ChunkDoctor?
+
+- **Find the chunks worth checking.** Passive monitoring rotates fairly through
+ loaded chunks and keeps a ranked list of suspicious areas.
+- **Understand every result.** Scores include confidence, top contributing
+ factors, and recommendations based on the metrics actually observed.
+- **Inspect deeper when needed.** Manual deep scans reveal redstone and
+ block-level signals without forcing neighboring chunks to load.
+- **Stay in control.** Interactive chat actions provide details, pagination,
+ exports, and permission-gated safe teleportation.
+- **Protect the server being diagnosed.** TPS hysteresis, bounded queues,
+ tick slicing, deadlines, and concurrency limits keep analysis work contained.
+
+## Performance-first by design
+
+| Safety property | What ChunkDoctor does |
+|---|---|
+| Chunk loading | Inspects loaded chunks only; never loads a chunk for analysis |
+| Passive scans | Collects counters without iterating every block |
+| Deep scans | Manual-only, tick-sliced, timed, and concurrency-limited |
+| Async work | Scores immutable snapshots in a bounded worker pool |
+| Overload behavior | Rejects excess analysis instead of growing an unbounded queue |
+| TPS protection | Pauses and resumes monitoring with configurable hysteresis |
+
+Bukkit objects never cross into asynchronous analysis tasks. Disk exports run
+away from the server thread and use atomic replacement inside the plugin data
+directory.
+
+## What ChunkDoctor can detect
+
+- entities, villagers, dropped items, armor stands, and hostile mobs;
+- minecarts, boats, and other vehicles;
+- hoppers, furnaces, containers, spawners, and other block entities;
+- redstone components, pistons, observers, and powered components during deep
+ scans;
+- unusually dense combinations that are more concerning together than alone.
+
+Results use four configurable levels: **LOW**, **MEDIUM**, **HIGH**, and
+**CRITICAL**. Confidence indicates how complete the observation was rather than
+overstating what the Paper API can prove.
+
+## Requirements
+
+| Requirement | Supported value |
+|---|---|
+| Server software | **Paper** |
+| Minecraft | **1.21.8** |
+| Java | **21** |
+| Client installation | **Not required** |
+| Required plugins | **None** |
+
+ChunkDoctor intentionally targets Paper. It does not claim compatibility with
+Spigot, Folia, Purpur, or other implementations unless a future release
+explicitly lists them.
+
+## Installation
+
+1. Stop the server.
+2. Download the production `ChunkDoctor-.jar`.
+3. Place it in the server's `plugins/` directory.
+4. Start Paper 1.21.8 using Java 21.
+5. Review `plugins/ChunkDoctor/config.yml`.
+6. Run `/chunkdoctor status`.
+
+Do not use the server-wide `/reload` command. ChunkDoctor provides `/cd reload`
+for its own validated configuration.
+
+## Commands
+
+The main command is `/chunkdoctor`; `/cd` is the short alias.
+
+| Command | Description | Console |
+|---|---|:---:|
+| `/cd help` | Show command help | Yes |
+| `/cd status` | Show monitor, queue, and budget status | Yes |
+| `/cd scan [radius]` | Scan loaded chunks around the player | No |
+| `/cd deep` | Deep-scan the current loaded chunk | No |
+| `/cd top [page]` | List the highest-risk chunks | Yes |
+| `/cd info ` | Show a detailed result | Yes |
+| `/cd teleport ` | Teleport to a validated safe location | No |
+| `/cd export [world x z]` | Export cached results as JSON | Yes |
+| `/cd reload` | Validate and reload configuration | Yes |
+| `/cd start` / `/cd stop` | Control passive monitoring | Yes |
+| `/cd clear [confirm]` | Clear cached results with confirmation | Yes |
+| `/cd notify` | Toggle personal notifications | No |
+
+All permissions default to server operators. Use `chunkdoctor.admin` for every
+feature, or grant the individual `chunkdoctor.*` permissions documented in the
+[repository](https://github.com/denfry/ChunkDoctor#permissions).
+
+## Configuration
+
+The generated configuration documents every setting. Important controls include:
+
+```yaml
+monitoring:
+ chunks-per-cycle: 2
+ max-milliseconds-per-tick: 2.0
+ pause-below-tps: 17.0
+ resume-above-tps: 18.5
+ maximum-pending-analyses: 64
+
+deep-scan:
+ blocks-per-tick: 2048
+ maximum-milliseconds-per-tick: 3.0
+ maximum-duration-seconds: 30
+ maximum-concurrent-scans: 1
+```
+
+Invalid numeric values are clamped to safe ranges. Unsafe export paths, broken
+TPS thresholds, and invalid world modes fail closed with clear log messages.
+
+## Anonymous metrics
+
+ChunkDoctor uses [bStats](https://bstats.org/plugin/bukkit/ChunkDoctor/32969) to collect anonymous, aggregated
+usage metrics such as the plugin version, Minecraft/server software version,
+Java version, operating system type, country, and current player count.
+ChunkDoctor does not add custom charts and does not collect player names, chat,
+world data, coordinates, scan results, configuration values, or report contents.
+
+Server owners can disable bStats globally in `plugins/bStats/config.yml` by
+setting `enabled: false`.
+
+## Support
+
+- [Report a bug](https://github.com/denfry/ChunkDoctor/issues/new?template=bug_report.yml)
+- [Report an incorrect score](https://github.com/denfry/ChunkDoctor/issues/new?template=false_score.yml)
+- [Request a feature](https://github.com/denfry/ChunkDoctor/issues/new?template=feature_request.yml)
+- [Read the Russian documentation](https://github.com/denfry/ChunkDoctor/blob/main/docs/README_RU.md)
+- [Review the changelog](https://github.com/denfry/ChunkDoctor/blob/main/CHANGELOG.md)
+- [Report a security issue privately](https://github.com/denfry/ChunkDoctor/security/policy)
+
+When reporting a problem, include the ChunkDoctor version, Paper build, Java
+version, relevant logs, and the smallest configuration needed to reproduce it.
+Do not publish security vulnerabilities in a public issue.
+
+## Open source and modpacks
+
+ChunkDoctor is open source under the [MIT License](https://github.com/denfry/ChunkDoctor/blob/main/LICENSE).
+You may include the original, unmodified Modrinth release in Modrinth modpacks.
+Please link back to the official project page rather than reuploading the JAR
+elsewhere.
diff --git a/gradle.properties b/gradle.properties
index 2676d1b..75e5cda 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,6 @@
org.gradle.jvmargs=-Xmx1G -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.caching=true
+
+# Public ID assigned to ChunkDoctor at https://bstats.org/plugin/add
+bstats_plugin_id=32969
diff --git a/src/main/java/dev/chunkdoctor/ChunkDoctorPlugin.java b/src/main/java/dev/chunkdoctor/ChunkDoctorPlugin.java
index b02e030..757e1f9 100644
--- a/src/main/java/dev/chunkdoctor/ChunkDoctorPlugin.java
+++ b/src/main/java/dev/chunkdoctor/ChunkDoctorPlugin.java
@@ -10,6 +10,7 @@
import dev.chunkdoctor.config.PluginConfig;
import dev.chunkdoctor.listener.WorldLifecycleListener;
import dev.chunkdoctor.message.MessageService;
+import dev.chunkdoctor.metrics.MetricsBootstrap;
import dev.chunkdoctor.model.ChunkAnalysisResult;
import dev.chunkdoctor.monitoring.MonitoringService;
import dev.chunkdoctor.monitoring.ResultRepository;
@@ -59,8 +60,10 @@ public void onEnable() {
getServer().getPluginManager().registerEvents(
new WorldLifecycleListener(deepScans, manualScans, repository), this);
+ MetricsBootstrap.start(this);
monitoring.restartIfEnabled();
- getLogger().info("ChunkDoctor 1.0.0 enabled. Scores estimate risk; they do not measure per-chunk TPS.");
+ getLogger().info("ChunkDoctor " + getPluginMeta().getVersion()
+ + " enabled. Scores estimate risk; they do not measure per-chunk TPS.");
}
@Override
diff --git a/src/main/java/dev/chunkdoctor/metrics/MetricsBootstrap.java b/src/main/java/dev/chunkdoctor/metrics/MetricsBootstrap.java
new file mode 100644
index 0000000..79f2e58
--- /dev/null
+++ b/src/main/java/dev/chunkdoctor/metrics/MetricsBootstrap.java
@@ -0,0 +1,46 @@
+package dev.chunkdoctor.metrics;
+
+import org.bstats.bukkit.Metrics;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public final class MetricsBootstrap {
+ private static final String RESOURCE_NAME = "bstats.properties";
+
+ private MetricsBootstrap() {
+ }
+
+ public static void start(JavaPlugin plugin) {
+ int pluginId = readPluginId(plugin);
+ if (pluginId <= 0) {
+ plugin.getLogger().warning(
+ "bStats metrics are disabled because bstats_plugin_id is not configured for this build.");
+ return;
+ }
+
+ new Metrics(plugin, pluginId);
+ }
+
+ static int readPluginId(JavaPlugin plugin) {
+ try (InputStream input = plugin.getResource(RESOURCE_NAME)) {
+ if (input == null) {
+ plugin.getLogger().warning("Missing " + RESOURCE_NAME + "; bStats metrics will not start.");
+ return 0;
+ }
+
+ Properties properties = new Properties();
+ properties.load(input);
+ String value = properties.getProperty("plugin-id", "").trim();
+ return Integer.parseInt(value);
+ } catch (NumberFormatException exception) {
+ plugin.getLogger().warning("Invalid bStats plugin ID; metrics will not start.");
+ return 0;
+ } catch (IOException exception) {
+ plugin.getLogger().warning("Could not read the bStats configuration: " + exception.getMessage());
+ return 0;
+ }
+ }
+}
diff --git a/src/main/resources/bstats.properties b/src/main/resources/bstats.properties
new file mode 100644
index 0000000..a1c4d9c
--- /dev/null
+++ b/src/main/resources/bstats.properties
@@ -0,0 +1 @@
+plugin-id=${bstatsPluginId}
diff --git a/wiki/Building-and-Releasing.md b/wiki/Building-and-Releasing.md
new file mode 100644
index 0000000..956c562
--- /dev/null
+++ b/wiki/Building-and-Releasing.md
@@ -0,0 +1,84 @@
+# Building and Releasing
+
+ChunkDoctor uses Gradle Kotlin DSL and Java 21.
+
+## Build from source
+
+Linux or macOS:
+
+```bash
+./gradlew clean build
+```
+
+Windows:
+
+```powershell
+.\gradlew.bat clean build
+```
+
+The installable artifact is:
+
+```text
+build/libs/ChunkDoctor-.jar
+```
+
+Do not deploy the `-plain.jar`; it excludes relocated runtime dependencies.
+
+## Dependency packaging
+
+The production JAR relocates:
+
+- Gson into `dev.chunkdoctor.lib.gson`;
+- bStats into `dev.chunkdoctor.lib.bstats`.
+
+This avoids classpath collisions with other server plugins.
+
+## Continuous integration
+
+Every push to `main` and every pull request:
+
+- validates the Gradle wrapper;
+- installs Java 21;
+- runs a clean build and tests;
+- uploads the production JAR as a short-lived workflow artifact.
+
+GitHub Actions are pinned to commit SHAs.
+
+## Release checklist
+
+1. Choose the semantic version.
+2. Set `version` in `build.gradle.kts`.
+3. Move notes from `[Unreleased]` into a matching changelog section:
+
+ ```text
+ ## [x.y.z] - YYYY-MM-DD
+ ```
+
+4. Build and test locally.
+5. Commit the release changes.
+6. Create the exact tag `vx.y.z`.
+7. Push the commit and tag.
+
+## Automated publishing
+
+The tag workflow:
+
+1. verifies that the tag matches the Gradle version;
+2. performs a clean build and test run;
+3. uploads the shaded JAR to Modrinth;
+4. creates the GitHub release.
+
+Required GitHub configuration:
+
+- repository secret `MODRINTH_TOKEN`;
+- repository variable `MODRINTH_PROJECT_ID`.
+
+The token requires the Modrinth `CREATE_VERSION` scope. Never commit it.
+
+## Version channels
+
+- stable versions publish as `release`;
+- versions containing `alpha`, `beta`, or `rc` publish to Modrinth as `beta`.
+
+The Modrinth changelog is extracted from the matching `CHANGELOG.md` section.
+Publishing fails if that section is missing or empty.
diff --git a/wiki/Commands-and-Permissions.md b/wiki/Commands-and-Permissions.md
new file mode 100644
index 0000000..7eb0ef0
--- /dev/null
+++ b/wiki/Commands-and-Permissions.md
@@ -0,0 +1,75 @@
+# Commands and Permissions
+
+The primary command is `/chunkdoctor`; `/cd` is its short alias.
+
+## Commands
+
+| Command | Purpose | Permission | Console |
+|---|---|---|:---:|
+| `/cd help` | Show command help | `chunkdoctor.use` | Yes |
+| `/cd status` | Show monitor, queue, and budget status | `chunkdoctor.use` | Yes |
+| `/cd scan [radius]` | Scan loaded chunks around the player | `chunkdoctor.scan` | No |
+| `/cd deep` | Deep-scan the current loaded chunk | `chunkdoctor.deep` | No |
+| `/cd top [page]` | Show the highest-risk cached chunks | `chunkdoctor.top` | Yes |
+| `/cd info ` | Show one detailed result | `chunkdoctor.info` | Yes |
+| `/cd teleport ` | Teleport to a validated safe location | `chunkdoctor.teleport` | No |
+| `/cd export` | Export all cached results | `chunkdoctor.export` | Yes |
+| `/cd export ` | Export one cached result | `chunkdoctor.export` | Yes |
+| `/cd reload` | Validate and reload configuration | `chunkdoctor.reload` | Yes |
+| `/cd start` | Start passive monitoring | `chunkdoctor.control` | Yes |
+| `/cd stop` | Stop passive monitoring | `chunkdoctor.control` | Yes |
+| `/cd clear [confirm]` | Clear cached results using two-step confirmation | `chunkdoctor.clear` | Yes |
+| `/cd notify` | Toggle personal risk notifications | `chunkdoctor.notify` | No |
+
+## Permission model
+
+Every permission defaults to server operators.
+
+| Permission | Grants |
+|---|---|
+| `chunkdoctor.use` | Base command, help, and status |
+| `chunkdoctor.scan` | Quick loaded-chunk scans |
+| `chunkdoctor.deep` | Manual deep scans |
+| `chunkdoctor.top` | Risk ranking |
+| `chunkdoctor.info` | Detailed cached results |
+| `chunkdoctor.teleport` | Safe teleport action |
+| `chunkdoctor.export` | JSON exports |
+| `chunkdoctor.reload` | Configuration reload |
+| `chunkdoctor.control` | Passive monitor start/stop |
+| `chunkdoctor.clear` | Cache clearing |
+| `chunkdoctor.notify` | Notifications and personal toggle |
+| `chunkdoctor.admin` | Every permission above |
+
+Grant only the capabilities an administrative role needs. In particular,
+`export`, `teleport`, `clear`, `reload`, and `control` should not be given to
+ordinary players.
+
+## Scan behavior
+
+`/cd scan [radius]` uses Chebyshev distance, so the requested radius forms a
+square around the player. Two independent limits apply:
+
+- maximum radius;
+- maximum number of accepted chunks.
+
+Unloaded chunks are skipped. The command never loads them to satisfy a scan.
+
+## Teleport safety
+
+Teleportation requires:
+
+- the target world to exist;
+- the target chunk to remain loaded;
+- a validated safe destination;
+- the caller to be a player with `chunkdoctor.teleport`.
+
+The command fails closed if any condition changes between selection and
+execution.
+
+## Cache clearing
+
+`/cd clear` does not immediately delete results. It returns a confirmation
+instruction. Only the matching `/cd clear confirm` action clears the in-memory
+cache.
+
+Export important results first. See [[Reports and Exports]].
diff --git a/wiki/Configuration.md b/wiki/Configuration.md
new file mode 100644
index 0000000..27308e4
--- /dev/null
+++ b/wiki/Configuration.md
@@ -0,0 +1,113 @@
+# Configuration
+
+ChunkDoctor generates `plugins/ChunkDoctor/config.yml` with comments describing
+every setting.
+
+## Recommended starting point
+
+```yaml
+monitoring:
+ interval-ticks: 100
+ chunks-per-cycle: 2
+ max-milliseconds-per-tick: 2.0
+ pause-below-tps: 17.0
+ resume-above-tps: 18.5
+ maximum-pending-analyses: 64
+
+deep-scan:
+ blocks-per-tick: 2048
+ maximum-milliseconds-per-tick: 3.0
+ maximum-duration-seconds: 30
+ maximum-concurrent-scans: 1
+```
+
+Start conservatively and observe MSPT with spark before increasing budgets.
+
+## Monitoring
+
+| Setting | Purpose |
+|---|---|
+| `enabled` | Start passive monitoring when the plugin enables |
+| `interval-ticks` | Delay between small monitoring cycles |
+| `chunks-per-cycle` | Maximum snapshots accepted per cycle |
+| `max-milliseconds-per-tick` | Main-thread collection deadline |
+| `rescan-cooldown-seconds` | Minimum delay before revisiting a chunk |
+| `result-expiration-minutes` | Lifetime of cached results |
+| `pause-below-tps` | Pause threshold |
+| `resume-above-tps` | Resume threshold; must exceed pause threshold |
+| `worker-threads` | Size of the bounded analysis pool; restart required |
+| `maximum-pending-analyses` | Hard upper bound for queued analysis |
+
+The separate pause and resume thresholds provide hysteresis, preventing rapid
+start/stop oscillation around one TPS value.
+
+## World filters
+
+`worlds.mode` accepts:
+
+- `blacklist` — analyze every world except the listed names;
+- `whitelist` — analyze only listed names.
+
+World names are compared case-insensitively. Invalid modes fall back to a safe
+validated value and produce a warning.
+
+## Manual scans
+
+`manual-scan.maximum-radius` and `manual-scan.maximum-chunks` are independent
+limits. Raising one does not bypass the other.
+
+Manual quick scans:
+
+- inspect loaded chunks only;
+- spread collection across ticks;
+- use the same bounded analysis queue as passive monitoring.
+
+## Deep scans
+
+| Setting | Protection |
+|---|---|
+| `blocks-per-tick` | Maximum block operations attempted in one slice |
+| `maximum-milliseconds-per-tick` | Time deadline for one slice |
+| `maximum-duration-seconds` | Overall scan timeout |
+| `maximum-concurrent-scans` | Global concurrency cap |
+
+Deep scans are manual-only. Do not increase both block and time budgets at once;
+change one value and measure the result.
+
+## Risk settings
+
+- `risk.weights.*` controls base contributions.
+- `risk.excess-thresholds.*` controls where nonlinear penalties begin.
+- level thresholds map the final 0–100 score to LOW, MEDIUM, HIGH, or CRITICAL.
+
+See [[Risk Scoring]] before changing weights.
+
+## Notifications
+
+Notification settings control:
+
+- minimum level;
+- minimum score increase;
+- per-chunk cooldown.
+
+Players also need `chunkdoctor.notify`. `/cd notify` stores the player's opt-out
+in persistent player data.
+
+## Exports
+
+`export.directory` must be one safe relative directory name inside the plugin
+data folder. Paths containing separators, drive syntax, or traversal components
+are rejected.
+
+See [[Reports and Exports]].
+
+## Applying changes
+
+Run:
+
+```text
+/cd reload
+```
+
+Most settings apply immediately. Worker-pool sizing requires a restart.
+Validation warnings identify values that were rejected or clamped.
diff --git a/wiki/Home.md b/wiki/Home.md
new file mode 100644
index 0000000..25819b1
--- /dev/null
+++ b/wiki/Home.md
@@ -0,0 +1,56 @@
+# ChunkDoctor Wiki
+
+ChunkDoctor is a performance-conscious Paper plugin that finds suspicious
+loaded chunks, assigns an explainable risk score, and tells administrators what
+to inspect.
+
+> A ChunkDoctor score estimates potential load. It is not a measurement of the
+> exact TPS cost of a chunk. Use ChunkDoctor to prioritize investigation and
+> [spark](https://spark.lucko.me/) to profile execution time.
+
+## Start here
+
+- [[Installation]] — requirements, installation, upgrades, and first-run checks.
+- [[Commands and Permissions]] — every command, alias, permission, and console
+ restriction.
+- [[Configuration]] — safe defaults and the complete configuration surface.
+- [[Risk Scoring]] — how score, level, reasons, recommendations, and confidence
+ are calculated.
+- [[Performance and Safety]] — thread boundaries, budgets, TPS protection, and
+ overload behavior.
+- [[Reports and Exports]] — JSON export behavior and schema overview.
+- [[Metrics and Privacy]] — exactly what bStats collects and how to opt out.
+- [[Troubleshooting]] — symptom-first operational guidance.
+- [[Building and Releasing]] — source builds and release automation.
+
+## Supported environment
+
+| Component | Requirement |
+|---|---|
+| Server | Paper 1.21.8 |
+| Java | 21 |
+| Client mod | Not required |
+| Required plugins | None |
+| Storage | In-memory results; optional JSON exports |
+
+ChunkDoctor intentionally does not claim Spigot, Folia, Purpur, or cross-version
+support until those combinations are tested and listed in a release.
+
+## Core guarantees
+
+- Only already-loaded chunks are analyzed.
+- Passive monitoring never scans every block in a chunk.
+- Deep scans are manual, tick-sliced, timed, and concurrency-limited.
+- Bukkit objects remain on the server thread.
+- Worker and export queues are bounded.
+- Unsafe export paths and invalid security-sensitive settings fail closed.
+- Every administrative capability has a dedicated permission.
+
+## Project links
+
+- [Source code](https://github.com/denfry/ChunkDoctor)
+- [Releases](https://github.com/denfry/ChunkDoctor/releases)
+- [Issue tracker](https://github.com/denfry/ChunkDoctor/issues)
+- [bStats](https://bstats.org/plugin/bukkit/ChunkDoctor/32969)
+- [Security policy](https://github.com/denfry/ChunkDoctor/security/policy)
+- [MIT License](https://github.com/denfry/ChunkDoctor/blob/main/LICENSE)
diff --git a/wiki/Installation.md b/wiki/Installation.md
new file mode 100644
index 0000000..6e7c4dd
--- /dev/null
+++ b/wiki/Installation.md
@@ -0,0 +1,88 @@
+# Installation
+
+ChunkDoctor installs as one server-side JAR on Paper 1.21.8.
+
+## Requirements
+
+- Paper 1.21.8;
+- Java 21;
+- filesystem write access to the server's `plugins/` directory;
+- no client mod and no mandatory plugin dependency.
+
+## Install
+
+1. Stop the server cleanly.
+2. Download `ChunkDoctor-.jar` from the
+ [GitHub Releases page](https://github.com/denfry/ChunkDoctor/releases).
+3. Do not download or install a `-plain.jar` development artifact.
+4. Copy the production JAR into `plugins/`.
+5. Start the server.
+6. Review `plugins/ChunkDoctor/config.yml`.
+7. Run `/chunkdoctor status`.
+
+Expected first-start log:
+
+```text
+ChunkDoctor enabled. Scores estimate risk; they do not measure per-chunk TPS.
+```
+
+## Verify the installation
+
+Run:
+
+```text
+/cd status
+```
+
+Confirm that:
+
+- ChunkDoctor is enabled;
+- the worker queue and runtime budgets are displayed;
+- the current world is not excluded by the world filter;
+- monitoring is not paused by the configured TPS threshold.
+
+Then stand in a loaded area and run:
+
+```text
+/cd scan
+/cd top
+```
+
+## Upgrade
+
+1. Read the release changelog.
+2. Back up `plugins/ChunkDoctor/config.yml`.
+3. Stop the server.
+4. Replace the old ChunkDoctor JAR with the new production JAR.
+5. Start the server and review validation warnings.
+6. Compare the generated defaults with your existing configuration.
+7. Run `/cd status` and a small `/cd scan`.
+
+Do not keep multiple ChunkDoctor JARs in `plugins/`.
+
+## Reloading
+
+Do not use the server-wide `/reload` command. It is unsafe for many Paper
+plugins and can leave tasks or classloaders in an inconsistent state.
+
+Use:
+
+```text
+/cd reload
+```
+
+This validates ChunkDoctor's configuration and applies reloadable settings.
+Changing the worker-pool size requires a full restart.
+
+## Uninstall
+
+1. Stop the server.
+2. Remove the ChunkDoctor JAR.
+3. Optionally archive or remove `plugins/ChunkDoctor/`.
+4. Start the server and confirm no other automation still calls `/cd`.
+
+JSON exports and configuration files are not removed automatically.
+
+## Next
+
+Continue with [[Commands and Permissions]] and [[Configuration]].
diff --git a/wiki/Metrics-and-Privacy.md b/wiki/Metrics-and-Privacy.md
new file mode 100644
index 0000000..eb4ec7d
--- /dev/null
+++ b/wiki/Metrics-and-Privacy.md
@@ -0,0 +1,56 @@
+# Metrics and Privacy
+
+ChunkDoctor uses bStats plugin ID
+[32969](https://bstats.org/plugin/bukkit/ChunkDoctor/32969) for anonymous,
+aggregated usage metrics.
+
+## Standard bStats data
+
+bStats may collect standard operational fields such as:
+
+- current online player count;
+- server software and Minecraft version;
+- ChunkDoctor version;
+- Java version;
+- operating-system type;
+- country-level location.
+
+ChunkDoctor does not register custom charts.
+
+## Data ChunkDoctor does not send
+
+- player names or UUIDs;
+- chat or commands;
+- world names or world data;
+- chunk coordinates;
+- scan results or risk scores;
+- configuration values;
+- JSON report contents;
+- filesystem paths.
+
+## Disable metrics
+
+bStats uses one shared server-wide configuration:
+
+```text
+plugins/bStats/config.yml
+```
+
+Set:
+
+```yaml
+enabled: false
+```
+
+Restart the server after changing the shared bStats configuration.
+
+## Performance
+
+bStats performs its network submission asynchronously. It is separate from
+ChunkDoctor's main-thread collection and bounded analysis pipeline.
+
+## References
+
+- [ChunkDoctor bStats dashboard](https://bstats.org/plugin/bukkit/ChunkDoctor/32969)
+- [bStats information for server owners](https://bstats.org/docs/server-owners)
+- [bStats source code](https://github.com/Bastian/bStats)
diff --git a/wiki/Performance-and-Safety.md b/wiki/Performance-and-Safety.md
new file mode 100644
index 0000000..8d72362
--- /dev/null
+++ b/wiki/Performance-and-Safety.md
@@ -0,0 +1,93 @@
+# Performance and Safety
+
+ChunkDoctor follows one rule: a lag diagnostic must not become the source of
+lag.
+
+## Thread model
+
+### Server thread
+
+- reads already-loaded chunks;
+- collects counters into immutable snapshots;
+- accesses Bukkit and Paper objects;
+- updates the result repository;
+- sends messages and notifications.
+
+### Bounded worker pool
+
+- calculates risk scores;
+- sorts explanations;
+- builds recommendations.
+
+### Export executor
+
+- serializes JSON;
+- writes a temporary file;
+- atomically replaces the destination when supported.
+
+Bukkit objects are never passed into worker tasks.
+
+## Passive monitoring
+
+Passive monitoring:
+
+- rotates fairly through worlds and loaded chunks;
+- limits chunks per cycle;
+- stops collection when the per-tick deadline is reached;
+- respects per-chunk rescan cooldowns;
+- pauses below the configured TPS threshold;
+- resumes only above the higher recovery threshold.
+
+It does not iterate every block.
+
+## Bounded overload behavior
+
+The analysis queue has a hard capacity. When full, new work is rejected rather
+than allowing an unbounded backlog.
+
+This protects:
+
+- heap memory;
+- shutdown time;
+- result freshness;
+- recovery after a server load spike.
+
+## Deep scans
+
+Deep scans are intentionally more expensive and therefore:
+
+- require a player command and permission;
+- operate on the current loaded chunk;
+- split block work across ticks;
+- enforce both block and time budgets;
+- have an overall timeout;
+- limit concurrent scans;
+- cancel when the world unloads or the plugin disables.
+
+## Filesystem boundary
+
+Exports resolve beneath the plugin data directory. Unsafe relative paths,
+traversal sequences, separators, and drive syntax are rejected before any file
+is written.
+
+## Administrative boundaries
+
+State-changing or sensitive operations use separate permissions:
+
+- monitor control;
+- configuration reload;
+- cache clearing;
+- teleportation;
+- export.
+
+Cache clearing uses explicit confirmation. Teleportation revalidates the world,
+loaded chunk, and safe destination.
+
+## Operational guidance
+
+- Keep default budgets until you have measured peak-time MSPT.
+- Test changes on staging before production.
+- Increase one budget at a time.
+- Use a world whitelist on networks with many utility worlds.
+- Export before clearing the cache.
+- Compare ChunkDoctor results with spark rather than treating score as timing.
diff --git a/wiki/Reports-and-Exports.md b/wiki/Reports-and-Exports.md
new file mode 100644
index 0000000..483b598
--- /dev/null
+++ b/wiki/Reports-and-Exports.md
@@ -0,0 +1,76 @@
+# Reports and Exports
+
+ChunkDoctor can export every cached result or one selected chunk as UTF-8 JSON.
+
+## Commands
+
+```text
+/cd export
+/cd export
+```
+
+Both commands require `chunkdoctor.export`.
+
+## Safety properties
+
+- Serialization runs away from the server thread.
+- The export queue is controlled by the plugin lifecycle.
+- Output is written to a temporary file first.
+- The final file uses atomic replacement when the filesystem supports it.
+- The configured directory must remain inside the plugin data folder.
+- Unsafe paths fail closed.
+
+## Document structure
+
+An export contains:
+
+- generation timestamp;
+- plugin and server versions;
+- result-level summary counts;
+- one object per exported chunk;
+- stable world UUID and display name;
+- chunk coordinates;
+- analysis timestamp;
+- score, level, and confidence;
+- complete observed metrics;
+- reasons and recommendations;
+- whether the result came from a deep scan.
+
+Abbreviated example:
+
+```json
+{
+ "generatedAt": "2026-07-28T12:00:00Z",
+ "pluginVersion": "1.0.0",
+ "serverVersion": "Paper 1.21.8",
+ "summary": {
+ "analyzedChunks": 1,
+ "criticalChunks": 1
+ },
+ "chunks": [
+ {
+ "key": {
+ "worldName": "world",
+ "chunkX": 52,
+ "chunkZ": -18
+ },
+ "riskScore": 84,
+ "riskLevel": "CRITICAL",
+ "confidence": "HIGH",
+ "deepScan": true
+ }
+ ]
+}
+```
+
+## Privacy
+
+Exports may reveal world names, coordinates, infrastructure patterns, and
+administrator observations. Treat them as operational data:
+
+- do not publish production exports without review;
+- remove sensitive coordinates before attaching a report publicly;
+- use private security reporting for exploit or abuse evidence;
+- restrict `chunkdoctor.export` to trusted staff.
+
+Exports are not sent to bStats. See [[Metrics and Privacy]].
diff --git a/wiki/Risk-Scoring.md b/wiki/Risk-Scoring.md
new file mode 100644
index 0000000..9ee88e9
--- /dev/null
+++ b/wiki/Risk-Scoring.md
@@ -0,0 +1,88 @@
+# Risk Scoring
+
+ChunkDoctor converts observable chunk conditions into an explainable risk score
+from 0 to 100.
+
+## What the score means
+
+A high score means:
+
+- potentially expensive objects are concentrated in one chunk;
+- the chunk deserves earlier manual investigation;
+- one or more configurable thresholds were exceeded.
+
+A high score does **not** prove:
+
+- the chunk consumes an exact amount of TPS;
+- the chunk is the only source of lag;
+- removing everything in the chunk will fix the server.
+
+Use spark to confirm execution-time cost.
+
+## Inputs
+
+Quick observations may include:
+
+- total entities;
+- villagers and hostile mobs;
+- dropped items and armor stands;
+- minecarts, boats, and other vehicles;
+- hoppers, furnaces, containers, spawners, and other block entities.
+
+Completed deep scans add block-level observations such as redstone components,
+pistons, observers, and powered states exposed through the public Paper API.
+
+## Calculation stages
+
+1. Each observed count contributes `count × configured weight`.
+2. Counts beyond configured excess thresholds receive nonlinear penalties.
+3. Density signals add a penalty when several expensive categories are
+ concentrated together.
+4. The result is clamped to the range 0–100.
+5. The configured thresholds map the score to a risk level.
+
+Default levels:
+
+| Score | Level |
+|---:|---|
+| 0–29 | LOW |
+| 30–59 | MEDIUM |
+| 60–79 | HIGH |
+| 80–100 | CRITICAL |
+
+## Confidence
+
+Confidence describes observation completeness, not server quality.
+
+| Confidence | Meaning |
+|---|---|
+| LOW | Some quick-scan context was unavailable |
+| MEDIUM | Quick observation had useful entity/context data |
+| HIGH | A manual deep scan completed successfully |
+
+A LOW-confidence HIGH score should be investigated, not ignored. A HIGH
+confidence does not turn the risk estimate into exact profiling.
+
+## Reasons and recommendations
+
+Each result contains:
+
+- the strongest score contributors;
+- relevant recommendations derived from observed metrics;
+- whether it came from a quick or deep scan;
+- the observation timestamp.
+
+Recommendations are conditional. For example, hopper advice appears only when
+hopper metrics justify it.
+
+## Tuning safely
+
+1. Export a baseline set of results.
+2. Change one family of weights or thresholds.
+3. Reload the configuration.
+4. Re-scan representative chunks.
+5. Compare false positives and false negatives.
+6. Validate suspicious areas with spark.
+
+Avoid tuning solely around one unusual farm. The model should remain useful
+across different worlds and play styles.
diff --git a/wiki/Troubleshooting.md b/wiki/Troubleshooting.md
new file mode 100644
index 0000000..f6e4cd3
--- /dev/null
+++ b/wiki/Troubleshooting.md
@@ -0,0 +1,93 @@
+# Troubleshooting
+
+Start with `/cd status`, the Paper log, and current TPS/MSPT observations.
+
+## ChunkDoctor does not enable
+
+Check:
+
+1. Java is version 21.
+2. The server is Paper 1.21.8.
+3. Only one ChunkDoctor JAR exists in `plugins/`.
+4. The installed file is not the `-plain.jar`.
+5. The first ChunkDoctor stack trace in the log, not later follow-on errors.
+
+## No scan results
+
+Check:
+
+- passive monitoring is started;
+- the current world passes the whitelist/blacklist;
+- TPS is above the resume threshold;
+- chunks around players are loaded;
+- the analysis queue is not full;
+- results have not expired from the in-memory cache.
+
+Run:
+
+```text
+/cd status
+/cd scan
+/cd top
+```
+
+## Monitoring remains paused
+
+Compare the server's current TPS with:
+
+```yaml
+pause-below-tps: 17.0
+resume-above-tps: 18.5
+```
+
+The monitor does not resume until TPS exceeds the higher threshold. This is
+intentional hysteresis.
+
+## Deep scan times out
+
+Possible causes:
+
+- a tall world section;
+- heavy concurrent server load;
+- very conservative block/time budgets;
+- a scan interrupted by world unload.
+
+Prefer reducing concurrent server activity or scanning a smaller representative
+area. Increase only one deep-scan budget at a time and monitor MSPT.
+
+## Export is rejected
+
+`export.directory` accepts a safe relative directory name. Values containing
+`..`, `/`, `\`, `:`, or an absolute path are intentionally rejected.
+
+Also verify write access to `plugins/ChunkDoctor/`.
+
+## Score is high but TPS is normal
+
+This is valid. Score estimates concentration of potentially expensive objects;
+it does not measure exact tick time. Profile the area with spark during the
+relevant workload.
+
+## Score appears incorrect
+
+1. Run a deep scan if safe.
+2. Export the result.
+3. Record the ChunkDoctor, Paper, and Java versions.
+4. Describe what exists in the chunk and why the score seems wrong.
+5. Open the
+ [incorrect-score template](https://github.com/denfry/ChunkDoctor/issues/new?template=false_score.yml).
+
+Review exports for sensitive coordinates before posting.
+
+## Command denied
+
+Verify the exact permission from [[Commands and Permissions]]. The base
+`chunkdoctor.use` permission does not grant every subcommand.
+
+## Getting support
+
+Use the [issue tracker](https://github.com/denfry/ChunkDoctor/issues) for normal
+bugs. Follow the
+[security policy](https://github.com/denfry/ChunkDoctor/security/policy) for
+path escapes, permission bypasses, denial-of-service issues, or other
+vulnerabilities.
diff --git a/wiki/_Footer.md b/wiki/_Footer.md
new file mode 100644
index 0000000..f8f9216
--- /dev/null
+++ b/wiki/_Footer.md
@@ -0,0 +1,5 @@
+[ChunkDoctor](https://github.com/denfry/ChunkDoctor) ·
+[Releases](https://github.com/denfry/ChunkDoctor/releases) ·
+[Issues](https://github.com/denfry/ChunkDoctor/issues) ·
+[Security](https://github.com/denfry/ChunkDoctor/security/policy) ·
+[MIT License](https://github.com/denfry/ChunkDoctor/blob/main/LICENSE)
diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md
new file mode 100644
index 0000000..c2557ed
--- /dev/null
+++ b/wiki/_Sidebar.md
@@ -0,0 +1,19 @@
+## ChunkDoctor
+
+- [[Home]]
+- [[Installation]]
+- [[Commands and Permissions]]
+- [[Configuration]]
+- [[Risk Scoring]]
+- [[Performance and Safety]]
+- [[Reports and Exports]]
+- [[Metrics and Privacy]]
+- [[Troubleshooting]]
+- [[Building and Releasing]]
+
+---
+
+- [Source](https://github.com/denfry/ChunkDoctor)
+- [Releases](https://github.com/denfry/ChunkDoctor/releases)
+- [Issues](https://github.com/denfry/ChunkDoctor/issues)
+- [bStats](https://bstats.org/plugin/bukkit/ChunkDoctor/32969)