P/T · USER FLOWS · REV 2026.05
[ USER FLOWS ] · REV 2026.05 · 5 PATHS

Paths
through the app.

How a user moves through each track — transitions, branches, terminal states. v5 is the new direction (gesture-driven object); v4 is the live Story + Levels lab; Tessra runs the v3 flow on mobile. See VERSIONS.md for the matrix.

01

v5 — touch the object

No screens, no menus. Open → a living mosaic that answers every gesture.

v5 drops puzzles entirely. One full-screen mosaic, daily-seeded, breathing on its own. Every gesture feeds the same surface: tap fires a ripple, drag leaves a trail, a long-press re-seeds the whole tiling. With nothing touching it for 8 s the object "calls back" with a pulse from centre.

OPEN /v5-object MOSAIC daily seed + palette ambient breathing GESTURE tap·drag·hold·idle RIPPLE BFS through tiles DRAG TRAIL mini-ripples / 70ms RE-SEED new tiling · 480ms PALETTE CYCLE chip top-right IDLE PULSE · 8s SETTLE fade back to ambient tap drag hold chip idle 8s loop · stays open
START
DECISION
SETTLE / EXIT
──── explicit gesture
┄┄┄ auto / loop

ALWAYS-ON

Underneath every state, each tile pulses on a slow sine (~5.8s period, phase offset by tile id). The surface looks alive even when untouched. A tap near an edge fires a converging counter-ripple from the opposite point.

NO TERMINAL

There is no "complete" state — the loop never ends, it only settles back to ambient. Rapid taps build a shared energy meter (decays ~1s half-life) that brightens edges. Re-seed fires a triple-pulse haptic.

02

v4 — Daily Story → CTA

A one-shot-per-UTC-day story, picked deterministically from 5 templates.

The live web lab. Frames auto-progress; manual tap-skip; swipe-down close. End-card → CTA into the levels. Mark-seen fires on close or on reaching the end, stored in progress.storiesSeen[date].

HOME /v4-story-canvas STORY CARD NEW · TAP TO WATCH (or WATCHED · REPLAY) INTRO date + "TODAY" 3.2s FRAME 2 sunsCount / paletteReveal FRAME 3 geometryFact 5.2s CTA OPEN LEVELS → LEVELS 20 figures HOME seen=true tap 5.0s · auto tap CTA × / swipe ↓ / end
START
DECISION
END / EXIT
──── explicit
┄┄┄ close

STATE

Story data is deterministic by daily.date. Marked seen in progress.storiesSeen[date]=true via onSeen() on finish or close.

EDGE CASES

If the UTC date rolls over (midnight) mid-watch, the current Story isn't interrupted; the next launch after midnight loads a different (new) Story.

03

v4 — Levels · L1 → L20

Linear progression; the next level unlocks on completion. After L20 — paywall stub.

The same 20 canonical Penrose figures, rendered on real multigrid patches. The next level unlocks on completing the previous one. After L20 — a paywall stub for the future mobile-payment integration.

HOME card LEVELS 1/20 LEVEL MAP 20 cards · locks LEVEL Lₙ patch + figure tap slots to fill REVEAL figure overlay n+1 unlocked PAYWALL after L20 stub RETRY same level pick n solve L20 done next →

FIGURE SPEC

Every level declares a spec inside FIGURES_V2: vertexSig / vertexN / chain / decapod / sunWithRing / twinSuns. The engine finds the figure inside a real multigrid patch by its canonical signature.

PROGRESS

progress.unlocked is the index of the first locked level. On complete: setProgress({ unlocked: id + 1 }) + save to penrose-v4-progress.

04

Canvas ∞ — suspended

Built in v4, pulled from the UI 2026-05-28 — float-seam at chunk boundaries.

An infinite Penrose plane: pan / zoom / pinch, brush picker, painted tiles saved to penrose-v4-canvas. Suspended — the chunk-based multigrid has floating-point seams at chunk boundaries. The flow below is the intended design; the fix is tracked in IDEAS.md (and is a prerequisite for the v5 infinite canvas).

HOME card CANVAS ∞ CANVAS load painted state render viewport GESTURE tap / pan / pinch / brush switch PAINT paint tile PAN/ZOOM move camera SWITCH BRUSH picker PERSIST localStorage REGEN CHUNKS on viewport extend HOME painted preserved tap card save back

GEOMETRY

We don't "generate an infinite plane." Instead tilesForViewport(vp) returns tiles inside the current bbox, split into 12×12 world-unit chunks. Each chunk is the output of generateInBox() from geometry-infinite.js, cached in a Map<chunkKey, Tile[]>.

WHY SUSPENDED

Tile ids derive from rounded vertex coordinates; at chunk boundaries floating-point rounding produces duplicate / mismatched ids, so seams show. Fix: sort intersections by line-index keys, not float-rounded (x,y). Then v5 can adopt the same renderer.

05

Tessra Mobile — launch → main

Expo / React Native, the Tessra brand. The store-ready product runs the v3 flow.

On first launch — onboarding (4 steps: Touch / Play / Daily / Flow). Then Home with Today's Mosaic and Playground. The web v3 flow is ported; v4 (Story, Canvas) and the v5 object are not — see the port-gap note below.

LAUNCH Tessra splash ONBOARDED? AsyncStorage progress check ONBOARDING 4 steps Touch · Play · Daily · Flow HOME 2 cards DAILY PLAYGROUND / LEVELS PAYWALL after L20 MENU/ABOUT replay onboarding no yes · skip done

CURRENT STATE

Store-ready (v1.0.0 b1). The v3 flow is ported (geometry, daily, levels, patch engine, paywall stub) plus a native rating prompt via expo-store-review. 20/20 reached on the simulator.

PORT GAP

Mobile's Daily is tile-reveal gameplay; web v4's Daily became a Story, and v5 drops puzzles for an instrument. To decide — port one direction to mobile, or split the tracks deliberately (see IDEAS.md).