From 562915672c3f9533fc1912a12f1ac51b349c595f Mon Sep 17 00:00:00 2001 From: "Flemming N. Larsen" Date: Sun, 13 Sep 2026 14:44:10 +0200 Subject: [PATCH 1/5] docs: propose Rumble rankings guide --- .clue/id-ledger.yaml | 6 +++++ .../open-questions.md | 11 +++++++++ .../proposal.md | 23 +++++++++++++++++++ .../CH-045-document-rumble-rankings/tasks.md | 15 ++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 changes/CH-045-document-rumble-rankings/open-questions.md create mode 100644 changes/CH-045-document-rumble-rankings/proposal.md create mode 100644 changes/CH-045-document-rumble-rankings/tasks.md diff --git a/.clue/id-ledger.yaml b/.clue/id-ledger.yaml index 0290c6948..2445a146e 100644 --- a/.clue/id-ledger.yaml +++ b/.clue/id-ledger.yaml @@ -275,6 +275,12 @@ events: - {id: CH-044, kind: numeric, state: retired, prefix: CH, component: "44"} - {id: CH-044-open-questions, kind: opaque, state: retired} - {id: CH-044-tasks, kind: opaque, state: retired} + - {id: CH-045, kind: numeric, state: reserved, prefix: CH, component: "45"} + - {id: TASKS-001, kind: numeric, state: reserved, prefix: TASKS, component: "1"} + - {id: OQ-001, kind: numeric, state: reserved, prefix: OQ, component: "1"} + - {id: CH-045, kind: numeric, state: live, prefix: CH, component: "45"} + - {id: TASKS-001, kind: numeric, state: live, prefix: TASKS, component: "1"} + - {id: OQ-001, kind: numeric, state: live, prefix: OQ, component: "1"} high-water: - {id: ADR-047, kind: numeric, state: reserved, prefix: ADR, component: "47"} - {id: AN-001, kind: numeric, state: reserved, prefix: AN, component: "1"} diff --git a/changes/CH-045-document-rumble-rankings/open-questions.md b/changes/CH-045-document-rumble-rankings/open-questions.md new file mode 100644 index 000000000..9855f5b33 --- /dev/null +++ b/changes/CH-045-document-rumble-rankings/open-questions.md @@ -0,0 +1,11 @@ +--- +id: OQ-001 +type: open-questions +status: open +links: [CH-045] +title: CH-045 open questions +--- + +# Open questions + +None. The merged external behavior and the user's selected documentation boundary are decision-complete. diff --git a/changes/CH-045-document-rumble-rankings/proposal.md b/changes/CH-045-document-rumble-rankings/proposal.md new file mode 100644 index 000000000..3ab93558c --- /dev/null +++ b/changes/CH-045-document-rumble-rankings/proposal.md @@ -0,0 +1,23 @@ +--- +id: CH-045 +type: change +status: open +links: [G-001, CAP-014, CAP-015] +title: Document current Rumble rankings on robocode.dev +--- + +# CH-045 — Document current Rumble rankings on robocode.dev + +## Why + +Merged [`robocode-dev/rumble-data#12`](https://github.com/robocode-dev/rumble-data/pull/12) changed the public ranking contract: catalog polling is change-aware, live rankings use only current active-version matchups, publication exposes meaningful freshness, and immutable cumulative month-end snapshots are available. The dashboard already links to `/rumble/rankings`, but that canonical public explanation does not exist, and the current Tank Royale Rumble design still presents several unimplemented LiteRumble-inspired metrics as current behavior. + +## What + +Add a dedicated `robocode.dev/rumble/rankings` guide explaining the exact APS calculation, distinct pairing weights, behavior epochs, active bot versions, update cadence, publication freshness, and cumulative monthly snapshots. Link it from the Rumble overview, audience guides, dashboard, and VitePress sidebar. Align the internal Rumble design and the two external-repository capability carriers with the merged behavior, and record the user-visible documentation change in the changelog. + +This is a plan-less change serving `G-001`, `CAP-014`, and `CAP-015`. It does not reopen completed `P-003`, and the repository states no vision. + +## Documentation impact + +The public Rumble rankings guide becomes the canonical reader-facing explanation. Existing quickstarts retain audience-specific instructions and link to it instead of duplicating the complete algorithm. Internal design remains the durable technical carrier for cross-repository behavior. diff --git a/changes/CH-045-document-rumble-rankings/tasks.md b/changes/CH-045-document-rumble-rankings/tasks.md new file mode 100644 index 000000000..351346a1b --- /dev/null +++ b/changes/CH-045-document-rumble-rankings/tasks.md @@ -0,0 +1,15 @@ +--- +id: TASKS-001 +type: tasks +status: open +links: [CH-045] +title: CH-045 implementation tasks +--- + +# Tasks + +- [ ] Add the canonical public ranking guide with exact APS, active-version, behavior-epoch, cadence, freshness, and snapshot semantics; serves `CAP-014` and `CAP-015`. +- [ ] Link the guide from Rumble navigation and each affected audience guide; serves `CAP-014` and `CAP-015`. +- [ ] Align internal Rumble design and capability carriers with the merged `rumble-data` behavior; serves `CAP-014` and `CAP-015`. +- [ ] Add the user-visible documentation change to the changelog. +- [ ] Build the VitePress site, run strict Cliewen validation, and verify links and the complete diff. From 59eaf6b50d1b488d2f212ed77d3ee76ea64eafe9 Mon Sep 17 00:00:00 2001 From: "Flemming N. Larsen" Date: Sun, 13 Sep 2026 14:51:54 +0200 Subject: [PATCH 2/5] docs: explain current Rumble rankings --- .clue/id-ledger.yaml | 20 +++++ CHANGELOG.md | 2 + .../CH-045-document-rumble-rankings/tasks.md | 8 +- .../CAP-014-rumble-bot-catalog/README.md | 2 +- .../CAP-014-rumble-bot-catalog/criteria.md | 9 +++ .../CAP-014-rumble-bot-catalog/design.md | 2 +- .../CAP-015-rumble-result-data/README.md | 2 +- .../CAP-015-rumble-result-data/criteria.md | 33 ++++++++ .../CAP-015-rumble-result-data/design.md | 8 +- docs/design/rumble/README.md | 6 +- .../rumble/aggregation-and-dashboard.md | 63 ++++++++------- docs/design/rumble/user-documentation.md | 8 +- web/docs/.vitepress/config.mts | 1 + web/docs/rumble/bot-author-guide.md | 4 +- web/docs/rumble/client-guide.md | 2 +- web/docs/rumble/index.md | 9 ++- web/docs/rumble/moderator-guide.md | 2 +- web/docs/rumble/rankings.md | 77 +++++++++++++++++++ 18 files changed, 200 insertions(+), 58 deletions(-) create mode 100644 web/docs/rumble/rankings.md diff --git a/.clue/id-ledger.yaml b/.clue/id-ledger.yaml index 2445a146e..962eb2120 100644 --- a/.clue/id-ledger.yaml +++ b/.clue/id-ledger.yaml @@ -281,6 +281,26 @@ events: - {id: CH-045, kind: numeric, state: live, prefix: CH, component: "45"} - {id: TASKS-001, kind: numeric, state: live, prefix: TASKS, component: "1"} - {id: OQ-001, kind: numeric, state: live, prefix: OQ, component: "1"} + - {id: RDA-001, kind: numeric, state: reserved, prefix: RDA, component: "1"} + - {id: RDA-002, kind: numeric, state: reserved, prefix: RDA, component: "2"} + - {id: RDA-003, kind: numeric, state: reserved, prefix: RDA, component: "3"} + - {id: RDA-004, kind: numeric, state: reserved, prefix: RDA, component: "4"} + - {id: RBC-005, kind: numeric, state: reserved, prefix: RBC, component: "5"} + - {id: RDA-001, kind: numeric, state: live, prefix: RDA, component: "1"} + - {id: RDA-002, kind: numeric, state: live, prefix: RDA, component: "2"} + - {id: RDA-003, kind: numeric, state: live, prefix: RDA, component: "3"} + - {id: RDA-004, kind: numeric, state: live, prefix: RDA, component: "4"} + - {id: RDA-005, kind: numeric, state: reserved, prefix: RDA, component: "5"} + - {id: RDA-005, kind: numeric, state: live, prefix: RDA, component: "5"} + - {id: RDA-006, kind: numeric, state: reserved, prefix: RDA, component: "6"} + - {id: RDA-007, kind: numeric, state: reserved, prefix: RDA, component: "7"} + - {id: RDA-008, kind: numeric, state: reserved, prefix: RDA, component: "8"} + - {id: RDA-009, kind: numeric, state: reserved, prefix: RDA, component: "9"} + - {id: RDA-006, kind: numeric, state: live, prefix: RDA, component: "6"} + - {id: RDA-007, kind: numeric, state: live, prefix: RDA, component: "7"} + - {id: RDA-008, kind: numeric, state: live, prefix: RDA, component: "8"} + - {id: RDA-009, kind: numeric, state: live, prefix: RDA, component: "9"} + - {id: RBC-005, kind: numeric, state: live, prefix: RBC, component: "5"} high-water: - {id: ADR-047, kind: numeric, state: reserved, prefix: ADR, component: "47"} - {id: AN-001, kind: numeric, state: reserved, prefix: AN, component: "1"} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e7e990be..ecb0dde41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ ### 🔧 Changes +- Documentation (Rumble): + - Added the canonical APS and rankings guide covering active bot versions, behavior epochs, update cadence, publication freshness, and immutable cumulative month-end snapshots. - Bot API (.NET, TypeScript): - `X`, `Y`, `Direction`, `GunDirection`, and `RadarDirection` now fall back to the bot's initial position when read before the first turn of a game, which is what the Java and Python Bot APIs already did. They still throw a `BotException` when no initial position is available. - Bot API (Python): diff --git a/changes/CH-045-document-rumble-rankings/tasks.md b/changes/CH-045-document-rumble-rankings/tasks.md index 351346a1b..c86d64380 100644 --- a/changes/CH-045-document-rumble-rankings/tasks.md +++ b/changes/CH-045-document-rumble-rankings/tasks.md @@ -8,8 +8,8 @@ title: CH-045 implementation tasks # Tasks -- [ ] Add the canonical public ranking guide with exact APS, active-version, behavior-epoch, cadence, freshness, and snapshot semantics; serves `CAP-014` and `CAP-015`. -- [ ] Link the guide from Rumble navigation and each affected audience guide; serves `CAP-014` and `CAP-015`. -- [ ] Align internal Rumble design and capability carriers with the merged `rumble-data` behavior; serves `CAP-014` and `CAP-015`. -- [ ] Add the user-visible documentation change to the changelog. +- [x] Add the canonical public ranking guide with exact APS, active-version, behavior-epoch, cadence, freshness, and snapshot semantics; serves `CAP-014` and `CAP-015`. +- [x] Link the guide from Rumble navigation and each affected audience guide; serves `CAP-014` and `CAP-015`. +- [x] Align internal Rumble design and capability carriers with the merged `rumble-data` behavior; serves `CAP-014` and `CAP-015`. +- [x] Add the user-visible documentation change to the changelog. - [ ] Build the VitePress site, run strict Cliewen validation, and verify links and the complete diff. diff --git a/docs/capabilities/CAP-014-rumble-bot-catalog/README.md b/docs/capabilities/CAP-014-rumble-bot-catalog/README.md index c6b8182b2..56f2d2b03 100644 --- a/docs/capabilities/CAP-014-rumble-bot-catalog/README.md +++ b/docs/capabilities/CAP-014-rumble-bot-catalog/README.md @@ -13,4 +13,4 @@ reversal-cost: low Provide a community-owned, source-only catalog of eligible Tank Royale Rumble bots, so authors can submit bots through reviewable pull requests and battle contributors can retrieve precisely the reviewed source that they run. -The capability is implemented in the external `robocode-dev/rumble-bots` repository. It adopts the Tank Royale booter convention, validates submissions in portable Python, and publishes a generated versioned catalog. Criteria remain draft until their external automated evidence is tagged and registered under P-001/M-002. +The capability is implemented in the external `robocode-dev/rumble-bots` repository. It adopts the Tank Royale booter convention, validates submissions in portable Python, and publishes a generated versioned catalog after a bot pull request is merged. `rumble-data` checks that catalog hourly, publishing a new bot or version while skipping ranking work for identical content. Criteria remain draft until their external automated evidence is registered in this corpus. diff --git a/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md b/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md index 16b85f280..4df2e69ef 100644 --- a/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md +++ b/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md @@ -43,4 +43,13 @@ Feature: rumble-bot-catalog — Rumble bot catalog When the generated catalog is synchronized for ranked clients Then the team entry carries both member identities in `teamMembers` while individual entries carry an empty list And generation rejects a missing, inactive, unknown, or nested team member + + @RBC-005 @draft + Scenario: Hourly catalog polling publishes only changed reviewed content + Test-type: Integration + Given the normalized local catalog and the reviewed source catalog + When the scheduled synchronization checks the source + Then a changed bot or version is stored and regenerated into current rankings + And identical content is not rewritten, aggregated, committed, or deployed + And catalog and result writers cannot overlap their publication sections ``` diff --git a/docs/capabilities/CAP-014-rumble-bot-catalog/design.md b/docs/capabilities/CAP-014-rumble-bot-catalog/design.md index 65d60f45a..578ac3d4f 100644 --- a/docs/capabilities/CAP-014-rumble-bot-catalog/design.md +++ b/docs/capabilities/CAP-014-rumble-bot-catalog/design.md @@ -22,4 +22,4 @@ The catalog schema is additive within a schema version: readers ignore fields th ## External evidence -The external `robocode-dev/rumble-bots` repository holds the implementation and focused integration tests for RBC-001 through RBC-003 at merged commit `c735e6ff4`. [rumble-bots#3](https://github.com/robocode-dev/rumble-bots/pull/3) at `d58e6b5` adds RBC-004 positive and negative evidence for TwinDuel membership, including rejection of malformed catalog entries. Run `python -m unittest discover -s tests -v` and `scripts/validate_bot.py --root . --owner flemming-n-larsen --smoke` in that repository to reproduce the evidence. During P-001/M-002, those tests will receive their RBC purpose tags and this criteria artifact can become active without changing the criterion meanings. +The external `robocode-dev/rumble-bots` repository holds the implementation and focused integration tests for RBC-001 through RBC-003 at merged commit `c735e6ff4`. [rumble-bots#3](https://github.com/robocode-dev/rumble-bots/pull/3) at `d58e6b5` adds RBC-004 positive and negative evidence for TwinDuel membership. Merged [rumble-data#12](https://github.com/robocode-dev/rumble-data/pull/12) adds RBC-005 evidence that normalized unchanged polls are not rewritten or aggregated and that changed bot/version catalogs enter the serialized publication path. Run each external repository's Python test suite to reproduce the evidence; criteria remain draft here until the external evidence can be registered in this corpus. diff --git a/docs/capabilities/CAP-015-rumble-result-data/README.md b/docs/capabilities/CAP-015-rumble-result-data/README.md index 135593925..173ca65dc 100644 --- a/docs/capabilities/CAP-015-rumble-result-data/README.md +++ b/docs/capabilities/CAP-015-rumble-result-data/README.md @@ -13,4 +13,4 @@ reversal-cost: low Provide an auditable, machine-written record of ranked Tank Royale Rumble battles, so contributors can submit results without write access and everyone can reproduce the leaderboard and match advice from immutable facts. -The capability is implemented in the external `robocode-dev/rumble-data` repository. Its portable Python scripts validate issue-ops batches, append accepted facts, derive projections, and serve the static dashboard. Criteria remain draft until their external automated evidence is registered under P-001/M-002. +The capability is implemented in the external `robocode-dev/rumble-data` repository. Its portable Python scripts validate issue-ops batches, append accepted facts, derive cumulative active-version projections, publish meaningful ranking freshness, and preserve immutable month-end dashboard snapshots. Criteria remain draft until their external automated evidence is registered in this corpus. diff --git a/docs/capabilities/CAP-015-rumble-result-data/criteria.md b/docs/capabilities/CAP-015-rumble-result-data/criteria.md index 4b4635a7b..0d68cac45 100644 --- a/docs/capabilities/CAP-015-rumble-result-data/criteria.md +++ b/docs/capabilities/CAP-015-rumble-result-data/criteria.md @@ -52,4 +52,37 @@ Feature: rumble-result-data — Rumble result data Then `1v1` and `melee` admit only the pinned number of distinct active individual entries And `twinduel` admits exactly two distinct active teams whose catalog members are active individuals, expand to the pinned participant count, and have disjoint member identities And validation rejects a team in an individual game type, an individual in TwinDuel, or a result whose catalog membership, eligibility, or member disjointness is invalid + + @RDA-006 @draft + Scenario: APS gives each distinct pairing equal weight within the active ranking epoch + Test-type: Integration + Given eligible facts for the current behavior version and the catalog's active bot versions + When a game type's leaderboard is generated + Then repeated battles are averaged within their exact participant pairing and APS is 100 times the mean of those pairing averages + And each pairing has equal weight regardless of its battle count + And a superseded participant or another behavior version does not contribute, while an active version with no samples has APS zero + + @RDA-007 @draft + Scenario: Publication freshness changes only when current ranking data changes + Test-type: Integration + Given a current leaderboard and its recorded ranking-tree hash and publication time + When publication regenerates or reconciles the current leaderboard + Then a changed ranking tree advances `lastUpdatedAt` + And identical ranking output, deployment, or snapshot creation preserves the previous time + + @RDA-008 @draft + Scenario: The first writer after a UTC month boundary preserves immutable cumulative history + Test-type: Integration + Given the current cumulative leaderboard and its month cursor + When a serialized writer first runs in a later UTC month + Then it copies current leaderboard and bot-detail JSON byte for byte for every completed month before accepting new input + And it never resets the live ranking or alters an existing snapshot + + @RDA-009 @draft + Scenario: A dashboard viewer can distinguish current rankings from read-only monthly history + Test-type: E2E + Given current and archived leaderboard data listed in the publication history manifest + When a viewer selects Current or a completed month and a game type + Then the dashboard loads the corresponding leaderboard and bot details + And it shows the ranking update time and identifies an archived month as read-only ``` diff --git a/docs/capabilities/CAP-015-rumble-result-data/design.md b/docs/capabilities/CAP-015-rumble-result-data/design.md index 42cf944eb..c1c90555c 100644 --- a/docs/capabilities/CAP-015-rumble-result-data/design.md +++ b/docs/capabilities/CAP-015-rumble-result-data/design.md @@ -22,10 +22,12 @@ An issue-ops submission is one fenced JSON batch envelope with `schemaVersion`, ## Projection and moderation contracts -`scripts/aggregate.py` is a pure function of raw facts and rollups, `catalog.json`, `engine.json`, registrations, `bans.json`, and `exclusions.json`. It emits one versioned leaderboard, pairing-statistics file, matches-needed file, and bot detail shard per game type, plus contributor totals. The projection identifier hashes its relevant inputs. Rankings use only the current `behaviorVersion` epoch and active bot versions. +`scripts/aggregate.py` is a pure function of raw facts and rollups, `catalog.json`, `engine.json`, registrations, `bans.json`, and `exclusions.json`. It emits one versioned leaderboard, pairing-statistics file, matches-needed file, and bot detail shard per game type, plus contributor totals. The projection identifier hashes its relevant inputs. Rankings use only the current `behaviorVersion` epoch and matchups whose complete participant set contains active, game-type-eligible identities. A new active version starts at zero samples, and a matchup containing a superseded identity stops affecting every participant's current APS. -CI serializes ingestion and is the sole writer of facts and projections on `main`. Moderators never rewrite a fact: an exclusion, ban, disqualification, or removed registration only affects the next recomputation. Monthly rollups replace raw files older than three full months on the archive branch after an equivalent projection is verified, retaining the event-sourcing guarantee. +CI serializes result and catalog publication and is the sole writer of facts and projections on `main`. Moderators never rewrite a fact: an exclusion, ban, disqualification, or removed registration only affects the next recomputation. Monthly rollups replace raw files older than three full months on the archive branch after an equivalent projection is verified, retaining the event-sourcing guarantee. + +`scripts/publication.py` owns operational metadata and history rather than putting a clock in aggregation. It hashes current leaderboard and bot-detail JSON, advances `lastUpdatedAt` only when that visible tree changes, and snapshots completed UTC months before the first writer reads new input. Snapshots are cumulative, append-only publication records; they do not reset the live ranking and are never rewritten by late results. Changed writer commits explicitly dispatch Pages, with an hourly tree comparison as a missed-deployment fallback. ## External evidence -The external `robocode-dev/rumble-data` repository holds the implementation and focused suite for RDA-001 through RDA-004. [rumble-data#4](https://github.com/robocode-dev/rumble-data/pull/4) added every-ranked-type matchmaking advice and catalog-synchronization coverage and was accepted at merge commit `18e916e4a381c8108568d7ca77e3e14d88dd4583`; M-007 is complete. [rumble-data#7](https://github.com/robocode-dev/rumble-data/pull/7) at `83e71d4` adds RDA-005 positive and negative validation and advice-generation evidence, including TwinDuel member disjointness while preserving repeated member slots within a team. [rumble-data#10](https://github.com/robocode-dev/rumble-data/pull/10) corrects the GitHub issue-submitter extraction with focused RDA-001 evidence. CH-032 then proved the live path: [rumble-data#11](https://github.com/robocode-dev/rumble-data/issues/11) accepted the client batch, and `f25c02f20e2938545563a0d2a017e53e9a208a7e` published the resulting facts and projections. Run `python -m unittest discover -s tests -v` in that repository to reproduce the evidence. During P-001/M-002, the external-evidence reconciliation set must include RDA-001 through RDA-005 before this criteria artifact can become active without changing the criterion meanings. +The external `robocode-dev/rumble-data` repository holds the implementation and focused suite for RDA-001 through RDA-005, including the accepted live client path. Merged [rumble-data#12](https://github.com/robocode-dev/rumble-data/pull/12) adds RDA-006 through RDA-009 evidence for pairing-first APS, active-version matchup filtering, ranking-tree freshness, immutable rollover, deployment reconciliation, and current/history dashboard selection. Run `python -m unittest discover -s tests -v` in that repository to reproduce the evidence. The criteria remain draft here until the external evidence can be registered in this corpus. diff --git a/docs/design/rumble/README.md b/docs/design/rumble/README.md index f6339f9be..c15bb8bf2 100644 --- a/docs/design/rumble/README.md +++ b/docs/design/rumble/README.md @@ -91,7 +91,7 @@ Changes (CH-xxx, via the `clue-delta` loop) should be created in this order; eac | 2 | `create-rumble-bots-repo` | Define and scaffold the bot submission repository: source-only layout, validation, ownership, slot budget, templates, governance, and generated catalog. | Tank Royale prep | | 3 | `create-rumble-data-repo` | Define and scaffold the data repository: result inbox, validation, raw facts, aggregation, compaction, matchmaking projections, dashboard, and GitHub Pages publishing. | Tank Royale prep | | 4 | `create-rumble-client` | Build the ranked/practice client, local journal, replay evidence store, container, and issue-ops submission transport; defer fork-PR submission until `rumble-data` supports it. | `rumble-bots` catalog and `rumble-data` engine/matchmaking files | -| 5 | `publish-rumble-docs` | Publish the user guides, onboarding flow, moderator handbook, FAQ, dashboard participation page, ADRs, and contributor-facing architecture docs. | Interfaces from the previous proposals | +| 5 | `publish-rumble-docs` | Publish the audience guides, canonical rankings reference, dashboard links, governance guidance, and contributor-facing architecture docs. | Interfaces from the previous proposals | The first proposal is intentionally a Tank Royale preparation change, not a Rumble implementation. It should make the existing engine, schema, Battle Runner, and bot metadata ready for the later repositories without creating those repositories. @@ -112,7 +112,7 @@ These directions keep the sub-documents consistent and should be reflected in th | Direction | Where detailed | |-----------|----------------| -| **Ruleset and scoring = RoboRumble/LiteRumble, unchanged** (APS primary; Win%, Survival, Vote, NPP/ANPP, KNNPBI, Glicko-2). Battle tested for two decades; do not reinvent. | Aggregation doc | +| **APS uses the RoboRumble/LiteRumble pairing-first principle.** Current output is APS plus battle and pairing counts; unimplemented classic metrics are not part of the current contract. | Aggregation doc | | **V1 battle types are 1v1, TwinDuel, and Melee.** These follow the popular LiteRumble/RoboRumble categories for the original game. TwinDuel is the 2v2 twin-team format. Mini, micro, nano, and giga categories are out of v1 because bytecode-size limits do not translate cleanly to source-code bots across multiple languages. | Client + aggregation docs | | **Own-bot priority: yes**, with the self-reported-only marker plus independent confirmation for trust. | Client doc | | **Engine pinning by `behaviorVersion`.** Release versions stay lockstep across all Tank Royale artifacts (the right model for the product); a separate integer `behaviorVersion`, owned by the server and bumped only on game-observable changes (server physics/scoring/turn processing/RNG plus Bot API behavior), is the compatibility contract. Compatibility, client rollout, and result **epochs** all key on it; releases that do not bump it (e.g. GUI-only) cause no rollout and no epoch reset. Supersedes the earlier patch-vs-minor rule. | Client + aggregation docs | @@ -140,7 +140,7 @@ These directions keep the sub-documents consistent and should be reflected in th | **Documentation is a first-class design area**: published user docs are Markdown under `/web/docs/rumble/`, internal ADRs and architecture descriptions stay under `/docs`, one quickstart exists per audience, error messages link into the published docs, and onboarding friction is budgeted. | User documentation doc | | **No per-client contribution cap**, matching the classic rumble (verified on the RoboWiki): saturation is handled by the priority mechanism alone; `targetSamplesPerPairing` plays the classic `BATTLESPERBOT` role. | Client + aggregation docs | | **SPDX `license` field joins the general booter bot config schema** (not rumble-only); implementation lands in the Tank Royale repo when the design leaves draft. | Submission doc | -| **Docs and repos are versioned by the Tank Royale version**: both repos are tagged at every engine pin change; docs always describe the pinned engine, old tags serve old readers. Template bots per platform ship in `rumble-bots`; scoring explanations live in the Rumble FAQ, linked from dashboard column headers. | User documentation doc | +| **Docs and repos are versioned by the Tank Royale version**: both repos are tagged at every engine pin change; docs always describe the pinned engine, old tags serve old readers. The canonical scoring explanation lives at `/rumble/rankings` and is linked from the dashboard. | User documentation doc | ## Game Type References diff --git a/docs/design/rumble/aggregation-and-dashboard.md b/docs/design/rumble/aggregation-and-dashboard.md index 3968e51af..e122a6c41 100644 --- a/docs/design/rumble/aggregation-and-dashboard.md +++ b/docs/design/rumble/aggregation-and-dashboard.md @@ -21,17 +21,16 @@ The "server" side that is not a server: how submitted results are ingested into ``` rumble-data/ -├── results/raw///*.json (immutable facts, append-only) -├── leaderboard/leaderboard.json (projection) +├── results/raw///*.json (immutable facts, append-only) +├── results/rollups/*.json (compacted immutable facts) +├── leaderboard/.json (current projections) ├── leaderboard/bots/-.json (per-bot detail shards) -├── matchmaking/matches_needed.json (projection: advice for clients) -├── matchmaking/pairings.json (projection: per-pairing stats) -├── clients.json (projection: per-client stats + flags) -├── engine.json (pinned behaviorVersion + release/image) -├── wellknown/rumble.json (canonical-location pointer) -├── scripts/validate.py (payload validation) -├── scripts/aggregate.py (facts → all projections, pure function) -└── site/ (static dashboard, served by Pages) +├── matchmaking/{pairings,matches_needed}-.json +├── clients.json (per-client battle totals) +├── engine.json (pinned behaviorVersion + settings) +├── scripts/aggregate.py (deterministic current projections) +├── scripts/publication.py (freshness + month rollover) +└── site/data/snapshots/-/ (immutable published history) ``` ## Ingestion: Single Writer, Batch Drain @@ -120,11 +119,11 @@ stateDiagram-v2 ## Aggregation: A Pure Function -The core invariant (P5): **every projection is a pure function of `results/raw/` plus the exclusion list.** No projection may depend on ingestion order, wall-clock time (beyond a "computedAt" stamp), or anything outside the repo. Anyone can run `python scripts/aggregate.py` locally and reproduce the leaderboard bit-for-bit. This is what makes the rumble auditable and fork-restartable without permission from anyone. +The core invariant (P5): every current projection is a pure function of raw facts and rollups plus the repository-tracked catalog, behavior version, registrations, bans, disqualifications, and exclusions. Aggregation owns no clock. Anyone can run `python scripts/aggregate.py --root .` locally and reproduce the current leaderboard bit for bit. Operational freshness and immutable publication history are maintained separately by `publication.py`. -### Ruleset and scoring: adopt LiteRumble, do not reinvent +### Current scoring contract -**Decision direction: the rumble uses the RoboRumble/LiteRumble ruleset and scoring system unchanged.** These rules have been battle tested for two decades; the design contribution here is the delivery mechanism, not new game math. +Tank Royale Rumble adopts the proven RoboRumble/LiteRumble principle of averaging by distinct pairing, while publishing only the metrics the current `rumble-data` implementation actually derives. Battle parameters are frozen in `engine.json` per ranked game type: @@ -136,17 +135,13 @@ Battle parameters are frozen in `engine.json` per ranked game type: These names intentionally follow the popular LiteRumble/RoboRumble categories for the original game: 1v1, TwinDuel, and Melee. Mini, micro, nano, and giga categories are not part of v1 because they depend on bytecode-size limits. Tank Royale Rumble distributes source code across multiple programming languages, so any size-class system needs a separate source-size design per language. -Scoring metrics, matching the LiteRumble columns (all except Glicko-2 are per-pairing statistics and therefore order-independent): +Current leaderboard columns are: | Metric | Meaning | |--------|---------| -| **APS** (primary) | Average Percentage Score: mean over pairings of the mean score share per pairing (formula below) | -| **Win%** | Fraction of pairings won (LiteRumble's replacement for PL, so it does not fluctuate with the number of bots) | -| **Survival** | Survival percentage (`1v1` and `twinduel`: per pairing; `melee`: out of total rounds, per LiteRumble) | -| **Vote** | Percentage of bots that score worst against you ("percentage you are best against") | -| **NPP / ANPP** | (Average) Normalised Percentage Pairs, computed in the batch pass | -| **KNNPBI** | K-Nearest-Neighbours Problem Bot Index, computed in the batch pass | -| **Glicko-2** | Rating for incomplete-pairing robustness; sequence-dependent, so computed as a batch projection with a deterministic ordering rule (timestamp, then payload hash) to stay reproducible | +| **APS** | Average Percentage Score: mean over distinct pairings of the mean battle score share within each pairing | +| **Battles** | Accepted eligible battle samples contributing to the entry | +| **Pairings** | Exact distinct participant sets contributing to the entry | The APS core: @@ -156,11 +151,11 @@ APS(bot, pairing) = mean of share(bot, battle) over that pairing's battles APS(bot) = mean of APS(bot, pairing) over all the bot's pairings ``` -Averaging per-pairing first means extra samples of one pairing (e.g. from own-bot priority, see the [client document](./client-battles-and-results.md)) improve precision without skewing weight. LiteRumble computes the heavier batch metrics (ANPP, NPP, KNNPBI, Vote) twice a day rather than on every update; the same split applies here (every-drain APS/Win%/Survival, daily batch for the rest) if full recompute proves slow. +A zero battle-total produces zero shares. Averaging per pairing first means extra samples of one pairing (for example, from own-bot priority in the [client document](./client-battles-and-results.md)) improve precision without skewing weight. APS is stored to four decimal places, displayed to two, and sorted descending with exact identity as the stable tie-break. Win%, Survival, Vote, NPP/ANPP, KNNPBI, and Glicko-2 are not current Tank Royale Rumble outputs; adding one would require a separate accepted contract and implementation. ### Ranked pool and result epochs -- The leaderboard ranks only the **latest active version** of each bot (`status: active` in `bots/index.json`, see the submission document). Superseded, retired, and disqualified versions keep their facts and per-version detail shards but leave the ranked table, exactly like a RoboRumble version bump. +- The leaderboard ranks only the **latest active version** of each bot (`status: active` in `bots/index.json`, see the submission document). A matchup contributes only while every participant's exact identity is active and game-type eligible. A new version starts with no samples; matchups containing its superseded predecessor stop affecting every participant's current APS. Accepted facts and already published month snapshots remain immutable. - Results are partitioned into **epochs by `behaviorVersion`** (the server-owned integer that bumps only on game-observable changes; see the client document's Engine Pinning section). The release version is irrelevant here: a GUI-only release, whatever its semver bump, keeps the behavior version and therefore the epoch. A `behaviorVersion` bump opens a new epoch: the ranked leaderboard is computed from the current epoch only, while old epochs remain browsable archives. This is the honest consequence of "mixed game behavior corrupts comparability": rather than pretending results across behavior versions are comparable, the rumble restarts sampling and lets matchmaking (everything is suddenly under-sampled) rebuild the table quickly. Before accepting a fact or emitting matchmaking advice, `rumble-data` resolves every entry against the synchronized catalog. `1v1` and `melee` use only distinct active individual entries. TwinDuel uses only pairs of distinct active team entries whose immutable `teamMembers` each resolve to active individuals, expand to the pin's participant count, and are disjoint across the two teams. Any result or proposed pairing that does not meet its game type's eligibility is rejected or omitted, respectively. @@ -193,15 +188,11 @@ These rules deliberately mirror the classic RoboRumble server behavior (verified ```json { "schemaVersion": 1, - "computedAt": "2026-07-02T15:00:00Z", - "computedFromCommit": "abc1234", "gameType": "1v1", + "behaviorVersion": 7, + "projectionId": "sha256-derived-id", "entries": [ - { "bot": "Raven 2.2", "platform": "JVM", "owner": "flemming", "authors": ["..."], - "aps": 78.42, "winPct": 91.4, "survival": 84.1, "vote": 3.2, - "anpp": 81.7, "knnpbi": -0.4, "glicko2": 1834, - "battles": 412, "pairings": 148, "pairingsTotal": 152, "unconfirmedPairings": 2, - "epoch": 7, "firstSeen": "2026-05-01" } + { "bot": "Raven 2.2", "name": "Raven", "version": "2.2", "platform": "JVM", "owner": "flemming", "aps": 78.42, "battles": 412, "pairings": 148, "epoch": 7 } ] } ``` @@ -210,11 +201,17 @@ Per-bot detail shards (`leaderboard/bots/-.json`) hold the full p ### Client accountability projection -`clients.json` carries per-client statistics: battles submitted, pairings covered, mean deviation from consensus on shared pairings, and flags. Moderators use it to decide quarantine; the dashboard can show a public "contributors" view, which doubles as recognition (another motivation lever alongside own-bot priority). +`clients.json` currently carries accepted battle totals by client ID. More elaborate trust or contributor statistics remain future design work rather than current dashboard behavior. + +## Publication freshness and monthly history + +The result and catalog workflows share one non-cancelling writer concurrency group. Before either reads new external input, `publication.py` checks the UTC month. The first writer after a boundary copies current leaderboard and bot-detail JSON byte for byte into `site/data/snapshots/YYYY-MM/`, adds the month to `site/data/history.json`, and refuses to change an existing snapshot. Multiple missed months receive the same last published cumulative state. The live ranking remains cumulative and does not reset. + +The history manifest records a hash of the complete current ranking-visible tree. `lastUpdatedAt` advances only when that hash changes, including after a moderation pull request regenerated data outside the publisher. Polls, identical aggregation, snapshot creation, and deployment alone do not advance it. ## Static Dashboard -Plain `site/index.html` plus vanilla JS on Pages, fetching leaderboard JSON at runtime. No build step: the aggregator already produced the JSON, so the "site generator" is nothing. Client-side sort/search over a few hundred rows per game type is trivial. A bot or team row links to its detail shard. Pages exists on GitHub, GitLab, and Codeberg, and since the site is static files reading sibling JSON, it also works from any web server or locally from a checkout. +Plain `site/index.html` plus vanilla JavaScript on Pages fetches leaderboard JSON at runtime. The Ranking period selector uses `site/data/history.json` to choose current data or an immutable month prefix, labels archives read-only, and shows the selected ranking's update time. A bot or team row links to the corresponding detail shard. Changed writer commits explicitly dispatch Pages because pushes made with the built-in Actions token do not trigger another workflow; an hourly scheduled comparison deploys only when the current `site/` tree differs from the latest successful Pages run. ## Forge Terms of Service @@ -240,4 +237,4 @@ Is storing results in a repo and running the pipeline on CI a misuse of GitHub? | Forge migration | All logic in `scripts/*.py`; CI YAML is a thin wrapper. Forgejo Actions is GitHub-Actions-compatible; a GitLab CI wrapper is a page of YAML. The single seam: how a payload reaches `validate.py`. | | Disputed leaderboard | Anyone recomputes locally from facts. Quarantine is a reviewable exclusion list, not deletion, so every governance action is auditable in Git history. | | Rejected payloads | Keep a 30-day `rejected/` log on the archive branch, then prune. | -| Aggregation cadence | Full recompute on every drain is the preferred model. Prototype `aggregate.py` early and measure whether it holds at the expected scale or whether the LiteRumble-style split (light metrics per drain, heavy batch metrics daily) is needed from launch. | +| Aggregation cadence | Result drains regenerate current projections; catalog checks skip aggregation entirely when normalized content is unchanged. Full recomputation remains fast enough for the current data set. | diff --git a/docs/design/rumble/user-documentation.md b/docs/design/rumble/user-documentation.md index 148814296..00d516f7f 100644 --- a/docs/design/rumble/user-documentation.md +++ b/docs/design/rumble/user-documentation.md @@ -13,7 +13,7 @@ reversal-cost: low > **Status: DRAFT** - design direction captured. > Part of the [Tank Royale Rumble umbrella design](./README.md). -**Implementation note (CH-034, P-003/M-009):** the plan promised one quickstart per audience under `/web/docs/rumble/`, which now exists as `bot-author-guide.md`, `client-guide.md`, `moderator-guide.md`, and an `index.md` landing page. The moderator guide lives under `/web/docs/rumble/` rather than as a separate `rumble-data/docs/moderator-handbook.md`, since no such handbook exists yet; it links to each repository's `GOVERNANCE.md` instead. `onboarding.md` and `faq.md` below remain aspirational: folded into the shipped guides for now, not yet split out as their own documents. +**Implementation note:** CH-034 delivered the audience quickstarts and landing page under `/web/docs/rumble/`. CH-045 adds `rankings.md` as the canonical scoring, version-eligibility, freshness, and history reference linked by the dashboard. The moderator guide stays with the published Rumble docs and links to repository governance. A separate onboarding page remains aspirational; onboarding instructions are currently folded into the client guide. ## Scope @@ -75,7 +75,7 @@ flowchart TD | `web/docs/rumble/bot-author-guide.md` | Bot authors | Tank Royale web docs | Quickstart: first bot from template to merged PR; practice mode; 1v1, TwinDuel, and Melee entry rules; versioning rules; slots; license how-to | | `web/docs/rumble/client-guide.md` | Battle contributors | Tank Royale web docs | Quickstart: container pull or install script, onboarding PR, configuration, ranked vs. practice, evidence backups, upgrading on engine bumps | | `web/docs/rumble/onboarding.md` | Battle contributors | Tank Royale web docs | The one-time registration PR: what to add under `clients/`, what the token needs, what happens next | -| `web/docs/rumble/faq.md` | Everyone | Tank Royale web docs | Rankings explained (APS and friends), supported battle types, why mini/micro/nano/giga categories are out of v1, "why is my bot not ranked yet", troubleshooting, ToS posture | +| `web/docs/rumble/rankings.md` | Everyone | Tank Royale web docs | Exact APS, samples and pairings, active bot versions, behavior epochs, update cadence, publication freshness, and immutable cumulative month snapshots | | `docs/moderator-handbook.md` | Moderators | `rumble-data` | Review checklists, quarantine and ban procedures, spam handling, operations runbook (cron re-enablement, compaction, fork drill) | | Dashboard "Participate" page | Everyone | `rumble-data/site` | Static entry page linking every document above; the only doc that lives on the Pages site itself | @@ -83,10 +83,10 @@ Notes: - Internal Rumble architecture and ADR material belongs under `docs/`, because it is for game developers, contributors, and coding agents. User-facing docs for bot authors and battle contributors belong under `web/`. - The moderator handbook doubles as the **bus-factor runbook** (P8): everything a successor needs is a document, not tribal knowledge. The quarterly fork drill includes following the docs cold, which keeps them honest. -- The FAQ owns the explanations that would otherwise be repeated in issues: what APS means, how long until a new bot is ranked, why results were rejected, what an epoch reset is, and why v1 supports 1v1, TwinDuel, and Melee but not bytecode-size categories. +- The rankings guide owns APS, active-version eligibility, behavior epochs, update cadence, freshness, and historical snapshots. Audience quickstarts link to it rather than maintaining separate versions of the algorithm. - Error messages link into the docs: every validation rejection and client refusal (engine-pin mismatch, unregistered account, license missing) carries the URL of the section that resolves it. Documentation nobody can find might as well not exist; error messages are where users actually are. - `rumble-bots` ships a per-platform template bot directory (copy, rename, go) as the first step of the author quickstart, derived from the sample bots in the main Tank Royale repository. -- The dashboard links metric column headers to the FAQ's explanations rather than maintaining its own tooltip machinery; one place to keep correct, no drift between site and docs. +- The dashboard links its APS explanation to the rankings guide; one public reference stays authoritative instead of drifting tooltip copy. ## Onboarding Friction Budget diff --git a/web/docs/.vitepress/config.mts b/web/docs/.vitepress/config.mts index d3f17db03..3c29db8be 100644 --- a/web/docs/.vitepress/config.mts +++ b/web/docs/.vitepress/config.mts @@ -109,6 +109,7 @@ export default withMermaid(defineConfig({ text: 'Rumble', items: [ { text: 'Overview', link: '/rumble/' }, + { text: 'Rankings and APS', link: '/rumble/rankings' }, { text: 'Bot Author Quickstart', link: '/rumble/bot-author-guide' }, { text: 'Battle Contributor Quickstart', link: '/rumble/client-guide' }, { text: 'Moderator Quickstart', link: '/rumble/moderator-guide' }, diff --git a/web/docs/rumble/bot-author-guide.md b/web/docs/rumble/bot-author-guide.md index dcd476808..1b2f0cae1 100644 --- a/web/docs/rumble/bot-author-guide.md +++ b/web/docs/rumble/bot-author-guide.md @@ -86,7 +86,7 @@ git push --set-upstream origin add- Use the repository's bot-submission checklist in the PR description. CI runs the validator again, and a moderator reviews the submission. A green check is required, but it does not replace review. -When the PR is merged, CI adds the bot to the generated catalog. `rumble-data` synchronizes that catalog at 23 minutes past every UTC hour. The bot then appears on the [dashboard](https://robocode-dev.github.io/rumble-data/) and waits for its first ranked battles. +When the PR is merged, CI adds the bot to the generated catalog. `rumble-data` checks that catalog at 23 minutes past every UTC hour. A changed catalog is regenerated into the [dashboard](https://robocode-dev.github.io/rumble-data/), where the new active version starts at APS 0 and waits for its first eligible battles. [Ranking and version rules](rankings.md) explain exactly when results count. ## Submit a TwinDuel team @@ -110,7 +110,7 @@ Member versions are part of the published team identity. To use a different memb The first merged PR for a bot or team name reserves that name for your GitHub account. Only that account, or another account registered to the same owner, may submit later versions. -Published source versions are immutable. When the source changes, increase the version in the bot configuration and submit it again. The latest version becomes active; older results remain in history but no longer determine the current rank. +Published source versions are immutable. When the source changes, increase the version in the bot configuration and submit it again. The latest version becomes active; the previous identity and matchups containing it leave the current ranking, while immutable month-end snapshots remain available. See [What happens when a bot gets a new version](rankings.md#what-happens-when-a-bot-gets-a-new-version). Each owner may have five active catalog entries by default. An individual bot or a TwinDuel team each uses one slot. Updating an existing entry to a new version does not consume another slot. diff --git a/web/docs/rumble/client-guide.md b/web/docs/rumble/client-guide.md index 1f7fecffb..0b02f7c8a 100644 --- a/web/docs/rumble/client-guide.md +++ b/web/docs/rumble/client-guide.md @@ -225,6 +225,6 @@ Never put the token in `rumble-client.json`, a shell script, Git, an issue, or a Repeat `--run` to produce more battles and `--submit` to send pending results. You can change `gameTypes` between sessions. The client uses published matchmaking advice to cover new and under-sampled matchups; that advice is guidance rather than a reservation, so two clients may safely run the same matchup. -Accepted results usually reach the [dashboard](https://robocode-dev.github.io/rumble-data/) within minutes. A scheduled ingestion sweep runs twice an hour if the immediate GitHub event is delayed. +Accepted results usually reach the [dashboard](https://robocode-dev.github.io/rumble-data/) within minutes. A scheduled ingestion sweep runs twice an hour if the immediate GitHub event is delayed. The [ranking guide](rankings.md) explains which accepted battles are eligible for current APS and why repeated samples improve a matchup without giving it more weight. For command and implementation details, see the [`rumble-client` README](https://github.com/robocode-dev/rumble-client#tank-royale-rumble-client). diff --git a/web/docs/rumble/index.md b/web/docs/rumble/index.md index c2013fa8a..aef0660d5 100644 --- a/web/docs/rumble/index.md +++ b/web/docs/rumble/index.md @@ -24,6 +24,7 @@ The cycle never really ends. A bot that dominates today may meet a smarter oppon | Enter my bot in the rankings | [Submit a bot](bot-author-guide.md) | | Donate computer time and run ranked battles | [Run a Rumble client](client-guide.md) | | See which bots are winning | [Open the live dashboard](https://robocode-dev.github.io/rumble-data/) | +| Understand APS and ranking history | [Read how rankings work](rankings.md) | | Help review submissions and keep the competition fair | [Moderate the Rumble](moderator-guide.md) | You can submit a bot without running a client, and you can run a client without owning a bot. Many competitors do both because the client gives matchups involving their own bots priority when more samples are needed. @@ -60,15 +61,15 @@ A matchup is a set of opponents that fought each other. One completed battle is ### Rankings and APS -The main ranking number is APS, or Average Percentage Score. For each matchup, Rumble calculates the percentage of the total score earned by a bot or team. It averages repeated battles of that matchup, then averages across all of the entry's matchups. Higher APS is better. +The main ranking number is APS, or Average Percentage Score. Rumble first averages repeated battles within each exact participant matchup, then gives every distinct matchup equal weight in the final average. [Read the ranking guide](rankings.md) for the precise formula, an uneven-sample example, and the eligibility rules. -A new bot or version starts with no battle samples, so its first position can move sharply. The ranking settles as it fights more opponents. When a game-observable engine change starts a new behavior version, the current leaderboard uses only results from that new epoch. Earlier results stay in the public data history, but they are not mixed with battles played under different rules. +A new active bot version starts at APS 0 with no battle samples. Its superseded version and every matchup containing that old identity stop affecting the current ranking. When a game-observable engine change starts a new behavior version, the current leaderboard likewise uses only results from the new epoch. Immutable month-end snapshots preserve earlier published rankings. ## When rankings update -An incoming result submission starts the ingestion workflow as soon as GitHub applies its `result-submission` label. A scheduled sweep also runs at 17 and 47 minutes past every UTC hour in case an event was delayed. Accepted results regenerate the leaderboard, and GitHub Pages publishes the changed dashboard after the data commit. In normal operation, a result appears within minutes; the scheduled sweep is the fallback, not a guaranteed deadline. +An incoming result submission starts the ingestion workflow as soon as GitHub applies its `result-submission` label. A scheduled sweep also runs at 17 and 47 minutes past every UTC hour in case an event was delayed. Accepted results regenerate the leaderboard, and changed ranking data explicitly requests a dashboard deployment. An hourly Pages reconciliation repairs a missed deployment without republishing an unchanged site. -The bot catalog synchronizes at 23 minutes past every UTC hour. A newly merged bot normally reaches the dashboard after that synchronization, then waits for clients to produce its first ranked battles. +The bot catalog is checked at 23 minutes past every UTC hour. An unchanged catalog stops without aggregation or deployment. A newly merged bot or version normally reaches the dashboard after a changed check, then waits for clients to produce its first ranked battles. The dashboard timestamp reports when ranking data changed, not when automation last ran. ## Current availability diff --git a/web/docs/rumble/moderator-guide.md b/web/docs/rumble/moderator-guide.md index 75c57603b..4033a7fe3 100644 --- a/web/docs/rumble/moderator-guide.md +++ b/web/docs/rumble/moderator-guide.md @@ -50,7 +50,7 @@ The validator detects identical normalized name skeletons and restricted code co ## Keep the automation healthy -Result submissions normally trigger ingestion when GitHub applies the `result-submission` label. A scheduled fallback runs at 17 and 47 minutes past every UTC hour. Catalog synchronization runs at 23 minutes past every UTC hour. The Pages workflow publishes dashboard changes after accepted data is pushed. +Result submissions normally trigger ingestion when GitHub applies the `result-submission` label. A scheduled fallback runs at 17 and 47 minutes past every UTC hour. Catalog checks run at 23 minutes past every UTC hour and skip aggregation when unchanged. Changed writers explicitly request Pages, while a 41-minute-past hourly reconciliation deploys only when the site differs from the latest successful deployment. See [Rumble rankings and APS](rankings.md) for the public freshness, active-version, and immutable snapshot contract. If GitHub disables scheduled workflows after repository inactivity, re-enable them. A newly labelled result issue also wakes the ingestion workflow, but moderators should not rely on incoming traffic as the only health check. diff --git a/web/docs/rumble/rankings.md b/web/docs/rumble/rankings.md new file mode 100644 index 000000000..fc99eacfa --- /dev/null +++ b/web/docs/rumble/rankings.md @@ -0,0 +1,77 @@ +# Rumble rankings and APS + +The [live Rumble dashboard](https://robocode-dev.github.io/rumble-data/) ranks active bots and TwinDuel teams using APS, or Average Percentage Score. APS compares how much of the available score an entry earns across its distinct matchups. Higher is better. + +## How APS is calculated + +For each accepted battle, Rumble calculates every participant's score share: + +```text +score share = participant totalScore / sum of every participant's totalScore +``` + +If the total score is zero, every participant receives a zero share for that battle. Rumble then groups battles by the exact sorted set of participating `name version` identities. That set is one distinct matchup, whether it has one sample or many. + +For each bot or team, Rumble: + +1. Averages its score shares across repeated battles of the same matchup. +2. Averages those per-matchup values across all of its distinct matchups. +3. Multiplies the result by 100. + +Each distinct matchup therefore has equal weight. Running one matchup more often improves the estimate for that matchup without making it dominate the overall ranking. + +### Example with uneven samples + +Suppose Alpha has these results: + +| Matchup | Samples | Mean score share | +|---------|---------|------------------| +| Alpha 1.0 vs. Bravo 1.0 | 9 | 90% | +| Alpha 1.0 vs. Charlie 1.0 | 1 | 30% | + +Alpha's APS is `(90 + 30) / 2 = 60`. It is not 84, which would incorrectly give the nine-sample matchup nine times the weight. + +APS is stored to four decimal places and displayed with two. Ties are ordered by stable bot identity. The dashboard also shows the number of accepted battles and distinct matchups behind each value; a high APS based on little data should be treated as preliminary. + +## Which battles count + +A battle contributes to the current leaderboard only when all of these conditions hold: + +- The result was accepted from a registered, unbanned contributor and has not been excluded by moderation. +- No participant is currently disqualified. +- The result uses the current `behaviorVersion`, which identifies the game-observable rules used for ranked play. +- Every participant's exact `name version` identity is currently active and eligible for that game type. + +The complete participant set matters. When one participant version becomes inactive, that old matchup stops affecting the live APS of every participant in it. + +For `1v1`, a matchup contains two active individual bots. For TwinDuel, it contains two eligible team entries whose members are active and do not overlap. For Melee, the complete set of active individual participants identifies the matchup. + +## What happens when a bot gets a new version + +A version is part of a bot's ranked identity. A new version becomes active only after its reviewed pull request is merged into [`rumble-bots`](https://github.com/robocode-dev/rumble-bots) and the catalog change reaches `rumble-data`. + +The new active version appears on the current leaderboard with APS 0, zero battles, and zero matchups until clients submit eligible battles containing it. The superseded version leaves the current table, and matchups containing that version no longer affect current rankings. Its accepted facts remain auditable, and month-end snapshots created while it was active remain unchanged. + +## Current rankings and monthly snapshots + +The current leaderboard is cumulative: it uses all eligible accepted facts in the current behavior epoch, not just battles from the current month. It does not reset on the first day of a month. + +Before the first result or catalog update proceeds in a new UTC month, `rumble-data` copies the previous current leaderboards and bot details into an immutable month-end snapshot. If no writer ran across several month boundaries, each missing month records the same last published cumulative state. Snapshots are read-only; later results, moderation, and bot versions never rewrite them. + +Use the dashboard's **Ranking period** selector to switch between **Current** and an available month. Historical months begin with the first boundary after snapshot support was deployed; earlier months are not reconstructed. + +Late accepted results update the current cumulative ranking. They do not change an already published monthly snapshot. + +## When the dashboard updates + +Result submissions normally trigger ingestion immediately. Scheduled result sweeps run at 17 and 47 minutes past every UTC hour if an event was delayed. + +The reviewed bot catalog is checked at 23 minutes past every UTC hour. When its normalized content is unchanged, `rumble-data` skips aggregation, commit, and deployment. A new bot or version changes the catalog and triggers regeneration. + +Changed ranking data explicitly requests a dashboard deployment. A separate Pages reconciliation runs at 41 minutes past every UTC hour and deploys only when the current site differs from the latest successful deployment. GitHub Actions schedules can run late, so these times are operating cadences rather than deadlines. + +The dashboard's **ranking data last updated** value changes only when the current leaderboard or bot-detail data changes. An unchanged poll, identical aggregation, deployment, or monthly snapshot does not make the ranking appear newer. + +## Verify the ranking data + +The ranking inputs, aggregation code, generated projections, publication history, and governance rules are public in [`rumble-data`](https://github.com/robocode-dev/rumble-data). Accepted result facts remain immutable; moderation changes which facts are eligible without deleting them. From 95aa6c846ba02f5ca39eadeffa83edfd962f90a8 Mon Sep 17 00:00:00 2001 From: "Flemming N. Larsen" Date: Sun, 13 Sep 2026 14:52:31 +0200 Subject: [PATCH 3/5] docs: digest CH-045 --- .../open-questions.md | 11 --------- .../proposal.md | 23 ------------------- .../CH-045-document-rumble-rankings/tasks.md | 15 ------------ 3 files changed, 49 deletions(-) delete mode 100644 changes/CH-045-document-rumble-rankings/open-questions.md delete mode 100644 changes/CH-045-document-rumble-rankings/proposal.md delete mode 100644 changes/CH-045-document-rumble-rankings/tasks.md diff --git a/changes/CH-045-document-rumble-rankings/open-questions.md b/changes/CH-045-document-rumble-rankings/open-questions.md deleted file mode 100644 index 9855f5b33..000000000 --- a/changes/CH-045-document-rumble-rankings/open-questions.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -id: OQ-001 -type: open-questions -status: open -links: [CH-045] -title: CH-045 open questions ---- - -# Open questions - -None. The merged external behavior and the user's selected documentation boundary are decision-complete. diff --git a/changes/CH-045-document-rumble-rankings/proposal.md b/changes/CH-045-document-rumble-rankings/proposal.md deleted file mode 100644 index 3ab93558c..000000000 --- a/changes/CH-045-document-rumble-rankings/proposal.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -id: CH-045 -type: change -status: open -links: [G-001, CAP-014, CAP-015] -title: Document current Rumble rankings on robocode.dev ---- - -# CH-045 — Document current Rumble rankings on robocode.dev - -## Why - -Merged [`robocode-dev/rumble-data#12`](https://github.com/robocode-dev/rumble-data/pull/12) changed the public ranking contract: catalog polling is change-aware, live rankings use only current active-version matchups, publication exposes meaningful freshness, and immutable cumulative month-end snapshots are available. The dashboard already links to `/rumble/rankings`, but that canonical public explanation does not exist, and the current Tank Royale Rumble design still presents several unimplemented LiteRumble-inspired metrics as current behavior. - -## What - -Add a dedicated `robocode.dev/rumble/rankings` guide explaining the exact APS calculation, distinct pairing weights, behavior epochs, active bot versions, update cadence, publication freshness, and cumulative monthly snapshots. Link it from the Rumble overview, audience guides, dashboard, and VitePress sidebar. Align the internal Rumble design and the two external-repository capability carriers with the merged behavior, and record the user-visible documentation change in the changelog. - -This is a plan-less change serving `G-001`, `CAP-014`, and `CAP-015`. It does not reopen completed `P-003`, and the repository states no vision. - -## Documentation impact - -The public Rumble rankings guide becomes the canonical reader-facing explanation. Existing quickstarts retain audience-specific instructions and link to it instead of duplicating the complete algorithm. Internal design remains the durable technical carrier for cross-repository behavior. diff --git a/changes/CH-045-document-rumble-rankings/tasks.md b/changes/CH-045-document-rumble-rankings/tasks.md deleted file mode 100644 index c86d64380..000000000 --- a/changes/CH-045-document-rumble-rankings/tasks.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -id: TASKS-001 -type: tasks -status: open -links: [CH-045] -title: CH-045 implementation tasks ---- - -# Tasks - -- [x] Add the canonical public ranking guide with exact APS, active-version, behavior-epoch, cadence, freshness, and snapshot semantics; serves `CAP-014` and `CAP-015`. -- [x] Link the guide from Rumble navigation and each affected audience guide; serves `CAP-014` and `CAP-015`. -- [x] Align internal Rumble design and capability carriers with the merged `rumble-data` behavior; serves `CAP-014` and `CAP-015`. -- [x] Add the user-visible documentation change to the changelog. -- [ ] Build the VitePress site, run strict Cliewen validation, and verify links and the complete diff. From 329af8c3bfb73d3307a4ccbe621e91d0350b45bd Mon Sep 17 00:00:00 2001 From: "Flemming N. Larsen" Date: Sun, 13 Sep 2026 14:54:45 +0200 Subject: [PATCH 4/5] docs: clarify unchanged catalog rollover --- docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md | 3 ++- web/docs/rumble/index.md | 2 +- web/docs/rumble/rankings.md | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md b/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md index 4df2e69ef..051e98c84 100644 --- a/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md +++ b/docs/capabilities/CAP-014-rumble-bot-catalog/criteria.md @@ -50,6 +50,7 @@ Feature: rumble-bot-catalog — Rumble bot catalog Given the normalized local catalog and the reviewed source catalog When the scheduled synchronization checks the source Then a changed bot or version is stored and regenerated into current rankings - And identical content is not rewritten, aggregated, committed, or deployed + And identical content is not rewritten or aggregated + And, when no independent publication state changed, no commit or deployment occurs And catalog and result writers cannot overlap their publication sections ``` diff --git a/web/docs/rumble/index.md b/web/docs/rumble/index.md index aef0660d5..ba786c246 100644 --- a/web/docs/rumble/index.md +++ b/web/docs/rumble/index.md @@ -69,7 +69,7 @@ A new active bot version starts at APS 0 with no battle samples. Its superseded An incoming result submission starts the ingestion workflow as soon as GitHub applies its `result-submission` label. A scheduled sweep also runs at 17 and 47 minutes past every UTC hour in case an event was delayed. Accepted results regenerate the leaderboard, and changed ranking data explicitly requests a dashboard deployment. An hourly Pages reconciliation repairs a missed deployment without republishing an unchanged site. -The bot catalog is checked at 23 minutes past every UTC hour. An unchanged catalog stops without aggregation or deployment. A newly merged bot or version normally reaches the dashboard after a changed check, then waits for clients to produce its first ranked battles. The dashboard timestamp reports when ranking data changed, not when automation last ran. +The bot catalog is checked at 23 minutes past every UTC hour. An unchanged catalog stops without aggregation or a catalog-driven deployment; a month-end snapshot can still make that run publish history. A newly merged bot or version normally reaches the dashboard after a changed check, then waits for clients to produce its first ranked battles. The dashboard timestamp reports when ranking data changed, not when automation last ran. ## Current availability diff --git a/web/docs/rumble/rankings.md b/web/docs/rumble/rankings.md index fc99eacfa..0212d1e27 100644 --- a/web/docs/rumble/rankings.md +++ b/web/docs/rumble/rankings.md @@ -66,7 +66,7 @@ Late accepted results update the current cumulative ranking. They do not change Result submissions normally trigger ingestion immediately. Scheduled result sweeps run at 17 and 47 minutes past every UTC hour if an event was delayed. -The reviewed bot catalog is checked at 23 minutes past every UTC hour. When its normalized content is unchanged, `rumble-data` skips aggregation, commit, and deployment. A new bot or version changes the catalog and triggers regeneration. +The reviewed bot catalog is checked at 23 minutes past every UTC hour. When its normalized content is unchanged, `rumble-data` skips aggregation and makes no catalog-driven commit or deployment. A month-end snapshot created by the same run is an independent publication change and may still be committed and deployed. A new bot or version changes the catalog and triggers regeneration. Changed ranking data explicitly requests a dashboard deployment. A separate Pages reconciliation runs at 41 minutes past every UTC hour and deploys only when the current site differs from the latest successful deployment. GitHub Actions schedules can run late, so these times are operating cadences rather than deadlines. From c3362cfd7e056055b55c73ed1c6d0e15705b182f Mon Sep 17 00:00:00 2001 From: "Flemming N. Larsen" Date: Sun, 13 Sep 2026 15:29:58 +0200 Subject: [PATCH 5/5] docs: say where TwinDuel member rules are enforced Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TumFGXBzSvL3FFd6SWiZt6 --- web/docs/rumble/rankings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/docs/rumble/rankings.md b/web/docs/rumble/rankings.md index 0212d1e27..f14e51865 100644 --- a/web/docs/rumble/rankings.md +++ b/web/docs/rumble/rankings.md @@ -44,7 +44,7 @@ A battle contributes to the current leaderboard only when all of these condition The complete participant set matters. When one participant version becomes inactive, that old matchup stops affecting the live APS of every participant in it. -For `1v1`, a matchup contains two active individual bots. For TwinDuel, it contains two eligible team entries whose members are active and do not overlap. For Melee, the complete set of active individual participants identifies the matchup. +For `1v1`, a matchup contains two active individual bots. For TwinDuel, it contains two active team entries. A team entry is only listed, and a TwinDuel result is only accepted, when the team's members are active and the two teams share no members. For Melee, the complete set of active individual participants identifies the matchup. ## What happens when a bot gets a new version