← World map and portals · all tasks
One body everywhere — entity ids move from the world to the account, and the on-screen keyboard finally runs at all
world-map-and-portals/014 · created 2026-08-16 · updated 2026-08-18
1. Entity ids belong to the account
Kris, reading 013’s write-up: “entity ids absolutely should not be per-world, obviously they should be per account.” Right — and it is the root cause one level below 013. A stale body could only exist because the same player wore a different id in every Area.
accounts.entity_id is minted once, at account creation, and travels to the world process
with the join grant beside display_name and character_seed. The world uses it verbatim and
only mints one when it wasn’t given one (a standalone or load-test run with no director).
- Migration is one UPDATE on boot for accounts predating the column,
gen_random_uuid()per row so 80 accounts can’t collide with each other. 23 backfilled on production; 26 accounts, 26 distinct ids, none missing. - There is no entity data to convert. A body already standing in a world under an older per-world id is renamed on first contact — same cell, same everything — and any other body for that account is dropped. A player keeps the spot they left off.
- Not the account id itself, deliberately: every peer in an Area sees every other body’s
entityIdinworld_update, and an account id is not theirs to see. It stays the same short opaque token it always was.
Two portal tests asserted “a fresh entity gets a new, different random id” — that assertion was the per-world assumption. They now assert the id is the same, and that what proves the entity left is its position: a fresh body on the spawn cell rather than the transferred one where it walked to.
2. The on-screen keyboard had never worked, anywhere
Kris asked for a way to enter a name in Chrome on an iPad. There already was one, shipped that morning and marked “not verified on a real iPad”. It had never worked on any device in any browser. Three separate faults, each fatal on its own, and none of them findable by reading:
- The feature was never on. The availability probe tested
probe is bool, andJavaScriptBridgehands a JS boolean back as an int (probe=1,TYPE_INT).available()was false everywhere, soattach()returned immediately and no field was ever hooked up. This is why no amount of reasoning about iOS gesture policy explained the iPad: nothing was running to be refused. focus()was always a frame late. WebKit — every browser on iPadOS, Chrome included — opens the keyboard only for afocus()inside the gesture’s own DOM handler. The old design focused from Godot’sfocus_entered, on the stated assumption that the signal fires inside the tap handler. It does not: Godot buffers input and flushes it at the top of the next frame. The engine cannot win this, so it stopped trying — it publishes where its fields are (as fractions of the viewport) and the browser hit-teststouchstartitself.- The engine took the focus back. Godot’s web export calls
canvas.focus()from its own touch listener, and focuses a hidden IME div whenever aLineEdittakes GUI focus — either one blurs the input and shuts the keyboard a frame after it opened.preventDefaultdoes not stop propagation, so a tap landing on a field is now swallowed outright, gesture and all.
The consequence is the shape of the whole thing: the DOM input IS the input, and the LineEdit mirrors it through one callback registered once. Nothing gives the field GUI focus.
shell/verify-touch-keyboard.mjs is the new check, and the reason this one is trustworthy:
it drives the real exported build in headless Chrome over CDP with touch emulation, taps
through to the signup field, and asserts the input is focused inside the touch event, that
the bar survives a second (the canvas-steals-focus failure), and that typing reaches the engine.
Every one of the three faults was caught by it, not by reading the code. 8/8.
What it still cannot check is WebKit’s own policy — no iPad here — but nothing in the design now depends on a browser being lenient about it, which is exactly what the old one did.
Verified
Director 155 tests, Godot 116, verify-touch-keyboard 8/8, and on production after deploying:
verify-home-door 10/10, verify-accounts-e2e 32/32. Shipped as APK v6 and a web
re-export at db6a49b.
Housekeeping: the DoorProbe and MapProbe characters were cleared from the Village (the
recurring live-verifier hazard first noted in 010) — which also cleared Kris’s own bodies in
the Village and The Badlands, so he respawns at the entry cell there. Home, account and
collection untouched.
View source in repository · also available as raw markdown.