Level Editor¶
Status: Current
Last reviewed: 2026-07-07
Source: client/src/admin/main.ts, client/admin.html, server/src/game/admin.ts, server/src/game/zonestore.ts, shared/src/prefabs.ts, shared/src/world.ts
/admin is a separate editor app, not part of normal gameplay. It is a free-fly scene editor with an object palette, transform tools, a property inspector, zone save/load, and playtest tools. Everything it changes is owned by the server.
Overview¶
The editor connects as a client, sends admin_login, and then issues admin_cmd messages. The server applies each command to the live ZoneData and broadcasts zone_object_update, so any connected game clients see the edit immediately. The editor renders every object individually (no instancing) so each one is pickable, and draws its own gizmos for invisible marker objects.
Editor Controls¶
| Action | Control |
|---|---|
| Fly | W A S D, Q/E down/up, hold Shift for fast |
| Look | Right mouse drag |
| Place | Select a palette prefab, left-click the ground (Shift to place several) |
| Select | Left-click an object |
| Move | Drag a selected object across the ground |
| Rotate | [ / ] |
| Scale | + / - |
| Duplicate | Cmd/Ctrl + D |
| Delete | Delete / Backspace |
| Undo | Cmd/Ctrl + Z |
| Cancel / deselect | Esc |
Grid snap (0.5 m) and rotation snap (15°) are toggles in the toolbar. Undo is a stack of inverse commands (create is undone by delete, and so on).
Object Palette¶
Prefabs are grouped by category, straight from the shared registry: buildings, village props, targets, lights, trees, nature, rocks, camp props, ember, NPCs, mobs, spawns, quest, loot, resources, volumes, audio, and portals. This covers houses, the smithy, bank, healer hut, training dummies, archery and magic targets, weapon racks, fences, crates, barrels, carts, wood piles, wells, banners, fire pits, torches and lanterns, trees, stumps, logs, bushes, mushrooms, rocks and boulders, goblin tents, cook pots, cages, trophy poles, bone piles, barricades, stakes, the ruined arch, the ember relay shrine, the cave gate, and every marker.
View Toggles¶
Toolbar checkboxes overlay editor-only debug geometry: colliders, marker gizmos, the safe-zone boundary, mob aggro/leash radii, and quest-trigger volumes.
Property Inspector¶
Selecting an object shows its editable properties: id (read-only), prefab, position X/Z, height offset, yaw in degrees, scale, collider kind, and a free-form JSON props block. The props block is where gameplay data lives, depending on prefab: npcId, npcName, role, dialogue, questIds for NPCs; variant, respawnSeconds for mob spawns; triggerId, radius for quest triggers; radius for safe zones; nodeType for resource nodes; targetZone for portals; plus free fields like faction and notes. Apply commits the change through an update_object command.
Save and Load¶
| Button | Effect |
|---|---|
| Save Zone | Validates and writes server/data/zones/<id>.json, keeping a timestamped .bak.json of the prior version. |
| Reload From Disk | Re-reads the file, discarding unsaved edits. |
| Export JSON | Downloads the current in-memory zone. |
| Import JSON | Loads a zone file into the live session (save to persist). |
Validation (validateZoneData) checks the schema version, required fields, unique object ids, known prefabs, and finite positions before any write.
Zone Schema¶
A ZoneData document (schema version 2):
| Field | Meaning |
|---|---|
schemaVersion |
Bumped when the shape changes; old files are backed up and regenerated. |
zoneId, zoneName |
Identity. |
worldSize |
Terrain extent in metres. |
terrain |
{ generator, seed }; the generator is a key into TERRAIN_GENERATORS. |
environment |
Sky/fog colours and distances, sun and hemisphere light settings. |
spawnPoint |
Player spawn { x, z, yaw } (also overridable by a player_spawn object). |
villageCenter, campCenter, campLeashRadius |
Landmarks used by generation and AI. |
objects |
The array of ZoneObjectInstance (see below). |
nav |
Reserved for baked navigation/collision metadata. |
Colliders, NPC definitions, mob spawns, quest triggers, target props, the safe zone, and the ember relays are all derived from objects by buildZoneRuntime. The editor never edits those derived lists directly; it edits the objects that produce them.
Prefab Schema¶
A PrefabDef in shared/src/prefabs.ts:
| Field | Meaning |
|---|---|
id, name, category |
Identity and palette grouping. |
collider |
null, { kind: 'circle', r }, or { kind: 'aabb', hx, hz } at scale 1. |
blockHeight |
Height used for projectile blocking; 0 lets shots pass over. |
instanced |
Client may batch these into one InstancedMesh. |
marker |
Gameplay-only object, invisible in play, shown as a gizmo in the editor. |
emberRelay |
Marks the relay prefab. |
defaults |
Starting props for new instances. |
Security¶
Auth lives in checkAdminToken:
- If
ADMIN_TOKENis set on the server, the login token must match it. - If
ADMIN_TOKENis not set andNODE_ENVisproduction, admin is refused entirely. - Otherwise (local dev, no token set), connections from
127.0.0.1/::1are accepted with any token.
Every admin_cmd is gated on conn.isAdmin; a normal player socket that sends one gets an error, not an action.
This is a stopgap
A single shared token is not real access control. The clear TODO is to put the editor behind proper account authentication and role checks once an account service exists. Do not expose /admin on a public build without at least an ADMIN_TOKEN.
Playtest Tools¶
| Tool | Command | Effect |
|---|---|---|
| God Mode | god_mode |
The admin player takes no damage. |
| Spawn Wave | spawn_wave |
Spawns a ring of goblins at the camp (no respawn). |
| Reset Camp | reset_camp |
Clears all goblins and respawns the marker-defined camp. |
| Reset Quests | reset_quests |
Clears the admin's own quest log. |
| Set Ember | set_ember |
Slider drives Ember pressure directly to any value. |
| Teleport | teleport |
Fly-to shortcuts to the village, the camp, or the selected object. |
| Spawn / Despawn | spawn_mob / despawn_entity |
Place or remove a single mob by id. |
The performance and network overlays are in the game client (toggle in settings or press F3), not the editor.