
Traditional Web3 games suffer from slow, expensive gameplay and bot-drained liquidity pools because every player action is tied to on-chain logic and wallet signatures.
- Decoupled Architecture: Run all game logic on a zero-dependency vanilla JavaScript frontend, using the blockchain solely as a dumb payout terminal.
- Dual-Currency Economy: Isolate daily in-game actions from real-world value, minting reward tokens only for deduplicated, one-time milestones.
- Server-Authoritative Settlement: Use a strict Node and PostgreSQL backend to verify off-chain ledgers and sign Solana transactions securely.
Relying on pure vanilla JavaScript IIFEs and global window state creates massive technical debt, and the server architecture only mitigates bot extraction rates rather than preventing automation entirely.
Script
The classic play-to-earn web game trap is a problem of trust and execution. You build a game, put the application logic on-chain, and within a week, bot farms have drained your liquidity pool. Every player action requires a wallet signature. The game functions more like a slow, expensive spreadsheet than an interactive experience.
The standard industry response is to write increasingly complex smart contracts to try and outsmart the farmers. Plumtown, a new open-source browser-based life simulation game, takes the exact opposite approach. They stripped the blockchain out of the gameplay entirely. The engineering here relies on a strict architectural split. The game features a massive, zero-dependency vanilla JavaScript frontend that handles all the simulation logic, paired with a highly protective server-authoritative Web2 backend. The blockchain is not the game engine. It is treated strictly as a dumb payout terminal.
A Dual-Currency Economy
The first question this architecture raises is how you actually prevent automated farmers from draining the wallet if the game logic is handled off-chain. The answer is a dual-currency economy that structurally isolates the gameplay from the real-world value.
Inside the game loop, players earn a currency called Plumbucks. This is standard virtual money. You earn it from completing virtual jobs, where a daily salary scales based on your character's career level and performance. You spend it in the build mode to place pixel-art furniture and upgrade your home. Plumbucks have zero external value. They cannot be withdrawn. They are completely trapped inside the simulation.
The reward coin, PLUM, is handled on a completely separate track. PLUM is only minted when a player achieves specific, deduplicated life milestones. Purchasing a first home, maxing out a skill tree, or completing a long-term lifetime aspiration. These are one-time events per character.
Because the two currencies never intersect, the system neutralizes basic farming scripts. A script that runs a character on a treadmill for ten hours just generates useless Plumbucks. The farmer cannot extract liquidity from the daily grind.
Managing a Server-Authoritative Architecture
This brings up the challenge of managing a server-authoritative architecture. What does that actually look like for a complex browser game where the client is completely untrusted?
The core engineering rule in this project is that the browser is never allowed to dictate reward amounts. The client can only report state changes. The Node HTTP API, backed by a PostgreSQL database in production, acts as a strict, unforgiving ledger.
When a player hits one of those life milestones, the client sends a network request. The server intercepts that request, queries the database to verify if that specific account actually meets the prerequisites for the milestone, checks the cooldown timers, and only then updates the off-chain ledger. A tampered client mints nothing the server does not recognize.
The actual Solana payout is entirely decoupled from the gameplay. When a player decides to cash out, they connect a Phantom wallet. The client uses the Solana web3.js library to request a free cryptographic signature, proving the player actually controls the wallet address. The client sends that signature to the backend. The Node server uses the tweetnacl library to verify the signature. If it passes, the server checks the player's off-chain ledger balance.
Only then does the server access the treasury private key—which is stored exclusively as an environment variable on the backend machine—to construct, sign, and broadcast the transaction to the Solana mainnet. The browser never touches the private key. It just asks the server for money, and the server decides if the request is valid.
A Reality Check on Botting
The project documentation makes a bold claim regarding this setup. It states that because the server caps rewards, the game cannot be botted or farmed. That claim does not hold up to mechanical scrutiny.
Any vanilla JavaScript client communicating over a standard Node HTTP API is trivially bottable. A python script can fire the exact same JSON payloads to the milestone endpoints as the browser game. The backend has no definitive way to prove the HTTP request was generated by human mouse clicks rather than a cURL command.
What the architecture actually provides is damage control. The server enforces hard daily earn caps, minimum withdrawal thresholds, and strict daily withdrawal limits. It does not stop the bots from playing. It simply mathematically limits their extraction rate, giving the operator time to identify anomalies before the treasury is emptied.
Furthermore, the project markets itself as non-custodial. While the final payout does go directly to a self-custodied Phantom wallet, the operator holds all pending reward credits on an off-chain ledger. The server is acting as the custodian of your earnings right up until the exact moment of withdrawal.
The Zero-Dependency Frontend
On the frontend, the project makes a very specific technical flex. Zero dependencies. No modern framework, no bundler, no build step. The entire interface is built with pure HTML5, CSS3, and vanilla JavaScript, served by a tiny Node static server.
State Management with IIFEs
This raises the obvious question of how they handle state, save migrations, and complex features like grid-based breadth-first-search pathfinding without a framework managing the render loop.
They do it using Modular Immediately Invoked Function Expressions, or IIFEs. The entire game engine attaches itself to a single global object on the window, called window.LifeSim. Every isolated system inside the game—the pathfinding algorithm, the needs decay, the economy, the emotions—is wrapped in an IIFE.
When the browser parses the script, the IIFE fires immediately. It establishes its own private scope for local variables, keeping them out of the global namespace, and then explicitly returns only the necessary public methods, binding them to window.LifeSim.
When you issue a command for a character to walk to a piece of furniture, the pathfinding module reads the grid coordinates from the global state and executes a breadth-first-search algorithm. It calculates the shortest path tile-by-tile. If the player opens the build mode and places a wall over that path mid-walk, the engine detects the collision, halts the movement, and recalculates the array on the fly. Doing this without a physics engine means reading directly from a multi-dimensional array stored on the window object.
Client-Side Save Migrations
When you rely purely on vanilla JavaScript and local storage, updating the game logic usually breaks old save files. They solve this with sequential client-side state migrations. When the script loads a save file from local storage, it checks a version integer. If the save file is version one, and the current engine is on version three, the file is passed through a pipeline of transformation functions.
It runs the version one to two migration, injecting new default variables for the updated relationship system. Then it runs the version two to three migration, appending the new skill trees. Only after the payload matches the current schema is it mounted to the global window state.
They backed this entire engine with one hundred and ten functional assertions in their test suite, covering the pathfinding, the economy, and the migration pipeline.
The Trade-offs of a Framework-Free Approach
It is an impressive display of raw browser capability, but the cognitive load required to maintain it is immense. Managing complex game state, rendering loops, and grid algorithms through raw DOM manipulation creates serious technical debt.
There is extremely high architectural lock-in here. If you fork this project to add custom features, you are forced to couple your code tightly to their specific window.LifeSim global pattern. You cannot easily pull in external NPM packages.
For a team of developers trying to collaborate, managing global variables and raw HTML element updates is a fast track to scope pollution and merge conflicts. This is exactly why the industry defaults to tools like Phaser JS paired with standard Web3 SDKs. Dedicated libraries handle the physics, the render loop, and the asset management, allowing you to use modern bundlers that enforce strict component isolation.
The Takeaway: A Practical Web3 Model
What this architecture teaches us is a highly effective way to escape the classic Web3 trap. You do not need to put your application logic on the blockchain. By treating the network strictly as a final settlement layer, you get the speed and performance of a traditional web application combined with the financial utility of crypto.
It is a practical, working boilerplate for indie developers who want to launch a gamified experience quickly, without taking on the operational overhead of wiring up custom on-chain transaction logic for every user action.
You play in Web2. You settle in Web3.
This is TAKEYOURPILLS.TECH. Go ship something.
References
- playPlumtown/Plumtown - GitHub