Status and roadmap

Tilework is at MVP / alpha level. This page separates what has been run from what has not. Last updated on 2026-10-02.

#Verified (executed)

Environment: macOS (Apple M1 Pro, arm64), Go 1.26.5, Google Chrome with fake camera and microphone, LiveKit 1.13.7. Real-browser scenarios run against a real LiveKit server.

CheckWhat it proves
Go tests (server/, also with -race): world rules, dead reckoning, map reload, chat history, store (sessions, invites with a 40-way race, chat pruning, backup, roles and the last-admin guard, member removal, invite revocation, audit trail), the admin HTTP API (403/401 for non-admins, role change, removal, invite lifecycle, /metrics policy), production configuration guard rails, eviction from the world, media tokens and reconciliationrules and invariants hold; media revocation is retried
Browser scenario proximitymovement sync, remote avatars tracked within a few pixels, a call forms ~1.6 s after approaching with real audio and video RTP, leaving revokes the SFU and stops capture; a per-person volume button turns the other person to 50 % and to muted for the listener only
consentnothing without opt-in; busy blocks automatic entry and leaves calls; invisible hides the avatar
roomsadmin-only room enforced by the server against raw inputs; tokens are scoped to one room; screen share is received; leaving a private room revokes access
accessonly admins mint invites; invite use limits; production refuses insecure configuration; CLI invite and online backup
editorreal UI clicks paint walls and create a room; others get it live; the server enforces it; it persists across a restart
socialoffice/direct/conversation chat scopes and privacy; three messages typed into the real chat composer with both panels open render for sender and receiver and the app stays alive (regression: an effect returned scrollIntoView()'s Promise on Chrome 154 and blanked the UI), rate limit, 500-character cut, persisted office history, profile change, desk owner label
adminthrough the real UI: members list, promote/demote an online member (evicted, reconnects with the new role), the last admin cannot be demoted, invite create/list/revoke (secret never listed, revoked link returns 403), activity log, removing a member sends them to the join screen with a notice and kills their session
a11yaxe-core (WCAG 2.1 A/AA + best-practice rules) reports zero violations on the join screen, people and chat panels, status menu, settings, administration (three tabs), consent dialog and the map editor; dialogs trap focus and close with Escape
securityreplayed token connects but is evicted by the reconciler within seconds; edited token rejected; expired token rejected only after LiveKit's 60 s clock-skew tolerance (found by this test, now documented)
resilienceWebSocket drop keeps position, call and audio; flooding cannot speed a player up; oversized frames close the socket; session and position survive a server restart
featureskeys 1-7 show reactions on both avatars; X opens a nearby note whose content is absent from the public map; follow walks around walls to the leader and stops on input; portals teleport once per arrival; a locked room blocks outsiders and admits one visitor after a knock; edited notes and whiteboards persist across a restart
spotlightstepping on the pad with consent goes on air; a listener in a separate private call receives real audio, video and screen share RTP; audience tokens are subscribe-only and scoped; withdrawing consent or stepping off empties the SFU room
movementwalking measures 72 px/s and running (R / Shift) 144 px/s in the browser; another browser sees the run bit and agrees on where the runner stops (< 1 px); double-clicking the map runs there along a server path; "Walk to" in the people panel crosses the office around walls in ~3 s; a member cannot be routed into an admin-only room; pets picked on the join screen are stored and drawn next to their owner for others; recorded frame by frame while the owner walks an L, the pet follows the owner's trail about 20 px behind (median), never more than 3 px off it (no cut corners), never faster than 1.35x a walker, and rests about 19 px behind; pets and change live through the profile; unknown pets are rejected
movement: prediction under jitter and at walls (2026-10-02)with every input delayed 0-120 ms at random in the page, a runner holding a key is never drawn stepping back (largest step back 0.00 px over 79 frames; the previous code drew up to 4.1 px back) and stops within 0.2 px of where another browser sees it; walking diagonally under the same jitter neither axis steps back; running into a one-tile wall stops touching it (y 243) for both browsers with no pull back afterwards (the previous code stopped at 243.75). Go tests: the server stops at the same contact point walking and running, a 600 ms late tick covers the whole distance (it covered 36 of 86 px), and a 900 ms late tick cannot cross a one-tile wall (it did); a leave and a return of the same person in one tick send only the last of the two. Decision 0017
idle (render on demand)with two people standing still the page draws about 4 frames a second (the frame loop keeps 60 FPS for input); every frame where a walking person's sprite moved or changed picture was drawn (37 of 37); my own walk draws at the display rate; a status change made while everybody stands still reaches the screen
rooms (screen share)besides isolation: the receiving browser expands the shared screen over the map (360 -> 1240 px) and shrinks it with Escape; the presenter's avatar shows a screen badge that clears when sharing stops; the capture runs at 1920x1080; sharing starts from the real Share screen button. When it cannot start, the bar says why, checked with browser errors stubbed in the page: the system blocked capture, the capture could not start, a page or browser without screen capture (a touch phone tapping the button), or no call yet; closing the picker shows nothing, and no camera message flashes in between (before this, a closed picker showed "Allow the camera and microphone"). Over the internet on the demo (2026-10-01, Chrome, two people in Office 3, build be569e2, before these messages) clicking Share screen delivered the screen to the other person
presencea wave crosses the office (behind a wall, out of view) and "Walk to them" runs to the waver; busy people are not disturbed and the sender is told; H raises a hand others see over the avatar and in the roster, H again lowers it, and two quick presses (before the server confirms the first) leave it down; a note typed in the status menu appears in the other person's people panel; Z dances for people nearby; the minimap shows server head counts and clicking it runs there; M hides it; on an emulated phone (390x844, touch) the on-screen pad walks the avatar; ? opens the shortcuts; an administrator announcement reaches both people as a banner that can be dismissed, and the activity log records it without the text
officessix private offices behind a hall; an administrator assigns Office 2 in the editor and everybody sees "Office 2 · name"; the owner walks in and gets no lock button; a visitor is stopped by the server, sees a Knock button at the door, the owner gets the request and clicks Let in, the visitor walks in and both share the office's own call while somebody in the hall stays out; a free office is open to anyone and can be locked (passed 3 runs in a row after fixing knocks on assigned offices being dropped by housekeeping)
Dockerimage builds (24 MB, non-root); the local Compose stack passes proximity, consent and rooms; in the container, production mode returns 403 without an invite, issues a Secure cookie with one, and refuses insecure configuration
Public demo on a real host (2026-09-30)the production Compose file (Caddy + Tilework + LiveKit 1.13.7) runs on an Oracle Cloud Always Free AMD micro VM (1 GB RAM + 2 GB swap, São Paulo) with DEMO=1; Caddy obtained Let's Encrypt certificates; LiveKit discovered and validated the public IP. e2e/demo-smoke.mjs from a laptop in São Paulo over the public internet: invite-less join as member, proximity call live, real audio and video RTP received, over UDP (selected ICE pair udp, RTT 10 ms). Demo-mode unit tests: no invite needed, never admin, per-IP sign-up limit, reset restores map and wipes chat and boards. On 2026-10-01 the demo was updated to the published image of commit a6a1e3a and, from the same laptop over the internet, the smoke test passed again (7/7, UDP, RTT 11 ms) and the movement scenario passed 14/14 against it (TILEWORK_SHARED_OFFICE=1 TILEWORK_EXTERNAL_URL=...): running at 144 px/s, walk-to, pets. Later on 2026-10-01 the demo ran b638676: movement 18/18 over the internet (pet median 20 px behind, never off the trail) and the smoke test with three messages typed into the chat composer (rendered for both, no errors). Then be569e2: on start the demo detected the old 60x36 map and installed the starter office with six private offices; smoke 9/9 (offices present, UDP media, chat composer) and two visitors walked into a free office and shared its own call (4/4). Assigning an office on the demo needs an administrator and was verified only locally

GitHub Actions (.github/workflows/ci.yml) runs the Go tests with -race, the web typecheck/build and this whole browser suite on an Ubuntu runner (software-rendered Chrome, real LiveKit); the run for the latest commit is green. The runner is much slower than a laptop (10-16 FPS), so the test walker steers frame by frame inside the page.

The 2026-10-01 gauntlet run passed the full local browser suite after fixes to editor and status-menu layering. Focused scenarios passed movement 18/18 and social 20/20, including real composer sends and small-screen control hit tests; 11 pure pet tests passed for trails, running, corrections, low frame rates and resting visibility. Static checks, Go race tests and the docs build also passed. The final review then exposed inaccessible expanded phone reactions, covered by two additional social checks. See the gauntlet procedure. Brief overlap at corners or a 180-degree reversal and temporary hiding behind a moving owner remain cosmetic limitations; the measured minimum gap applies to the tested forward L route, not every reversal.

Rerun scripts/gauntlet.sh to reproduce the whole local verification.

#Measured (local reference, not a capacity claim)

Scenario A (no media) up to 1,000 bots and a 500-client reconnect storm, with the generator on the same laptop: see Benchmark results. Media through a real SFU (B, D and a scaled C, 0 % loss up to 40 people in calls, ~1.3-1.8 % of one M1 core per forwarded Mbit/s) is in the same document. Highlights: state-change records cut server output about 5x and brought the worst tick at 1,000 concentrated bots from 143 ms to 60 ms; a 500-client mass reconnect finished in 2.0 s with no failures.

On a real host, generator on another machine (2026-10-01). The production stack on the free Oracle VM.Standard.E2.1.Micro (São Paulo), loaded over the internet from one laptop in São Paulo: presence runs of 50 to 500 bots all joined with nothing kicked. 100 bots kept every tick percentile low (p99 5 ms); at 200 the mean tick was still 3 ms and the median move reached others in 88 ms, but p99 went above 50 ms and the worst tick took 180 ms; from 300 steal passed 70 %, consistent with the shape's CPU cap, and at 500 the mean tick (99 ms) exceeded the 66 ms period. Media: 4 people with video and 20 in audio-only groups ran clean (0 % and 0.5 % loss); 12 with video lost 27 %, 20 with video 63 %, and the 60-person audio-only run (59 connected) 51 % at only 4.8 Mbit/s. The VM's CPU cap is the most likely limit, but the generator laptop's CPU and upload were not measured. Single runs; tables in Benchmark results.

Movement and rendering costs (2026-10-02, Apple M1 Pro, single machine). go test -bench Tick ./internal/world with 300 players, a third of them changing direction every tick (far busier than people are): 1.99 -> 0.52 ms per tick concentrated in the reception and 1.12 -> 0.38 ms spread over the map, three runs each, same allocations; output bytes are unchanged. Chrome (ANGLE on Metal, 1440x900) with nobody moving, three 10 s windows per build: renderer process 10.2 -> 5.4 % and GPU process 15.4 -> 3.3 % of one core. Before = the previous commit, after = decision 0017. Neither number is a capacity claim; the free VM was not re-measured.

#Not run / not implemented

  • Scenario C at its full size (100 people in 25 calls). B (20 people), a scaled C (40 people) and D (20-person meeting) were run locally with real synthetic media; 60 people and above saturate a single laptop. On the free cloud VM media saturates much earlier (see above). TURN through restrictive networks is untested.
  • The 2 vCPU / 4 GB reference server with dedicated CPUs. Only the CPU-throttled free micro VM was measured with a separate generator. A two-hour soak (only a 10-minute, 300-bot presence soak was run: flat goroutines/RSS, see benchmark-results.md).
  • Browser FPS/CPU on the reference laptop. Measured only on an Apple M1 Pro: 59.9 FPS (normal) and 29.6 FPS (economy, capped) with 300 bots, see Benchmark results.
  • The movement fix on the public demo. The demo still runs the previous image; the fix was verified locally (with injected jitter), not over the internet on the demo host, and the VM benchmarks were not rerun with the faster tick.
  • TURN relay on a public host through UDP-blocked networks. Direct UDP media and the production Compose stack with Caddy/TLS were verified on the demo; relay behaviour was not.
  • Moderation is minimal: admins can remove members and revoke invites, but there is no ban list, temporary mute, reporting flow or content moderation; the audit log is not tamper-proof.
  • Accessibility: only the automated axe-core audit and keyboard checks were run. The game canvas is not operable with a screen reader and nobody has tested with NVDA, VoiceOver or JAWS.
  • Cost numbers: only the formula and bench/cost.py exist; no prices were verified.
  • Only English is shipped (the i18n dictionary mechanism exists for more locales).

#Roadmap

What comes next, including features people asked for (breakout rooms, a stage, audio-only areas, a lounge) and what hosted plans need, is in the Roadmap. How the project plans to sustain itself: Business model. The milestones of the first version:

  1. Architecture, decisions, repository, local environment. Done.
  2. First functional slice with real proximity audio/video. Done.
  3. Invites, presence, chat, persistence. Done (see limitations above).
  4. Rooms, screen sharing, personalisation, basic editor. Done.
  5. Benchmarks and evidence-driven optimisation. Started: scenarios A, B, D and a scaled C run locally, and presence and media runs on the free Oracle VM with the generator on another machine; the reference server, TURN and repeated runs are pending.
  6. Reproducible self-hosted install and documentation. Started: the production Compose stack runs the public demo on a free Oracle VM (scripts in deploy/oracle/); multi-arch image published to GHCR. Not tested: TURN relay for UDP-blocked networks (the relay port range is closed on the demo host), load on the demo host.

Deferred on purpose: recording, AI transcription, native apps, marketplace, enterprise integrations and 3D worlds.

#Tilework rename

On 2026-10-02 the project adopted Tilework, including its application, documentation, generated site, art, repository links, module, CLI and runtime namespace. Local validation ran Go vet and race tests, both TypeScript checks, the production client build, web lint and unit checks, the generated-site check and 224 checks across all 15 real-browser scenarios. The final gauntlet record identifies the reviewed source tree in .gauntlet/last-gates.log.

Existing deployment and browser-state changes are documented in Upgrading to Tilework. Container publication, the site deployment and a public demo media smoke check are separate release checks; none establishes capacity. Historical benchmark evidence is unchanged, and earlier recorded commands used the previous operational names. No new cloud load benchmark was run for this rename.

Edit this page on GitHub