3D worlds as graphs
August 2, 2026

Games are an art form that combine everything I love: Music, design, animation, programming, art, storytelling… It’s the ultimate medium, and its unfortunately inaccessible for most. This isn’t a skill issue, its a tooling issue.

In a world where AI is being used to automate away the creative process, I think it’s more important than ever to build instruments, not just tools.

This project started as an experiment to see how closely I could bridge the gap between intent and a tangible interactive world you can share and experience with others in realtime.

It became an agent-native, realtime-multiplayer 3D world engine built on Trellis, my local-first graph OS: a game is a graph, and the renderer, the systems, the network layer, and the AI all read from the same file. MIT-licensed at github.com/trentbrew/world-engine, with a live demo at trellis-sync-3d-playground.vercel.app.

The constraint that shaped it#

Traditionally, 3d multiplayer experiences are code-first. Write components, wire a network layer, drag scenes together in an editor. The focus is on how to get the thing to work.

Personally I love that part of the process, but I believe building games is the kind of creative work that should be accessible to everyone, not just programmers.

So I asked myself: what is the least lossy path from an idea to a shared world you can experience with others?

It was important to me that this be built for the web, and not locked behind an app store or some other walled garden. It works with or without an internet connection, and the same world file can be shared across devices.

Worlds as data#

A world is a graph. An entity is an @id with a bag of components. A component is a named bag of typed fields. A type is a reusable composition of components.

{
  "@id": "type:Enemy",
  "@type": "EntityType",
  "components": ["Transform", "Render", "Health"],
  "defaults": { "Render": { "color": "#c0392b" } }
}

JSON-LD is just the serialization; the model underneath is a graph, and every kind of state in a game is a node or an edge of it. Add a component to an entity and its view appears. No per-type components, no new .svelte files. You can define brand-new components and types inside the world file itself: the ontology, the renderer, the systems, and the network layer all read from the same graph.

That means the durable artifact is the world file, not a binary scene. A world can be diffed, versioned, and generated like any other data, and it lives in the same op-log as everything else in Trellis.

Syncing the graph#

Every field in the graph declares how it travels:

  • authored: durable, part of the rules. Written once, then static.
  • synchronized: streamed at ~20 Hz by the entity’s owner.
  • derived: a formula, never stored, never sent. Every client recomputes it.

That’s the whole model: authored, synchronized, derived. Enough to describe almost every piece of state in a game, and close to it for most other software. A Slack channel is the same shape: permissions are authored, presence is synchronized, notification counts are derived. Same rule, different budgets: a game derives on every frame, Slack derives on write.

Formulas are pure and deterministic, so every client agrees on the result without a byte over the wire:

"position": "=vec(cos(t * Orbit.speed) * Orbit.radius, 0.6, sin(t) * Orbit.radius)"
"grounded": "=Transform.position.y <= 0.55"
"tint":     "=Health.current > 25 ? 'ok' : 'hurt'"

The bandwidth argument is simple: sync the few authoritative inputs, derive the rest. The worked example is three cubes orbiting each other. An Orbit component and an Orbiter type, defined in data, animated entirely by one formula, zero engine code.

Multiplayer itself is peer-to-peer: each tab owns its Player, the host owns the rest of the world, entities spawn and despawn as tabs open and close. The feeling I’m after is what Maggie Appleton calls ambient co-presence: people quietly existing nearby, like strangers reading in the same cafe, sharing a space without demanding your attention. BroadcastChannel by default; a WebSocket relay for real cross-machine sessions. Behaviors are the one place code is required (a component plus a system that reads it each tick), and most of the time you don’t even need one. A derived formula field covers it.

The realtime story is an ownership story#

There is no server in the hot path. Every tab is a peer; transport is just a seam.

Authority is per-entity ownership, and ownership is just another edge in the graph:

Player A
   owns

 Entity 27

Entity 27
   streams

 realtime fields

Everyone else
   derives

 formulas

Your avatar is yours: you integrate its physics, you stream it. Everything else in the world belongs to the host, which isn’t a server, just the peer with the lowest id. When the host’s tab closes, the lowest remaining id inherits the world. No election, no ceremony, nobody to message.

Each owner publishes its entities’ realtime fields at ~20 Hz. Peers apply patches only from an entity’s rightful owner (state for something you don’t own is dropped on arrival), and they skip the owning behaviors. So a behavior runs exactly once per entity, not once per tab. Every message is authoritative or ignored, which is what keeps 20 Hz cheap.

Presence is the rest: join, heartbeat, timeout, leave. A peer that misses three heartbeats is forgotten, and everything it owned despawns with it. The room cleans up after itself.

And it all rides on the authored/synchronized split: the rules live in the op-log (diffable, versionable, the Trellis tier), while realtime state lives on the wire and never enters it. You get multiplayer where the persistent artifact is still just a graph, and the live state is something you never think about twice.

AI stops writing code and starts writing worlds#

Once the world is data, agents can act in it the same way a human does. There’s no special API to learn: an agent writes a field on an entity and the world changes: a door opens, an enemy turns. It spawns an entity and it exists for everyone. It can even own a peer slot and stream state like any other player.

That’s what agent-native means here. Not a plugin surface bolted on for bots; the same graph the renderer reads is the graph the agent writes. If you can read the world file, you can act in the world.

The last place this pays off is the one I didn’t expect: you don’t have to build the scene at all. The generative model is an agent writing to the graph too, just at a different cadence than a player. World Labs generates photoreal 3D spaces from a text prompt: Gaussian splats, not meshes, spaces you can actually walk through. The engine treats them as data like everything else. Drop the export in static/worldlabs/{slug}/, point a GaussianSplat component at it, and it’s in the world:

"GaussianSplat": { "src": "/worldlabs/townsquare/hall.spz" }

The splat is the look. The collider export from the same generation is the law: collider.glb gives the space physical presence, so walls stop you and stairs hold you. Sight and physics stay in sync because they come from the same prompt.

And because a splat is data, it plays by the same rules as everything else. The Frame Shift museum is one hall at two decades (1890 and 1960), and the era dial just rewrites GaussianSplat.src.

The Solarpunk town square isn’t behind a loading screen; it’s a statue you walk into, a RoomPortal carrying you to another generated world.

That’s the loop: describe a place, walk into it, invite someone in, change it with a line of JSON. AI stops generating code and starts generating worlds.

What shipped#

The whole engine is out: renderer, ontology loader, formula evaluator, the sync and ownership layers, the player controller and animation system. Native gamepad support is built in: each client’s pad binds by member slot, so the host takes the first controller and the next peer the second. MIT, fresh history, no vendored assets. Drop a world file in static/games/ and you have a game. github.com/trentbrew/world-engine.

Where it’s going#

Making games with GameMaker is how I learned to code, and I hope this can be a similar gateway drug. But I don’t think this idea stops at games. Games just happen to be the easiest place to see it.

Every application describes a world: users, permissions, documents, tasks, messages, products, conversations. The implementation changes, but the shape doesn’t. It’s always entities connected by relationships, interpreted by systems.

AI makes this more interesting, not less. Agents don’t just write code; they inhabit worlds. The better we describe those worlds as structured graphs instead of opaque programs, the easier they become to inspect, modify, reason about, and collaborate on.

That’s the direction I’m excited to keep exploring.

This project is still brewing…

A few features I’m itching to include on the engine roadmap:

  1. Animation tool for rigged models
  2. Generative 3d character creation
  3. Procedural terrain generation
  4. More AI integration (image generation, voice synthesis, etc.)
  5. Publishing platform for sharing cross platform
  6. Tauri mobile app for creating iOS and Android games