
Kamba Adeema
කඹ ඇදීම - the tug of war from the Sinhala and Tamil New Year (Avurudu) festivities, turned into a simple 3D betting game played in front of a crowd of two hundred.
- Role
- Genre
- Return
- 97.09% (closed form)
- Stack
- Scale
- Fairness
- Block seed, sealed per match
01The Game
Kamba Adeema is the tug of war that usually wraps up a Sri Lankan Avurudu sports day, rebuilt as a betting game. Two halves of a village, Uda Pila and Yati Pila, put four people each on one rope, with around two hundred spectators watching.
On every heave, each of the eight pullers either grips or slips, like eight separate fair coin tosses. The difference between the two sides settles the bet and drags the rope that many marks. Traditional games don’t come with a paytable, so most of the work was figuring out the one honest question you can bet on, and deciding what to leave out.
One honest question
Which side wins this heave? It’s settled in about four seconds, and it’s exactly what anyone watching a tug of war is already wondering.
A separable contest
Stamina, collapsing and the lime lines turn it into a real contest, but none of them are allowed to affect the price.
Put the village in
Without people, the field is just a physics demo. Two hundred spectators, flags and parasols only cost five draw calls.
02Screenshots
These screenshots were captured straight from the game. Tap any of them to see it bigger.





03Core Loop
- 1
Take the rope
Four pullers on each side line up on the generated field.
- 2
Back a side
Pick Uda or Yati and set your bet on a plank with a minus and a plus button.
- 3
Heave
Eight fair coin tosses decide whether each puller grips or slips.
- 4
Haul
The difference settles the bet and drags the rope. If one side is exhausted and collapses, the rope moves twice as far.
- 5
The line
The match ends when one side is dragged over the line, and then the seed is revealed.
04Key Features
A rope that owns the contest
The characters check the rope’s position every frame. Their hands are placed onto its moving curve, and their bodies lean based on how tight it is.
Stamina & collapse
Gripping uses up stamina. If a tired side gets pulled two marks or more, it’s dragged twice as far, which makes real comebacks possible.
An instanced crowd
Five instanced meshes draw around 200 spectators, along with their arms, flags and parasols, on a terraced bank and two corner stands.
Village-fair look
Sun-faded gold, terracotta for Uda, lime green for Yati, hand-painted signs and a carved medallion for the title. The whole thing is meant to feel like a village fair.
DOM mirror
The 3D interface has a hidden HTML copy underneath, with focusable buttons that have ARIA labels, real slider inputs and spoken commentary.
A draw refunds
A four-all heave doesn’t belong to either side, so you get your stake back, the same way a draw works in any draw-no-bet market.
05The Maths
The swing is Uda grips minus Yati grips, a number between −4 and +4. Using Vandermonde’s identity, this simplifies to a shifted Binomial(8, ½), giving 1, 8, 28, 56, 70, 56, 28, 8 and 1 ways out of 256.
The one market
Yati win it 93/256 of the time (36.33%), it’s four all 70/256 of the time (27.34%), and Uda win it 93/256 of the time. Both sides pay 1.92×, and a draw gives your stake back.
Fair is exactly evens
The fair price is (1 − 70/256) / (93/256) = 2.00×, because both sides are the same. The return is 1.92 × 93/256 + 70/256 = 97.09375%.
Stamina never touches the coin
A collapse doubles how far the rope moves, but the bet is always settled on the swing. check() makes sure the probabilities add up to 1, that each outcome is counted only once and that both sides are priced the same.
Measured, not asserted
I wrote a faithful copy of the game logic and ran 6,000,000 heaves and 500,000 matches through it. The swing matched Binomial(8, ½) to within 0.045 percentage points, and the measured return came out between 97.08% and 97.11%.
A match-wide seal
One block value seeds a whole match of about 18 heaves. It’s locked in when the match starts and revealed when one side crosses the line.
06Under the Hood
React ↔ three.js contract
React looks after the wallet, the bet, the match and the current phase. The 3D world gets a read-only WorldView and sends back Intents, which means the 3,500-line scene and the 1,500-line phase machine can change independently.
Wait on the rope, not a clock
The haul phase waits until the rope has finished moving (ropeBusy), so a one-mark nudge and a four-mark collapse each take as long as they look like they should.
Generated pitch
Player positions, rope length, mark size and the lines are all worked out from a few constants, so the field, the rules and the characters always stay in sync.
Server seed route
The RNG token stays on the server, and the endpoint always responds with a 200, so if the upstream service is slow to wake up the browser never sees an error.
07Challenges & Solutions
08Results & Takeaways
- A finished 3D game based on a traditional Sri Lankan sport, with characters that grip the rope properly, an instanced crowd and a full accessibility layer.
- Every match is locked to a block value from the first heave until one side crosses the line.
- Next up: publishing a hash of the seed in advance, settling bets on the server, and village-against-village multiplayer.
What I learned
- The best pricing work I did was cutting things. Fifteen correctly priced squares were worse than one.
- How easy a bet is to understand matters as much as the maths behind it.
- Check by measuring. Running a faithful copy over millions of heaves catches things that reasoning alone misses.