Avatar unlock system — take-home case
Download PDF

Take-home case

Avatar unlock system

Players earn avatars by playing: an identity we assume they lack today. After launch, GameOps, the team running the live game, changes the catalogue safely without a game release.

Reading time
Main ~11 min · Appendix ~11 min
AI software
Claude Code, ChatGPT

You asked for three things: the back-office UI, the APIs, and how game and server work together. Start anywhere.

  1. Catalogue screen: Golden Cat is Live with a 'Change in review' chip1Back-office UIGameOps create an avatar, set its rule, such as “win 2,000,000 coins”, and publish it after a second GameOps user approves it (in production).Back office screens
  2. PlayerPUT /v1/me/equipped-avatarBack officePOST …/changes/{id}/publishInternalGET /internal/v1/…/avatar-notices2APIsThe game makes four calls, the back office moves changes through review and publish, and the spin system adds one optional field.APIs and full reference
  3. The avatar picker dialog: Golden Cat 1.4M of 2M with an amber ring, Lucky Dog 1.7M of 5M with a blue ring3Game and server interactionSpins pay as today; afterwards the server credits progress, records the unlock, and the game shows it.Walk through one change · Earn and equip

My role

Main document

1

Problem and answer

What we solve, the design in short, and the goals

The problem. Players have no identity earned through play (assumed). GameOps (the live-ops team) must change avatars without a game release and catch a wrong threshold before players see it.

The answer. The server owns avatars as rewards. GameOps publishes reviewed rules; after each spin pays, the server counts it toward progress; the game shows the unlock and lets the player equip it. Players pick from what they own: the default picture, avatars earned by play, and rarely a replacement for a pulled avatar.

Terms. A settled spin is one spin the spin system has recorded and paid. A bar is a player's progress toward one target avatar. A revision is a numbered published catalogue. A notice is a message the server keeps for the game until the game confirms it showed it.

Six decisions shape the design: #1 ladder (one avatar chased per mode at a time), #2 what counts, #3 locked goal, #4 where the award is decided, #5 taking an avatar out, #6 publication.

New avatar path. GameOps publishes a new avatar, Golden Cat (owned after winning 2,000,000 coins in total) → Ana (a player) spins and fills her bar → the server records ownership and a notice → the game shows "Golden Cat unlocked" → Equip now.

Goals. Player: show who I am and earn something lasting from play. Business: more players return and pick an avatar, from a reward that costs no coins, compared with a holdout kept without the feature.

2

Walkthrough

One avatar change, from Maria's edit to Ana equipping it

Maria (GameOps) edits a rule, Jordi (GameOps) approves and publishes it, and Ana, a player, plays.

3

How unlocking works

What fills a bar, the ladder, and six decisions

The rule. Each earnable avatar has a mode (bet or win) and an unlockThreshold in coins. When the bar reaches the threshold or more, the player owns it, once. The brief left "win X coins" open; the table is my answer. All slot games in the app count (assumed). The spin system records each spin's kind (assumed); I call it spinKind.

What happened Bet bar Win bar
Ana pays 200K to spin and wins 100K +200K +100K
That spin gives her free spins; they win 900K 0 +900K
Ana pays 2M to buy the bonus; it wins 500K +2M +500K
Marketing gifts her a free spin; it wins 50K 0 0 (assumed)

Bet bar = coins Ana paid. Win bar = coins Ana won. Gifts count for nothing.

Example: Golden Cat needs a 2,000,000 win and the bar is at 1,200,000. A win of 799,999 is not enough; 800,000 or 900,000 unlocks it. The extra 100,000 is dropped; the next avatar's bar starts from zero (#1, ladder).

The ladder. A player has at most two bars, one per mode.

  1. Next target: the live, earnable, not paused, not-yet-owned avatar with the lowest unlockThreshold in that mode (tie: earliest published). Until the player's next settled spin it shows as "next up": a locked tile with its rule.
  2. Assignment: that spin makes it the target, locks its rule as it was when the spin settled (not when processed), and counts at once. The ring appears then.
  3. Fresh bars: each new bar starts at zero, so one spin unlocks at most one avatar per mode.
The ladder for Ana, as in Figure 6 Two rows, one per mode, avatars in threshold order. Win mode: Golden Cat, win 2M, owned; Blue Owl, win 3M, current target at 0.2M of 3M; then the next live win avatar. Bet mode: none owned yet; Lucky Dog, bet 5M, current target at 2.5M of 5M; Jade Tiger, bet 20M, next up and locked. OWNED CURRENT TARGET · ONE BAR NEXT UP WINMODE BETMODE Golden Cat Win 2M coins 2M / 2M · owned ✓ Blue Owl Win 3M coins · pinned 0.2M / 3M Next win avatar not published yet from 0 after it None yet Lucky Dog Bet 5M coins · pinned 2.5M / 5M Jade Tiger 🔒 Bet 20M coins locked, shows its rule after it
Ana's two bars. Win: Golden Cat is owned; Blue Owl is the new target, starting from zero. Bet: Lucky Dog is the target; Jade Tiger stays locked until Lucky Dog is owned.

Six decisions

Question Our answer What we gave up
#1 What does a player chase? One avatar per mode (bet, win) at a time; each new bar starts at zero Chasing every avatar at once; extra coins past the target are lost
#2 What counts as progress? Bet bar: coins paid (the stake, or a bonus price, once). Win bar: coins won, spin by spin Round totals, and wins minus the bet
#3 Can the goal change mid-chase? No. The threshold locks when a player starts the chase; a rule change applies only to players who start later A wrong locked threshold needs a logged manual fix
#4 When is the award decided? After the spin has paid, from the settled-spin record The unlock shows a moment after the coins
#5 Can an owned avatar be taken away? Retire: owners keep it. Pull and replace (rare, for legal or offensive content): owners get a named replacement Replacement art must exist before a pull
#6 How do changes go live? As reviewed revisions, with a second approver in production and a kill switch for emergencies Changes take longer to publish

Product assumption. Every account starts with the default avatar (today's silhouette), owned and equipped. Others are earned.

4

Back office

How GameOps edit, review and publish an avatar

Who is who. Maria (GameOps) edits a rule, Jordi (GameOps) approves and publishes it, and Ana plays. Back-office screens show full numbers (2,000,000), player screens short ones (1.4M / 2M). Win rings are amber, bet rings blue.

Change workflow. Maria opens a change (Draft) → submits it (In review; checks run) → another GameOps user approves it → it goes live as the next revision. An edit clears the approval; Send back returns it to Draft.

Step 1. Maria edits the rule (rule editor): an image from the asset library, availability, and the unlock rule. She submits it.

Figure 2. Rule editor: Maria's change c-311 raises Golden Cat from 2,000,000 to 3,000,000What to look at. The unlock rule shows only for an earnable avatar. The right column reads it back: “Players own Golden Cat after winning 3,000,000 coins”. Saving c-311 in review clears its approval.

5

Player screens

The avatar picker and the unlock toast

Avatar picker (a dialog over Settings). Ana sees what she owns and what she can still earn; grant-only avatars stay hidden until granted. A ring marks her target in each mode; other earnable avatars show their rule ("Win 3M coins", "Bet 20M coins").

Figure 5. Avatar picker: Ana chasing two targetsWhat to look at. Each current target carries a ring in its mode colour: Golden Cat 1.4M / 2M (win, amber) and Lucky Dog 1.7M / 5M (bet, blue). Other earnable avatars show their rule.
Figure 6. Avatar picker a few spins after Golden Cat unlockedWhat to look at. Golden Cat is owned, with a NEW badge. Blue Owl is the new win target, its bar started from zero (0.2M / 3M); Lucky Dog reads 2.5M / 5M.

Slot screen. One addition: the toast "Golden Cat unlocked" with Equip now / Later, after the server records the grant. It does not block play.

Figure 7. Slot screen at the unlockWhat to look at. The only change to the slot screen: the toast with Equip now and Later, shown once the server has recorded the grant. Play is not blocked.

6

System parts

The building blocks and the data they keep

Figure 1. System picture: new parts blue, existing greyWhat to look at. The existing spin system pays spins. The new Evaluator reads each settled spin and updates the Avatar store. The game and back office use the Avatar API; pending notices can also ride on the spin reply.

New parts blue, existing grey. The game reads the catalogue from the Avatar API, so changes need no game release. Reading notices adds a small, capped delay to the spin reply.

Entity Key Main fields Rule
Avatar avatarId status, availability, mode, unlockThreshold, assetId, pullReason Rule fields only when earnable; a pulled avatar is retired with a pullReason
Change → revision changeId; revision changeVersion, baseRevision, approvedBy, items Approver is not the author (production); a published revision never changes
Ownership (playerId, avatarId) source, grantedAt One row per player and avatar, kept after Retire and Pull and replace
Selection playerId equippedAvatarId, selectionVersion Must be owned and not pulled; version +1 per change
Bar (playerId, mode) targetAvatarId, pinned unlockThreshold and revision, amount Pinned at assignment; only an audited correction may lower it
Spin processing (gameId, settledSpinId) increments, assignments, grants Saved with bars, grants and notices in one step; kept while the spin record can be replayed

Notices (owned, replacement, progress_moved) wait until the game confirms it showed them. Audit entries record who changed what and when, with before and after values, for every change, approval, publish and pause.

7

APIs

The main calls, with examples and errors

Three kinds of calls: player (the player comes from the session), admin (back-office login, GameOps role) and internal (service identity). The game never reports an award; the server decides (#4, where it is decided). The main calls:

Player

Game

4
MethodPathPurpose
GETLive and retired avatars of the current revision
GETThe player's state: owned, equipped, bars, pending notices
PUTEquip an owned, equippable avatar
POSTMark a notice as shown
Back office

Back office

3
MethodPathPurpose
PUTEdit a change; clears any approval
POSTRecord the approver, in production not the author
POSTValidate and publish the approved version as the next revision
Internal

Spin system

1
MethodPathPurpose
GETPending notices for the spin reply, under a short timeout

Keeping the catalogue fresh. The phone keeps the catalogue with its revision number. Every player-state reply carries the current number; if it is newer, the phone downloads the catalogue again.

How the phone notices a new catalogue The phone keeps the catalogue with revision 7. A player-state reply says catalogueRevision 8. Eight is newer than seven, so the phone downloads the catalogue again. If the numbers match, it downloads nothing. Phone keeps the catalogue, revision 7 Player-state reply catalogueRevision: 8 8 is newer than 7 download the catalogue again If the numbers match, the phone keeps its copy and downloads nothing.

PUT /v1/me/equipped-avatar · Game · Session

Purpose. Equip an avatar the player owns.

Field Type Required Meaning
avatarId number yes Avatar to equip
selectionVersion number yes Version the device last saw; stale → SELECTION_CONFLICT

Response 200: the full player state, with selectionVersion +1.

Errors, checked in this order: 404 AVATAR_NOT_FOUND → 403 AVATAR_NOT_OWNED → 409 AVATAR_NOT_EQUIPPABLE → 409 SELECTION_CONFLICT.Show 4 error codes with what the game doesHide the error codes
HTTP Code When Game does
404 AVATAR_NOT_FOUND Unknown avatarId Refetch the catalogue
403 AVATAR_NOT_OWNED Not owned Show it locked
409 AVATAR_NOT_EQUIPPABLE Owned, but pulled Refetch catalogue and state
409 SELECTION_CONFLICT Stale selectionVersion; details.equipped = {avatarId, selectionVersion} Redraw from details

Also: 400 INVALID_REQUEST, 401 UNAUTHENTICATED, 503 SERVICE_UNAVAILABLE.

Retry. Safe. If the first call was saved, the retry returns SELECTION_CONFLICT; the game treats it as success when details.equipped is the avatar it asked for, and otherwise redraws. If the first call was not saved, the retry works like a first call.

Example: the phone saves avatar 9 as version 13; the tablet, still on 12, gets 409 SELECTION_CONFLICT with the current choice.Show the example · 6 linesHide the example
Phone:  PUT /v1/me/equipped-avatar   { "avatarId": 9, "selectionVersion": 12 }
200     full player state (A1): equipped avatar 9, selectionVersion 13
Tablet, still on 12:   { "avatarId": 17, "selectionVersion": 12 }
409 { "error": { "code": "SELECTION_CONFLICT",
      "message": "The equipped avatar changed since you last loaded it", "requestId": "r-5501",
      "details": { "equipped": { "avatarId": 9, "selectionVersion": 13 } } } }

requestId is for diagnosis.

Admin: publish a change. Retire and Pull and replace are change items too, published the same way. Approve and publish are separate calls. If Maria edits in between, the approval is cleared and the publish is refused. Maria wrote c-311; Jordi approved and publishes it:

Example: Jordi publishes c-311 as revision 8; if a check fails, nothing is published and 422 VALIDATION_FAILED names the field.Show the example · 7 linesHide the example
POST /admin/v1/changes/c-311/publish   Idempotency-Key: k-8812   If-Match: "5"
200 { "changeId": "c-311", "revision": 8, "publishedBy": "jordi", "approvedBy": "jordi" }
If a check fails, nothing is published:
422 { "error": { "code": "VALIDATION_FAILED",
      "message": "A publish check failed; nothing was published", "requestId": "r-7730",
      "details": [ { "avatarId": 17, "field": "assetId",
                     "reason": "IMAGE_NOT_READY" } ] } }

Internal: the settled-spin record and the notice read. The Evaluator reads the record (assumed contract). The spin system adds pending notices to its reply as the new optional avatarNotices.

Example: the settled-spin record of s-123, then the notice read that adds n-41 to the spin reply, or nothing on 503 or a timeout.Show the example · 7 linesHide the example
Record:  gameId slot-42 · settledSpinId s-123 · playerId p-882 · settledAt 2026-11-06T10:15:02.120Z
         spinKind paid · paidStake 200000 · creditedWin 900000
GET /internal/v1/players/p-882/avatar-notices
200 { "notices": [ { "noticeId": "n-41", "type": "owned", "avatarId": 17 } ] }
503 { "error": { "code": "SERVICE_UNAVAILABLE",
      "message": "The avatar service is unavailable", "requestId": "r-9120" } }
    on 503 or no answer within the timeout, the spin reply goes without avatarNotices

Errors: SELF_APPROVAL_NOT_ALLOWED, AVATAR_NOT_FOUND, AVATAR_NOT_OWNED, AVATAR_NOT_EQUIPPABLE, SELECTION_CONFLICT, CHANGE_CONFLICT, PRECONDITION_FAILED, VALIDATION_FAILED and SERVICE_UNAVAILABLE.

8

Earn and equip

How the game and server talk when Ana earns and equips

Earning. Ana has Golden Cat at 1,400,000 of 2,000,000 (win) and Lucky Dog at 1,700,000 of 5,000,000 (bet). The spin reply goes back as today; the award is decided afterwards (#4, where it is decided).

Figure 8. Earning an avatarWhat to look at. Ana's spin s-123 completes Golden Cat: it pays first (1–3), the Evaluator records the unlock in one save (4–5), the game learns on a refresh (6–9) or the next spin reply.

The game never announces an award before the server has recorded it. It estimates each bar from the spins it has seen and asks the server only when an unlock is likely. The award reaches the game by whichever comes first: a refresh shortly after the estimate reaches the threshold, or avatarNotices in the next spin reply.

Equipping. Ana uses a phone and a tablet, both at selectionVersion 12.

Figure 9. Equipping on two devicesWhat to look at. Phone and tablet both start at version 12: the phone saves first and the tablet gets SELECTION_CONFLICT and redraws. Below, a timed-out save is retried and the conflict is treated as success.

When things go wrong

Case Server Player sees
Same settled spin twice Counted once No second toast
Evaluator down; notice read times out Spins pay as today; on recovery, spins are processed in order; lag alarm The toast comes later
Last spin completes a target Unlock and notice saved The refresh shows the toast, or the next fetch
Fetch fails, or the catalogue is empty, out of date or missing an avatar the latest player state names Keep the last good copy; retry Cached or default picture; Save waits

9

User stories

What must be true, as Given / When / Then

Story 1. As a GameOps user, I publish an avatar rule, and a wrong value does not reach players unchecked (#6, publication).

Given When Then
Maria raises Golden Cat's unlockThreshold from 2,000,000 to 3,000,000 in production she submits diff against revision 7; "Players own Golden Cat after winning 3,000,000 coins"
Maria is the author she approves SELF_APPROVAL_NOT_ALLOWED
Jordi approved Maria edits approval cleared; the change stays In review
Jordi approved, then Maria edits the publish call arrives PRECONDITION_FAILED with the version Jordi approved, CHANGE_CONFLICT with the new one; revision 7 stays
image not ready, or unlockThreshold missing or at or below 0 coins anyone publishes VALIDATION_FAILED names the field; revision 7 stays
Jordi approved he publishes revision 8 live, audited; CHANGE_CONFLICT if a newer revision went live

Story 2. As a player, I earn an avatar by spinning (#1, ladder; #2, what counts).

Given When Then
separate fixtures: Golden Cat pinned at 2,000,000, bar 1,200,000 credited win 799,999 / 800,000 / 900,000 not owned / owned / owned; overflow discarded
Lucky Dog pinned at 5,000,000, bar 0 24 / 25 / 26 paid spins of 200,000 not owned / owned / owned; spin 26 assigns Jade Tiger (44), whose bar reads 200,000
win bar 1,900,000, bet bar 4,900,000 a 200,000 paid spin pays 100,000 both owned in one save, two notices
s-123 was processed it arrives again no change

Story 3. As a player, I equip an avatar I own (#5, taking an avatar out).

Given When Then
Ana owns retired Silver Wolf, version 12 she equips it equipped, version 13
she does not own Blue Owl (21) she equips it AVATAR_NOT_OWNED
the tablet holds 12 after the phone saved 13 it saves SELECTION_CONFLICT with the current choice; redraw
Golden Cat is equipped its Pull and replace by Phoenix is published Phoenix owned and equipped (selectionVersion + 1), one replacement notice; Golden Cat leaves the catalogue and owned, and equipping it returns AVATAR_NOT_EQUIPPABLE

10

Launch and measures

How we roll out, switch off and measure

Release controls.

Control Run by Effect
Staged rollout: internal, 1%, 10%, 50%, then all but the holdout The existing feature-flag tool (assumed) Advances while health signals hold; a bar starts at the player's first settled spin in the rollout
Rule pause A senior GameOps user, audited Progress keeps counting, but nothing unlocks and no new chase starts until resumed
Kill switch (feature off) The game's on-call engineer, in the feature-flag tool Hides avatar screens, bars and toasts; shows the default picture; the server keeps recording
Notice read off The game's on-call engineer, in the feature-flag tool Spin replies carry no avatarNotices; the game gets notices on its next fetch

Measures. Watched against the holdout: share of active players with a non-default avatar at day 30. Progress is never shown as a near-win.

11

Dependencies

What other teams must deliver, in order

Dependencies, in order. The server team builds the Avatar API, Evaluator and Avatar store once item 1 is agreed.

  1. Spin-system team: replayable records kept in a fixed order per player (settledSpinId unique per game, spinKind, per-spin increments), avatarNotices, speed and freshness targets. The spin reply also carries gameId and settledSpinId, to match processedThrough (assumed). Fallback: the same evaluation inside the spin service after payout, one player at a time.
  2. Back-office owner: GameOps role, second approver, senior permission for pause.
  3. Asset library: ready, versioned, removable images; default and replacement art.
  4. Client teams: the game's avatar screen and the back-office Avatar module, built against an API stub. GameOps with the economy and responsible-play owner: agree the first catalogue, pacing and every threshold before submitting. QA: boundaries, duplicate and late spins, replay order, two devices, load, on a settled-spin test feed.
  5. Legal: the Pull and replace message, and whether a won avatar counts as digital content.

With more time: a player-chosen goal and ladder order; a starter set; VIP gifts via grant_only.

Appendix

Full API contracts, more screens and edge cases.

A1

API reference

Every endpoint, payload and error

OpenClose

Auth column: "Session" is the signed-in player, "GameOps" the existing back-office role, "Senior" the senior GameOps permission, "Service" an internal service.

The shared catalogue. GET /v1/catalogue is one list for all players, cached on the device: live and retired avatars, never pulled ones. The game asks "has it changed since revision 7?"; if not, the server answers "not modified" and sends nothing (standard HTTP caching: ETag and 304).

Example: revision 7, the live catalogue before c-311 is published, with three of its avatars.Show the example · 8 linesHide the example
{ "revision": 7,
  "avatars": [
    { "avatarId": 17, "name": "Golden Cat", "status": "live", "availability": "earnable",
      "mode": "win", "unlockThreshold": 2000000, "imageUrl": "…/golden-cat?v=3" },
    { "avatarId": 9, "name": "Silver Wolf", "status": "retired", "availability": "earnable",
      "mode": "win", "unlockThreshold": 1000000, "imageUrl": "…/silver-wolf?v=1" },
    { "avatarId": 44, "name": "Jade Tiger", "status": "live", "availability": "earnable",
      "mode": "bet", "unlockThreshold": 20000000, "imageUrl": "…/jade-tiger?v=1" } ] }

Revision 7, live before c-311, holds seven avatars: default (1), Silver Wolf (9, retired), Golden Cat (17, win 2,000,000), Blue Owl (21, win 3,000,000), Lucky Dog (30, bet 5,000,000), Phoenix (40, grant only), Jade Tiger (44, bet 20,000,000).

Player state, the success response of GET /v1/me/avatars and of equip:

Example: Ana's state a few spins after s-123: Golden Cat owned, Silver Wolf equipped, Blue Owl the win target at 200,000.Show the example · 12 linesHide the example
{ "owned": [ { "avatarId": 1, "source": "default" },
             { "avatarId": 9, "source": "rule_completion" },
             { "avatarId": 17, "source": "rule_completion" } ],
  "equipped": { "avatarId": 9, "selectionVersion": 13 },
  "bars": [ { "mode": "win", "targetAvatarId": 21, "unlockThreshold": 3000000,
              "revision": 8, "amount": 200000 },
            { "mode": "bet", "targetAvatarId": 30, "unlockThreshold": 5000000,
              "revision": 6, "amount": 2100000 } ],
  "notices": [],
  "pausedAvatarIds": [],
  "processedThrough": { "gameId": "slot-42", "settledSpinId": "s-140" },
  "catalogueRevision": 8 }

owned leaves out pulled avatars. equipped.avatarId is the stored equippedAvatarId. pausedAvatarIds lists paused rules; it lives here because a pause makes no catalogue revision. A mode with no target returns, for example, { "mode": "win", "nextUpAvatarId": 21, "amount": 0 }; nextUpAvatarId is computed on read and copied into progress_moved. processedThrough is the last settled spin this state includes. The game adds what it counted after that spin, or, if the spin is not in its local history (say, another device), everything eligible since the state arrived. A higher catalogueRevision triggers a catalogue refetch.

The game estimates each bar locally and asks the server only when an unlock is likely; a refresh or the next spin reply brings the award.Show the estimate and refresh rule · 1 paragraphHide the rule

Client estimate and refresh. Each bar shows the last confirmed amount plus eligible contributions after processedThrough. At the pinned threshold, or on a no-target mode's first eligible spin, the game refetches after a short delay, then a few times with growing gaps; each answer resets the estimate. a noticeId already shown is skipped while local history lasts. Login, app return and the avatar screen also fetch. No new connection; an existing push channel may notify a closed app (assumed).

Notices. Each has a stable noticeId, player, type and avatar IDs, is saved with the change that caused it and stays pending until acknowledged:

Notice What it carries Player sees Made once per
owned avatarId "Golden Cat unlocked" with Equip now grant; a replacement grant makes a replacement notice instead
replacement avatarId, replacedAvatarId "Avatar removed and replaced with Phoenix" player, pulled avatar and pull revision, also when the replacement was already owned
progress_moved removedAvatarId, mode, amount, nextUpAvatarId "Your progress now counts toward Blue Owl" player, removed target and revision

Settled-spin record. paidStake and creditedWin never count the same coins twice. Triggered free spins carry paidStake 0. A bought feature's price is only on its purchase record (bought_feature); its free games are bought_feature records with paidStake 0.

All model fields.

Entity Fields
Avatar avatarId (number, not reused), name, status (draft, in_review, live, retired), availability (default, earnable, grant_only), mode (bet, win), unlockThreshold (coins, inclusive), assetId, imageVersion, firstPublishedAt, pullReason (legal, offensive), paused (rule pause; players get it as pausedAvatarIds)
Change changeId, status (draft, in_review, published, discarded), changeVersion, baseRevision, authorId, approvedBy, publishedBy, published at, items (create, edit, retire, pull_and_replace with replacementAvatarId and pullReason); published as revision (whole number per environment) with its effective time
Ownership playerId, avatarId, source (default, rule_completion, replacement), grantedAt, gameId and settledSpinId of the completing spin
Selection playerId, equippedAvatarId, selectionVersion
Bar playerId, mode, targetAvatarId, pinned unlockThreshold and revision, amount (with no target, carried progress)
Spin processing gameId, settledSpinId, playerId, processedAt, increments, assignments, grants

Change lifecycle. Only a PUT edit raises changeVersion by 1. A published change is closed and read-only. A draft or in-review change can be discarded with If-Match; it closes as discarded, audited. "Approve and publish" makes two calls: approve, then publish.

Player

Game endpoints

4
MethodPathAuthSafe to retry?Purpose
GETSessionYes (read, ETag = revision)Live and retired avatars of the current revision; pulled ones left out
GETSessionYes (read)Player state
PUTSessionYes, by selectionVersionEquip an owned, equippable avatar; returns the player state
POSTSessionYes, by noticeIdMark a notice as shown
Back office

Back-office endpoints

19

If-Match carries changeVersion in quotes (If-Match: "5"), the value GET /admin/v1/changes/{changeId} returns, also as its ETag. Stale version: 412 PRECONDITION_FAILED. Lifecycle conflict: 409 CHANGE_CONFLICT.

MethodPathAuthSafe to retry?Purpose
GETGameOpsYes (read)Catalogue list, newest first: status, availability, mode, unlockThreshold, updatedAt (latest change or revision touching it), open changeId and its status, live revision
GETGameOpsYes (read)Search the existing asset library; returns assetId, imageVersion, ready state
POSTGameOpsIdempotency-KeyOpen a change on the live revision (baseRevision)
GETGameOpsYes (read)The change with changeVersion, items and status
PUTGameOpsIf-Match: changeVersionEdit items: create, edit, retire, pull_and_replace (with replacementAvatarId, pullReason); clears an approval
POSTGameOpsIdempotency-Key, If-Matchdraft → in_review; runs validation
GETGameOpsYes (read)Diff against live, preview sentences, validation results
POSTGameOps, not the author (production)Idempotency-Key, If-MatchRecord approvedBy
POSTGameOpsIdempotency-Key, If-Matchin_review → draft, with a comment
POSTGameOpsIdempotency-Key, If-Match: changeVersionValidate and publish as the next revision; publishes only the version that was approved, otherwise CHANGE_CONFLICT
POSTGameOpsIdempotency-Key, If-MatchClose a draft or in_review change as discarded, audited; stale version returns 412
GETGameOpsYes (read)Version history
GETGameOpsYes (read)One revision's full content
POSTGameOpsIdempotency-KeyOpen a change that restores that revision; normal review and lifecycle validation
POSTSeniorIdempotency-KeyRule pause: the rule keeps crediting progress, grants nothing and is not assigned as a new target, until resumed
POSTSeniorIdempotency-KeyRule pause: the rule keeps crediting progress, grants nothing and is not assigned as a new target, until resumed
POSTSeniorIdempotency-KeyPropose an audited player-state correction for one rule, run while it is paused
POSTSenior, not the proposerIdempotency-KeyRecord the second approval; the correction then runs
GETGameOpsYes (read)Audit log, filtered by avatar, user, action or time
Internal

Internal

1 + 2
MethodPathAuthSafe to retry?Purpose
GETServiceYes (read)Pending notices for the spin system's reply, under a short timeout
—ServiceKeyed by (gameId, settledSpinId)Assumed contract: the spin system writes it, the Evaluator reads it; no call is made
—Existing sessionBy noticeIdNew optional field on the spin system's existing reply to the game

Errors. Every error is { "error": { "code", "message", "requestId", "details"? } }; message is a short English sentence for logs and support, never shown to players.

Every code, its HTTP status, when it happens and what the caller does.Show all 13 error codesHide the error codes
Code HTTP When Caller does
INVALID_REQUEST 400 Malformed body or unknown field value Fix the call
UNAUTHENTICATED 401 No or expired session Sign in again
FORBIDDEN 403 Role lacks the action (for example pause without the senior role) Ask someone with the role
SELF_APPROVAL_NOT_ALLOWED 403 The author approves their own change in production Ask another GameOps user
AVATAR_NOT_OWNED 403 Equip of an avatar the player does not own Show it locked
AVATAR_NOT_FOUND 404 Unknown avatarId Refetch the catalogue
NOT_FOUND 404 Unknown changeId, noticeId, revision or correctionId. acknowledging a notice twice succeeds Reload the list
AVATAR_NOT_EQUIPPABLE 409 Owned, but pulled Refetch catalogue and state
SELECTION_CONFLICT 409 Stale selectionVersion Redraw from details
CHANGE_CONFLICT 409 A newer revision went live after baseRevision; the version being published is not the approved one (say, edited after approval); or an edit or action hits a closed change (published, discarded). reads of a closed change still work; a repeated publish with the same Idempotency-Key returns its first result Reload, reapply, review again; if never approved, get approval; if already published, reload the live revision
PRECONDITION_FAILED 412 If-Match names a stale changeVersion (the change was edited since the caller loaded it) Reload the change and re-check
VALIDATION_FAILED 422 Failing checks on submit or publish; each detail names field and reason: IMAGE_NOT_READY, THRESHOLD_OUT_OF_BOUNDS (missing or not above 0 coins), MODE_MISSING, AVAILABILITY_MISSING, REPLACEMENT_INVALID, DEFAULT_PROTECTED Fix the fields
SERVICE_UNAVAILABLE 503 Avatar service down; Retry-After set Keep cached state; retry later

details: SELECTION_CONFLICT → equipped {avatarId, selectionVersion}; VALIDATION_FAILED → a list of {avatarId, field, reason}; other codes have none.

Retries. (1) Settled spins, keyed by (gameId, settledSpinId), count once. (2) Equip is keyed by selectionVersion; a stale retry that passes the earlier checks gets SELECTION_CONFLICT with the current choice: success if that is the requested avatar, else redraw. (3) Acknowledgements: noticeId; admin edits: changeVersion. (4) Admin POSTs: Idempotency-Key; a repeat returns the first result. Only publish creates a revision.

Versions. /v1 changes only for breaking changes; the game ignores unknown fields and renders an unknown equipped avatar as the default.

A2

More screens

Version history and audit log

OpenClose

The review screen shows the version history and the audit log below the diff and approval.

Figure 4b. Review: history and audit logWhat to look at. One version history per environment, each row with Republish; below it the read-only audit log with before and after values and filters.

Version history, one table per environment, newest first. Columns: revision, published at, publishedBy, approvedBy, kind (publish, republish), avatars touched, and "View" (the full revision and its diff with the one before). Republish on a row opens a change that restores that revision, diffed against live, with normal review and lifecycle validation. It never brings back a pulled avatar or its image. A banner says: "Republishing changes future assignments only. Players already chasing an avatar keep their pinned target, and owned avatars stay owned."

Audit log. Columns: time, user, action (create, edit, retire, pull and replace, submit, approve, send back, discard, publish, republish, pause, resume, correction), object (avatar or change), field, before, after, revision. Filters: avatar, user, action, date range. Read-only. Kill-switch use is audited in the feature-flag tool. Looking up one player's avatars is out of scope for now.

Labels. Last updated first. A live avatar with an edit in review stays Live and shows "Change in review". Draft and In review labels appear only on never-published avatars.

Player screens. The picker shows live earnable avatars and every avatar the player owns; retired and grant-only ones appear only to owners. Only current targets have a ring, one per mode (win amber, bet blue); tapping one repeats it in the preview. Players see short numbers (1.4M / 2M, "Win 2M coins"); the back office and API, full ones. A newly owned avatar shows NEW until the player equips it or saves a choice after seeing it (kept by the game, assumed; no new field). The slot screen shows only the unlock toast (Equip now / Later); progress is in the picker. Settings has no ring.

A3

Edge cases

Retire, pull, pause and late spins

OpenClose

Retire (a reviewed change item). The avatar stays in the catalogue as retired; owners keep and can equip it; it is no longer assigned or credited. a spin settled before the Retire went live still credits, and can complete, the retired target, even if processed later. After those spins, the unfinished amount moves to that mode's bar, seeds the next target and sends progress_moved. A Pull applies to every spin processed after it commits, so it never completes the pulled avatar.

Pull and replace (rare: legal or offensive content). A change item with a pullReason and a live grant_only replacementAvatarId; second approval as for any production change; audited. On publish the avatar becomes retired with its pullReason and leaves everyone's catalogue; its image stops being served, controlled copies are removed (assumed asset process), and clients stop showing it. ID, name, status, ownership and audit history stay. one step per owner, keyed by player, pulled avatar and revision, runs once. It grants the replacement (one ownership row per player, so never twice). It sends one replacement notice instead of owned, even if the replacement was already owned, without naming the pulled avatar. It equips the replacement with selectionVersion + 1 only if the pulled avatar is still equipped, so a newer choice stays. Progress moves as for Retire. No coin compensation; deleting offline copies is not promised. In an emergency before approval, the kill switch hides the whole feature.

Rule pause (senior, audited). It applies to spins processed after it takes effect. A paused rule still credits progress but grants nothing and is not assigned as a new target; the picker shows it greyed as "Paused". Audited corrections run during the pause. at resume, one step per player, keyed by player, avatar and resume, makes the due grants against the corrected record, in the player's processing order, never twice. Each grant is one save, as for a completing spin: ownership, the owned notice, the cleared target and the discarded overflow.

Rule changes and removals, as players see them:

Case Server Player sees
Rule changed mid-chase Players already chasing keep their target; later assignments use the new revision Ana keeps 2,000,000; Ben (after revision 8) gets 3,000,000
Golden Cat retired Owners keep and can equip it; progress moves on, with a progress_moved notice "Golden Cat has left; your 1.2M now counts toward Blue Owl"
Golden Cat pulled and replaced Image removed; owners get Phoenix, equipped if they still wear the cat Owners: "Avatar removed and replaced with Phoenix"; players chasing it: "Your progress now counts toward Blue Owl" (wording from Legal)

Health signals and what to do:

Health signal Action
Spin reply time; notice-read timeouts Notice read off; stop the rollout
Award lag, backlog Alert; hold the rollout
Bars at or above their pinned threshold without ownership, outside a rule pause; unprocessed records Pause the rule, replay, audited correction
Equip errors after a client release Hold that release
Catalogue or image failures, avatar-screen crashes Kill switch, fix, switch back on

More edge cases

Case What happens
A settled spin arrives late or out of order Only if the spin system breaks per-player order: a late spin may credit the target pinned when it is processed, not the next one; results can drift from settled order by more than one spin; a replay keeps recorded assignments; the processing record stops a second credit
Carried progress already meets the next target's threshold That avatar is granted at the assigning spin; the remainder is discarded, as on completion
Every avatar in a mode owned, or none live No bar; the game shows "all collected" or nothing to earn; the first settled spin after a new avatar goes live assigns it
A cheaper avatar is published mid-chase It becomes the target at the next assignment, not now
A threshold is lowered Pinned players keep the old target; no automatic sweep. If that is unfair, the correction "apply revision N to players in progress on this rule" lowers pinned targets only. It runs during a rule pause, so anyone already at or above the new target is granted at resume
A mistyped threshold went live Pause the rule, find the affected pins and grants, approve an audited correction, resume. A rollback fixes future assignments only and reverses no grant
Retire or Pull and replace of the default avatar Refused: VALIDATION_FAILED with DEFAULT_PROTECTED
An approved change goes stale because another revision went live Publish returns CHANGE_CONFLICT; the author reapplies the edit on the new revision and it is reviewed again
A jackpot win Counts in win mode as credited; it completes at most one win-mode avatar
The player spins on two devices at once Spins are processed one at a time per player, in settled order
An old app version without the avatar screen Nothing changes for it; progress and ownership are recorded and appear after the update
A player leaves the rollout (rollout reduced) Their progress and ownership are kept; the flag hides the feature
A guest signs in Assumed: the account keeps the same player ID, so progress and ownership carry over
A notice is not acknowledged (for example, the app is uninstalled) It stays pending; fetched ownership remains the authority on the next login
Figure
100 %
Drag to move · + / − to zoom · 0 fits, 1 is 100 % · Esc, Back or a click outside the picture closes