---
id: game-interface/004
title: Android build is missing the whole interface — shell is DOM-only, APK ships the bare 3D world
epic: game-interface
state: done
priority: 1
blocked_by: []
estimate: L
created: 2026-08-11
updated: 2026-08-12
claimed_by: laptop-claude-session-2026-08-12
claimed_at: 2026-08-11T21:30:00.000Z
delivers: []
review_artifact: land-of-lor/game/README.md
---

## What

Kris played the live `/play` page (works — title screen, HUD, Ingredients panel all
present) and then installed the APK on his phone: the 3D world runs, but none of the
interface layer is there — no title screen, no HUD buttons, no Ingredients/Spells/Gear
panel, not even the help bar. He suspected the UI had only ever been built as an HTML
overlay rather than into the Godot project itself. That's exactly right, confirmed by
reading the actual architecture:

- `game/README.md` and `game/shell/README.md` both say plainly: the shell is "the DOM
  layer... floated over the untouched Godot web export." It boots `./engine/index.js`
  and draws HTML/CSS on top of the `<canvas>` from the *page*, not from anything inside
  the `.godot` project.
- `game/export-android.sh` exports `$HERE/world` only — the raw Godot project
  (`game/world/`, `run/main_scene = client_main.tscn`). There is no Control-node UI
  anywhere in `world/`; grepped for it, nothing exists.
- So the APK is, correctly, exactly the Godot project on its own: touch-to-move in the
  3D world, and nothing else. This was never a bug in the export script — the shell
  genuinely has no path into a native Android build. It's a real, undocumented gap:
  `game/README.md`'s "Known limits (deliberate, v0.0)" section lists several intentional
  v0.0 shortcuts (static health bar, hardcoded owned-set, Spells/Gear placeholders) but
  never says "Android has no interface at all" — this wasn't a decision anyone signed
  off on, it fell out of the shell being built web-first and nobody checked the APK
  against deliverable 9's acceptance criteria before shipping it.

Deliverable 9's own acceptance criteria ("title screen... HUD... Ingredients tab") make
no browser/mobile distinction — the APK not meeting them is a real gap against what was
promised, not a scope question.

## The fork this needs

Two genuinely different ways to close this gap:

1. **Wrap the existing web build in a WebView shell for Android**, instead of exporting
   the raw Godot project. Reuses the shell, the Claude Design pass, and the card
   renderer/editor contract completely untouched — zero redesign. Downsides: WASM inside
   a mobile WebView is materially slower than Godot's native Android renderer; touch
   input, fullscreen/back-button behaviour, and Android WebView quirks (version
   fragmentation) all need real verification, not assumption; app size grows (bundling
   the web build into the APK).
2. **Build the interface natively in Godot** (Control nodes: title screen, HUD, tabbed
   stuff panel, card grid/modal) so one implementation ships identically on web and
   Android. Better native performance and input handling, no WebView involved at all.
   Downsides: the whole Claude Design handoff (`docs/design/HANDOFF.md`, the two
   `.dc.html` references) is HTML/CSS — none of it transfers directly to Godot's UI
   system, so the visual pass has to be rebuilt by hand in-engine and now has two
   places that can drift (DOM shell for web, Control-node UI for Android) unless the web
   shell is later retired in favour of the same native UI running inside the Web export
   too.

**RESOLVED by Kris (2026-08-12, planning session):** option 2 — native Godot UI — is the
direction, explicitly confirmed ("we want this correctly implemented in Godot — and well
architected too"). The WebView wrap is rejected. The DOM shell's UI layers get retired to a
thin tap-to-play loader (Kris chose the gate over auto-loading the ~40MB engine on page
open); the Godot Control-node scenes become the visual source of truth for the interface.
The original assumption text follows for provenance:

**Assumption (flagging, not blocking):** defaulting to option 2 — native Godot UI — as
the direction to prototype first, because "one implementation, both platforms" matches
this project's existing bias (`world/`'s whole reason for being one shared project is
"movement code can never diverge between client and server by reimplementation drift" —
the same argument applies to the interface). But this is a real architectural fork with
a real cost (redoing the Claude Design visual pass by hand), not a mechanical fix, so
whoever picks this up should read both options above before starting rather than take
the assumption on faith — and Kris should get a chance to weigh in during review, since
it resets which surface (`shell/` DOM vs. Godot Control nodes) is the visual source of
truth going forward.

## Done

Both platforms show the same interface: title screen, HUD (health bar + Ingredients /
Spells / Gear), tabbed stuff panel with the Ingredients grid and card modal, working
identically on the live `/play` page and on a fresh install of the APK on an Android
phone. Update `game/README.md`'s "Known limits" section either way — this stops being an
undocumented gap once someone's made the call.

## Review

Kris installs the rebuilt APK on his phone and confirms the interface is there and
usable one-handed in landscape, same as the browser version.

## Built (2026-08-12)

The interface was rebuilt as native Godot Control-node scenes in
`land-of-lor/game/world/client/ui/` — title screen, HUD (press-hold labels, tap-to-move
passthrough), stuff panel with the always-8-across grid, and the element card with all
three rarity frames (legendary = animated foil shader) and the un-collected template.
Architecture notes in `land-of-lor/game/README.md`: `app.tscn` root flow;
`player_state.gd` is the deliberate MMOG seam (demo owned-set, placeholder health,
signals) that server-backed accounts later replace; `element_db.gd` reads
`data/elements.json`, synced from the canonical `shell/elements.js` by
`sync-elements.sh` inside both export scripts. `client_main.gd`, the six shared `.gd`
files, and the protocol are byte-identical; 64/64 pure-logic tests pass. The web shell
is now a thin tap-to-play loader (Kris chose the gate over auto-download); the card
editor + its HTML renderer stay as authoring tools (documented drift tradeoff: editor
preview vs Godot render — data cannot drift). Verified end-to-end in a CDP-driven
browser: loader → engine boot → native title → Start → live-droplet world with HUD,
1600+ remote entity updates streaming. New APK (27.7 MB, package
`au.com.cocreations.lor.game` v0.0.1, lor2026-signed) uploaded to the droplet.

Pre-existing quirk noticed while verifying (NOT introduced here, present in the old poc
web build too when connected): the client logs occasional "Parse JSON failed" console
errors during live traffic — protocol-side, frozen code, harmless to gameplay; worth a
look whenever the protocol next opens for changes.