# Orbital Lab

The Adom logomark is a hydrogen 2p orbital.

    psi = (2p_x + 2p_y) / sqrt(2)

A 2p orbital pointing along the [110] diagonal is not "p_z rotated": it is an equal
superposition of 2p_x and 2p_y, and its angular surface, cut to the mark's quadrants
and measured against the real logo vectors, matches the brandmark at 99.3% IoU.
This page is the working proof.

<img src="screenshots/final-anim-hold.png" width="760" alt="The 3D orbital held on the brandmark at the epoch">

<img src="screenshots/lab-logo-cheat.png" width="760" alt="The alignment tab measuring the orbital against the real logo vectors">

## What is on this page

- **Overview (this page)**: the live lab. Four tabs: Explore (all 16 real spherical
  harmonics through l = 3 as live gain sliders over an evolving superposition),
  Logo cheat (align the orbital against the real logo vectors and measure the fit),
  Scratch (the earlier cut kept for experiments), and Final (the directed cut: teal
  intro, play, dinner call, cheat, sit, back out, a fresh roll every pass). Scroll below the lab for the loader docs.
- **loader.html**: the animation as ONE dependency-free file for webview load screens.
- **orbital-lab.html**: the full standalone app (CDN Babylon).
- **assets/**: the logomark vectors and the 128-sample measured edge profile.
- **tools/**: the generators. `build-readme.py` produces readme.html for the wiki
  sandbox, `build-loader.py` distills loader.html from the lab (the GLSL is extracted
  verbatim so there is one source of truth), `orbitals.py` solves the analytic
  hydrogen states the whole thing is built on.

## Use it as a webview loader

`loader.html` is raw WebGL: no Babylon, no CDN, no fonts, no fetch. One file, about
24 KB on disk and under 10 KB gzipped, zero external requests, one draw call over
12,800 vertex-shader-displaced triangles, roughly 0.06 ms of JS per frame. The GLSL
is extracted verbatim from the Babylon lab at build time, so the two render
identically; the wheel is tuned against the lab's MEASURED response: the same
12-notch burst dips both viewers to the same radius (3.99 vs 3.995) and both
spring home in the same beat, zooming toward the cursor with the springy feel
that always breathes the framing back to center. Serve it beside your app and overlay it while the real content loads:

    <iframe src="loader.html"
            style="position:absolute;inset:0;width:100%;height:100%;border:0;background:#0d1117">
    </iframe>
    <!-- when your app is ready, fade the iframe out and remove it -->

It sizes to the limiting axis of any container and never repeats: a fresh random
state is rolled at every epoch.

| param | default | meaning |
|---|---|---|
| pt | 4.5 | playground seconds of free evolution between epochs |
| dt | 3 | seconds to steer onto the mark, and back out |
| ct | 0.3 | seconds before the epoch the outline cheat fades in |
| st | 0 | seconds the mark is held dead still |
| logo | 100 | peak opacity of the flat teal mark, in percent |
| llead | 1 | seconds the logo fade starts before the sit and ends after it |
| tlead | 1.5 | seconds before the epoch the dark lobe fades to teal |
| ov | 1 | 0 makes the cheat its own act after the steer |
| intro | 1 | 0 skips the teal logo intro |
| mode | performance | lightweight bundles fps 30, dpr 1, seg 48, no AA; performance (alias sexy) is full quality; explicit params override |
| seg | 80 | sphere tessellation 24 to 128 |
| aa | 1 | 0 disables antialiasing |
| dpr | 2 | devicePixelRatio cap, 1 to 3 |
| fps | off | cap the render rate for spinner duty (floor 20); rAF yields between frames and stops when hidden |
| bg | 0d1117 | hex background, or transparent to composite over the container's background (GL alpha); also settable live by postMessage |
| hint | 0 | 1 shows the small click-to-zoom pill in armed contexts; off by default for purity; also flippable live by postMessage |
| margin | 0 | percent of the container left empty around the mark (80 = small and centered, 20 = nearly fills); zoom-out caps at 40% past the margined framing |
| interact | 0 | 1 arms click-to-zoom (previews and demos, never load screens) |

## Why raw WebGL, and not Babylon or three.js

A load screen exists to be on screen while your real app is still fetching; an
animation that first downloads a 3D engine defeats its own purpose. The main
viewer on this page IS Babylon.js 9.18, where an engine earns its weight; the
loader is that lab distilled to one dependency-free file.

| approach | payload gzipped | requests | first frame |
|---|---|---|---|
| Babylon.js + app | ~1.4 MB engine + page | CDN + page | after download and parse |
| three.js + app | ~170 KB engine + page | CDN + page | after download and parse |
| loader.html | 9,912 bytes, everything included | zero | tens of milliseconds |

Measured on this build: 24,379 bytes on disk, 9,912 gzipped, including shaders,
the 286-point logomark vectors, the 128-sample edge profile, and the timeline.
Runtime: 0.057 ms of JS per frame (about 1.4% of a 240 Hz budget), one draw call
over 12,800 triangles / 6,561 vertices, all displacement in the vertex shader,
and requestAnimationFrame pauses it when hidden so a backgrounded webview costs
nothing. Spinner duty at 32x32 on a 240 Hz display, measured: one instance about
1 to 3% of a core, eight concurrent about 9%, eight capped at fps=30 about 2%
total; the mode presets on the same fleet: performance 8.2% of a core,
lightweight 1.7%, about five times cheaper. Host page locked at 240 fps
throughout; divide by four on 60 Hz; hidden is exactly zero.

Fidelity: tools/build-loader.py extracts the GLSL verbatim from the Babylon lab
at build time (one source of truth), reproduces Babylon's left-handed matrices
exactly, chirality pixel-verified. Camera feel is tuned against the lab's
measured curve: a Babylon-style decaying inertia integral plus the lab's 6% and
5% springs at 240 fps cadence, frame-rate independent, zoom toward the cursor,
glide home before every logo.

## The mouse: arming, zoom to cursor, springiness

Inert by default (a load screen must never eat input); `?interact=1` arms it:
one click captures the wheel (teal ring + glow), click-away or blur releases.

Zoom to cursor solves the world point under the pointer, `w = n*r/m + pan`,
applies the radius step, then re-solves pan so that point stays pinned:
`pan' = w - n*r'/m`. Measured drift across a six-notch corner zoom: 0.002
world units.

Three exponential systems, frame-rate independent via `pow(k, dt*240)`:

| system | per frame | feel |
|---|---|---|
| inertia integral | notch deposits `vel += r*0.085`; consumed by `vel*(1-0.8^(dt*240))` | notches glide, not step |
| radius spring | `r += (4.4-r)*(1-0.94^(dt*240))` | zoom breathes back home |
| centering pull | `pan -= pan*(1-0.95^(dt*240))` | the mark re-centers |

Tuned by measurement: identical 12-notch bursts driven at both viewers in
instrumented pup windows; lab dips to 3.995 / home in ~200 ms, loader 3.990 /
~250 ms. The epoch override glides any user pose to the calibrated home before
every logo, locks the wheel through the sit, and hands control back from home
the instant the sit ends.

## Resizing

The loader treats size as live input: window resize plus a ResizeObserver,
canvases re-backed at devicePixelRatio (capped 2), FOV axis picked by the
limiting dimension. Proven across 300x620, 1100x300, 420x420, 180x140,
1300x700, 900x560 with the GL buffer tracking the container 1:1, including
continuous mid-animation resizes; re-backing is coalesced into the render loop
(events mark dirty, the size applies and draws in the same frame), so drags
repaint clean every frame with no blank flashes. Run `resize-proof.html` from the repo to
torture-test it in your own container.

## The three tuned cuts

The settings the loop was tuned to, saved live while dialing it in. All three ship in
the Final tab's snapshot pulldown, and each maps to loader URL params.

| cut | numbers | why it feels the way it does |
|---|---|---|
| john fav (the page preview) | pt 5, dt 0.7, ct 0.2, st 1, logo 100%, llead 0.6, tlead 0.2 | The keeper: a 0.7s dive into a real 1s sit, teal unifying only 0.2s before arrival so the colour snap lands WITH the logo. Loader: `?pt=5&dt=0.7&ct=0.2&st=1&llead=0.6&tlead=0.2` |
| john 1 (default) | pt 4.5, dt 3, ct 0.3, st 0, logo 100%, llead 1, tlead 1.5 | The stately cut: a long 3s dive so you watch the physics converge, the cheat held to the last 0.3s so you never catch it working, no sit, the logo moment living entirely on its 1s lead. Loader: no params. |
| snap 2 | pt 5, dt 1, ct 0.2, st 0, logo 100%, llead 0.3, tlead 1.5 | The middle cut: a 1s dive, a tight 0.3s logo flash. Punchy, still readable. Loader: `?pt=5&dt=1&ct=0.2&llead=0.3` |
| snap 3 | pt 5, dt 0.7, ct 0.2, st 0, logo 80%, llead 0.5, tlead 1.5 | The heartbeat cut: a 0.7s dive, logo capped at 80% so the 3D shows through the flash. A pulse, not a poster. Loader: `?pt=5&dt=0.7&ct=0.2&logo=80&llead=0.5` |

Knob meanings: pt free play between epochs; dt the dive onto the mark; ct how late the
outline cheat is held back (smaller = sculpting nearly invisible); st dead-still hold;
logo the flat mark's peak opacity; llead starts the logo fade that many seconds before
the epoch and ends it that many after; tlead turns the dark lobe teal that early.

## Rebuilding

    python3 tools/build-loader.py   # loader.html from orbital-lab.html
    python3 tools/build-readme.py   # readme.html for the wiki sandbox

Note: the wiki page renders readme.html, so page-visible edits go there, not here.
