How fair play works
Counter Attack never decides an outcome on its own. Every dribble and every shot is settled by a public random number from the Storm beacon, and your browser checks that number's signature itself.
One move, step by step
- When you tap, the game reads a fresh latest beacon height L (a new request made after your tap) and commits to block T = max(L, highest block it has ever seen) + 2 (+ 3 if the answer took longer than 0.25 s), a block that does not exist yet. Free play has no transaction, so there is no inclusion block N: L is the freshest block the browser could see, and L + 1 may already form while that answer travels (blocks come every ~0.2 s), so L + 2 is the nearest safe target. Every target is strictly higher than every earlier target in your visit, and a block that is already published or already used is refused. A double dribble commits to two blocks at once, T and T + 1: the first dribble is settled by T, the second by T + 1. The block number is shown on screen before the result is known, so neither you nor the game can pick it.
- When block T appears, the game fetches it from
and checks the BLS12-381 threshold signature σ against the network group key, then recomputesR = SHA-256(tag ‖ σ). The group key is pinned at the first block of your visit; if it ever changes you get a warning. u = first 8 bytes of SHA-256(R ‖ round id ‖ "|step|" ‖ client seed) / 2⁶⁴. The move succeeds when u is below the chance shown on the button. The client seed is random per round and shown in the Fair play sheet.- On a missed shot, the next 8 bytes pick how it is shown, in fixed proportions: . Nothing is shown as "almost" more often than it happens.
- Reloads. A started round always finishes. When you tap, the move is committed to a future block T and that commitment (block, round, step, seed, commit time, tip at tap) is saved before block T is requested; the result is saved the moment it is computed, before the animation plays. A move always settles on its block T, whatever the timing of a reload: reloading or leaving never changes the result and never returns the stake. On resume the game replays the saved result, or computes it from T if it was not saved yet.
- Edited saves. On resume the saved record is checked (T must be more than 2 blocks above the tip at tap, block T's beacon time must be after the commit time, the record must be well formed, sealed and on the right revision, and a saved result must match block T). If any check fails, the move counts as lost and the stake stays with the house; the Fair play sheet shows "Step record failed integrity check - counted as lost". So no edit and no reload can ever do better than simply playing on.
- Honest limit (free play). Free play is a browser demo: the chips live in your own browser, so nothing stops you from editing your own local balance. On-chain play (below) commits on a contract instead, so it does not have this limit.
- Every step's proof (block, R, σ, key, u, chance, result) is kept. The Fair play sheet in the game lists them and can fetch and verify them again.
If the network is slow the player waits in a wind-up pose; after a couple of seconds (or two failed requests) the game shows which block it is waiting for. If the main beacon API fails twice in a row it tries the backup API, and accepts an answer only if it comes from the same Storm chain with the same group key. It never falls back to a local random number. Visual details (where in the net the ball goes, confetti) use a separate cosmetic stream that cannot touch the result.
Signing tags by chain: . DST BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_.
On chain (Connect wallet)
- Connect a wallet (browser wallet, WalletConnect, or Lightning). You sign one readable message that lets this browser play for you: the browser key can start rounds and make moves, and can only ever withdraw to your own address. Then you approve one deposit (a StormCore transfer of STR into the CounterAttack contract on Storm devnet 2).
- Every tap is one transaction from that browser key (no wallet popup). It carries the move (dribble, double, shoot); the first tap of a round also carries the stake, the level and your seed. It lands in block N. The contract records the commitment with target N + 1.
- The result is
h = SHA-256(RN+1 ‖ player ‖ round ‖ step ‖ seed); success when the first 8 bytes < chance × 264; miss type from the next 8 bytes (45 / 25 / 20 / 10). A double uses RN+1 then RN+2. If a beacon round was skipped, the first formed beacon after it is used (the chain's own roll-over rule). - The contract reads R on chain from the Storm Random precompile and settles in the next transaction (your next tap, or a settle call sent as soon as the round ends; anyone may call it). Stakes and goal payouts move inside the contract; the house's maximum payout for your round is reserved when it starts. Nobody can know RN+1 while your transaction can still change: validators sign their share for block N + 1 only after block N is final.
- The game shows the result as soon as RN+1 is published and its BLS signature checks out in your browser, computed with the same formula as the contract; the Fair play sheet lists each move's transaction, block N, the beacon block, and whether the contract agreed.
- Test network: STR on devnet 2 has no value.
Live check
The maths
A goal from a spot pays floor₄(0.9625 ÷ (chance of reaching the spot × shot chance)), so every stopping point returns 96.25% on average and no way of playing beats another. Multipliers on screen are cut, never rounded up, to 2 decimals. A double dribble is two ordinary dribbles settled one after the other, each by its own block; its shown chance is their product cut to a whole percent.
Free play · chips have no value.