No description yet.

$ git clone https://gitman.io/git/krokodeal/quack

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 — 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 maps every sound to the action that now makes it.

Run

Node 20.19+ or 22.12+ (what Vite needs).

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 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 (MIT): everything you see.

License

MIT — see LICENSE.