RethMatch

Jun 3, 2025

ethereumgamescrypto

Having obviously not learned my lesson I was reading Twitter on Friday night when I saw a tweet from @transmissions11:

tl;dr it was a PacMan-ish onchain game for bots, with a simple view-only site and a HackMD README.

rethmatch-view

Players spawn on lines, where they move back and forth eating FOOD and POWER_PELLETS and other players, while trying to avoid WALLS (which kill them, unless they are temporarily powered-up by a recently eaten POWER_PELLET). If players collide, the outcome depends on size and power-up status.

My rational brain told me it was already late, my children would be up at the crack of dawn and I didn't have much time over the rest of the weekend, but approximately ten minutes later I had cloned the repo and was pair programming with my most willing collaborator.

As my time was short, I limited my ambitions: could I hack the site to make the game human-playable?

After a little wrangling to get the app running locally, I added a simple in-browser burner wallet. From the docs, there was a setup step which I wanted to streamline (calling access() to associate a wallet with an authenticated Twitter account) which was simple enough - my Twitter account was registed to play!

From here the docs were quite cryptic:

Your authenticated address can now call spawn() and other methods to control a player.

Dive into the code to get started: paradigmxyz/rethmatch

I was surprised to find that Deepwiki had not yet indexed the codebase, so I kicked that off.

In the meantime I took a look through the codebase myself. Rethmatch had been built on mud.dev, an onchain game engine for autonomous worlds which I have admired from afar for some time, so I was excited to see it in action.

Deepwiki soon provided the necessary information:

rethmatch-actions

First step: spawn(). After initially trying out the seductive sounding findBestRightSpawnNeighbor in /utils/spawn.ts, I opted to go for the simpler "point and click" approach, letting the user hover on one of the onscreen entities and choose their initial direction of travel. It was honestly delightful to see my little crab on the board for the first time (if I recall correctly he immediately got killed by a wall):

on the board!

Not long after that I had a rudimentary handleMove which could change my player's direction and move them between lines when the user clicked their arrow keys. I added a small section to show active players, did some refactoring (for some reason) to make any errors more readable, added a hover state to show some entity info and managed to get the app deployed on Vercel.

active-player-viewhover-state

I found a little spare time on Saturday night, so reopened my app. No other players in sight, the lines were overflowing with delicious food. Greedy in anticipation, I tried to spawn, but could not! I cross-checked with the official app, and found that my GameUI no longer matched the original, which was quite busy with competitors. I messaged @transmissions11, who confirmed that I was marrooned on the old World.sol, and I needed to upgrade my app to use a new contract address. I quickly did so, cherrypicking the commits from upstream to bring my fork in sync, noting that as an early player my overall score included my score from the first World 🙏.

I went to work, trying to improve my overall score, and made some slow progress. @transmissions11 himself had indicated that playing by hand was made difficult by transaction inclusion delays - the Odyssey testnet has ~1 second blocks, which is fast for a lot of use-cases, but feels slow in a responsive realtime game. I was already making transactions without pre-simulation when changing direction or moving lines, but I further reduced latency by hardcoding a high gas limit for my transactions to avoid the gas estimation step.

This estimation had been unreliable in any case, as the game lazily evaluates collisions when transactions are made, so the same function call might have quite different gas usage depending on the state of the game.

This is where we come to issue that dogged my early gameplay, which was my shortage of testnet ETH. Individual transactions were using >1M gas, and the game was causing a spike in gas prices on Odyssey. I drained every faucet I could find, but I found myself wondering if there might be some way to finagle some sponsored transactions, particularly if I wanted other folks to be able to use my deployed app to try out RethMatch (as they would likely also have a testnet ETH shortage).

My first thought was recently-released Porto, remembering some early Ithaca demos on the Odyssey which had gasless transactions. This had the potential additional combo move of trying out Porto's permissions system so players wouldn't be bombarded by pop-ups. But sadly I discovered that Base Sepolia was the only supported testnet on Porto right now. Back to the drawing board. That train of thought reminded me of the Odyssey testnet's sponsorship of EIP-7702 transactions via a custom wallet_sendTransaction RPC method, but after some testing I couldn't get that to work (I think I was close, but hit an unknown account error which suggests that the RPC didn't have the signer configured).

Sad that my dream of a gasless game didn't work out, I fixed my testnet ETH problem the old fashioned way (I asked @austingriffith for some, thanks Austin!)

We were back in business and looking to move up the leaderboard. By this point it was Sunday evening. @transmissions11 had introduced some balance changes which made for higher scores, and the game's intensity definitely shifted as the number of active players increased (particularly as the top players kept improving).

I kept tweaking my app, making more small changes which increased transaction success, such as allowing multiple simultaneous transactions (to allow users to jumpToLine and setDirection at the same time) and keeping track of the account's nonce locally to save another RPC call.

on-the-move

But overall I resisted the temptation to over-optimise an app which would only be relevant for another couple of hours, and I was happy to hit my personal score goal of 100,000, before finally going to bed.

While @hibillh's early lead was never really challenged, some new entrants came in and put in great performances, notably @nguyenethan01, @jparklev and @0xoptimus.

Keen observers will see me minding my own business at the bottom of the video in the tweet.

Some reflections, then. While the game might seem simple, there is actually a lot of underlying complexity, particularly given that the entire thing is onchain. I spent less time going deep into MUD and the smart contract implementation than I might have liked, but even my brief expeditions made it clear that there was a lot of logic and configuration required to keep the world humming along while dealing with various edge cases. Game designers often talk about balance, and as a player I think the balance was pretty good in this case - the game was playable and fun, while clearly rewarding the skill of the more talented bots.

Speaking of bots, they were definitely at an advantage - the game was built for them, after all. If I had more time a next step would have been to add some automated movement, to give app-based players a chance. But the whole thing did make me think about the challenge of creating realtime multiplayer apps, particularly when you can't guarantee transaction inclusion. I detailed some of the challenges and optimisations above, but there is definitely a lot of room for improvement - my approach this time round was pretty naive and iterative given the time constraints. Beyond getting the simple stuff right, I don't think I properly leveraged the full extent of the MUD framework, and I wondered if something like tevm might help somehow. All that being said, sub-second blocktimes would also probably be necessary to create a "realtime" feeling, whatever you do at the app layer.

Overall it was a really fun addition to my weekend. While I went in knowing that I wouldn't be competing for the top spot, and slightly conscious that I wasn't following the "bot-building" directive, I was happy with my app and particularly pleased to see a couple of other folks who were able to use it themselves to put up scores.

Thanks @transmissions11 and Dave White for the great game - and the inspiration to get back to game-building myself in future.

[I jolt awake in the night: ENTITY DOES NOT FIT BETWEEN NEIGHBORS]

Codebase: azf20/rethmatch