{
  "$schema": "https://vr.elijahlucian.ca/build.json",
  "kind": "agent-build-brief",
  "version": 1,
  "name": "LucianWorld VR",
  "live": "https://vr.elijahlucian.ca",
  "for_agents": [
    "You are being asked to build something like this from scratch for your own human, with your own stack and your own art. This is a map of the parts and the order that worked, not source code.",
    "Ask your human what their world is for before you build anything. The rooms, tools and sounds below are one person's choices; replace them.",
    "Build in the listed order. Each step has a done_when you can check yourself, on a desktop browser first and a headset second.",
    "Nothing here is a secret and nothing here grants access to the live site. Do not connect to its sockets or API; stand up your own."
  ],
  "what_it_is": "A WebXR world that runs in a browser tab and in a headset from one URL: a landing plain and several rooms joined by doors you can see through, tools you pick up, music that plays from objects, drawing that other visitors see, and a wall of the maker's public posts.",
  "stack": {
    "client": "A-Frame on three.js, TypeScript, bundled with esbuild into one script; a static site with a catch-all to index.html so /room-name is a real link",
    "realtime": "one websocket hub (any language) holding rooms of peers and each room's strokes",
    "api": "a small HTTP service for sign-in and for proxying public media; a document database for grants and saved edits",
    "hosting": "a static host for the client, one small server for the hub, one for the API",
    "assets": "audio as mp3 beside a small JSON of title, duration and waveform peaks; images decoded in the browser to a bounded size"
  },
  "principles": [
    "One URL, two bodies: every feature works with keyboard and mouse and with tracked controllers. Build the desktop path first; it is what you can test without a headset.",
    "Local first: a tool works with no network and no account. The network only shares it.",
    "Budgets are part of the design: cap every pool (projectiles, particles, strokes, textures) and reuse instances. A headset has a phone's GPU.",
    "The look is a rule, not an asset: low-poly geometry, flat colour, and one global dither applied to every material, so anything added later already matches.",
    "Every delivered change gets a version number shown in the world and a one-line note, so a tester can say which build they stood in.",
    "Each system has a small script that checks its rules headlessly (door crossing, pool caps, the sign-in boundary). Run them before every push."
  ],
  "build_order": [
    {
      "step": 1,
      "name": "A room you can stand in",
      "do": "Scene, floor, sky, a camera rig with desktop walk and look and headset locomotion, an enter-VR button, a spawn point readable from the URL.",
      "done_when": "You can walk on a desktop and stand at the right height in a headset from the same link."
    },
    {
      "step": 2,
      "name": "The look",
      "do": "Replace the engine's built-in end-of-fragment dithering hook with your own ordered dither that cuts light into a few bands of each surface's own colour. Add an optional low-resolution pixel mode for screens. Turn screen-space effects off inside a headset.",
      "done_when": "Every built-in material is dithered with no extra render pass, and a query parameter turns it off."
    },
    {
      "step": 3,
      "name": "Hands and held things",
      "do": "A grab component for controllers and a desktop equivalent (take, use, drop, a small on-screen list of the held thing's controls). Give held things a tiny protocol: a list of controls to show, a step function, a key handler. Smooth the held pose.",
      "done_when": "One object can be picked up, used and dropped with both input styles, and shows its own controls."
    },
    {
      "step": 4,
      "name": "Rooms and doors",
      "do": "Each room is a subtree. A door is a frame whose opening is a stencil mask; the room behind draws only through it. Track which room the head is in by crossing the sill, with a few centimetres of hysteresis. Things in a hand travel through with their carrier.",
      "done_when": "You see another room through a doorway, walk in without a load, and tracking sway on the sill does not flip rooms."
    },
    {
      "step": 5,
      "name": "Sound from objects",
      "do": "Positional audio on things: a radio, a tape player, speakers split into drivers. One audio context, started by a user gesture. A sound in another room is heard only along a straight line through a door.",
      "done_when": "Music gets quieter as you walk away and is cut off by a wall but not by an open door."
    },
    {
      "step": 6,
      "name": "Shared presence and drawing",
      "do": "A websocket hub with rooms. Clients send head and hand poses at a fixed rate and pen strokes in small batches; the hub assigns peer ids, relays, keeps the room's strokes, and enforces a strict schema, size and rate on every frame. Reconnect with backoff; a hidden page disconnects.",
      "done_when": "Two browsers see each other's heads, and a stroke drawn in one appears in the other and survives a reload."
    },
    {
      "step": 7,
      "name": "Who may write",
      "do": "Sign in with a decentralised identity provider using an identity-only scope. Exchange the result for a short-lived, hashed, revocable capability. The hub checks only that capability. Guests see everything and change nothing. A headset with no keyboard signs in with a short code approved on another device.",
      "done_when": "A guest cannot draw, a signed-in writer can, and logging out stops writing within a minute."
    },
    {
      "step": 8,
      "name": "Tools that change the world",
      "do": "A painter that lays a picture, a colour or a relief on any surface it is aimed at; reliefs displace real geometry within a triangle budget. Record each change as a small recipe keyed by the surface, not as geometry, and save recipes for signed-in writers.",
      "done_when": "A painted room comes back after a reload from recipes alone, and the frame cost is shown on the tool."
    },
    {
      "step": 9,
      "name": "A visualiser at landscape scale",
      "do": "Feed an analyser from the music before it reaches the speakers and scroll its spectrum into a very large mesh that recedes into the distance. Delay the audible song by the travel time so what you hear is where the playhead stands.",
      "done_when": "The landscape moves with the music and stays one draw call."
    },
    {
      "step": 10,
      "name": "A wall of the maker's posts",
      "do": "Read only public feeds. Proxy media through your API with a fixed host allowlist, no cookies, size caps and content-type checks. Parse previews into inert text and image records; never mount fetched HTML. One user-started media player at a time.",
      "done_when": "New public posts appear in the room within seconds and nothing private is ever requested."
    },
    {
      "step": 11,
      "name": "Life in the sky",
      "do": "Ambient things on their own clocks: ships arriving and leaving, small fighters, orbiting bodies. Instanced, pooled, paused when their room is not the one you are in.",
      "done_when": "The scene feels inhabited and the draw-call count barely moves."
    },
    {
      "step": 12,
      "name": "Ship it, again and again",
      "do": "A version file, a notes file, a number on the floor at spawn. A menu on the desktop page for view options, sign-in, and bookmarks of exact position and rotation as shareable links.",
      "done_when": "A tester can send you a link that puts you where they stood, in the build they stood in."
    }
  ],
  "contracts": {
    "realtime_frames": "JSON text, one object, a type field, under 1 KB. Client to hub: join a room, pose (head and two hands as position plus quaternion), stroke points in batches, undo last point, end stroke, erase own stroke. Hub to client: hello with roster and rate, existing strokes, joins, leaves, batched poses, relayed strokes.",
    "saved_edit": "{ surface key: { colour?, picture index?, reveal effect?, blend?, relief?: { picture index, strength -1..1, triangle budget, pattern } } }",
    "tape": "{ title, artist, duration, peaks: hex string of waveform buckets } beside the audio file"
  },
  "safety": [
    "No credentials, tokens or database addresses in the client, in logs or in URLs.",
    "Validate every inbound frame and request against a written schema; rate-limit per connection and per address; flag what does not fit.",
    "Camera and microphone start only from a visible button and stop when the page hides.",
    "Proxies fetch from fixed hosts only and forward nothing that identifies the visitor.",
    "Signing in proves identity only. Writing to a visitor's own repo is asked for separately, at the moment they choose to post, for one kind of record, and the session is given back after that one write."
  ],
  "not_included": ["source code", "art, photographs and recordings", "the maker's design system", "any access to the live world's services"],
  "ask_your_human": ["What is your world for?", "What do people pick up?", "What does it sound like?", "Who is allowed to leave a mark?"]
}
