Engineering · clash-jev

Can Jev three-crown its way to Ultimate Champion?

@desmond·21 September 2026·10 minute read

I got early access to Jev, TypeSafe AI's new System One model, and decided to play around with it.

Jev is a decision model. TypeSafe describes System One models as "built to make fast, structured decisions that software can use directly". It does not generate text.

Jev
You sendA state and one or more named questions. The state is text, a JSON object or an array.
Question typesA choice among labelled options, a score on a numeric scale, or a yes/no that TypeSafe calls a noul.
A choice questionInstructions and a map of option names to descriptions. A description is plain text or an object with fields you pick, such as what and not_for. A question holds up to 255 options.
You get backThe chosen option, a probability for every option that sums to 1, and a confidence score derived from the shape of that distribution.
SpeedAbout 135 ms a request in my matches.
InputText only. Everything Jev knows about a match has to reach it as JSON, which is why most of this project is computer vision.

I built clash-jev, a bot that uses Jev to make near real-time decisions on live game data.

How it works

Nothing is trained to play. The bot uses no reinforcement learning and no simulator.

Research project. Automating play is against Supercell's terms of service. I built this to study decision making. The title is a joke. The project is not affiliated with or endorsed by Supercell.

Part 1Architecture

Android tablet · USBLaptop · clash-jev runtime, one Python processTypeSafe API · externalLaptop disk · runs/Laptop · offline trainingLaptop · publish CLIVercel · replay siteViewerVISIONClash Royalethe game, 1440×2304screenrecordH.264, 720×1152, 8 Mbitinput tapAndroid input serviceCapturePyAV decoder thread,keeps the newest frameScreenMaptwo affine transformsHand, elixir, towersOpenCVBadge finderhue threshold at 2×Troop network + trackerONNX via cv2.dnn, 4 reads/sState builderbriefing + facts, JSON,1 a secondPolicystrategy → card → squarePhysical checkelixir? legal target?Actuatorone open adb shellRecorder10 fps, 480 px, mp4Loggerone JSON line a decisionJev · System OnePOST /v1/systemonechoice + probabilitiesruns/*.jsonlstate, questions, probabilitiesruns/*.mp4frames stamped with run timeCollect modecrops + framesLabel pagetracks, letter keysTrain + evaluatePyTorch, 5 foldstroop_model.onnx1.7 MBclash-jev publishblur names, read the scoreEdge function /api/jevforwards one request, stores nothingStatic siteNext.js export + replaysBrowserreplay, Re-ask Jevadb exec-outH.264 stream1state3HTTPS, 3 requests,about 135 ms each4play5adb shellinput tap62frame → readingsweightsafter a matchdeployreplaysre-askHTTPS, with the viewer's own key
Dashed boxes are deployment boundaries. Numbers 1 to 6 follow one decision through the live loop. Violet is Jev and the path to it, green is vision, amber is recording and replay, and the dashed green line carries the trained weights into the runtime.

The diagram shows every component, the boundary it runs in, and what crosses each boundary.

Part 2Design decisions

I wanted to measure Jev's decisions and not my own. These are the decisions I made about how to ask, and why.

DecisionWhy
Every question carries its full option list. That means all 12 strategies, all 4 cards in hand and every square that is legal for the chosen card.A shortened list leads Jev toward an answer. I wanted to see whether its general reasoning carries over to a situation it has never seen, such as defending with a Giant, without me selecting the sensible options first.
The state holds measurements only. It has no playable flag, no list of counters and no previous decision.A derived fact is me doing part of the reasoning. When the state listed the cards that counter a troop, the right card went from an even split to 98%. That number measured my list.
Option texts define and never judgeA not_for that says "not when a tower is under attack" decides on Jev's behalf. A not_for that marks where one strategy becomes its neighbour keeps the options distinct and leaves the judgment to Jev.
Code acts only after Jev has chosenIf code removed the cards Jev cannot afford, I could never see Jev choose to wait for one. The log records what Jev wanted even when the bot cannot do it.
Three questions in sequence, not oneOne question over every combination of strategy, card and square would hold hundreds of options with near-identical descriptions. Three short lists keep the options distinct, and they show at which step a decision went wrong.
A fallback is never logged as JevWhen a request fails the bot plays a baseline move. The log gives it its own source, so no analysis of Jev includes a move Jev did not make.

Part 3Capture

Capture gets the tablet's screen onto the laptop as frames the vision code can read. Every reader gets the newest frame, about 350 ms after the screen showed it.

adb exec-out screenrecord --output-format=h264 --size 720x1152 --bit-rate 8M --time-limit 180 -
ChoiceWhy
Video stream, not screenshotsA frame arrives about 350 ms after the screen shows it, for any number of readers. A screenshot over adb takes 1.4 to 4.5 s on the same tablet.
Frames are converted in the decoder thread onlyPyAV converts lazily, and converting one frame from two threads crashes inside libswscale. Readers copy a finished NumPy array.
A packet that fails to decode is skippedscreenrecord stops after three minutes and must be restarted. The first packets after a restart are often damaged, and the next keyframe restores the picture.
No new frame means a still screenH.264 sends only what changed. The last frame is still the current screen.
720 columnsWide enough to read a ten-pixel badge at double scale, and cheap to decode.

Screen layout

Screen layout makes every reading independent of the device's resolution.

Screen layout
Reference layoutI define every reading on a 419×633 layout.
ScreenMapMaps a device frame onto the reference, and maps taps back to device pixels.
Two affine transformsClash Royale scales the arena and the bottom bar as two separate blocks on taller screens.
CalibrationOnce per aspect ratio, from the tower bars and the river for the arena and from the hand cards for the bottom bar.

Part 4Reading the board

This stage turns a frame into readings. It reads the hand, the elixir and each tower's health, and it finds every troop with its owner, position and health. Colour thresholds and template matching do most of the work, and one small network names the troops.

Cards, elixir and towers are read on the reference layout. Troops are read on the same layout at double size, because a level badge is only ten pixels tall at 419 wide.
ReadingMethod
ElixirTen sample points along the bar, compared with the bar's purple.
TowersFilled width of each health bar, divided by the widest that bar has been.
TroopsLevel badges give position, owner and health. The troop network gives the name.
Hand, next cardCard art matched against a bank of known cards.
Opponent's elixirAn estimate. It refills on the match clock and pays for each new enemy troop.

The hand reader

Three rows of four hand cards as the game draws them: in full colour, greyed out, and washed out with a lighter wedge. Beside each card is its local-contrast form, which looks the same in all three rows.
Left, hands as the game draws them when a card is affordable, not affordable, and at zero elixir. Right, what the reader compares. The game changes brightness. The lines of the drawing do not change.

The hand reader names the four cards in hand and the next card. It has to give the same answer when the game greys a card or washes it out.

Level badges

The badge finder locates every troop and reads its owner and health. It hands the troop network one crop per troop.

Three panels of one frame: the arena, the crimson and blue pixels that pass the hue threshold, and the result with a box and a name with a confidence on each troop.
A frame, the pixels that pass the badge hue thresholds, and the result. The box under each badge is the 96×96 crop the network names.
ChoiceWhy
Find troops by their level badgeEvery troop on the board gets a badge with a fixed colour, crimson for the enemy at hue 167 to 175 and blue for yours at hue 98 to 111. A colour threshold finds them in under a millisecond.
Read badges at double sizeA badge is ten pixels tall at 419 wide and disappears in resampling. At 838 wide it survives.
Keep shapes that are square and 60% filledTower bars and spell effects share the colours. The digit's dark outline cuts gaps in a badge, so a closing step runs first.
Measure a shape by its shorter sideAn archer's pink hair merges with her pink badge into a tall shape, and the shorter side still matches a badge.

Part 5The troop model

The troop model names the card under each badge. I collected the pictures, labelled them by hand and trained the network myself.

Collect, name, train. The loop is in the repository, so you can teach the bot the cards of another arena.

Collecting and labelling

This step turns matches I play into labelled training pictures, with as few keystrokes as possible.

A grid of small troop pictures, seven per card, for the twelve cards the network knows.
Samples of the 96×96 crops, by card. Each crop is the region under one badge.
ChoiceWhy
Collect modeI play a match by hand. The bot saves the crop under every badge and one full frame a second. It sends no Jev requests and no taps.
Label tracks, not picturesCrops of one troop taken a fraction of a second apart are grouped by time and position. One key names the whole track. 383 tracks covered about 3,200 crops.
Letter keys onlyA badge shows a level number, and a digit key invites the labeller to type that number.
A spotlight on the troop in questionA crop of a fight can hold five troops. The page dims everything except the one under the badge.
Train only on crops that show their badgeThe tracker keeps a troop for a moment after it dies, so late crops show an empty spot. 1,975 of 3,200 crops passed.

The network

The network maps a 96×96 crop to one of twelve cards.

The troop network. Four convolution blocks, about 420,000 parameters, 1.7 MB as ONNX.
ChoiceWhy
A small network trained from scratchI also fine-tuned a ResNet-18. It is 26 times larger and scored 70% where this network scored 75% on the same test.
Swap the red and blue channels in trainingA card's art is the same for both players and only team colours differ, so crops of enemy troops also teach the network to name yours.
Mirror, shift, vary brightnessTroops face both ways, badges are found a few pixels off, and spell effects change the light.
ONNX through OpenCV's DNN modulePlaying needs no deep-learning library. Badges and names for a whole frame take 16 ms.

Evaluation

The evaluation measures how often the network names a troop it has never seen.

Evaluation
SplitWhole minutes of play are held out, dealt into five folds.
LeakageNo troop appears on both sides of a split, and every card appears in training.
Why not whole matchesOne match holds 58% of the crops and every Bomber and Skeleton.
Result296 of 354 troops named correctly on held-out minutes, which is 84%.
Giant31 / 31
Goblin Hut5 / 5
Musketeer17 / 18
Goblin Cage11 / 12
Minions18 / 20
Archers29 / 33
Knight37 / 45
Goblins51 / 63
Mini P.E.K.K.A21 / 26
Skeletons11 / 14
Spear Goblins62 / 81
Bomber3 / 6

The weak pair is Goblins and Spear Goblins, which differ by a spear a few pixels long. Bomber has six test crops.

Tracking across frames

Tracking turns single-frame reads into stable troops, so one wrong read or one hidden badge does not reach the state.

Part 6The game state

This stage writes the readings down as the one JSON document Jev sees, once a second. Anything the vision code knows and does not write here does not exist for Jev.

The document
BriefingExplains the game to a reader who has never seen it. It covers the objective, elixir, lanes, and what each card archetype is strong and weak against.
Facts of this instantThe clock, the elixir, the hand, the next card, the troops on both sides and the towers.
One formatA card in hand and a troop on the board use the same description.
{ "troop": "bomber", "class": "ranged support", "archetypes": ["splash", "ranged support"],
  "health": "low", "damage": "medium", "targets": "ground only", "flies": false,
  "lane": "left", "where": "my side", "tiles_from_my_tower": 10,
  "seen_for_seconds": 4.2, "health_remaining": 1.0 }
ChoiceWhy
One description for a card in hand and a troop on the boardJev can join the two. The troop says it flies, the card says it cannot attack anything that flies.
Distances in tilestiles_from_my_tower lets Jev judge how close a threat is. The state never says a threat is close.
Cost and elixir left after playing, never "playable"What is affordable is part of the decision. If Jev picks a card it cannot afford, the bot declines, and Jev can pick it again a second later.
your_unplayed_pickThe state carries no previous decision, with one exception. It is the card Jev picked a second ago and could not afford, with the elixir still needed. With it Jev keeps choosing the card it is waiting for.
Card descriptions I wrote by handStrengths and weaknesses of the cards of the first two arenas. None says when or where to play a card.

Part 7Asking Jev

This stage gets one play decision out of Jev. A decision takes up to three requests, because the card question depends on the strategy and the square question depends on the card.

save_elixir and hold_elixir_for_threat end the decision after one request. The chosen strategy never removes a card or a square from the later lists.
"defend_left": {
  "what":    "Enemy units in the left lane are close to your left princess tower, or to
              your king tower once that princess tower has fallen: on your side of the
              river or about to cross it, walking toward the tower or already hitting
              it. Play a card against them, in the left lane.",
  "not_for": "When no enemy unit in the left lane is close to your tower
              (`tiles_from_my_tower` is the distance of each): units that are still far
              away on the enemy's side are not attacking it yet. Also when you mean to
              draw the attackers to the middle of your side: that is defend_centre."
}

Jev matches the wording of each option against the state, so the wording decides a great deal. I found each choice below by replaying logged moments under a changed wording and comparing the probabilities.

ChoiceWhy
A condition goes in not_forAt ten elixir, "not when you already hold 10 elixir" inside the description of save_elixir left it at 0.27. The same sentence in not_for brought it to 0.02.
No catch-all optionA wait strategy that matched any quiet board took 48% of the vote at ten elixir. save_elixir covers waiting.
Squares named and described by positionA square once named left_defence was chosen by defend_left almost every time. It is now left_tower_front, and its text gives distances in tiles.
Condition written inside the description0.27
Same sentence in a not_for field0.02
The arena with every named square marked: bridge, tower front and back in each lane, two centre squares, the enemy towers and the two pockets.
Every square Jev can choose. Troops go on your half. The pockets open when that enemy princess tower falls.

When to ask

The match loop decides when a question is worth a request.

One full decision inside one second. The thin green slice is vision, at 16 ms. Jev's median times in a recorded match were 147, 129 and 128 ms for the three steps.

Part 8Acting

Acting turns Jev's pick into two taps on the tablet, or into a log line when the pick cannot be done.

Acting
Physical checkDoes Jev hold enough elixir for the card, and does the card have a legal target?
If it passesThe bot writes two taps into one adb shell that stays open. That takes about 135 ms, where two separate adb commands take 315.
If it failsThe bot taps nothing, and the log records the pick with the reason.

Part 9Record, replay, re-ask

This stage records a match so that every decision can be inspected later, and publishes it without exposing the opponent.

The clash-jev web app in replay mode. A pipeline strip runs across the top. The match video with troop labels is on the left, Jev's choice and the three steps with their probabilities are in the middle, and the game state and a decisions log are on the right.
One page serves the live game and the replays. The strip across the top lights up stage by stage as each decision is made.
ChoiceWhy
Log every decision in fullThe state, the three questions as worded, every probability and the time of each step. A replay needs nothing else.
Stamp each video frame with the time since the run startedSecond N of the video is second N of the log, so the video's clock drives the page. A match is about 3 MB.
Blur the opponent's nameAn opponent is a real person who did not ask to be shown. Publish blurs the banner during the match and finds the crimson banner on the results screen by colour.
Read the score off the results screenGold crowns above and below the banner give the final score, which the tower readings can get wrong.

Asking Jev again

The Re-ask Jev panel. On the left, the strategy question as a form. On the right, the result: recorded Defend Left 79 percent, now Defend Centre 31 percent, hold elixir for threat 23 percent, push left 16 percent.
Second 58 of a match, with enemy troops at the left tower. The recording says defend_left at 79%. With that one option deleted, the vote goes to defend_centre at 31% and hold_elixir_for_threat at 23%. save_elixir stays at 3%.

Re-ask Jev sends a recorded moment to Jev again with an edited question, to show what a change of wording does.

ChoiceWhy
The state is lockedThe experiment is the same moment under a different question. An edited board is a different moment.
An unedited re-ask reproduces the recordingdefend_left came back at 79% and 80% against 79% recorded, so a move of twenty points comes from the edit.
One click is one request on your keyThe panel asks nothing until you press the button.
A forwarder of about thirty linesThe TypeSafe API does not accept calls made from a web page. The forwarder passes the request on with your key and stores neither.

Part 10Technical limits

So can it three-crown its way to Ultimate Champion? Not yet. It currently has a 98% win rate and sits at 400 trophies. The recorded matches are on the replay site, each decision with its state, its questions and every probability.

Watch a match, then change the question

Every recorded decision can be re-asked with your own TypeSafe key.