# Event achievements

Open [Event achievements](https://arena.victris.ai/achievements) from My Bots, choose an
event, and sign in to see your progress. Conditions and schedules are public;
awards and flag delivery use your verified browser account session. Agent keys,
bot tokens and MCP tokens cannot reveal flags.

After revealing a flag, follow **How to submit flags** on the selected event's
page. Event staff supply the instructions and submission link; submissions are
handled by the event's scoring site.

SAINTCON 2026 uses the HC qualifier window: **October 27, 2026 at noon through
October 30 at 6:00 AM, America/Denver**. A qualifying match must start at or after
opening and finish strictly before closing. The shipped event is disabled until
the operator configures accepted flags and enables it.

## Precon playtest and preregistration

The first SAINTCON precon runs continuously from **Monday, October 12, 2026 at
noon through Friday, October 16 at 6:00 AM, America/Denver**. It uses SAINTCON's
opening and closing hours, on the requested October 12–16 dates. Its event and
ladder ID is `saintcon-precon-2026-10-12`. Preregister at
[the precon ladder](https://arena.victris.ai/ladders?ladder=saintcon-precon-2026-10-12)
with your Arena account; open enrollment admits players immediately before play
opens. Bot matches remain unavailable until noon on October 12. Develop with the
starter bots and simulator in the meantime, then queue with
`ARENA_LADDER=saintcon-precon-2026-10-12`.

This online playtest helps players learn the game and build bots, with separate
standings and five **unofficial testing flags**. All five achievements, house
opponent requests, private reveal/copy, evidence and revocation use the normal
achievement system. Testing flags are not part of the official HC challenge and
have no HC submission link. The precon is enabled by default and uses
`flag_mode: "testing"`, which supplies stable, deliberately predictable
`ARENA_TEST` values without HC secrets. A second precon may follow the next week;
it is not scheduled yet.
Precon enrollment, ratings and results do not enroll players in the October 27–30
main event or count toward its achievements.

## Official and testing flags

An event's optional `flag_mode` is `official` (the default) or `testing`. Testing
events automatically get five distinct `ARENA_TEST{<event ID>:<achievement ID>}`
values. They stay stable across restarts, differ between events and achievements,
and are explicitly unofficial. Only an earned, nonrevoked award with active
membership can be revealed through the verified account route. Testing flags
never borrow configured HC values or enable an official event without its secrets.
Flag values do not appear in public definitions, progress responses or audits.
The flag mode freezes with the scoring definition once earning begins.

Compose and `.env.example` enable the first precon by default. On an existing
deployment, include `saintcon-precon-2026-10-12` in `ARENA_ENABLED_EVENTS`, or
clear a saved admin pause with Enable achievement earning. An explicit empty
`ARENA_ENABLED_EVENTS` still disables all manifest earning. Accounts, `--house`,
champion weights and approved opponent policies are required for testing too;
only the external HC flag secrets are unnecessary. Local anonymous practice can
set `ARENA_ENABLED_EVENTS=` to keep earning disabled.

## Public event metadata

Definitions may include a `metadata` object with `kind` (`precon`, `main`, or
`community`), `purpose`, `audience`, `format`, and `preparation`. All five fields
are required when metadata is supplied. The event page and enrollment directory
show these public details, alongside the schedule in the event's timezone.
Keep secrets out of metadata. Older definitions without metadata remain valid.
A practice event sets `submission_url` to `null` to omit flag submission links;
otherwise the URL must use HTTPS. Metadata is frozen with the event definition.

## Branding images

Each event may have an `image` object (`url`, `alt`, `width`, `height`) or `null`.
The URL must use HTTPS, alternative text is required (1–300 characters), and
normalized dimensions fit within 1600 × 900. Images appear above the event details
and on enrollment cards. A manifest can supply an existing public image URL;
older definitions without an image remain valid.

Admins can open `/admin#events`, choose the event from the list, and upload, replace or remove
its branding image. Uploads accept nonanimated PNG, JPEG and WebP, up to 5 MiB
and 40 million pixels. The server decodes the image, applies orientation, resizes
without cropping or enlarging, strips source metadata and writes a WebP copy to
R2 under `events/<event ID>/<content hash>.webp`. A descriptive alternative text
makes the branding accessible.

Artwork can change after an event opens: it does not alter the frozen scoring
conditions, roster, dates or awards. Admin image choices, including
removal, override manifest images and survive earning toggles, restarts and
manifest reloads. Removing an image detaches it from the event; previous R2
objects remain available for cached pages and recovery.

Configure a separate public asset bucket and its public HTTPS domain using
`ARENA_ASSETS_R2_ENDPOINT`, `ARENA_ASSETS_R2_BUCKET`,
`ARENA_ASSETS_R2_ACCESS_KEY_ID`, `ARENA_ASSETS_R2_SECRET_ACCESS_KEY`, and
`ARENA_ASSETS_R2_PUBLIC_URL`. These are runtime settings; keep credentials out of
manifests, browser configuration and build arguments. The ladder starts normally
without asset settings; upload controls report unavailable until storage is
configured. A partial configuration fails startup with the missing setting name.
Do not make the private backup bucket public. See [DEPLOY.md](https://github.com/Victris-AI/saintcon-arena-fighter-game/blob/master/docs/DEPLOY.md#event-branding-storage).

## Five achievements

| ID | Achievement | Required win |
| --- | --- | --- |
| `first-blood` | First Blood | Beat any standard house bot. |
| `bank-shot` | Bank Shot | Beat any official house bot and land a damaging ricochet in that match. |
| `wall-breaker` | Wall Breaker | Beat any official house bot and cause a damaging repulsor wall slam in that match. |
| `house-sweep` | House Sweep | Beat each standard kind: TypeScript starter, brawler and kiter. |
| `champion-slayer` | Champion Slayer | Beat the official champion. |

Awards are independent: one match can earn several. Progress combines all bots and
versions belonging to your account, with one award per achievement per event.
Attach your bot to your account before the match starts; later claims do not import
old results. New versions, retirement and handle changes preserve earned awards.

Only ranked wins on the event's `ladder_id` against its approved official house versions count (the champion at any version: it keeps training through an event). Active enrollment must exist at start and completion; later admission cannot qualify an earlier match. Revoking enrollment also blocks future flag reveals. See [multiple ladders](/docs/ladders).
Results must be `last_standing`, `timeout_hp` or `timeout_damage`, with neither
fighter disqualified. Human games, local simulation, uploaded replays, player
opponents, draws, forfeits and voided games do not qualify. The account must be
eligible at start and completion. All four official opponents must be connected
with approved versions for the event to accept new qualifying starts.

Earned cards link to match evidence. Reveal a flag, then copy it to the event's
submission site. Flags are fetched on demand and held in page memory; reload,
logout or a session/account change clears them. Closing or disabling an event
stops earning, but earned flags remain revealable while their server secrets are
available. Staff revocation blocks subsequent reveals.

## Request an opponent

Join the event ladder at [Events](https://arena.victris.ai/ladders), receive admission, and queue with `ARENA_LADDER=<event ladder ID>`, then choose an official opponent on the event
page. One pending request is allowed per account across events. It expires after
ten minutes or at event closing, whichever is sooner. Requests and requested starts
have a one-minute account cooldown, including after cancellation and restart.
Disconnecting or leaving the queue cancels that bot's pending request.

A request reserves your selected bot; your other bots continue normal matchmaking.
It is a preference: normal quotas, pacing, capacity, owner exclusions and rematch
holds still apply. Requested games are ranked, with server-selected maps and seeds
and balanced seats. The scheduler admits one initial preferred start; four ordinary
ranked starts earn another, up to four credits. Requests can use otherwise idle
capacity. Requests are considered in order, skipping unavailable selected bots or
opponents.

## Operator setup

The CLI loads `events/saintcon-2026.json` and `ladders/default.json` by default. The first precon uses open enrollment on `saintcon-precon-2026-10-12`. SAINTCON uses the separate `saintcon-2026` ladder with staff approval. Its opponent names in the manifest belong to that ladder; Public practice wins do not count. Use `serve --events <path>` or
`ARENA_EVENTS_FILE` for another public JSON manifest (an array of definitions).
`ARENA_ENABLED_EVENTS` overrides each definition's `enabled` value with a
comma-separated list of IDs; an empty value disables all listed events. Every
enabled ID must exist in the manifest. Testing events need no `ARENA_EVENT_FLAGS`
entry; official events still require all five distinct configured values. In Compose, the path is inside the
container; leave it empty to use the bundled preset.

Configure Supabase accounts and start with `--house`. Approved versions for SAINTCON
are `starter-17`, `brawler-18` and `kiter-17`; the manifest names the champion's
`l3-6420`, but the champion counts at any version. Set the **runtime-only secret**
`ARENA_EVENT_FLAGS` to a JSON object with this structure, replacing each placeholder
with a distinct accepted value supplied by HC:

```json
{
  "saintcon-2026": {
    "first-blood": "<accepted First Blood flag>",
    "bank-shot": "<accepted Bank Shot flag>",
    "wall-breaker": "<accepted Wall Breaker flag>",
    "house-sweep": "<accepted House Sweep flag>",
    "champion-slayer": "<accepted Champion Slayer flag>"
  }
}
```

These are placeholders, not playable flags. Keep real values in the deployment
secret store, never in the manifest, source, build arguments or a `VITE_*` variable.
The CLI removes this variable from house bot child environments. Flags are absent
from SQLite, replays, statistics, public views and audits. Back up the secret store
separately: restoring the database alone cannot restore flags.

Set `ARENA_ENABLED_EVENTS=saintcon-2026` after configuration. An enabled event without
accounts, complete flags, `--house` or champion weights doesn't stop the ladder: it starts with
that event's earning paused and logs why. Check `GET /api/events`:
`ready` must be true and `unavailable_opponents` empty once all house bots are
connected with approved versions. Opening and closing follow the manifest's UTC
instants automatically.

Releases don't depend on events: an enabled event doesn't pin the code, rules or maps, so
fixes and a retrained champion can ship while it runs. Combat changes are deliberate,
announced releases (`rules_hash` and the engine version in `/api` show what's running).
Definitions freeze once matches start or an enabled event opens;
`enabled` can still toggle for an emergency pause. Use a new ID for changed
conditions. Removing a definition preserves its stored event and awards, so
explicitly disable it first. An enabled stored event still requires its secrets at
startup; retain finished events' flag secrets for reveals.

For future events, add new IDs with their own dates, approved opponents and flag
sets. The current revision, `house-v1`, uses these same five conditions. Distinct
IDs keep awards and progress separate. New conditions need a reviewed revision
and evaluator, rather than editing a running event.

## Evidence and recovery

The server snapshots ownership, seats, house identity and versions before starting
the runner. It commits match results, statistics, progress and awards together.
Duplicate completion cannot double count matches or awards. Event facts remain
available after replay trimming.

The admin console's Events section lists each ladder with the events that play on
it. An event's controls show official opponent and flag readiness, and
valid/revoked award counts per unique player account. Staff can enable or disable earning; these choices persist across
manifest reloads. Enabling requires approved house policies, configured accounts and all five flags. Flag values remain server-only. Hidden ladders' events are
omitted from public discovery; their account achievement routes remain usable.

An event's **Schedule** editor moves its opening and closing times, entered in the
event's time zone. An event on an event ladder shares the ladder's window, so
saving moves the ladder (and its other events) too. Staff can move only future
times: once play opens the opening time is final, once it closes the window is
final, and a new time must be in the future, so recorded matches and awards keep
the window they were judged by. Moved windows outrank the manifest on restart.

The admin console's Events section also includes a **Flag submission** editor for the
public submission URL and instructions, with a player preview. URLs must use
HTTPS or be left blank. Instructions allow up to 4000 characters, line breaks,
HTTP(S) URLs and `[label](https://…)` links; HTML stays plain text. Leave both
fields blank to hide the submission section. Do not include secret flag values.
Manifest `submission_instructions` is optional and defaults to an empty string.
Admin edits override manifest submission details across restarts and remain
editable after scoring definitions freeze.

The Events tab also inspects an account's evidence and revokes awards
with a reason. Staff cannot reveal somebody else's flag. Reconciliation previews
missing awards, then fills them from retained facts, preserving existing awards,
original timestamps and revocations. It does not change matches, ratings or
counters. It cannot import pre-event matches or invent start ownership evidence.

For offline recovery, stop the ladder and use its data directory:

```bash
npm run arena -- events list --data /path/to/data
npm run arena -- events reconcile saintcon-2026 --data /path/to/data
npm run arena -- events reconcile saintcon-2026 --apply --data /path/to/data
```

The first reconciliation is a dry run. Back up the database before applying a
repair. The admin HTTP route supports the same preview/apply flow while running.
SQLite schema 14 adds event records; rollback needs a compatible database backup.

## Scoring modes

This release delivers one shared flag per achievement per event. Private delivery
limits who can retrieve it from Arena; HC determines scoring for submitted copies.
Arena revocation cannot retract a copied shared flag or reverse a score at HC.

`EventFlagProvider` separates evaluation from delivery. Contestant-bound flags
remain a future adapter: HC must confirm verified enrollment and a validator tied
to that identity, with stable event-specific flag derivation. Arena handles alone
do not establish HC identity. Both options are described in
[the design](https://github.com/Victris-AI/saintcon-arena-fighter-game/blob/master/docs/HACKERS_CHALLENGE_DESIGN.md); only shared mode ships here.
