# quack
A tranquil 3-D duck pond in the browser, made for phones first. No UI, no HUD —
the scene is the interface. Drag to look around, pinch to lean in, and tap the
water to throw a piece of bread.
quack is a port of [celphase's duck-pond](https://github.com/celphase/duck-pond) —
a high-quality duck *audio* synthesizer written in Rust — from sound to sight.
The ducks live the same lives: the time budget of dabbling, resting and preening,
the depth bands they drift between, broods trailing their mother, a drake
escorting his mate. They also found their voices: the vendored Rust stage runs in
a worker behind an AudioWorklet, so every call the flock decides on is
synthesized, placed in the listener's frame, delayed and reverbed in the same
core as the original pond. Bread, dabbling and the water bed are wired in too;
[docs/audio.md](docs/audio.md) maps every sound to the action that now makes it.
## Run
Node 20.19+ or 22.12+ (what Vite needs).
```sh
npm install
npm run dev # dev server (Vite)
npm run build # production build into dist/
npm run preview # serve the build locally
```
Add `?seed=N` for a repeatable pond. The default seed is `20261018`.
Production builds always write source maps. The fifth title card points at
`SOURCE.src_url` in `src/config.ts`; while that is empty it downloads
`quack-source.zip`, packed from the working tree by `scripts/source-zip.mjs`
(credentials, build output and the vendored Rust clone stay out).
## Controls
| input | action |
| --- | --- |
| drag | look around — the head turns in place, clamped so the pond stays in view |
| pinch / wheel | zoom — open the view wide, or lean in close (clamped) |
| tap / click | throw bread — it leaves your hand on release |
| a press that drifts past 12 px | that was a camera drag, not a throw |
| cursor (fine pointer) | every duck's head follows it across the screen |
Sound follows the first press or key, as browser autoplay rules require; a tap
to throw bread is usually all it takes. `?audio=0` keeps the pond silent, and
`?headphones=1` starts in headphone rendering. Hidden conveniences for whoever
opens the console: `space` pauses (and holds the sound), `T` runs the clock at a
quarter speed, `H` toggles headphone rendering, `Y` skips a season, and `1`–`4`
pin the year at spring / summer / autumn / winter.
## The pond
- Twelve mallards: hens **Mabel**, **Clover** and **Hazel**; drakes **Rufus**
(Clover's mate) and **Bramble**, on his own; and two broods — Mabel trailing
Pip, Dot, Bean and Fig, Hazel with Nib, Tuft and Sprout.
- A full year passes in 28 minutes, seven minutes to a season, each blending
into the next. The sun wanders and the light warms and cools; winter freezes
the pond from every edge inwards, but for one patch of open water at its
middle — the lead — which the flock gathers to and keeps open all season, and
out onto the ice of which they will walk for bread.
- The flock runs duck-pond's activity model — swim, dabble, rest (and sleep:
head turned onto the back, bill tucked in but still visible), preen, stray.
Ducklings follow their mother and wait when they lose her; a drake keeps
near his mate.
- Bread: one piece per throw, two or three bites each, and a stretch of
begging where the bread landed until the ducks give up and drift back to
their day. Bread nobody comes for sinks away.
- You stand at the water's edge on the near bank. The ground climbs clear of the
water, so the shore is a border at your feet — tilt down and it is right there —
and `World.shore` marks out the walkable stretch of it (130 m each way, 18 m
deep).
## Layout
```
index.html entry point: canvas host, loading card, error card
src/main.ts boot, frame loop, input (tap-throw, hidden keys), debug handle
src/config.ts every tuning dial, in one export per scope
src/world.ts terrain, water, sky, shore, forest, grass, season paint
src/seasons.ts the year: four keyframe seasons, always blending
src/pond.ts the flock: roster, time budget, families, bread and begging
src/duck.ts duck rig and poses
src/camera.ts first-person rig: a planted eye, turning and zooming
src/effects.ts ripples, splashes, dust and sinking particles
src/audio.ts the sound stage: event plumbing, listener frame, worklet
src/hints.ts the title cards: controls, credit, source
src/utils.ts RNG, value noise, easing, geometry helpers
vite.config.ts production build shape: source maps always on
public/ loading-card stills, audio worker/worklet, wasm, generated source zip
scripts/shot.mjs puppeteer screenshot harness used while tuning
scripts/sample.mjs reads a shot back as a colour column, for tuning
scripts/source-zip.mjs packs the source-card fallback from the working tree
scripts/loader-shot.mjs re-shoots the card's backdrop and packs it to JPEG
scripts/build-audio.mjs rebuilds the wasm stage from the vendored clone
audio/duck-pond-stage.patch the additive Rust API this repo's audio needs
docs/port-notes.md port decisions, tuned constants, debug recipes
docs/audio.md sound by sound: what the flock does and the core hears
docs/deploy.md build, deploy, rollback, loading-card re-shoots
```
Two folders on the side, deliberately kept out of this repo's history:
- `duck-pond/` — the vendored Rust clone and the study notes behind the port.
`scripts/build-audio.mjs` rebuilds `public/quack_audio.wasm` from it; the
small stage API added there is kept in `audio/duck-pond-stage.patch`.
- `temp/` — scratch space (screenshots and such), git-ignored.
## Credits
- **[duck-pond](https://github.com/celphase/duck-pond)** by celphase (MIT):
the behavior model, time budgets, family rules and speeds are ported from
its `scene.rs`, and the Rust synthesizer, propagation and reverb run
unchanged in `public/quack_audio.wasm`.
- **[three.js](https://threejs.org)** (MIT): everything you see.
## License
MIT — see [LICENSE](LICENSE).