# The room's door and the room's transit chamber

- Status: ownership rule accepted by the owner 2026-08-06; the door's own
  art and its editor control are NOT built. A centre-out dissolve was tried
  the same day and **the owner did not want it** — the door stays the plain
  sheet of light described below.
- Related: `docs/decisions/0007-one-database-worlds-rooms-users.md`
  (ownership and the schema), `docs/LPR-FORMAT.md` (package format),
  `src/inception/world/museum.js` (every number below is code, not prose).

## What a room owner owns

A room is not only the space behind its wall. Whoever owns the room owns:

1. **The room document** — furniture, faces, visibility, owners
   (`content/inception/rooms/<id>.room.json`, `rooms` table).
2. **The door on the corridor** that leads into it — the sheet of light in
   the corridor's glass wall at that portal slot, plus its nameplate. Today
   the door is a flat colored plane; it is meant to become a Live Painting
   like everything else, chosen by the room's owner.
3. **The transit chamber ridden on the way in** — the tunnel the visitor
   travels while the room loads. Today ONE chamber is shared by the whole
   museum (`space-museum-celestial-rain-v5`); the rule says a room owner may
   give their room its own arrival.

Read as a sentence: *the door and the journey are part of the room's
welcome, so they belong to the person who made the room.* The corridor
itself, and any slot with no room behind it, stays with the world owners
(`WORLD_OWNERS`).

Both are editable **inside the room editor** when the control is built —
the same panel, the same ownership check, and the same server-side re-check
on `PUT /api/worlds/:world/rooms/:id`. Nothing about ownership needs to
change to allow it: the door and the chamber ride in the ROOM document, so
`authorizeWrite` already refuses a stranger.

### The seam (declared now, built later)

```jsonc
{
  "id": "room-a",
  "owners": ["…"],
  // The corridor door into this room.
  "door": {
    "lpr": "space-museum-door-<name>-v1",  // compiled package id
    "image": "doors/<name>.jpg",           // the photograph it replays
    "timeOffsetSeconds": 0
  },
  // The arrival ride. Absent = the museum's shared chamber.
  "transit": { "lpr": "space-museum-<name>-arrival-v1" }
}
```

`validateRoomDoc` gains the same rule the faces already have — a package
without its photograph is refused — and `LIVE_TARGETS.portal` stops being
`fixture: null`.

## The door, as a surface to paint

Everything here is from `src/inception/world/museum.js` (`ROOM.throat`,
`MUSEUM_MATERIALS`) and `threeMuseum.ts` (`buildPortal`).

### Size

| | value |
| --- | --- |
| Width × height | **2.4 m × 3.0 m** (`ROOM.throat.width` × `ROOM.throat.height`) |
| Aspect | **0.8 — a 4:5 portrait**; author the canvas in that ratio or the door stretches |
| Shape | one flat rectangle (`PlaneGeometry`), no frame, no bevel, drawn `DoubleSide` |
| Stands on | the floor: its centre is at **y = 1.5 m**, its foot at y = 0 |
| Corridor position | x = ±2.92 m (the glass is at ±3.0), facing across the corridor |
| In-room twin | the return door is the SAME 2.4 × 3.0 rectangle, standing on the room floor near the +Z wall — one artwork can serve both |
| `throat.depth` | 1.2 m, declared but unused — rooms are detached pockets, so a door is a membrane, not a tunnel |
| Touch radius | 1.3 m from the rectangle (`PORTAL_RULES.touchDistance`) — the bottom ~0.5 m is where a 1/3-scale avatar actually walks |

Suggested canvas: **512 × 640** (4:5). That is 1.31 MB per RGBA upload, so
a door repaints on the shared scheduler like any other face; **256 × 320**
is the cheap tier if all six slots go live at once. Do not author a square:
`LIVE_TARGETS.portal` still says 256 × 256 and that entry is wrong for the
geometry — it changes with the first real door package.

### Colour

| part | colour | how it is used |
| --- | --- | --- |
| Open door | **`#59d7ff`** | `MUSEUM_MATERIALS.portalCore`, unlit (`MeshBasicMaterial`), `DoubleSide` |
| Its breath | — | a 0.22 Hz sine (~4.5 s per cycle) drives BOTH channels every frame: opacity `0.9 × level` and the colour scaled `0.35 → 1.0` of `#59d7ff`. At the trough the door is gone, not merely dim. (The 0.85 opacity set at build time is overwritten on the first frame.) |
| Sealed slot ("Coming soon") | **`#17435a`** | same plane, opacity 0.5, constant — a sealed door does not breathe |
| Frame | `#22313d` | declared, but no frame is drawn (owner 2026-08-02: "the doorway is pure light") |
| Nameplate above | `#ffffff` text on transparent | 3.0 × 0.75 m, centred at y = 4.35 m, breathing in phase with its door |
| Corridor glass around it | `#9fd8ff` at 0.10 | breathes at 0.09 Hz — the slower pulse the door plays against |
| Glass trim rings | `#123` / emissive `#5fd0ff` | the skeleton; deliberately does NOT breathe |

Painting for that palette: the door reads as **cyan light against near-black
space**, and it is the brightest thing on that wall. A package that keeps
its own light in the `#59d7ff` family will sit in the corridor the way the
current door does; anything darker will disappear into the glass during the
quiet half of the breath.

## The transit chamber, as a surface to paint

The one the museum uses today, for reference while authoring a room's own
(`TRANSIT_CHAMBER` in `museum.js`, package
`content/lpr/space-museum-celestial-rain-v5.lpr.json`):

- An open cylinder (radius **3 m**, length **14 m**) joined seamlessly to a
  front hemisphere; the visitor stands on the axis and flies at the dome.
- ONE painting wraps the whole interior: canvas **2048 × 1024** (2:1). The
  barrel wears `length / (length + πr/2)` ≈ **0.75** of the canvas height
  and the dome cap the top ~0.25, meeting at one continuous ring.
- The canvas's **top is the pole ahead**, so objects falling DOWN the canvas
  read as travel streaming past the visitor.
- Alive layers run on the GPU as a texture offset (2026-08-05), which is why
  a 2048 × 1024 canvas costs nothing per frame. Keep new arrivals on that
  path — a per-frame repaint of this surface uploads 8 MiB a frame.
