# Breathglass design notes

GameLab experiment: **glassblowing with a phone.** Breath, tilt, swing and a lamp are the controls, and the score is how many cents your vessel is off the note a patron asked for.

## The mash-up

- **Wild Choir:** a body is an instrument, and you're judged on harmony. Here the body is a glass vessel and its notes are calculated from its shape.
- **Waymark:** the phone itself is the controller, and the camera pulls material out of the room. Here the camera is a light meter (the glory hole) and a colour sampler (frit).
- **New to GameLab:** glassblowing. Gathers, the glory hole, jacks, paddles, the annealer.

## Constraints (deliberate)

- **Static only.** HTML, CSS and ES modules. No build step, no server, no accounts.
- **No third parties at runtime.** Three.js r185 is bundled in `vendor/` (copied from the Relay Zero vendor set, MIT). Every sound is synthesised in Web Audio.
- **Every sensor is optional** and asked for on its own tap. Each has a button or key stand-in, so the game plays on a desktop.
- **Mic and camera are analysed, never stored.** Only the shelf (shapes, colours, measurements) is saved, in `localStorage` under `breathglass.v1`.
- **Portrait and landscape.** Portrait docks the controls at the bottom; landscape moves them to a side column.

## Files

| File | Role |
| --- | --- |
| `index.html`, `styles.css` | Shell, HUD, sheets. The inline boot script holds the build number and writes the versioned import map |
| `assets.js`, `loader.js` | File manifest and the start-up loader (file checks, WebGL check, reset app cache) |
| `workshop.js` | Game loop, modes, input, commissions flow, shelf, storage, hints |
| `glass.js` | The molten-glass model (pure) |
| `acoustics.js` | Helmholtz voice, shell-mode ring, tuning, shape names (pure) |
| `orders.js` | Patrons, commissions, judging, the Lantern Choir (pure) |
| `practice.js` | Practice-room drills: targets, hold timers, skip and revisit (pure) |
| `sensors.js` | Breath detection, orientation to screen-frame gravity, swing, camera light meter, palette sampling, hum pitch tracking |
| `audio.js` | Furnace, breath voice, struck rings, shelf voices, crack and shatter |
| `render.js` | Studio, pipe, per-frame vessel mesh, heat glow shader patch, shelf, shards |
| `tests/` | `node --test` suite for the pure modules |

## The glass model

- 36 nodes on the mid-wall profile, from the pipe (node 0, pinned) to the tip (on the axis).
- Each segment carries a fixed glass volume. Wall thickness = volume / frustum area, so it's derived, never stored. Volume is conserved through every operation, including remeshing.
- Fluidity: `exp((T - 1000) / 75)`, capped at 3, zero below 560 °C.
- **Breath** inflates along the outward normal at a rate set by the bubble's mean radius over its mean wall. Thin spots run a little ahead (blowouts are possible) but only weakly. The back half is biased forward because the end at the pipe is cooler and anchors the bubble; that's what forms a neck.
- **Gravity** has an axial part (stretch or squat; the neck carries the weight of everything beyond it) and a lateral part integrated in the spinning frame (sag).
- **Jacks** pinch a ring inward. They work properly only while the pipe turns, otherwise they leave marks.
- **Surface tension** mostly slides nodes along the surface, with only a small normal component, so it doesn't deflate the bubble. Thick segments also feed thin neighbours.
- **Heat:** Newton cooling scaled by 1/sqrt(wall), the pipe drinks heat near node 0, the glory hole pulls toward 1180 °C, and conduction runs along the wall.
- **Failure:** cold below 520 °C builds a crack meter. A wall under 0.28 mm while blowing bursts.

## Acoustics

- **Voice:** `f = c / 2π · sqrt(A / (V · L'))` with `c = 343 m/s`. A is the narrowest opening between the cracked-off mouth (a fixed 5.5 mm bore) and 40% down the profile; V is the air beyond the neck; L' is the neck length plus 1.5 neck radii.
- **Ring:** the (2,0) shell mode, `f = 66900 · w / R²` in cm for soda-lime glass, from the widest band. Partials at the n = 3 and n = 4 mode ratios (2.83, 5.42).
- Judging is in cents against the commission. Truth (from sag) and wall evenness shape the pay.

## Sensors

- Breath = loudness over an adaptive noise floor × spectral flatness (100 Hz to 4 kHz) × a low-band bias. Echo cancellation is on, so the game's own breath tone doesn't feed back.
- Hum = autocorrelation pitch when the sound is loud and tonal.
- Gravity comes from `deviceorientation` beta/gamma (more consistent across iOS and Android than raw accelerometer signs), rotated into the screen frame by `screen.orientation.angle`. A "Flip tilt" setting covers odd devices.
- Light furnace = the fraction of blown-out pixels in a 48×36 camera frame, plus a little mean brightness.

## Shipping a change

Bump `build` in the boot script in `index.html` whenever any of our own files change. That version goes on every module URL, so browsers fetch the new set together. Add any new module to `assets.js` and to `mods` in the boot script (the test checks both).

## Known limits

- The ring model treats the widest band as a cylinder; real glass also rings in its foot and stem, and this glass has neither.
- iOS routes audio through the play-and-record session while the mic is on, which can make the game quieter.
- Transmission and bloom are heavy on old phones; the game switches itself to Lite if a frame takes longer than about 42 ms.
