---
id: world-map-and-portals/010
title: What playing it actually found — starting at home, closed doors, a keyboard on iPad, and the builds nobody had shipped
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/world/client/ui/theme/web_keyboard.gd
---

## What prompted it

Kris played the deployed build on a phone, an iPad and a laptop on 2026-08-16 and found
six things. Every one of them was real.

## 1. Strangers standing in the world

The live Village contained `MapProbe`, `Wanderer-6137`, `E2E-Verifier`, `Sir K` and a second
`MapProbe`, clustered on the spawn cell. **They were the verification scripts' own
characters** — the live verifiers create a guest, join, walk, and their entity persists in
the world snapshot. Cleared, and noted as a recurring hazard: any live verify run leaves a
character behind until someone empties `state->'entities'` for the authored areas.

## 2. A new player started in the commons, among those strangers

The client joined the public world by default. Now there is a `join_home_world` RPC and new
adventurers **start in their own home**, walking out of the front door into the Village.
A better first minute, and the first thing a player sees is a place that is theirs.

## 3. A door that teleported you to the corner of the map

The Village's portal at (20,0) leads to the Wizard Battle Pit, which is `restricted`. Walking
into it refused the transfer — and because the transfer erases your entity from the source
world *before* the director answers (that is what makes crash-safety hold), a refusal left
the player with no entity at all. The client's recovery re-said hello and the world built a
**brand-new entity at the default spawn**: the top-left corner, silently, with no message.

Now a refusal **restores the actual entity** — same id, same character — at the cell the
player stepped in from, and the HUD says *"That way is closed to you — for now."* The
`portal.test.ts` case that documented this as a known gap has been rewritten to assert the
fix.

## 4. Hidden portals weren't hidden in the game

`hidden_unless_passable` filtered the world *map* but not the world. The in-game grid is
hydrated per area, shared by everyone in it, so it can't answer a per-player question. The
director now works out at join time which doors this account may not see and sends the list
**with the join grant** — one shared grid, drawn differently per player. Tested with a
stranger and a member standing in the same area seeing different doors.

## 5. No keyboard on iPad

Godot's web export draws to a canvas, so there is no focused DOM element for the browser to
raise a keyboard for, and `DisplayServer.virtual_keyboard_show()` is a no-op on web. Every
text field in the game was dead on a touch device — signup included, which is a hard stop.

Fixed with a real DOM `<input>` floated over the canvas (`game/shell/text-input.js`), hooked
into every `LineEdit` at the factory (`theme/lor_forms.gd`) via
`theme/web_keyboard.gd`. Touch devices only, so laptop web is untouched. iOS only opens the
keyboard for a genuine gesture on a real input, so the focus call is synchronous inside the
tap that focused the Godot field — anything deferred and Safari refuses.

**Not verified on a real iPad from this session** — the plumbing is tested and deployed;
the device check is Kris's.

## 6. The Android build was never deployed

Correct. The world-map work went out to the web and the server, and the APK was left at the
2026-08-13 build — so the phone showed no map, no profile menu and no portals. Built and
uploaded twice today under the line-in-the-sand rule: `APK_NAME` v1 → v2 → **v3**, with
`play.astro`'s download link following each time.

## Verified

Director 150 tests, Godot 116. On production: a new account lands in its own 16×16 home with
one door at the bottom middle and nobody else inside; the public map is unchanged; both
builds are live.