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 @@

Build Release + Servers using ChunkDoctor on bStats + GitHub stars Paper 1.21.8 Java 21 MIT License @@ -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 | +|---|---|---| +| [![ChunkDoctor explains an 84 out of 100 CRITICAL chunk risk score](docs/assets/gallery/chunkdoctor-risk-analysis.png)](docs/assets/gallery/chunkdoctor-risk-analysis.png) | [![ChunkDoctor ranks loaded chunks by risk](docs/assets/gallery/chunkdoctor-risk-ranking.png)](docs/assets/gallery/chunkdoctor-risk-ranking.png) | [![ChunkDoctor deep scan progresses within block and time budgets](docs/assets/gallery/chunkdoctor-deep-scan.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 +[![Servers using ChunkDoctor](https://bstats.org/signatures/bukkit/32969.svg)](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 logo: a Minecraft chunk with a diagnostic pulse + +# ChunkDoctor + +**Find suspicious loaded chunks before they become a server-wide problem.** + +[![Build status](https://img.shields.io/github/actions/workflow/status/denfry/ChunkDoctor/build.yml?branch=main&style=for-the-badge&label=build)](https://github.com/denfry/ChunkDoctor/actions/workflows/build.yml) +[![Latest release](https://img.shields.io/github/v/release/denfry/ChunkDoctor?display_name=tag&sort=semver&style=for-the-badge)](https://github.com/denfry/ChunkDoctor/releases) +[![Paper 1.21.8](https://img.shields.io/badge/Paper-1.21.8-2f3136?style=for-the-badge)](https://papermc.io/) +[![Java 21](https://img.shields.io/badge/Java-21-e76f00?style=for-the-badge&logo=openjdk&logoColor=white)](https://adoptium.net/) +[![MIT license](https://img.shields.io/badge/License-MIT-22c55e?style=for-the-badge)](https://github.com/denfry/ChunkDoctor/blob/main/LICENSE) +[![Servers using ChunkDoctor](https://bstats.org/signatures/bukkit/32969.svg)](https://bstats.org/plugin/bukkit/ChunkDoctor/32969) + +
+ +![ChunkDoctor showing an explainable risk score for a loaded Minecraft chunk](https://raw.githubusercontent.com/denfry/ChunkDoctor/main/docs/assets/chunkdoctor-hero.png) + +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)