← World map and portals · all tasks

The door that went nowhere — a portal frame lost to the disconnect behind it, the test map that flashed on every launch, and your own home missing from your own map

done priority 1 estimate M

world-map-and-portals/012 · created 2026-08-16 · updated 2026-08-18

What prompted it

Kris played v4 on the phone on 2026-08-16, with the readable Area name and the world map’s “You are here” marker newly in place — and those two things are exactly what made the real bug legible. Five screenshots and a recorded transcript. Three findings, all real.

1. The door out of your own home went nowhere

Step into the portal at the bottom of your home and you reappear in the top-left corner of your own home. Step in again and nothing happens at all.

The server was never wrong. A TypeScript client walks home → Village against the live droplet and gets portal_result with the right destination every time. The droplet’s own logs show the give-away: across the whole session only the home world process was ever spawned. No Village process, so no transfer ever completed.

What breaks is one frame. world_server.gd sent portal_result and called disconnect_peer() in the same tick, and this Godot WebSocketPeer reproducibly only ever surfaces the close — never the data frame behind it. The client then took its “the connection died” branch, whose recovery is a fresh join_world against the world it was in. But transfer step 1 had already erased its entity from that world, so the world built a brand-new one at the default spawn, (1,1). The top-left corner. And the second attempt was a different, fresh entity standing on a door cell it had never stepped onto — portals fire on the transition, not on standing there.

Reproduced exactly with the real Godot client, headless, against production before anything was touched. That reproduction is the whole reason this was a one-sitting fix rather than a week of guessing.

client_main.gd’s own comment on _on_game_disconnected() had diagnosed this race in detail and concluded it was unfixable from the read side. It was right. The fix belongs on the write side, which is where nobody had looked:

2. The ancient test map flashed up on every single launch

A small green grid with one grey block, for about half a second, every time the game started. It is grid.gd’s build_test_map() — 10×10, four blocked cells at y=5 — the fixture both sides hardcoded back in the multiplayer POC. client_main builds it in _ready() so movement code always has a grid, which is fine; the bug was that it also drew it, in the gap before hello_ack named the real Area. Nothing is drawn now until the server has named an Area.

Worth recording while it was asked: blocked cells today are one int per (x, y, layer) in a flat PackedInt32Array, rendered as grey boxes. Authored areas have nonebuild_for_area() makes an open field of the right dimensions, because ground types aren’t designed yet (world-traversal-and-ground). The test map is the only thing in the codebase that has ever had a wall in it.

3. Your own home wasn’t on your own map

Not fogged — absent. Homes have no slug, and the map query was WHERE slug IS NOT NULL. That is exactly what keeps every other player’s home off the map, and it stays. But the person standing in one has to be able to find it, and with the new “You are here” marker there was no ball for it to sit in.

The map now carries the caller’s own home, and only theirs: a fixed opaque id your-home (never a database id), named, ownerLabel: "yours", positioned beside the hub it opens onto, with its door drawn. The two “Locked realm” balls Kris asked about are Deep Cut and the Wizard Battle Pit — both restricted, neither a home.

On Kris’s expectation

Right about the first hop: the home door leads to the Village of Lor. The Badlands is one further — Village → Beginner Arena → Badlands. There is no direct Village→Badlands portal in the authored world.

Verified

New npm run verify-home-door, 10/10 against the live droplet: a real client out of a real home, into the Village, arriving as the same character on the hub entry cell, plus the map it sees on arrival and what a stranger sees. verify-accounts-e2e 32/32, verify-world-map 10/10 — no regression. Director 153 tests, Godot 116. And the actual Godot client, headless against the droplet, landing in Village of Lor 40×40 at (20,38).

Shipped as APK v5 (installed to Kris’s phone over adb) and a re-exported web build — /play had been serving fbf8bed, which predates all of this.

View source in repository · also available as raw markdown.