---
id: world-map-and-portals/009
title: Homes connect to the world, and the game gets a way out of itself
epic: world-map-and-portals
state: done
priority: 1
blocked_by: []
estimate: L
created: 2026-08-16
updated: 2026-08-18
claimed_by: null
claimed_at: null
delivers: []
review_artifact: land-of-lor/game/director/test/home-portal.test.ts
---

## What prompted it

Kris played the deployed build and found four things (2026-08-16).

### 1. A home was a sealed box

Account creation made a home Area with no portal out of it. A player who reached their home
could never leave. Fixed: `ensureHomeArea` now creates the door in the same breath as the
home — from the home's bottom-middle cell into the hub Area — and a boot backfill opens
homes that predate it (it ran in production and reported `opened a way out of 1 home area(s)`).

### 2. `home_area` portals never worked

The Village's `village-to-home` portal was **drawn on the map and inert underfoot**. It
failed at three layers: `getPortalsForArea` filtered out every row with a null destination,
so the cell never reached the world process's grid; and the director had no resolution branch
at all. Now the row reaches the grid carrying a `destSpecial`, and the director resolves it
per player, at travel time, to that player's own home.

**A sentinel `destWorldId: "home_area"` was considered and rejected.** `getWorldById` queries
a UUID column, so a non-UUID string doesn't return null — it throws (Postgres 22P02), which
escapes into a fire-and-forget catch. Neither `confirmed` nor `rejected` would ever be sent,
and the player — already erased from the source world by step 1 of the sequencing — would
hang forever, with the request re-sent on every control reconnect. Hence a real protocol
field.

### Why the home portal is one-way

Kris asked for bidirectional, and the *experience* is: out through your own door, back
through the Village's home portal. The implementation cannot be a bidirectional row, and
this is correctness, not preference:

> A two-way portal's return leg lives at the **destination** cell. Every player's home portal
> points at the same Village cell, so N players means N return legs stacked on one cell, and
> `grid.set_portal()` keeps only the last. Stepping there would drop you into a stranger's
> home.

`test/home-portal.test.ts` is the proof: two travellers walk the same Village cell and each
arrives in their own home.

### 3. The Village must not be deletable

It carries `is_home_hub` (one per server, enforced by a partial unique index) and an authored
`entry_x`/`entry_y`. The editor shows a hub badge and replaces the delete button with an
explanation; the loader refuses a file with no hub, a hub that isn't public, an out-of-bounds
entry cell, or an entry cell sitting on a portal.

### 4. The in-game map was unreachable, and there was no way out of the game

The map screen existed but hung off the account screen, which is only reachable from the
title — so an in-game player could not get to it at all. The top-right "Signed in as …" was
a click-through Label. It is now a profile pill opening a menu: the player's name, **World
Map**, **Account**, and **Main Menu** (Kris's wording) to leave the world.

**And nothing was visible anyway:** the deployed web build was `02456a9` from 2026-08-13,
**39 commits behind**, predating the world map entirely. Re-exported and redeployed.

## Also fixed along the way

- **The public map fogged too much.** The design says fog private areas *a public portal
  points at*; the endpoint fogged every private area unconditionally, publishing exact map
  coordinates for places nothing visible connects to. Now an area is fogged only if a visible
  portal touches it, and omitted otherwise.
- **Portal slugs leaked names.** A portal's authored slug was used as its public id, so
  `deep-cut-to-beginner-arena` spelled out the name of a place the caller couldn't see. Edges
  touching a fogged area now get an opaque id. Caught by a test asserting no private name
  appears anywhere in the body.
- **The three demo homes are gone from the seed** (Kris's call). They were mock data from the
  design; real player homes have no slug and never appear on the map.
- **Production wiped and re-seeded** — 85 accounts and 120 worlds removed, backed up first to
  `/backups/lor_game-20260815T213814Z-pre-wipe.sql.gz`.

## Deliberately not done

Players stacking on the arrival cell — `world-map-and-portals/008`, which waits on player
collision existing at all.