Documentation

The chain decides the card.

Every stat, every ability, every element on every OrdCard is read out of the SHA-256 of the inscription it belongs to. No card is authored, no card is edited, and no value in a match comes from anywhere else. This page is how you check that instead of believing it.

Engine version 422 tests, all passingZero runtime dependencies
01

What the engine is

One pure function. In go the identifiers of six inscriptions and one match seed. Out come the winner and the log, action by action. There is no database read, no clock, no floating point and no randomness that is not derived from the seed.

simulate(teamA, teamB, seed, options) -> { winner, actions, log }
packages/engine · the entire public surface of a match

The consequence of that signature is the whole product. Because the function is pure, the same inputs give the same output on any machine, forever, which is what makes a match something a stranger can recompute rather than something we assert. The database stores results so pages load quickly. It is a cache. The hash is the record.

Figure 01
inscription_id64 hex, an i, an indexinscription_numberassigned by the indexerSHA-256utf8(id) ‖ uint64BE(number)b[0]b[31]twelve bytes carry the card · twenty are reserved and unreadStat lineb0 to b7Tiebreakb8Elementb9Abilityb10Magnitudeb11Tiercosmetic onlynumber only,never hashed,never in combat
Two facts about an inscription go in. Twelve bytes of the resulting digest carry the entire card, and twenty are reserved and never read by combat. Note the line that goes around the hash: the inscription number also decides the rarity tier directly, and the tier never enters resolution.
Inputs per card
2

The inscription id and its number. Nothing else.

Bytes that matter
12

Of the 32 the digest returns.

Divisions per hit
1

Every multiplication happens first, in integers.

Action cap
60

A match cannot fail to end.

02

The eight rules

These came before the code and outrank it. Three separate times during design a good-sounding idea reintroduced buyable advantage, which is why the rules are written as prohibitions and why each one carries the measurement that justified it.

RuleIn one lineWhat it costs to keep
INV-1Nothing outside the hash touches combatRarity, sat provenance, collection, market price, sold items, reputation, holding time and wallet balance are all forbidden inputs to resolution.
INV-2Money buys prestige, never win rateMeasured with a rarity bonus switched on, a wallet of mythics beat a wallet of commons 80% of the time. That is why the bonus is zero.
INV-3Wallet size does not decide the resultThe hand is drawn, not picked. Once both hands come out of the same distribution, collection size stops being a variable at all.
INV-4Total reproducibilityGiven an id and a number, any SHA-256 implementation produces the card. Given a seed, the teams and the engine version, anyone replays the match action by action.
INV-5PermissionlessThe engine knows inscriptions, not collections. There is no approved list, and no collection has to integrate, ask or pay.
INV-6The backend is the authoritySimulation runs on the server. The client signs intents and animates a log it did not compute.
INV-7Reputation is not sellableCareer, wins, streaks and trophies cannot be bought, transferred between cards or edited.
INV-8Progression never touches combatLevel, streak, milestones and cosmetics change appearance and title. They never change a number the damage expression reads.

Why progression is cosmetic, in one table

The eighth rule is the one that costs the most to keep, because a card game with no power progression gives up the easiest retention loop there is. It was measured before it was decided. A veteran carrying a flat stat bonus against a newcomer carrying none:

Veteran bonusWin rateReading
+0%49.7%The control. No bonus, no advantage.
+5%63.1%Five levels of five percent is already worth more than most abilities.
+10%78.4%
+15%88.1%
+25%97.5%
+50%100.0%Six months of play and the newcomer cannot win at all.
n = 15,000 per row. The same arithmetic killed the rarity bonus: with it switched on, a mythic wallet beat a common wallet 80% of the time.

In a small audience that closes the front door. So progression buys frames, titles, emblems and a public record, and never a point of attack.

03

The card generator

A card is not stored. It is computed, every time it is read, from two facts that are already public on Bitcoin. Delete our database and every card in the game comes back identical.

seed = sha256( utf8(inscription_id) || uint64BE(inscription_number) )
§2.2 · the only place a card comes from

Why the number is in the hash

The inscription id alone would be enough to derive a card, and it would be a serious mistake. An id is chosen by the person inscribing: they can grind candidate transactions offline until one produces a card they like, and at that point stats stop being drawn and start being selected. The inscription number is assigned by the indexer at confirmation and cannot be picked, so mixing it in is what keeps the draw a draw. Negative numbers, the cursed inscriptions, encode as two’s complement so that two implementations cannot disagree about them.

One real card, byte by byte

Below is a genuine inscription, its genuine digest, and the card the engine derives from it. Nothing here is an example filled with plausible numbers: the stat line is recomputed on every request from the two values printed beside it.

Figure 02
da
0
5d
1
1c
2
6c
3
20
4
be
5
67
6
52
7
99
8
03
9
97
10
dd
11
0d
12
93
13
2a
14
c4
15
f5
16
e1
17
61
18
c5
19
8f
20
0c
21
04
22
bc
23
95
24
95
25
0f
26
cb
27
5b
28
85
29
ba
30
37
31
  • b[0..1]HP 75 + (u16 % 31)
  • b[2..3]ATK 28 + (u16 % 15)
  • b[4..5]DEF 15 + (u16 % 11)
  • b[6]SPD 9 + (b % 8)
  • b[7]CRT 8 + (b % 5)
  • b[8]TIE queue tiebreak
  • b[9]ELEM b % 5
  • b[10]ABIL walks the weighted table
  • b[11]MAG magnitude, budgeted
  • b[12..31]RESERVED unread by combat
The 32 bytes of this card's digest. Bright cells are read by the engine, dim cells are the reserved tail. Health comes out of the first two, attack out of the next two, and so on down the map.
The card
RunestoneRunestoneAIR
COMMON63.140.674
HP83
ATK29
DEF15
SPD16
CRT10
ABILITYINITIATIVEACTS FIRST · EVERY ROUND

COMMON SAT
NO EXOTIC PROVENANCE

ed76bf35…2a0i0ORDCARDS
Number
63,140,674
Element
AIR
Ability
INITIATIVE
Tier
COMMON
da5d1c6c20be6752990397dd0d932ac4f5e161c58f0c04bc95950fcb5b85ba37

The stat line

StatBytesFrom the digestRangeThis card
Healthb[0..1]75 + (u16 % 31)75 to 10583
Attackb[2..3]28 + (u16 % 15)28 to 4229
Defenseb[4..5]15 + (u16 % 11)15 to 2515
Speedb[6]9 + (b % 8)9 to 1616
Critb[7]8 + (b % 5)8 to 1210
Two bytes where the range is wide, one where it is narrow. An aether card adds two crit on top and the result is capped at fourteen, which is the only place an element changes a number. Version 4 halved every range and kept its centre, so cards sit closer together and the player decides more of the match.

The element, and the wheel it sits in

This is the most valuable decision in the game, and it is not close. Playing the wheel, which means putting each of your cards where it has the edge and away from the ones that have it over them, measures 91.6% against 54.3% for ignoring the element and pairing on the numbers alone. That is +37 points, against +12 for ordering well and +1.3 for choosing the right tool.

Figure 03
FIREAIREARTHWATERAETHERevery elementbeats twoand loses to twowith the arrow 118%against it 85%mirror 100%nothing is neutral
Byte 9 modulo five picks the element. Each one beats the two after it on the wheel and loses to the two before it, so an advantaged strike lands at 118% and a disadvantaged one at 85%. Nothing is neutral except a mirror. Version 4 closed the wheel around five: aether used to stand outside it, and with two aether cards on the table the whole elemental decision was worth nothing at all.

The tier does not come from the hash

Rarity is read off the inscription number directly, on a fixed ladder, and it never reaches resolution. This is the asymmetry worth understanding before anything else on the page: two cards with the same stats and different tiers are the same card in a match, and the engine has a test that proves it by running an identical match with every tier inverted and comparing the logs byte for byte.

TierInscription numberCards that can ever exist
MYTHICbelow 1,0001,000
LEGENDARY1,000 to 9,9999,000
EPIC10,000 to 99,99990,000
RARE100,000 to 999,999900,000
UNCOMMON1,000,000 to 9,999,9999,000,000
COMMON10,000,000 and upopen
CURSEDnegative numbersclosed
Populations are cards, not players, and they never grow: a tier is a band of numbers and the band behind us is closed.

The ability, and the budget it is drawn against

Byte 10 walks a weighted table to pick which of the sixteen abilities the card carries. Byte 11 sets the magnitude, but not on its own: the magnitude is scored against the stat line the same digest already produced, so a card that rolled well on stats lands lower in its ability’s range. The correlation is deliberately negative, around minus zero point three, with a hard cap. A card does not get to be both the strongest body and the strongest effect.

percentile = weighted_position_of(hp, atk, def, spd, crit)
position   = clamp( b[11]/255 * 0.6  +  (1 - percentile) * 0.4 )
magnitude  = lo + round( position * (hi - lo) )
§3.2 · the magnitude budget, simplified to its shape

Five of the sixteen abilities are binary. They have no magnitude at all, and the engine represents that absence explicitly rather than as a zero, so nothing downstream can accidentally read a missing value as a very weak one. This card holds INITIATIVE, which is one of them.

The hash, made visible

The strip across the foot of every card is not decoration. It is twenty-two linked pairs whose lengths are read straight off the same digest, so the figure below belongs to this inscription and to no other, permanently.

04

The sixteen abilities

Five families, one ability per card, drawn from a weighted table rather than a flat one. The weights sum to exactly 256, which is verified by a test, so the complicated abilities stay rare and a first match stays legible.

Draw rate of each ability

Byte 10 walks a weighted table whose weights sum to exactly 256, so a share here is a share of every card that will ever exist. A flat draw would put every bar at 6.25%.

Defense35.9% of all cards
  • Barrier3011.7%
  • Armor3011.7%
  • Tenacity166.3%
  • Riposte166.3%
Support14.8% of all cards
  • Rally2810.9%
  • Formation62.3%
  • Vengeance41.6%
Tempo18.0% of all cards
  • Initiative2810.9%
  • Double Strike103.9%
  • Charge83.1%
Sacrifice14.8% of all cards
  • Legacy166.3%
  • Detonation166.3%
  • Split62.3%
Attack16.4% of all cards
  • Pierce145.5%
  • Attrition145.5%
  • Precision145.5%
Bars scaled to the widest. Weights read live from the engine's own table.

A plain modulo would hand every ability 6.25% of all cards. That reads fair and plays badly: the effects that need reading, the ones that fire on death or change the queue, would be as common as the ones you can learn in a sentence, and the first match a new player watches would be a knot. Weighting is how the simple effects carry the introduction and the complicated ones stay worth finding.

FamilyThe ideaShare of all cards
DefenseAbsorb. Buy the exchange that would have ended you.35.9%
SupportSpend the ability on somebody else's numbers.14.8%
TempoChange when a thing happens instead of how big it is.18.0%
SacrificeThe card is worth something the moment it dies.14.8%
AttackRemove a term from the damage formula, or add one.16.4%

Reference

AbilityFamilyMagnitudeEffectDraw
BARRIERdefense51 to 61%The first hit that would land arrives much weaker. Spent once, then gone.11.7%
ARMORdefense9 to 19%Every hit, and every splash, arrives reduced by the magnitude.11.7%
TENACITYdefense49 to 59%The blow that would kill arrives weakened, once. If it still kills, it kills.6.3%
RIPOSTEdefense12 to 22%A share of the damage taken goes straight back to the attacker.6.3%
RALLYsupport2 to 3The whole side gains flat attack. Snapshot at setup, and it does not stack.10.9%
FORMATIONsupport3 to 4Attack and defense, but only while two allies are still standing.2.3%
VENGEANCEsupport21 to 31Grows in attack each time an ally on its side dies.1.6%
INITIATIVEtempononeActs before everything else, in every round, without touching a single stat.10.9%
DOUBLE STRIKEtempo67 to 71%Two strikes instead of one, each at a reduced share of full force.3.9%
CHARGEtempo53 to 63%Nothing in round one. From round two on, a large attack multiplier.3.1%
LEGACYsacrifice8 to 16On death, hands attack and health to the frontmost surviving ally.6.3%
DETONATIONsacrifice6 to 14On death, flat damage to every living enemy at once.6.3%
SPLITsacrifice42 to 52%On death, leaves a copy of itself at a share of its own stats, with no ability.2.3%
PIERCEattack49 to 59%Cuts through most of the defense before the subtraction happens.5.5%
ATTRITIONattack4 to 13%At setup, cuts the attack of the strongest enemy on the board.5.5%
PRECISIONattacknoneCarries +10 crit and rerolls the roll when it misses.5.5%
Read live from the engine's own table. Precision's crit bonus is fixed at +10 rather than budgeted, because a binary ability has no magnitude to tune.
05

The kit

Three tools, free, permanent and identical for everyone. One per card. They exist because a game whose only decision is a permutation of three does not hold anyone for a season, and they are the only shape of depth this product can accept.

ToolEffectWhat it is forWin rate alone
Whetstone+1 attackAlways does something, never does anything surprising. The correct pick with no information.55.0%
Aegis+4 healthBuys an action rather than damage. Worth most on a slow card that would die before its turn.55.5%
Lodestoneelemental penalty softened 50%Worth nothing in a neutral matchup and more than either of the others against the wrong element.55.1%
Calibrated by simulation, not by feel. The three sit inside a band 0.5 points wide, so choosing between them is a read of the opposing board and not a ranking.

The store is locked out by structure, not by promise

A match takes a loadout, and the loadout is filtered against the kit before anything else happens. An item that is not one of these three is not a weaker item in resolution: it is discarded, silently, and the slot resolves empty. There is no argument, no configuration and no code path by which a sold item reaches the damage expression. The specification predicted that this rule would be pushed on every quarter revenue dipped, so it was made a test instead of a memo.

How much the kit is worth, measured

QuestionMeasuredReading
The whole kit against any two of the three50.3%95% interval 49.71 to 50.84, entirely inside the one point margin the gate requires. Breadth of catalogue is worth almost nothing.
Choosing the right tool against always the same one50.5%Real and above chance, and thin. Playing the item system well is worth about two points.

Those two numbers together are what licenses a shop to exist at all. Access to the kit matters; breadth of catalogue does not. So the kit is given away, and anything sold on top of it is decorative in fact rather than decorative in the marketing copy.

06

The match

Three cards a side. You do not pick your three best: the game deals you a hand and you choose inside it. That single rule is what keeps a wallet of five competitive against a wallet of five hundred, and it is measured rather than asserted.

  1. 01DrawUp to 5 cards come out of your eligible collection, by a published algorithm from a published seed.
  2. 02Keep threeYou choose the team out of what you were dealt, never out of the whole wallet.
  3. 03OrderYou arrange the three. Yours line up against theirs, position by position, so this is the decision the match turns on.
  4. 04ResolveThe server runs the engine. Integer arithmetic, no floating point, no roll that is not derived from the seed.
  5. 05PublishThe log, the seed and the teams come back together, which is everything a stranger needs to recompute it.
hand_size = max(3, min(5, collection - 1))
The hand, and the clamp that keeps a small collection playable

Draw from the collection, keep 3, order them. The subtraction is what stops a collection from ever dealing itself entirely, and the clamp is what stops a collection of three from being dealt a hand too small to field a team.

What a bigger collection is worth

A wallet of 100 cards against a wallet of the size on the axis. 50% is symmetry: at 50% the bigger wallet has no advantage left. Left is the rule the game ships. Right is the same match if players simply picked their three best.

Drawn handshipped
50%62.5%50.1%
3456102050
Free choicefor comparison
50%89.1%55.4%
3456102050

Cards in the opposing wallet, along the bottom of each plot.

n = 90,000 per point · engine version 4 · axis spaced by sample, not by value

The left plot is the mechanism the game ships and the right one is the same match if players simply picked their three strongest. Free choice never becomes fair: even fifty cards against a hundred, a difference of only two to one, still hands the bigger wallet 55.4%. The drawn hand arrives at symmetry at six cards and stays there no matter how large the other wallet grows.

What a small collection actually costs

Your collectionYou beat a wallet of 100
3 cards37%
4 cards37%The fourth card is worth nothing at all: the hand size formula returns three either way.
5 cards45%
6 or more50%From here the board is flat, and it stays flat however large the other wallet gets.
n = 90,000 per row, engine version 4.

A draw anyone can check

A shuffle you cannot verify is a shuffle the house could have dealt. So three things are published with every hand and all three are required together: the seed the draw came from, the eligible collection frozen and ordered at the moment of the draw, and the algorithm itself, which is a partial Fisher-Yates driven by a hash stream. With all three, anyone can redo the draw and confirm that nobody chose anybody’s hand.

07

Combat

Resolved on the server, in integers, with every tie broken by a rule rather than by a roll. Nothing in a match is decided by chance except the crit roll, and even that comes out of the seed.

The queue

Speed descending, then the tiebreak byte ascending, then the inscription id lexicographically, then the side. Four keys, and after the fourth there is nothing left that can tie, because two cards with the same id on the same side are the same card. The tempo ability is a partition of that queue rather than a bonus inside it: cards holding it form a block at the front, sorted among themselves by the same chain, in every round.

Figure 04
01SIDE B
Card D
spd 11 · b8 40
02SIDE A
Card B
spd 19 · b8 12
03SIDE B
Card E
spd 19 · b8 88
04SIDE A
Card A
spd 14 · b8 7
05SIDE B
Card F
spd 9 · b8 61
06SIDE A
Card C
spd 6 · b8 3
Tempo block, ahead of everythingSpeed, then b[8], then id, then side
Six cards ordered with no roll anywhere. The tempo block comes first, then everything else falls in on speed, then on byte 8, then on the id, then on the side.

Who gets hit

Position against position. Your first card fights their first card, your second fights their second, and when a lane empties the next living card behind it steps across. This is the newest structural rule in the engine, and it is what lets the player work out who will face whom by looking, instead of by simulating.

Figure 05
RETIRED · LOWEST HEALTHI88HPII41HPIII96HPposition is irrelevantSHIPPED · FRONTMOST ABSORBSI88HPII41HPIII96HPposition is the decision
The same board under both rules. Under the retired rule every strike chases whichever enemy is weakest, so where the player put their cards changes nothing. Under the shipped rule every strike lands on the front, and the arrangement becomes the decision.
RuleSkill ceilingMirror
Lowest health50.6%49.9%Version 1. Ordering was decoration: playing it perfectly was worth six tenths of a point.
Frontmost absorbs71.9%49.7%Versions 2 and 3. Retired in version 4: alone it measures the same depth as lane, and it loses to lane once the stat ranges are compressed.
Lane against laneshipped73.7%50.1%Shipped in version 4. Alone it ties the frontal rule; with compressed stat ranges it is what turns the reading into something inferable instead of simulable.
Skill ceiling is a player ordering their three perfectly, by brute force over all six permutations, against a player ordering naively. Mirror is the same team against itself, which is the fairness check: all three rules sit on fifty.

Lane assignment measured higher and was not adopted. The click budget for a first match is six clicks and the replay runs twenty-five seconds, and “the front card takes the hits” is legible in one sentence and visible on screen as it happens, while lanes have to be taught before the first blow lands. Two points of ceiling do not pay that teaching debt.

The damage expression

Integer arithmetic with every multiplication before the single division. This is not fastidiousness: floating point would give different results on different architectures, and the reproducibility that makes the whole product checkable would quietly stop being true.

  1. 01Attackatk = floor(base * (100 + charge) / 100) + rally + vengeance + legacy + formationAttrition has already cut the base at setup. The multiplier resolves before the additive band, so an ability that adds attack is never multiplied by one that scales it.
  2. 02Rawraw = floor(atk * force * element * crit / 1000000)Four integers multiplied, then divided once. Force is the strike's share of full power, element is 118, 100 or 85, crit is 150 or 100.
  3. 03Defensedamage = raw - floor(defense / 2)Halved before it is subtracted, so defense slows a hit and never cancels one. Pierce skips this line entirely.
  4. 04Mitigationdamage = floor(damage * (100 - armor) / 100)Armor reduces attacks and splash damage alike. It is the only percentage applied after the division.
  5. 05Floordamage = max(1, damage)Applied after mitigation, not before. A card can never be immune by arithmetic, only by an ability that says so.
  6. 06Barrierif barrier and damage >= 1 then damage -= floor(damage * m / 100), barrier spentChecked last, so a barrier is spent on a hit that would have hurt rather than on one the floor had already reduced to nothing. Version 4 gave it a magnitude: it used to cancel the hit outright, which is binary and therefore impossible to tune.

Deaths, and the end of a match

Deaths are processed through a first-in-first-out queue, so effects that fire on death, and effects those effects trigger in turn, resolve in a defined order rather than in whatever order the interpreter happened to walk an array. A match ends when one side has no living cards, copies included. If it reaches 60actions it ends anyway, decided by the higher sum of remaining health as a percentage, then by the lower sum of tiebreak bytes, then by the ordered concatenation of the side’s inscription ids. Nothing is ever re-rolled.

r(i) = sha256( seed || uint32BE(i) )      // i is monotonic and never reused
§6.3 · the match number stream

Every roll the match makes comes out of that stream, and the counter only ever moves forward. The published log records the counter range each action consumed, so a replay that diverges by a single roll is visible rather than mysterious.

08

Balance, measured

An ability is acceptable when a card carrying it beats a card carrying nothing between 55% and 61% of the time. Strong enough to matter, weak enough to lose to a better draw. Every number below came out of the project's own simulator at 40,000 matches, paired seeds, sides alternated.

Win rate of one ability against none

A card carrying the ability, on a team that is otherwise neutral, against a card carrying nothing. Everything between 55% and 61% is declared acceptable: strong enough to feel, weak enough to lose to a better draw.

  • Rally
  • Legacy
  • Vengeance
  • Charge
  • Initiative
  • Split
  • Pierce
  • Formation
  • Double Strike
  • Detonation
  • Precision
  • Armor
  • Tenacity
  • Attrition
  • Barrier
  • Riposte
50%55%61%
  • 58.8
  • 57.5
  • 57.4
  • 57.1
  • 56.9
  • 56.9
  • 56.8
  • 56.7
  • 56.7
  • 56.5
  • 56.3
  • 56.2
  • 56.2
  • 56.1
  • 56.0
  • 55.9
n = 40,000 per row · 95% interval ±0.49 points · engine version 4

The control run, no ability against no ability, measures 50.1% with no side bias, which is the zero the whole table is read against. All sixteen abilities sit inside the band, between 55.9% and 58.8%. The spread between the strongest and the weakest is 2.9 points, against 6.4 in version 3 and 16.1 immediately after version 4 changed the ranges and before the recalibration caught up.

How a balance change is actually made

Version 4 is not a patch, it is a reform, and it shipped as one version for the same reason every version does here: the frozen fixture is regenerated once instead of once per change. What drove it was a measurement nobody had taken. Under the old ranges, in 63% of matches either all six orderings won or none did, which means the result was already decided by the cards and the player's choice changed nothing in almost two thirds of the games.

Two changes fixed that and neither works alone. The stat ranges were halved around their own centres, so two teams are rarely far apart on raw numbers; and targeting became positional, so the player can see who will face whom. Together they take the share of matches where the ordering decides the result from 36.7% to 61.1%, and what playing well is worth from 62.9% to 70.1%. Compressing alone actually makes things worse under the old targeting, which is why the pair is the unit.

ChangeBeforeAfterWhy this shape
Stat ranges halved, centres kept36.7%61.1%Share of matches where the ordering actually decides the result. The single biggest lever found across eleven experiments, and five constants of code.
Targeting became positional62.9%70.1%What playing well is worth. Alone it ties the frontal rule; it is the compression that makes the difference, because the player can now infer the matchup instead of simulating it.
Barrier, Tenacity and Pierce gained a magnitude3 stuck3 tunableAll three measured above the ceiling under the new ranges and had no dial at all. Each became the graded version of its own effect, so the maximum of the range reproduces the old behaviour exactly.
All sixteen magnitudes remeasured16.1 pts2.9 ptsSpread between the strongest and the weakest ability. Compressed stats make the effect matter more than the number, so every range had to move.
Weights and the cumulative column were left untouched throughout, so no card in the universe changed which ability it holds. Only the magnitudes of Formation and Armor cards moved, and they moved for every such card at once, retroactively, because a card is derived and not stored.
09

Check it yourself

None of the above is worth anything if the only way to confirm it is to ask us. Here is how to reproduce a card with twelve lines of code and no dependency on this project at all.

import { createHash } from "node:crypto";

const id = "ed76bf35918948b8b78be0e6397ad4916d926dd3d65679a8b7e67d8699a672a0i0";
const number = 63140674;

const n = Buffer.alloc(8);
n.writeBigInt64BE(BigInt(number));
const b = createHash("sha256").update(id, "utf8").update(n).digest();
const u16 = (i) => (b[i] << 8) | b[i + 1];

console.log({
  hp:   75 + (u16(0) % 31),   // 83
  atk:  28 + (u16(2) % 15),   // 29
  def:  15 + (u16(4) % 11),   // 15
  spd:   9 + (b[6]  % 8),     // 16
  crit:  8 + (b[7]  % 5),     // 10
});
Node, no libraries. Substitute any inscription id and its number.

The comments are the values this page derived for that inscription on this request. If your run disagrees with them, one of us has a bug, and the disagreement is the whole point of publishing the expression.

The regression the test suite used to allow

This is worth telling because it is what “deterministic” costs in practice. For a long time every test in the engine compared the engine to itself. One derived a card twice and checked the two results matched. Another ran the same match twice in the same process. A third checked stats fell between their minimum and maximum. All of them passed, and not one of them would have failed if somebody had swapped which bytes of the digest became which stat. An innocent refactor could have silently rewritten every card in the universe with the suite still green.

So the suite now holds a frozen fixture: 17 cards pinned with their exact hashes, stats, abilities and magnitudes, covering every rarity band, every element, binary and scaled abilities, the cursed case and a numeric edge, plus one entire 16 action match with the counter range of each action. The fix was validated by injecting a regression on purpose, swapping two bytes in the health calculation. Every older test still passed. Only the fixture broke, and it broke card by card.

Tests
22

All passing, in under two seconds.

Frozen cards
17

Every tier, element and ability shape.

Frozen match
16

Actions, with the counter range of each.

Engine version
4

Old matches replay under their own version.

What versioning means here

A card is never edited in a database, because a card was never in a database. When a rule changes, the version increments and matches that already ran stay valid and replayable under the version they ran on. That is the only honest way to change a deterministic engine, and it is also why changes are batched and measured first: every version is a promise that has to be kept forever.

One last thing

The back of every printed card carries its full inscription id and a link to its page. Never trust the front. Recompute it. We would rather you did.

See the card system