Avatar unlock system — take-home case

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 ~12 min · Appendix ~10 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, then lowest avatarId). Until a spin adds to that mode's bar, 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. A promotional spin adds nothing, so it assigns nothing.
  3. Fresh bars: after a completion the next bar starts at zero, so one spin unlocks at most one avatar per mode. Only Retire or Pull and replace carries unfinished progress to the next target (A3).
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 · locked 0.2M / 3M Next win avatar not published yet from 0 after it None yet Lucky Dog Bet 5M coins · locked 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; after a completion the next 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 Rule
Avatar avatarId Rule fields only when earnable; a pulled avatar is retired with a pullReason
Change → revision changeId; revision Approver neither wrote nor edited it (production); a published revision never changes
Ownership (playerId, avatarId) One row per player and avatar, kept after Retire and Pull and replace
Selection playerId Must be owned and not pulled; version +1 per change
Bar (playerId, mode) Locked at assignment; only an audited correction may lower it
Spin processing (gameId, settledSpinId) 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.

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 locked at 2,000,000, bar 1,200,000 credited win 799,999 / 800,000 / 900,000 not owned / owned / owned; overflow discarded
Lucky Dog locked 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 spin in the rollout that adds to it
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. Adoption: share of exposed active players with a non-default avatar at day 30. Return: day-7 return of players with the feature against the holdout, counted from assignment. 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, a playerSpinSequence that only goes up per player across games and devices, spinKind, per-spin increments), avatarNotices, speed and freshness targets. The spin reply also carries gameId, settledSpinId and playerSpinSequence, 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

For engineers: payloads, fields, the back-office calls and every error.

Auth column: "GameOps" is the existing back-office role, "Senior" the senior GameOps permission.

The shared catalogue. The game asks "has it changed since revision 7?"; if not, the server answers "not modified" and sends nothing (standard HTTP caching: the ETag is the revision, the answer a 304). Every publish raises the revision. Only a Pull and replace takes an avatar out of the catalogue; a retired avatar stays in it as retired.

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", "playerSpinSequence": 1220 },
  "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; its playerSpinSequence is the player's running spin number across games and devices. The game adds only the spins it saw with a higher number, so a spin from another device is never counted twice or missed. A higher catalogueRevision triggers a catalogue refetch.

The game fetches this state after a short delay when a bar's estimate reaches its locked threshold, or on the first spin that adds to a mode with no target, then a few more times with growing gaps, within a fixed budget (say, five tries in a minute) and respecting Retry-After. After that it waits for the next spin, login, app return or avatar screen, so a toast may be late but is never lost. Each answer resets the estimate. Login, app return and the avatar screen also fetch. A noticeId already shown is skipped while local history lasts. No new connection is opened; an existing push channel may notify a closed app (assumed). A newly owned avatar shows NEW until the player equips it or saves a choice after seeing it; the game keeps this mark (assumed), with no new field.

Notices

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

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, editorIds, 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, locked unlockThreshold and revision, amount (with no target, carried progress)
Spin processing gameId, settledSpinId, playerId, processedAt, increments, assignments, grants
Settled-spin record (existing, assumed) gameId, settledSpinId, playerSpinSequence (only goes up per player, across games and devices), playerId, settledAt, spinKind, paidStake, creditedWin. The spin system writes it and the Evaluator reads it; no call is made. 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
Back office

Back-office endpoints

16

Two headers keep changes safe:

  • If-Match carries the changeVersion the caller last saw, in quotes (If-Match: "5"). It is the value GET /admin/v1/changes/{changeId} returns, also as its ETag. Edit, submit, approve, send back, publish and discard all carry it. If the change moved on since, the call fails with 412 PRECONDITION_FAILED.
  • Idempotency-Key is a key the caller picks for one action. A repeat with the same key returns the first result instead of doing it twice. The key is bound to the user, method, path, body and If-Match; reusing it with different input returns 400 INVALID_REQUEST. Keys, their bound requests and their original results are kept permanently, alongside the audit history. An exact repeat returns the original result before checking the current version or state, and never runs the action again.

An action the change's state does not allow (for example, editing a published change) returns 409 CHANGE_CONFLICT.

Change lifecycle.

  1. Every successful edit, submit, approve, send back, discard and publish raises changeVersion by 1 and returns it as the new ETag; publish sends the version that approve returned.
  2. A published change is closed and read-only.
  3. A draft or in-review change can be discarded with If-Match. It closes as discarded, audited.
  4. "Approve and publish" makes two calls: approve, then publish.
  5. After an avatar's first publish, its mode and availability are fixed; to change them, create a new avatar and retire the old one. Only the default avatar is default.

Opening a change, POST /admin/v1/changes, sends { "baseRevision": 7 } and returns 201 with changeId, changeVersion 1 and status draft. An edit sends the whole item list: PUT /admin/v1/changes/c-311 with If-Match: "2" and { "items": [ { "action": "edit", "avatarId": 17, "unlockThreshold": 3000000 } ] } returns 200 with the change and ETag: "3". A create item sends name, availability, assetId and imageVersion, plus mode and unlockThreshold when earnable, and no avatarId; the server assigns the avatarId on the first save, later edits and retries keep it, and IDs are never reused. Send back sends { "comment" }; acknowledging a notice returns 204. A correction, POST /admin/v1/corrections, is one of two operations. { "operation": "apply_revision", "avatarId", "revision", "playerIds", "reason" } lowers those players' locked thresholds to that revision's value and keeps their amounts. { "operation": "adjust_progress", "avatarId", "reason", "players": [ { "playerId", "lockedRevision", "delta" } ] } adds each delta once, in that player's processing order, so spins credited meanwhile stay. Either returns 201 with its correctionId and status proposed and runs after a second senior approves it; a player whose target or locked revision changed since is skipped and listed for a new review.

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
POSTGameOpsIdempotency-Key, If-Matchdraft → in_review; runs validation
GETGameOpsYes (read)Diff against live, preview sentences, validation results
POSTGameOpsIdempotency-Key, If-Matchin_review → draft, with a comment
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-KeyEnd the pause: make the due grants against the corrected record, then assign the rule again
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

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, unknown field value, or an Idempotency-Key reused with different input 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 Someone who wrote or edited the change approves it 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 moved on 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, FIXED_AFTER_PUBLISH (mode or availability of a published avatar), 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. Each repeat below is matched by its key ("keyed by"), so it never applies twice.

  1. Settled spins are keyed by (gameId, settledSpinId). Each counts once.
  2. Equip is keyed by selectionVersion. A stale retry that passes the earlier checks gets SELECTION_CONFLICT with the current choice. The game treats it as success if that is the requested avatar, and otherwise redraws.
  3. Acknowledgements are keyed by noticeId, admin edits by changeVersion.
  4. Admin POSTs are keyed by Idempotency-Key.
  5. 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 Version history and audit log

What the review screen shows below the diff

OpenClose

For GameOps: what the review screen shows 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. An avatar missing from that revision is retired the normal way (owners keep it, progress moves), never deleted. A banner says: "Republishing changes future assignments only. Players already chasing an avatar keep their locked target unless it is retired, 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.

A3 Edge cases

Retire, pull, pause and late spins

OpenClose

For GameOps and engineers: what Retire, Pull and replace and Rule pause do, how players see them, and rare cases.

Retire (a reviewed change item).

  1. The avatar stays in the catalogue as retired. Owners keep it and can equip it. The picker shows it only to owners.
  2. 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.
  3. After those spins, the unfinished amount moves to that mode's bar, seeds the next target and sends progress_moved.
  4. 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).

  1. It is a change item with a pullReason and a live grant_only replacementAvatarId. It needs a second approval, as any production change does, and is audited.
  2. 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 once they load the new catalogue revision.
  3. Its ID, name, status, ownership and audit history stay.
  4. One step per owner, keyed by player, pulled avatar and revision, runs once, and before that owner's next read or equip if it has not run yet. It:
    • grants the replacement (one ownership row per player, so never twice);
    • sends one replacement notice instead of owned, even if the replacement was already owned, without naming the pulled avatar;
    • equips the replacement with selectionVersion + 1 only if the pulled avatar is still equipped, so a newer choice stays;
    • drops any undelivered owned notice for the pulled avatar; a game that already received one stops showing it once it loads the new catalogue revision.
  5. Progress moves as for Retire.
  6. No coin compensation. Deleting offline copies is not promised.
  7. In an emergency before approval, the kill switch hides the whole feature.

Rule pause (senior, audited).

  1. It applies to spins processed after it takes effect.
  2. A paused rule still credits progress, but grants nothing and is not assigned as a new target. The picker shows it greyed as "Paused".
  3. Audited corrections run during the pause.
  4. 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.
  5. 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 locked 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 locked 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 spin that adds to that mode 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 Players with a locked target keep the old one; no automatic sweep. If that is unfair, the correction "apply revision N to players in progress on this rule" lowers locked 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 locked targets 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