diff --git a/pages/concepts/entities.mdx b/pages/concepts/entities.mdx
index 4da93eb..b5e866a 100644
--- a/pages/concepts/entities.mdx
+++ b/pages/concepts/entities.mdx
@@ -1,8 +1,106 @@
+import { Callout } from 'nextra/components'
+
# Entities
-In Dreamlab, entities are any object in the world. They can move, have physics and colliders, render sprites, and more.
+In Dreamlab, an entity is a distinct object that can be created, updated, or destroyed.
+They can respond to events such as physics ticks or network packets and run logic in the game world.
+
+
+ You will most likely not want to work with Entities directly. Dreamlab has an
+ abstraction called Spawnable Entities that you should use to represent objects
+ in the world.
+
+
+## Spawnable Entities
+
+Spawnable Entities are an abstraction on top of Entities that have stricter requirements.
+They must have a position in the world, they must be able to be created and destroyed at runtime, and they must be able to be synced over the network.
+
+### Defining
+
+
+ The export `z` from `@dreamlab.gg/core/sdk` is actually a re-export of
+ [Zod](https://zod.dev). Refer to their documentation for more info on how to
+ define arguments.
+
+
+```ts filename="TypeScript"
+import { createSpawnableEntity } from '@dreamlab.gg/core'
+import { z } from '@dreamlab.gg/core/sdk'
+
+// Define the arguments for this entity
+const ArgsSchema = z.object({})
+
+const createExampleEntity = createSpawnableEntity(ArgsSchema, context => ({
+ // ... implement all required members
+}))
+```
+
+### Registering
+
+Spawnable Entities must be registered with the `game` instance in order for them to be created by name.
+This is most commonly done inside of a `sharedInit()` function, which is a convention that world scripts use to run initialization code on both client and server.
+
+
+ Refer to your world scripts for how `sharedInit()` relates to the
+ initialization of a world.
+
+
+Entity names registered with the game instance **must be unique**. To avoid collisions, you should namespace your entity names as shown below.
+Although we recommend namespacing using the `@project/entity` format, this is just a convention and you are free to solve uniqueness issues however you like.
+
+```ts filename="shared.ts"
+export const sharedInit = async game => {
+ // register your entity with the game
+ game.register('@example/example-entity', createExampleEntity)
+}
+```
+
+### Spawning
-The best way to learn the anatomy of an entity is an example. Below are some sample entities of varying complexity.
+{/* TODO: Write better copy */}
+
+#### required
+
+- `entity`
+- `args`
+- `transform.position`
+
+#### optional
+
+- `transform.rotation` / `0`
+- `transform.zIndex` / `0`
+- `uid` / `random cuid`
+- `label`
+- `tags` / `[]`
+
+```ts filename="server.ts"
+import type { InitServer } from '@dreamlab.gg/core/sdk'
+
+export const init: InitServer = game => {
+ // ... server-side initialization
+
+ await game.spawn({
+ // Reference the entity we registered by name
+ entity: '@example/example-entity',
+
+ // TODO
+ args: {},
+
+ // TODO
+ transform: {
+ position: [],
+ rotation: 0,
+ zIndex: 0,
+ },
+
+ // TODO
+ tags: [],
+ })
+}
+```
+
+---
## Example 1 - Bouncing Ball
@@ -102,19 +200,6 @@ export const createTestBall = createSpawnableEntity(
)
```
-### Registering Entities
-
-All entities need to be registered with the Dreamlab engine before they can be used. This is done in the `sharedInit` function that runs on both the client and the server.
-
-```ts filename="TypeScript"
-export const sharedInit = async game => {
- // register testBall
- game.register('testBall', createTestBall)
- // spawn the rest of our predefined level
- await game.spawnMany(...level)
-}
-```
-
### Spawning Entities
For this example, we want our bouncy ball to be synced between the client and server and also spawn over time.