Agent Play Watch Canvas: What Changed (April 11–12, 2026)

12 April 2026

Back to Newsroom

The watch canvas stopped being a postcard and became a scrollable park—sky, crowds of code, and camera math that finally matches the map.

This piece walks through a focused burst of work on the Pixi-based watch / preview canvas (packages/play-ui and the mirrored packages/web-ui/src/canvas/vendor bundle), plus SDK and web-ui server pieces where they touch the world. The commits cluster around mobile-friendly interaction, atmospheric visuals, a scrollable park world, correct camera math, and version bumps.

1. “Touch the Watch” — Proximity Controls for Phones

Commit: 08ef1cbAdd proximity touch controls for mobile viewports
Follow-ups: 9292181, bcfa94a — refactors and styling

What it is: A dedicated mobile proximity UI: draggable Assist (A) and Chat (C) controls so users without a keyboard can still trigger the same actions as desktop. The preview settings / toolbar path grew hooks to wire this in, with Vitest coverage (preview-proximity-touch-controls.test.ts).

Why it matters: The watch experience is no longer “keyboard-first only”; small viewports get first-class affordances.

2. Session Interaction Panel — Dismissible, Clearer Chrome

Commit: 67c0932Enhance session interaction panel with close functionality and UI improvements

What it is: The session interaction surface in the preview gained close behavior and general UI polish (preview-session-interaction-panel.ts and vendor twin). The same commit train on web-ui also pulled in platform analytics, Open Graph assets, and a stats area—broader product chrome around the canvas, not only the game view.

Why it matters: Preview sessions feel more like a finished panel (escape/close, layout), while the repo also moves forward on marketing/analytics adjacent to the play surface.

3. “Living Sky” — Airplanes, Banners, and Sky Decor

Commit: 45aab3bAdd airplane layout and sky decor components with tests

What it is: A non-world layer above the scrolling map:

  • sky-decor.ts — orchestrates sky decoration over time (e.g. planes, greeting banner behavior).
  • airplane-silhouette.ts / airplane-layout.ts — layout constants and drawing for airliner-style silhouettes.
  • sky-decor-logic.ts + tests — isolates logic that tests can hit without pulling the full Pixi stack where possible.

Why it matters: The horizon reads as alive (motion, depth) without changing simulation rules; it is decorative and can respect reduced-motion style behavior (see sky-decor implementation patterns in those files).

4. The Big One — “Scrollable Park World” and Scene Overhaul

Commit: 895d817Enhance world navigation and scene rendering with new features

This is the largest functional narrative in the history you asked about.

4.1 Minimum playable bounds (SDK contract)

Files: packages/sdk/src/lib/world-bounds.ts, expandBoundsToMinimumPlayArea, MINIMUM_PLAY_WORLD_BOUNDS (roughly 20×20 grid semantics), exported via packages/sdk/src/index.ts / browser.ts.

What it does: The server and client agree on a floor for how big the world must be; snapshots that describe a tiny occupant box still expand to at least this minimum play rectangle so the watch UI always has room to scroll and clamp.

4.2 Park backdrop that matches the grid in pixel space

Files: scene-backgrounds.ts — notably buildParkWorldBackdrop

What it does: Builds a wide, tall park illustration (sky band, grass, trees, benches) sized from pixel dimensions derived from bounds, so the art scrolls with the world instead of being a fixed 720×520 postcard.

4.3 Camera follow + clamp (first pass)

Files: world-nav-math.tsclampCameraToWorldRect

What it does: After this commit, the camera tries to center the human while not scrolling past an axis-aligned “world” rectangle. That unlocked horizontal panning and the feeling of a real map.

4.4 Human spawn and toolbar behavior

In main.ts: Spawn uses normalized constants (e.g. HUMAN_DEFAULT_SPAWN_UX / UY) and defaultHumanSpawnInWorld so the avatar lands in a predictable corner of the minimum rectangle, then clampWorldPosition keeps it legal.

In preview-settings-toolbar.ts: includeThemePanel — when false, the Scene theme control can be omitted (park-only / locked presentation).

In scene-theme.ts / scene-registry.ts: ACTIVE_SCENE_THEME pins the default to park; ENABLE_CROWD_LAYER can disable the old full-screen crowd layer so the new world read is not visually doubled.

4.5 Server alignment

File: packages/web-ui/src/server/agent-play/world-map.ts

What it does: World map construction from occupants uses expandBoundsToMinimumPlayArea so HTTP/API snapshots line up with what the canvas expects.

4.6 Street registry (data + optional art module)

Files: packages/web-ui/src/canvas/vendor/world-streets.ts, tests, and world-street-signs.ts

What it does: A registry of named streets with world anchor points (STREETS, getStreetById). A companion module can mountStreetSignPosts (small sign graphics per street). In the current tree, mountStreetSignPosts is not referenced from main.ts—it is available in the vendor bundle but not wired into the live scene graph, so treat it as optional / future unless you connect it.

5. Precision Fix — Scroll Rectangle Matches World Coordinates

Commit: 4a2e9a7Implement computeWorldRootScrollRect function and associated tests

Problem class (in plain language): The first camera clamp used a rectangle that assumed content sat in a naive 0-based box, while worldToWorldRootLocal maps northern tiles to negative local Y when worldOriginScreenY is negative. That camera-bounds mismatch could pin vertical scroll and hide “upper” map content even when you walked there.

Fix: computeWorldRootScrollRect takes the same parameters as your world mapping and returns the true min/max X/Y in worldRoot space by walking the four corners of the padded map. getCameraClampRect in main.ts (and vendor) now delegates to that function.

Tests: packages/play-ui/src/world-nav-math.test.ts documents the behavior.

Why it matters: This is the difference between “camera feels broken” and “camera respects the same math as entities and props.”

6. Housekeeping and Versioning

  • 09b2790packages/play-ui/package.json3.4.0
  • 37aa346 — SDK 3.2.0 in package metadata
  • b5af96f — validation script for main.ts node consistency between play-ui and vendor (keeps duplicated trees honest)

7. What Is Not in the Commits (Conversation vs Git)

From our thread, there was experimentation with a large cross-street sign built in Pixi, anchor offsets, then removal when placement did not feel right. In the current repository state, main.ts does not integrate that feature, and the cross-street-sign source files are not present under packages/play-ui/src. The committed story for “streets” is mainly world-streets.ts (+ optional world-street-signs.ts in vendor). If you need a single sentence for stakeholders: street names and anchors are in the codebase; the fancy corner sign prop was backed out or never landed on main.

8. How to Read the Codebase Now (Map of Ideas)

Minimum world size @agent-play/sdkworld-bounds.ts

Park sky/grass/props: scene-backgrounds.tsbuildParkWorldBackdrop

Camera clamp (corrected): world-nav-math.tscomputeWorldRootScrollRect + clampCameraToWorldRect

Human follow camera: main.tsupdateCameraAndWorldRoot

Spawn tuning: main.tsHUMAN_DEFAULT_SPAWN_*, defaultHumanSpawnInWorld

Sky planes / banner: sky-decor.ts, airplane-silhouette.ts, sky-decor-logic.ts

Mobile A/C pad: preview-proximity-touch-controls.ts

Street list (data): web-ui vendor — world-streets.ts