# jason — preview

A minimal iPhone companion for the GATE visualizer. Warm monochrome surfaces, sparse typography set in IBM Plex, thin rules, and a touch-reactive field of light.

Everything below is rendered from the app's own runtime. The point coordinates, telemetry numbers, playback positions, and log lines are produced by the shipping `jasonCore` code paths — `SignalGeometry`, `DemoDeviceTransport`, and `AppModel` — not by hand-authored mockup data. See [How these were made](#how-these-were-made).

---

## The interface

![jason interface demo](docs/preview/jason-demo.gif)

*90 frames replaying a scripted session through the app's own motion system: a drag with 1:1 tracking, a flick whose momentum carries after the finger lifts, four scene blends — one deliberately interrupted mid-flight — an enveloped audio change, and the idle state. The caption, timecode, progress bar, FPS, and point count under each frame are the transport's actual values at that step.*

---

## Lock

The demo opens behind a passcode gate, so a shared build stays private. The idle field stands in for the mark; the type is IBM Plex throughout — Plex Sans for "Locked", Plex Mono for the kicker, prompt, and entry.

| Light | Dark |
|---|---|
| ![Lock, light](docs/preview/lock-light.png) | ![Lock, dark](docs/preview/lock-dark.png) |

The passcode is a single shared secret (`jason`) defined in `LockViewController.swift` — a placeholder to keep the interface private, not an account system. On success the gate springs up and fades, revealing the app already warm behind it; a wrong entry shakes the field and clears it. Reduce Motion swaps both for a straight cut.

---

## Now

The hero screen. A live field of light reacts to touch and to the demo audio signal; the now-playing chrome yields to the artwork.

| Connected | Dark |
|---|---|
| ![Now, light](docs/preview/now-light.png) | ![Now, dark](docs/preview/now-dark.png) |

| Touch | Idle |
|---|---|
| ![Now with touch interaction](docs/preview/now-touch.png) | ![Now, idle](docs/preview/now-idle.png) |

Dragging repels nearby points within a radius of `0.21 × min(width, height)`, falling off linearly with distance — the deformation in the third image is `SignalGeometry.points(…, interaction:)` output, not an illustration of it. When the device is not connected, the view substitutes a slow breath signal and dims toward 52% opacity while pulling the field very slightly back: alive in silence, per the visualizer's doctrine.

### How the motion works

The field is not redrawn frame by frame from raw geometry; it is driven by a small physics layer in `jasonCore/Motion` so that it behaves like something physical you can touch.

- **Touch tracks 1:1 and responds on touch-down**, not on release. Waiting for the finger to lift before showing anything is what makes an interface feel dead.
- **Releasing hands the finger's velocity to a spring** and projects where the flick was going (`Momentum.projectedOffset`, the exponential-decay model that matches scroll deceleration). Without that handoff the field visibly stops the instant the finger lifts — a seam you can feel.
- **Scene changes blend and can be interrupted at any frame.** Scenes are weighted layers rather than a from/to pair, so tapping a third scene halfway through a transition re-aims from wherever every point currently is. The GIF shows this deliberately: "Interrupted mid-blend" changes scene while the previous blend is still running, and the field stays continuous. A from/to design fails this — the test `rapidSceneTapsStayContinuousAndBounded` was written against that failure.
- **Springs are described as response + damping ratio**, not mass/stiffness. Critically damped (`1.0`) is the default everywhere; overshoot is reserved for motion the user's own gesture set going, like a flick's tail.
- **Audio drives the field through an envelope** with fast attack and slow release, so a level change swells rather than jumping.
- **Reduce Motion** replaces all of it with a static, still-legible field, and the display link stops entirely.

Because the integration is closed-form and time-based rather than per-frame, the same motion settles over the same wall-clock interval at 30 Hz or 120 Hz, and it is reproducible — which is what lets these previews be generated at all.

## Scenes

![Scenes](docs/preview/scenes.png)

Four curated systems carried over from the Rust visualizer's flagship rigs. Each thumbnail is a live `SignalView` rendering that scene's own geometry family — concentric rings, a bilateral wing fold, crossing filaments, and a single orbiting trajectory.

## Settings

![Settings](docs/preview/settings.png)

Appearance follows System / Light / Dark. The Demo section states the connection situation plainly: transport is a local simulation, the device API is not connected. Developer Mode is an opt-in toggle that adds a fourth tab immediately.

## Developer

![Developer](docs/preview/developer.png)

The operator surface, dense and immediate. Force Boot/Idle/Connected, inject level and frequency bands, change the point budget and render flags, and read telemetry and the command log.

The values shown are a real recorded session. Note two things the capture demonstrates rather than asserts:

- **Telemetry is derived, not decorative.** `POINTS 13,075` is the transport computing `min(budget, budget × (0.58 + level × 0.24))` for a level of 0.61 against an 18,000 budget.
- **The clamp contract holds.** The log's top line reads `render → 900000 pts` because that is the command the UI sent; the transport clamped it to the 60,000 ceiling. An injected level of `4.2` likewise lands at `1.0`.

---

## What this is, and is not

This is an **offline local demo**. It does not discover or control physical JASON hardware, because the Rust visualizer does not expose a companion network protocol yet. Every simulated connection is labeled `DEMO` in the interface — that badge is deliberate, not placeholder art.

A `DeviceTransport` protocol sits between the UI and the demo transport so a network implementation can drop in without the UI changing. The proposed contract for that — Bonjour discovery, a WebSocket session, snapshot/event/command envelopes with request IDs — is written up in [`visualizer-passoff.md`](visualizer-passoff.md) for the agents working in the visualizer repository. Until it is ratified and implemented on the Rust side, this app keeps saying `DEMO`.

## How these were made

There is no iOS Simulator on Linux, and this app is built on Debian with [xtool](https://xtool.sh/). So the previews are generated by instrumenting the app instead of screenshotting it:

```sh
./tools/preview/generate.sh
```

Two steps:

1. **`Sources/preview-capture`** — a host-side executable that drives the real `AppModel` and `DemoDeviceTransport` through a scripted session, stepping the simulation deterministically via `DemoDeviceTransport.advance()`. The animation is stepped through the same `SceneTransition`, `SignalEnvelope`, and touch springs that `SignalView` uses, so the GIF shows the shipping motion behaviour rather than raw geometry. After each command it records the authoritative snapshot, the telemetry, the event log, and the resulting point field. Output is one JSON recording: 97 point fields, 8 snapshots, 90 animation frames.
2. **`tools/preview/render.py`** — draws that recording using the UIKit layer's own metrics: `Palette`'s GATE tokens, `UIFactory`'s 52pt square buttons and hairline rules, and `SignalView.draw(_:)`'s ellipse sizing (`base × (0.78 + weight × 0.45)`, ink at 86% / 52%).

The renderer reproduces UIKit's *drawing rules*; the *data* is the app's. That boundary is the honest one to state: these are faithful renderings of real runtime state, not screenshots of the app running on iOS hardware. Device screenshots require a physical iPhone, `xtool setup` authentication, and a USB deploy — see [`README.md`](README.md).

Determinism is covered by tests (`tools/test-core.sh`, 44 passing): two transports stepped identically must produce byte-identical snapshots, springs must settle to the same value at any frame rate, and an interrupted scene blend must never jump. That is what makes these previews reproducible.
