Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .clue/id-ledger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,21 @@ events:
- {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: live, prefix: RDA, component: "5"}
- {id: CH-001, kind: numeric, state: reserved, prefix: CH, component: "1"}
- {id: ADR-003, kind: numeric, state: reserved, prefix: ADR, component: "3"}
- {id: TASKS-001, kind: numeric, state: reserved, prefix: TASKS, component: "1"}
- {id: OQ-001, kind: numeric, state: reserved, prefix: OQ, component: "1"}
- {id: CH-001, kind: numeric, state: live, prefix: CH, component: "1"}
- {id: ADR-003, kind: numeric, state: live, prefix: ADR, component: "3"}
- {id: TASKS-001, kind: numeric, state: live, prefix: TASKS, component: "1"}
- {id: OQ-001, kind: numeric, state: live, prefix: OQ, component: "1"}
- {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: RBC-005, kind: numeric, state: reserved, prefix: RBC, component: "5"}
- {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"}
20 changes: 16 additions & 4 deletions .github/workflows/ingest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ on:
workflow_dispatch:

permissions:
actions: write
contents: write
issues: write

concurrency:
group: rumble-result-ingestion
group: rumble-publication-writer
cancel-in-progress: false

jobs:
Expand All @@ -21,9 +22,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Snapshot completed UTC months before accepting results
run: python scripts/publication.py --root . --rollover-only
- name: Stage labelled issue inbox
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -49,15 +54,22 @@ jobs:
printf '%s\n' "$number" >> .inbox/processed-issues
done < .inbox/issues
- name: Regenerate projections and commit accepted facts
env:
GH_TOKEN: ${{ github.token }}
run: |
python scripts/aggregate.py --root .
python scripts/publication.py --root .
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
site_changed=false
[ -n "$(git status --porcelain -- site)" ] && site_changed=true
for path in results leaderboard matchmaking clients.json site/data; do
[ -e "$path" ] && git add -- "$path"
done
git diff --cached --quiet || git commit -m 'chore: ingest Rumble result batch'
git push
if ! git diff --cached --quiet; then
git commit -m 'chore: ingest Rumble result batch'
git push
[ "$site_changed" = false ] || gh workflow run pages.yml --ref main
fi
- name: Publish durable receipts
env:
GH_TOKEN: ${{ github.token }}
Expand Down
29 changes: 27 additions & 2 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ on:
push:
branches: [main]
paths: [site/**]
schedule:
- cron: '41 * * * *'
workflow_dispatch:

permissions:
actions: read
contents: read
pages: write
id-token: write
Expand All @@ -23,9 +26,31 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
fetch-depth: 0
- id: reconcile
name: Compare the site with the latest successful deployment
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
if [ "$GITHUB_EVENT_NAME" != schedule ]; then
echo 'deploy=true' >> "$GITHUB_OUTPUT"
exit 0
fi
deployed=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/pages.yml/runs?status=success&per_page=1" --jq '.workflow_runs[0].head_sha // empty')
if [ -z "$deployed" ] || ! git cat-file -e "$deployed^{commit}" || ! git diff --quiet "$deployed" HEAD -- site; then
echo 'deploy=true' >> "$GITHUB_OUTPUT"
else
echo 'deploy=false' >> "$GITHUB_OUTPUT"
echo 'The deployed site already matches the current site tree.'
fi
- if: steps.reconcile.outputs.deploy == 'true'
uses: actions/configure-pages@v5
- if: steps.reconcile.outputs.deploy == 'true'
uses: actions/upload-pages-artifact@v3
with:
path: site
- id: deployment
if: steps.reconcile.outputs.deploy == 'true'
uses: actions/deploy-pages@v4
34 changes: 30 additions & 4 deletions .github/workflows/sync-catalog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,48 @@ on:
workflow_dispatch:

permissions:
actions: write
contents: write

concurrency:
group: rumble-publication-writer
cancel-in-progress: false

jobs:
synchronize:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python scripts/sync_catalog.py --root .
- run: python scripts/aggregate.py --root .
- name: Snapshot completed UTC months before synchronizing the catalog
run: python scripts/publication.py --root . --rollover-only
- id: catalog
name: Synchronize catalog and regenerate only when it changed
shell: bash
run: |
python scripts/sync_catalog.py --root .
if git diff --quiet -- catalog.json; then
echo 'changed=false' >> "$GITHUB_OUTPUT"
echo 'The source catalog is unchanged; skipping aggregation.'
else
echo 'changed=true' >> "$GITHUB_OUTPUT"
python scripts/publication.py --root .
fi
- name: Commit synchronized catalog and projections
env:
GH_TOKEN: ${{ github.token }}
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
site_changed=false
[ -n "$(git status --porcelain -- site)" ] && site_changed=true
git add catalog.json leaderboard matchmaking clients.json site/data
git diff --cached --quiet || git commit -m 'chore: synchronize Rumble bot catalog and projections'
git push
if ! git diff --cached --quiet; then
git commit -m 'chore: synchronize Rumble bot catalog and projections'
git push
[ "$site_changed" = false ] || gh workflow run pages.yml --ref main
fi
4 changes: 4 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python -m unittest discover -s tests -v
- if: github.event_name == 'pull_request'
run: python scripts/check_snapshots.py --base "origin/${{ github.base_ref }}"
- run: python scripts/aggregate.py --root .
- run: git diff --exit-code -- leaderboard matchmaking clients.json site/data
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## Unreleased

- Make catalog-driven ranking publication change-aware, explicitly deploy changed generated site data, expose ranking freshness, and add immutable cumulative month-end snapshots selectable from the dashboard.
6 changes: 4 additions & 2 deletions GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ Aggregation reapplies current registrations, bans, bot disqualifications, and ex

## Ingestion and dashboard operations

A newly labelled result issue normally triggers ingestion immediately. The scheduled fallback runs at 17 and 47 minutes past every UTC hour. Runs are serialized, and each drain processes the complete labelled inbox, commits accepted facts and projections together, publishes receipts, and closes processed issues.
A newly labelled result issue normally triggers ingestion immediately. The scheduled fallback runs at 17 and 47 minutes past every UTC hour. Result and catalog writers share one non-cancelling concurrency group. Each drain first snapshots any completed UTC month, processes the complete labelled inbox, commits changed accepted facts and projections together, explicitly requests Pages when site data changed, then publishes receipts and closes processed issues.

Catalog synchronization runs at 23 minutes past every UTC hour. Dashboard deployment runs after a push changes `site/`.
Catalog polling runs at 23 minutes past every UTC hour. If normalized source content is identical, it skips aggregation, commit, and deployment. A changed catalog is regenerated and committed, and its writer explicitly requests the Pages workflow because a push made with the built-in Actions token does not start another push-triggered workflow. Pages also reconciles the current `site/` tree against the latest successful deployment at 41 minutes past each UTC hour and deploys only when they differ.

At the first writer run after a UTC month boundary, `scripts/publication.py` copies current leaderboard and bot-detail JSON into `site/data/snapshots/YYYY-MM/` before accepting or synchronizing new input. Existing snapshots are immutable and pull-request verification rejects modifications or deletions. If writers were inactive across multiple boundaries, each missing month receives the same last published cumulative state. Snapshot creation does not reset current rankings or advance `lastUpdatedAt`.

If GitHub disables scheduled workflows after inactivity, re-enable them. An incoming labelled issue can wake ingestion, but it does not replace routine operational checks.

Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,25 @@ Most battle contributors should use the Rumble Client rather than create result

## When the dashboard updates

A result issue normally starts ingestion as soon as GitHub applies the `result-submission` label. A scheduled fallback runs at 17 and 47 minutes past every UTC hour. Each successful drain commits accepted facts, regenerates the projections, and triggers a Pages deployment when dashboard data changed.
A result issue normally starts ingestion as soon as GitHub applies the `result-submission` label. A scheduled fallback runs at 17 and 47 minutes past every UTC hour. Each drain regenerates the projections, but it commits and requests a Pages deployment only when accepted facts, current ranking data, or month history changed.

The reviewed bot catalog synchronizes at 23 minutes past every UTC hour. A newly merged bot appears after that synchronization and starts with no ranked samples.
The reviewed bot catalog is checked at 23 minutes past every UTC hour. Identical source content stops without aggregation, a commit, or a deployment. A newly merged bot or bot version changes the catalog, triggers ranking regeneration and publication, and starts with no ranked samples.

GitHub Actions schedules may run late, so these times describe the automation cadence rather than a delivery guarantee.
The Pages workflow also checks at 41 minutes past each UTC hour whether the current `site/` tree differs from the latest successful deployment. This reconciliation recovers a missed deployment without republishing an unchanged site. GitHub Actions schedules may run late, so these times describe the automation cadence rather than a delivery guarantee.

The dashboard's “ranking data last updated” value advances only when current leaderboard or bot-detail JSON changes. A workflow run, unchanged regeneration, deployment, or monthly snapshot by itself does not advance it.

## How results become rankings

Submitted issue bodies are transport, not durable storage. The ingestion workflow validates each result independently and writes accepted records under `results/raw/` using content-addressed filenames. It publishes a receipt only after the accepted fact has been pushed.

`scripts/aggregate.py` derives the leaderboard, pairing statistics, matchmaking advice, client totals, and dashboard data from repository-tracked inputs. The generated projections are disposable; accepted facts are the source of truth.

The current dashboard ranks each game type by APS, or Average Percentage Score. It also shows how many battles and distinct matchups contribute to each entry.
The current dashboard ranks each game type by APS, or Average Percentage Score. For each accepted battle, a participant's score share is its `totalScore` divided by the sum of all participants' `totalScore` values; a zero total produces a zero share. Battles are grouped by the exact sorted set of participating bot name-and-version identities. Repeated battles are averaged within each distinct pairing, then APS is 100 times the mean of those pairing averages, so every distinct pairing has equal weight regardless of how many samples it has. Stored APS is rounded to four decimal places and the dashboard displays two.

Only accepted facts matching the current `engine.json` `behaviorVersion` and matchups whose complete participant set consists of currently active, game-type-eligible identities contribute. The live leaderboard contains only catalog entries whose exact name and version are currently `active`. When a new version becomes active, it is a separate identity that starts at APS 0 with no samples; its superseded version disappears, and matchups containing that old version stop affecting every participant's live APS. Entries sort by descending APS with a stable bot-identity tie break.

The live ranking remains cumulative rather than resetting each month. Before the first result or catalog writer proceeds in a new UTC month, it saves the previous current leaderboard and bot details as an immutable month-end snapshot. The dashboard period selector exposes these read-only snapshots; late results affect the current ranking only and never rewrite past months. The first snapshot is created at the first month boundary after this feature is deployed, with no synthetic backfill.

## Repository map

Expand All @@ -35,6 +41,8 @@ The current dashboard ranks each game type by APS, or Average Percentage Score.
| `catalog.json` | Synchronized copy of the reviewed Rumble bot catalog. |
| `engine.json` | Pinned game behavior and ranked presets. |
| `site/` | Static dashboard published through GitHub Pages. |
| `site/data/history.json` | Current publication freshness and available monthly snapshots. |
| `site/data/snapshots/YYYY-MM/` | Immutable cumulative month-end leaderboard and bot-detail JSON. |
| `wellknown/rumble.json` | Canonical repository pointer used by clients. |

`engine.json.clientImage` is optional while no production Rumble Client image is published. When added, it must use an immutable image digest. Ranked compatibility is determined by `behaviorVersion`, not by the presence of an image.
Expand Down
Loading
Loading