← World map and portals · all tasks
Homes connect to the world, and the game gets a way out of itself
world-map-and-portals/009 · created 2026-08-16 · updated 2026-08-18
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-arenaspelled 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.
View source in repository · also available as raw markdown.