Replying to @cryptochica_arg
Escuchando el space con el mejor swag de todos los tiempos @casiojapi @Parsley3D
Lo mejor de devconnect ocurrió el mismo día
Cliente multiverificador de zkvms en @eth_proofs
La rave de @MiladyLatAm
Me hicieron recordar a qué vine a ethereum
"Hope, it is the quintessential human delusion, simultaneously the source of your greatest strength, and your greatest weakness." - El arquitecto
Knots ya es el 13% del network y Core solo sigue hacia abajo
Bitcoin es increíble, solo ahí pueden pasar estas cosas
If you find bugs or have feedback, send me a DM.
And if you’re interested in what we’re building, follow @realtimeplayxyz
On the client side, the website runs in the player’s browser. Here I implemented several architecture and UX techniques.
1. Wallet in Local Storage
From the begining it was clear to me that I shouldn’t use wallet confirmations like most dApps do. Inspired by autonomous worlds (e.g. @DFArchon), I decided to use only wallets stored in the browser’s local storage. That way there’s no need to click the wallet confirmation button.
2. Parallelization
In early versions, I waited and blocked the user from starting a new game until the previous one finished. That might make sense in other contexts, but my goal is to minimize latency between games. That’s why I implemented what I call Optimistic Rendering: as soon as I know the outcome, I display it on screen and let the player start a new game.
3. Web2 Game Architecture
Before getting into Web3, I worked professionally on Web2 game development. Gradually I realized that designing a real time Ethereum app is very similar to making a video game. Here are the techniques I used.
3.a Full gameLoop() implementation
Usually, for a Web frontend or mobile native app, we rely on listeners and callbacks: code blocks triggered by user actions. Games, including my simple War game, are too chaotic for that. So I put all the game logic into a single function that runs at fixed intervals. We call this function the gameLoop(), and each run a tick.
3.b Action buffer
JavaScript in the browser uses multithreading models that fit listeners and callbacks, but they’re terribly bad for games. At first I resisted using a classic input buffer and tried thread locks and validations instead. My code became a mess. Only when I stopped reacting immediately to user actions did things click. Now I maintain a queue of inputs waiting to be executed, not ideal, but makes the code much more readable and less error-prone.
3.c Single RPC call per tick
Each call can take up to 300 ms—that’s too long. So one rule: always make exactly one RPC call per tick. And yes, that includes reading the nonce and current gas price. You must avoid abstractions offered by Web3.js, Ethers, or Viem when building your txs. To achieve this I used a kind of Call Singleton structure: a Solidity function that returns all necessary information each tick. You can also achieve the same with Multicall.
I have a lot more to detail on this topic, so I hope to write a more technical, in-depth blog post soon.
Let's continue…
The backend manages an Ethereum private key on a server I control. Keep in mind:
1. It must serve multiple players at once. To do this, use WSS instead of hammering the RPC with polling, we index transactions in a traditional BDD or in memory, and batch txs with Multicall.
2. It must resist economic attacks with smart contract mechanics that allow slashing players who try to cheat (they can’t).
3. Classic Web2 security. Firewalls, uptime guarantees, or even a multi-server model.
I used the Commit-Reveal pattern.
With 3 on-chain transactions, you can generate a random number in an adversarial game: player vs house.
My friend and I created a version of War: the player and the house each draw a card, and the highest card wins. The challenge is generating 100% on-chain random numbers with the lowest latency possible.
En cuanto al cliente, es un website que corre en el browser del jugador. Aquí implementé diferentes técnicas de arquitectura y UX.
1. Wallet en local storage
Desde el inicio fue bastante evidente para mí que no debía usar confirmaciones de de la wallet como lo hacen la mayoría de dApps. Es por eso que, inspirado en los mundos autónomos (e.g. @DFArchon) . Decidí usar únicamente wallets guardadas en el local storage del browser. De esta manera no hay necesidad de dar click al botón de confirmación de la wallet.
2. Paralelización
En las primeras versiones, esperaba y bloqueaba al usuario de iniciar un nuevo juego hasta que el anterior terminara. Esto pareceriera tener sentido, pero no en mi caso porque yo busco reducir al menor tiempo posible la latencia entre cada juego. Es por eso que implementé algo que llamo Optimistic Rendering, donde, desde el momento que ya conozco el desenlace del juego lo muestro en pantalla y permito al jugador iniciar un nuevo juego.
3. Arquitectura de Juego Web2
Antes de iniciar en Web3, me dedicaba profesionalmente a desarrollo de videojuegos web2. Poco a poco me fui dando cuenta que el diseño de una aplicación de tiempo real en Ethereum se parece mucho al desarrollo de un videojuego. Aquí las técnicas que utilicé.
2.a Implementación total de un gameLoop()
Usualmente, cuando hacemos un frontend Web o una aplicación nativa movil, estamos acostumbrados a operar bajo la arquitectura de listeners y callbacks. Pero los videojuegos son demasiado interactivos y caóticos para esto. Es por eso coloqué absolutamente toda la logica del juego en una sola función que ejecuto cada cierta cantidad de tiempo. A esta funcion le llamamos el gameLoop() y a cada ejeción un tick.
3.b Buffer de acciones
Javascript en el browser funciona de manera multithread. Esto es ideal para los modelos clásicos de listeners y callbacks, pero es pésimo para juegos. Es por eso que al inicio me resisití a usar un buffer clásico. En vez, intenté aplicar los clásicos thread locks y validaciones. Hasta que vi mi código y era un desastre. Fue hasta dejé de reaccionar de manera inmediata a las acciones del usuario que todo tomó mayor sentido. Sí, ahora mantengo una lista de inputs esperando ser ejecutados, pero el código es mucho más legible y mucho menos propenso a fallos.
3.c Una sola RPC call por tick
Cada llamada puede llegar a tardar hasta 300ms, es demasiado tiempo. Por eso una regla, sí o sí es hacer una llamada única RPC por tick. Y sí, esto incluye la lectura del noce y el precio del gas actual. Así que hay que tener cuidado y no usar abstracciones de Web3.js, Ethers o viem para construir nuestras txs. Para lograr esto usé como un especie de Call Singleton, o sea una función en Solidity que devuelve absolutamente toda la información necesaria en cada tick. Alternativamente se puede lograr lo mismo con Multicall.
Tengo mucho por detallar en este tema, así que espero pronto escribir un blog post más técnico y a detalle.
Continuamos..













