← World map and portals · all tasks
Areas and Portals in the database, and travel between them unblocked
world-map-and-portals/003 · created 2026-08-15 · updated 2026-08-18
What landed
The Area model and the portal graph, in Postgres, plus the change that made authored travel possible at all.
ensureAccessAllowed(director/src/areas.ts) — membership or public, with a public Area auto-membering whoever walks in. Before this, portal-transfer step 3 rejected every destination the account wasn’t already a member of, and the commons was the only world anyone was ever a member of: a seeded, authored world was unwalkable. Private and restricted areas are unchanged, and the test asserting a private destination is refused still passes.- Area columns on
worlds— slug (partial-unique, NULL for player worlds), area_type, access, description, grid width/height/layers, map_x/map_y/map_pinned, parent_area_id, server_id. Defaults chosen so every pre-existing row describes itself truthfully with no backfill: 10x10x1 is exactly whatWorldGrid.build_test_map()builds. - New tables —
servers(one row: Spellgrove),area_types(a lookup table, not a DB enum, so a new type is one insert),area_rules,portal_criteria,area_access_entries, andportals. portals— one row per portal, with the return leg of a two-way portal occupying the destination cell rather than getting a row of its own.getPortalsForArea()expands that into the exactPortalConfigshape the world process already consumed, so_apply_portals()and every pre-existing test cannot tell the difference.- Home areas at account creation, with an idempotent backfill in
migrate(), so ato_special = 'home_area'portal can never fail mid-step.
Why the worlds table wasn’t renamed to areas
Closed 2026-08-16. The rename this section defers happened, as
world-map-and-portals/015— protocol, database and GDScript in one cutover, which is exactly the condition this reasoning set for doing it. The section stays as written: it is why the wait was right, not a mistake to erase.
“worldId” is the wire protocol, the GDScript, the CLI harnesses and every handoff doc.
Renaming the table alone leaves the vocabulary split in two, which is worse than either
consistent state. New code reads in Area terms via src/areas.ts; a full rename (protocol,
DB and GDScript together) is its own deliberate change, the way migrate-v2 was.
Verification
npm test in game/director — 139 passing, including portals-table.test.ts (bidirectional
expansion, one-way, cascade, cell collisions) and portal-authored.test.ts, where a stranger
joins a public area they have never been a member of, walks into a table-authored portal, and
arrives at the authored destination cell.
View source in repository · also available as raw markdown.