Wish We Were Here - the guide for AI assistants. The plain-text original is llms.txt; the place itself is here.
# Wish We Were Here — for an agent

> A cosy 3D diorama sandbox: https://www.dngames.de/wishwewerehere/

Wish We Were Here is a browser toy: small hand-built 3D places to begin from, which you nudge into
something of your own and send to someone as a link. Nobody needs an account or an install — the person
opens the link and the place is there. A place travels inside the URL itself, so a link can be sent
anywhere and keeps working; a short link is the optional alternative, and it is the only thing here
that is stored on a server, encrypted, for 30 days.

This file is written for an *agent*: what the app is first, then how to drive it directly.


Everything runs in the visitor's browser. It is a static site — no backend, no accounts, no cookies,
no analytics — and a scene handed to someone travels in the URL fragment (`…/#s=…`), which a browser
never sends to a server.

You can help a person in three ways. Pick the one that needs least of you.

1. **Write the link yourself, in plain text.** No code, no encoding, nothing computed: every character
   is legal in a URL fragment as it stands, and this is the route to prefer.

       https://www.dngames.de/wishwewerehere/#txt=theme=summer_meadow&title=Blue+Hour&p=campfire@0,0,0&p=log_bench@1.2,0,1.6

   `theme=` is a world id from the list below; `p=` repeats, one object each, written `type@x,y,z` or
   `type@x,z` (then y is 0); `title=`, `note=` and `author=` are optional, with `+` or `%20` for
   spaces. Spaces are the only thing worth encoding, and a `+` will do.

   **A link carries a type and a position, and nothing else.** There is no room in `type@x,y,z` for a
   colour, a size or a shape, so a place built this way is built from the catalogue exactly as it
   stands. That is the right answer for most places, and the wrong one when the scene needs an object
   nobody has — which is what route 2 is for, and why it carries a whole scene rather than a list of
   names. A link can only carry what the app already knows.

   **The whole address is the link, and the `#` is part of it.** Hand it over as one unbroken line of
   text: the full `https://www.dngames.de/wishwewerehere/`, then `#txt=`, then the scene written exactly
   as the example above writes it. The `#` is what tells the browser that everything after it is the
   scene rather than a path on the server — without it the browser asks for a file called `txt=…`, which
   does not exist, and the person gets an error page instead of a place. Never give the fragment on its
   own for someone to paste behind a domain name, and do not offer it as a labelled link: a chat window
   is where addresses get wrapped, shortened or quietly cut, and this one does not survive that.

   Positions take the full `x`, `y` and `z`, so objects can be stacked and shaped: this app has no
   dinosaur, but a dinosaur made of boulders is a perfectly good place, and `y` is the interesting
   one. It is a sculpture medium as much as a diorama.

   **Prefer many small objects to a few large ones — that is advice about shaping, not about
   sourcing.** A house is a house, however it is placed; forty rocks can be a dinosaur, a dragon, a
   mountain range or a ship, because small pieces are what give a silhouette its outline. One
   assistant built a T-Rex out of `boulder` and `small_rock` and it read as a T-Rex; an earlier
   attempt reached for the biggest objects in the catalogue and produced something that looked like a
   village. Density is the point when you are shaping something — and when the thing you want is not
   in the catalogue at all, invent it (see the section below) and hand the place over as a reply.

2. **Hand over a reply they paste in.** Any chat, no code needed: produce
   `{"ops":[{"op":"generate_scene","args":{"state":{ ... }}}]}` and the person pastes it into the app's
   "Ask your AI" panel, which documents the state shape in its own prompt.

   **This is also the route to use when you cannot be sure a link arrives intact** — a chat window that
   wraps or shortens addresses, a person copying by hand, a channel you do not control. It carries no
   address to mangle, it fits in one message, and it cannot half-arrive: either the person pastes it and
   the place opens, or nothing happens and they still have the words. Prefer it over the link when the
   transport between you and the person is not yours to trust.

3. **Drive the tab, if you can run script inside it.** Open
   https://www.dngames.de/wishwewerehere/?agent=1 and call `window.wwwhAgent` (documented below). No
   install, no bridge, no account - the app skips its title screen in that mode and is ready to drive.
   The local MCP bridge on `127.0.0.1:8787` offers the same vocabulary to a client that can run a
   process.



**Do not invent an encoded link.** If you write a `#...=` URL, it must be built character by character
from the rules above - plain text, nothing computed. A link you cannot verify yourself will be refused by
the app, and the person is left with an error instead of a place. If you cannot write the plain-text
form, hand over a reply for the app's "Ask your AI" panel instead.

## Two routes that work

The link above opens a small place — a campfire, a bench and a pine — and it was checked against the
app before it was written here. Read it: it is the whole scene, in plain text, and nothing in it was
computed. Copy its shape.

    https://www.dngames.de/wishwewerehere/#txt=theme=summer_meadow&title=A+Small+Example&p=campfire@0,0,0&p=log_bench@1.2,0,1.6&p=pine_tree@-3,0,2

That link says: world `summer_meadow`, title `A Small Example`, and three objects —
`campfire` at 0,0,0, `log_bench` at 1.2,0,1.6, `pine_tree` at -3,0,2. The same place as JSON, which is
what the second route carries:

    {
      "theme": "summer_meadow",
      "meta": { "title": "A Small Example" },
      "props": [
        { "type": "campfire", "position": [0, 0, 0] },
        { "type": "log_bench", "position": [1.2, 0, 1.6] },
        { "type": "pine_tree", "position": [-3, 0, 2] }
      ]
    }

## Worlds and objects, so you do not have to guess

None of this needs script. These are the ids and type names this build has; a client that can run
script gets more detail from `wwwhAgent.themes()` and `wwwhAgent.catalogue()`, but nothing here
requires it.

Worlds, for `theme`:

    summer_meadow  japanese  christmas  beach  city  space  pumpkin  moon  fireplace  aquarium  greenhouse  orrery  workshop  starlight_hill  sunset_cliff  highland_cascade  countryside_road  medieval_stronghold  stargate

Object types, for `type`:

    meadow_pine  meadow_flowers  meadow_boulder  meadow_path_stones  meadow_tent  meadow_campfire  meadow_log_bench  meadow_bike  meadow_eagle  meadow_clouds  meadow_fireflies  meadow_grass  meadow_path_pebbles  meadow_stone_moss  jp_pond_edge  jp_gravel_patch  jp_pond  jp_pond_rocks  jp_bamboo_fence  jp_stepping_stones  jp_sakura  jp_red_bridge  jp_stone_lantern  jp_torii  jp_koi  jp_crane  jp_petals  jp_fireflies  xmas_stars  xmas_woods_ring  xmas_pond  xmas_wall  xmas_pebble_path  xmas_path_edge  xmas_garland  xmas_light_strand  xmas_fir  xmas_gazebo  xmas_fire_pit  xmas_bench  xmas_bird  xmas_hare  xmas_fireflies  beach_sand  beach_water  beach_pebbles  beach_palm  beach_lounge_chair  beach_umbrella  beach_jetty  beach_boat  beach_crab  beach_seagull  city_sidewalk  city_skyscraper  city_wall  city_tree  city_streetlamp  city_cafe_table  city_bike_rack  city_kiosk  city_dust  space_stars  space_planet  space_asteroids  space_station  space_solar_wing  space_comms_dish  space_cargo_pod  space_shuttle  space_plasma  pumpkin_back_wall  pumpkin_ground  pumpkin_moss  pumpkin_crescent_moon  pumpkin_stars  pumpkin_cabin  pumpkin_framing_tree  pumpkin_jack  pumpkin_ghost  pumpkin_leaves  moon_ground  moon_craters  moon_stars  moon_earth  moon_lander  moon_rover  moon_flag  moon_experiment  moon_astronaut  moon_dust  hearth_fireplace  hearth_flames  hearth_room_shell  hearth_window  hearth_rug  hearth_armchair  hearth_side_table  hearth_wood_basket  hearth_cat  hearth_dust  aquarium_back_wall  aquarium_seabed  aquarium_shipwreck  aquarium_chest  aquarium_coral  aquarium_kelp  aquarium_fish  aquarium_bubbles  greenhouse_back_wall  greenhouse_ground  greenhouse_structure  greenhouse_potting_bench  greenhouse_potted_plant  greenhouse_hanging_pot  greenhouse_wicker_chair  greenhouse_bluebird  greenhouse_rain  greenhouse_pollen  orrery_stars  orrery_sun  orrery_orbit_ring  orrery_planet  orrery_asteroid_belt  workshop_floor  workshop_walls  workshop_car_lift  workshop_light_fixture  workshop_sports_car  workshop_tool_chest  workshop_tire_rack  workshop_engine_hoist  workshop_sparks  workshop_dust  hill_ground  hill_stars  hill_moon  hill_peaks  hill_tree  hill_bench  hill_bush  hill_rock  hill_fireflies  hill_shooting_star  cliff_sky  cliff_sea  cliff_sand  cliff_plateau  cliff_island  cliff_beam  cliff_bench  cliff_bush  cliff_rocks  cliff_beach_rocks  cliff_gull  cascade_massif  cascade_rocks  cascade_plateau  cascade_bed  cascade_river  cascade_backfall  cascade_fall  cascade_lagoon  cascade_bench_cliff  cascade_pond_rocks  cascade_bench  cascade_pine  cascade_bush  cascade_tufts  cascade_butterfly  cascade_mist  lane_ground  lane_hill  lane_road  lane_barn  lane_fence  lane_tree  lane_bush  lane_bird  castle_sea  castle_island  castle_path  castle_keep  castle_cloud  castle_pine  castle_barrel  castle_bird  mesh_spec  pine_tree  snow_pine  oak_tree  sakura_tree  palm_tree  bamboo  bush  boulder  small_rock  wildflowers  grass_tuft  fallen_log  reeds  tent  gazebo  log_bench  bench  campfire  lantern  street_lamp  tiki_torch  string_lights  fairy_lights  stepping_stones  stone_path  fence  stone_lantern  torii_gate  red_bridge  pond  koi_pond  ocean  lily_pad  fountain  rowboat  road_bike  bicycle  camper_van  boat  building  neon_sign  traffic_cone  cafe_set  mailbox  fire_hydrant  bus_stop  planter_box  asteroid  station_core  solar_array  coms_dish  cargo_pod  shuttle  planet  gas_giant  space_dust  eagle  robin  hare  deer  koi  crane  crab  fireflies  petals  snow  dust  clouds

A type that is not in that list is dropped, with a note, and the rest of the state still opens. The
*parameters* a type accepts are not listed here because they live in the app's builders - they are what
`catalogue()` reports.

## Inventing an object the app has not got

The list above is what the app ships with, not the limit of what a place can hold. When a scene needs
something no type expresses — a clothesline, a wheelbarrow, a lamp post, a particular animal — invent
it: a prop whose `type` is `mesh_spec`, described out of primitives.

**It travels in a reply, not in a link.** `p=type@x,y,z` has no room for a shape, so a `mesh_spec`
named in a `#txt=` link arrives as nothing in particular. Hand the place over as route 2 and the
object arrives whole.

Here is one, as a whole answer — the deepest nesting you will ever write, so copy the shape of it
exactly, closing braces and all. It is one line:

    {"ops":[{"op":"generate_scene","args":{"state":{"meta":{"title":"The Yard"},"props":[{"type":"mesh_spec","position":[2,0,1],"params":{"spec":{"version":1,"parts":[{"primitive":{"kind":"box","width":3,"height":0.04,"depth":0.04},"material":{"color":10033947}}]}}}]}}}]}

`parts` is a list of primitives — `box`, `cylinder`, `cone`, `sphere`, `torus`, `dodeca`, `icosa`,
`plane` — each with its own dimensions and a `material`; a dozen of them make a convincing object. A
part may also carry a `transform` (`position`, `rotation`, `scale`) to place it within the object, and
the spec may name one `animation` (`spin`, `bob`, `pulse`) that the app applies itself. An explicit
mesh — `vertices` and `indices` — is there for the rare shape primitives cannot make.

**Stay inside the budgets, or the object is not built at all**: at most **24 parts** and **12 distinct
materials** — and a material differs by every field, so giving each rock its own shade of grey counts
as a material each time. Reuse one colour for a family of rocks. (One colour too many is folded onto
the nearest one already in use rather than losing the object, but the geometry budget still refuses
it.)

**Invent where it earns its place.** An invented object costs a few hundred characters where a
catalogue one costs a few dozen, so it is the wrong tool for a rock — and the right one for the thing
nobody has, which is what makes a place feel made for the person who asked for it.

## 1. Drive a tab you control — `?agent=1`

If you can run script in a browser page (you are a browser agent, a devtools session, an extension),
open the app with the flag appended:

    https://www.dngames.de/wishwewerehere/?agent=1

That installs `window.wwwhAgent`. Nothing listens and nothing dials out — this is for a tab you
already control, and the flag is the whole of the permission. Without it the object does not exist.

The flag is a mode rather than a decoration: **there is no title screen** (the world is already open
when the tab finishes loading, camera and all), and the app's own two "Ask your AI" buttons — the pill
in the corner and the Wizard's — are disabled and read *Agent driving*, since a prompt to carry to some
other assistant has no purpose in a tab you are already driving. Sound starts on the first real
gesture, because a page nobody has clicked is not allowed to make noise.

    wwwhAgent.version           // op protocol version
    wwwhAgent.ops               // every op, with the argument shape it takes
    wwwhAgent.apply(op, args)   // run one
    wwwhAgent.catalogue()       // { type: { label, category } } — what add_prop may place
    wwwhAgent.themes()          // [{ id, label }] — what set_theme may name
    wwwhAgent.moods()           // [{ id, label, feel }] — what set_music may name
    wwwhAgent.docs              // 'llms.txt'

Look before you edit:

    wwwhAgent.apply('get_scene', {})   // the whole scene state
    wwwhAgent.apply('describe', {})    // the theme, and the looks this scene offers

Then edit:

    wwwhAgent.apply('add_prop', { prop: { type: 'log_bench', position: [0, 0, 2] } })
    wwwhAgent.apply('update_prop', { id: 'log_bench_1', patch: { rotation: [0, 0.6, 0] } })
    wwwhAgent.apply('set_filter', { filter: 'custom2' })   // a look describe() named
    wwwhAgent.apply('set_music', { mood: 'vast' })

Every call goes through the same command layer as the app's own buttons, so an edit is an ordinary
edit: undoable with the app's own Undo, autosaved into the visitor's browser, and validated. A bad
type, an unknown id, or an edit declaring a stale revision is refused by throwing an `Error` carrying
a `code` and one plain sentence to act on.

The ops, in full — `wwwhAgent.ops` carries the argument shape of each:

    get_scene  describe  update_prop  remove_prop  add_prop  clear_scene
    set_filter  set_music  set_title  set_theme  generate_scene

Every call records an undo step and autosaves, so prefer one big change over a hundred small ones:
`generate_scene` for a whole place, and one op per intention.

## 2. Hand a scene over, with no tab to drive

If you cannot run script in the page, write the scene and let a person carry it.

- **A whole place.** Send exactly one JSON object:
  `{"ops":[{"op":"generate_scene","args":{"state":{ … }}}]}` and the person pastes it into the app's
  AI panel ("Ask your AI" while creating, or the pill in the corner while editing). It is applied as
  one undo step. That panel also writes the prompt to hand *you*: it contains the current scene, the
  reply format, the prop catalogue, and the ranges everything is clamped to.
- **A link.** A scene is data — compressed into a share link. A person can send one; whoever holds it
  can open that place. Two shapes exist, and the second is opt-in: an ordinary link carries the whole
  scene in the URL **fragment** (`…/#s=…`), which no server ever receives; a **short link**
  (`…/#p=<id>.<key>`) is made only when a person presses *Make a short link*, and then the scene is
  sealed with AES-GCM in the browser and the ciphertext is kept for 30 days by the small endpoint at
  `/w/` on this site. The key travels in the fragment, so the store cannot read what it holds, and the
  sender's name and note are never uploaded at all. If that endpoint refuses — down, rate-limited,
  gone — the app falls back to the full fragment link, which needs no server.

## The shape of a scene

    {
      "meta":     {"title": "<this place>", "subtitle": "<the world it belongs to>"},
      "terrain":  {"color": <decimal>, "size": <metres>},
      "sky":      {"color": <decimal>, "fog": {"color": <decimal>, "density": <0.004–0.03>}},
      "lighting": {"ambient": {"color": <decimal>, "intensity": <0–2>},
                   "sun": {"color": <decimal>, "intensity": <0–3>, "position": [x, y, z]}},
      "camera":   {"position": [x, y, z], "target": [x, y, z]},
      "filter":   "custom",
      "grades":   [{"label": "<two or three words>", "bloom": <0–2>, "exposure": <0.4–2>,
                    "colorTint": [r, g, b], "saturation": <0–2>, "contrast": <0.5–2>,
                    "vignetteAmount": <0–1>, "sepiaAmount": <0–1>, "grainAmount": <0–1>,
                    "warmth": <-1–1>}],
      "music":    {"mood": "<an id from moods()>"},
      "props":    [{"type": "<a type from catalogue()>", "position": [x, y, z]}]
    }

- Colours are one whole number in decimal — red × 65536 + green × 256 + blue — never hex.
- The ground is the app's own flat plane, which `terrain` colours. Water, shores, floors, back walls
  and distant ridges are *props*, placed on that ground; water sits just above it (y 0.02–0.2).
- `grades` are a place's own postcard looks: up to five, the first being the one it opens with.
  `filter` selects one of them, or a world's own grade.
- `theme` is optional, and is only a substrate — a palette a prop falls back on, the soundtrack, and
  the baseline a share link is differenced against. A place that states its own ground, sky, light and
  camera needs no world, and its prompt names none.
- `lighting` is one ambient and one sun, and that is the whole of it. Every other light belongs to an
  object: placing `campfire`, `lantern`, `street_lamp`, `tiki_torch`, `stone_lantern` or `neon_sign`
  gives that object a real point light, so what stands near it is lit by it (the eight lights nearest
  the camera are the live ones, and the types that flicker already flicker). `string_lights` and
  `fairy_lights` only glow — an `unlit` part reads as lit without lighting anything.
- Anything left out is filled from the world the scene lands on, and every number is clamped rather
  than refused — except the two names: `meta.title` is this place, and `meta.subtitle` is the world it
  belongs to, two or three words for a kind of country ("Frozen Lake Country"). The subtitle is the
  line under the title, the words in the share message and the postcard caption, so write one; a place
  that names no world is shown as "Somewhere quiet".
- `position` is `[x, y, z]` in metres, y up, ground at y 0; about 20×20 metres around the middle is a
  place, and 12–30 objects reads as somewhere real.

## What this is not

- **No remote interface.** There is no endpoint to POST to, and nothing on the deployed site listens.
  A scene lives in a browser tab, so an agent needs either a tab it controls (above) or a person to
  carry the data. The app's MCP bridge is a process on the machine that has the project, bound to
  `127.0.0.1`, and it is deliberately not reachable from the web — do not look for it here.
- **Nothing is stored on the server**: no accounts, no sessions, no cookies, no analytics. Drafts live
  in the visitor's own browser, and a shared place is private to whoever holds the link.
- **A person is watching.** Ask before changing a scene someone is looking at, prefer one clear edit
  over many, and leave their arrangement recognisable.