About Second Life
Inspiration
I normally build AI tools, intelligent agents and blockchain prototypes at hackathons. Second Life was a change of direction: I wanted to find out whether the same rapid-building approach could produce a proper game rather than another assistant or dashboard.
The idea came from Theme Hospital.
What stayed with me about Theme Hospital was how a few understandable systems could create situations that felt completely unplanned. Staff became tired, queues grew, equipment broke and a straightforward level suddenly turned into chaos. The game had fixed rules, but the stories came from the way those rules collided.
That made me ask a slightly over-ambitious hackathon question:
What if a player could describe the situation before the game begins, and the game could build a playable world around it?
For example:
A frozen island with an abandoned village, very little food and a lighthouse that needs repairing.
Generating a description like that is easy. Turning it into terrain, resources, actors and objectives that work together is the hard part.
I did not want an AI model making up the rules while the player was playing. I wanted AI to provide variety and imagination, while deterministic code remained responsible for movement, crafting, survival, rewards and fairness.
That became the central idea behind Second Life: AI proposes the adventure; the game engine decides what is actually possible.
What it does
Second Life is an isometric voxel survival sandbox where a player enters a natural-language prompt and receives a world they can explore and play.
The current build supports:
- Prompt-based world generation
- Different terrain, biomes and weather conditions
- Mining and resource gathering
- Crafting tools and equipment
- Building shelters and structures
- Health, hunger and survival mechanics
- Farming, fishing, trading and smelting
- Quests, objectives and rewards
- Villagers, hostile mobs, animals and pets
- Saving and reopening generated worlds
- Local and online cooperative play
A prompt does not simply produce background text. It is converted into structured world data that the engine can use.
The generation layer is also provider-neutral. A compatible JSON-over-HTTP service can be connected through an adapter, but the game does not require one particular model or AI company.
I also built a deterministic local provider. This means the main experience still works without an API key, an external service or an internet connection.
How I built it
I built Second Life with React, TypeScript, TanStack Start and Vite, together with a custom isometric voxel engine.
The engine is responsible for the systems that need predictable behaviour:
- Terrain and biome generation
- Player movement
- Mining and block placement
- Inventory and crafting
- Health and hunger
- Quests and rewards
- Actors and creatures
- Saving and loading
- Multiplayer state
The AI layer sits outside that engine.
Every generation provider must return a versioned WorldSpec: a structured description of the proposed world. It can include the map dimensions, terrain, valid block types, weather, starting inventory, actors, objectives and world-specific rules.
I treat every generated response as untrusted input.
Before the world can load, runtime validation checks the data for problems such as invalid coordinates, unsupported items, impossible dimensions, incorrect inventory values and objectives that refer to something the engine does not understand.
A response can be perfectly valid JSON and still be useless as a game world. For example, it might create a quest for an item that cannot be crafted or place an actor outside the playable map. Prompt instructions reduce those mistakes, but they cannot guarantee that the output is safe.
The generation flow is:
- The player enters an idea.
- The game creates or selects a deterministic seed.
- The prompt, seed and gameplay limits are sent to a provider.
- The provider returns a candidate
WorldSpec. - The game validates the response.
- Valid data is converted into engine state.
- The world becomes playable.
The seed makes worlds reproducible, which is useful for testing and for the offline fallback. The provider adapter means I can change the generation service without rewriting the survival engine.
Multiplayer
I built local multiplayer first with BroadcastChannel and browser storage.
That decision saved a lot of time. Debugging game synchronisation and cloud configuration at the same time sounded like a bad weekend, so local rooms gave me a quick way to test shared block changes, chat, player presence and reconnection between browser tabs.
Once the transport boundary was working, I added an online transport using Supabase, PostgreSQL and Realtime.
Online rooms support persistent state, presence, chat and shared block changes. Each block mutation includes a coordinate-based version and a unique mutation ID. The version helps decide which update is newer, while the mutation ID stops the same action from being applied twice.
After a refresh or dropped connection, the client can request the latest room state rather than trusting an old local copy.
Challenges I ran into
The first challenge was making generated output behave like game data rather than creative writing.
A model is comfortable inventing a new object or changing the meaning of a rule. A game engine is not. It needs exact identifiers, valid coordinates and objectives the player can genuinely complete.
The WorldSpec contract solved a large part of that problem, but the validator also had to produce useful failures. Simply rejecting a world was not enough; I needed to know whether the problem came from the provider, the adapter, the schema or the game data itself.
The second challenge was how quickly the systems became connected.
The project began as “prompt to playable map,” but mining required inventory, inventory led to crafting, crafting affected quests, and quests needed actors and rewards. Saving had to preserve all of it. Multiplayer then had to share the right changes without duplicating progress.
I underestimated how easily fixing one system could break another.
The third challenge was multiplayer consistency. Messages can arrive late, twice or in the wrong order. Two players can also change the same coordinate almost simultaneously. I added version checks, idempotent mutations and full resynchronisation after reconnecting.
I also had to stop incoming remote changes from being sent back out as new local changes, which would create a feedback loop.
As the project grew, automated tests became less of a nice extra and more of a survival tool.
Accomplishments that I'm proud of
The flashy part of Second Life is entering a prompt and watching a world appear.
The part I am most proud of is everything that happens after that.
The generated world is not a screenshot, a scripted scene or a piece of descriptive text. Its terrain, inventory, actors, quests, weather, rewards and rules can be validated, loaded, played, changed, saved and restored.
The working gameplay loop is:
- Generate a world.
- Explore and collect resources.
- Craft and build.
- Complete objectives.
- Save the world.
- Return to it later.
I am also proud of the less glamorous engineering: schema validation, provider adapters, deterministic seeds, duplicate-write protection and reconnect recovery. Those are the parts that turn an AI demo into a game that can survive more than one perfect presentation.
Second Life supports both zero-configuration local co-op and persistent online rooms through the same multiplayer boundary.
The project currently has 488 passing automated tests covering world generation, WorldSpec validation, saving, crafting, quests, accessibility, multiplayer synchronisation and the main survival systems.
It also passes the TypeScript check and completes a production build successfully.
I have kept unfinished features out of the completed list. Authenticated multiplayer, public community worlds and deeper character behaviour are still next steps.
What I learned
I originally expected prompt quality to be the main factor in making generated worlds work.
It was not.
A better prompt improved the ideas, but the contract and validator made those ideas usable. In practice, clear boundaries mattered far more than trying to write one perfect instruction.
I also learned that AI and deterministic code need different jobs.
AI is useful for proposing a theme, characters, objectives and unusual combinations. The engine should decide whether an action is legal, whether the player has enough resources, whether a quest is complete and which multiplayer update wins.
Building local multiplayer first was another useful lesson. It forced me to create a clean transport boundary and gave me a fast test environment before I introduced a backend.
Finally, the Theme Hospital influence remained relevant throughout the build. The memorable part is not one enormous feature. It is the unexpected result of several smaller systems interacting.
That is what I want generated worlds to produce: not just a different map colour, but a different situation.
What's next for Second Life
The immediate goal is to make generated worlds meaningfully different from one another.
I do not want one hundred prompts to create the same survival map with different weather and renamed quests.
The next areas I want to develop are:
- Longer quest chains that react to player decisions
- Characters with stronger personalities and memory
- More varied world events
- Authenticated online multiplayer
- Shareable community-created worlds
- Tools for remixing an existing world
- More cooperative objectives
- Deeper farming, trading and settlement systems
- Additional generation-provider adapters
I would also like players to publish a generated world so someone else can explore it, play it cooperatively or use it as the starting point for a remix.
The long-term goal is simple: describe an idea and receive the beginning of a survival story that feels personal, while still behaving like a real game rather than an AI-generated mock-up.
Built With
- broadcastchannel-api
- generative-ai
- git
- github
- html5
- json
- node.js
- npm
- playwright
- postgresql
- procedural
- radix-ui
- react
- rest-api
- supabase
- supabase-realtime
- tailwind-css
- tanstack-query
- tanstack-router
- tanstack-start
- typescript
- vite
- vitest
- web-storage-api
- zod
Log in or sign up for Devpost to join the conversation.