fuzzyworld

basin / files / readme.md

readme.md

Blame: the commit that last changed each line · file · history

ef44314f 2026-06-30 Pavel Safronov1# basin
2
85526504 2026-07-01 Pavel Safronov3A browser puzzle game about water basins — read a coastline and deduce where the
4rivers begin.
5
f8e9c765 2026-10-27 Pavel Safronov6- **▶ Play:** <https://apps.fuzzyworld.net/basin/>
7- **Source:** <https://code.fuzzyworld.net/basin/>
85526504 2026-07-01 Pavel Safronov8
9Built with Vite + vanilla TypeScript + Canvas2D; no framework, no backend. Every
10board is a seed, so a board is just a link.
11
12## How it works
a7bf123a 2026-07-01 Pavel Safronov13
14Each board has a heightmap that falls to the sea at its **ocean** sides. Water
15released from a *spring* flows downhill, branching to every strictly-lower
16neighbour, and exits wherever it reaches an ocean edge. Every board is generated
17with a hidden set of springs; their combined flow lights up a **target edge
36e6a8b9 2026-07-02 Pavel Safronov18signature**, shown as gold outlines on the *shore* — the land cells immediately
19inland of each wet ocean edge, rather than the ocean cells themselves.
a7bf123a 2026-07-01 Pavel Safronov20
21Each of the four sides is independently rolled **ocean** (blue — water exits) or
22a **mountain wall** (high ground that blocks water). Walls make canyons: e.g.
23north + south walls with east + west oceans is a valley that drains sideways.
24Water can never leave through a wall.
25
03c2cc12 2026-07-01 Pavel Safronov26The terrain can also be **aged**: an *erosion* amount simulates how much
27weathering happened before you arrived. At 0 the board is a pristine dome; turn
28it up and simulated hydraulic erosion carves grooves and channels, ragging the
29coastline and moving where water finally reaches the sea — so a heavily-aged
36e6a8b9 2026-07-02 Pavel Safronov30board reads a visibly different edge signature from its pristine self. Erosion
31is self-limiting: if a particular seed would wash away too much standing land,
32generation automatically backs off the effective aging for that board so it
33never degenerates into an almost-all-water board.
34
35**Reach** controls how far from the shore the hidden springs are drawn from:
36low values favour land just above the waterline, high values favour high
37ground near the peak, and the midpoint has no preference (the original
38behaviour). This only shapes which legible answer generation reaches for — it
39never affects the unambiguous-solution guarantee below.
03c2cc12 2026-07-01 Pavel Safronov40
85526504 2026-07-01 Pavel Safronov41## How to play
42
a7bf123a 2026-07-01 Pavel Safronov43**Goal:** find the positions of the hidden springs that reproduce that signature.
44Tap cells to explore the terrain — a wrong tap just previews its flow (and marks
45any edges it wrongly wets in red), while landing on a real spring **locks it in**
46(green, numbered). Find them all to solve the board.
47
85526504 2026-07-01 Pavel Safronov48Generation guarantees the puzzle is **unambiguous**: the hidden springs are the
49*only* set of that size whose combined flow produces the target signature, so the
50gold-edge hint always pins down a single answer (no redundant or interchangeable
51springs). Boards that can't be made unique fall back to the richest signature
52found, so a board is always produced.
53
36e6a8b9 2026-07-02 Pavel Safronov54Springs also never sit on a shore cell — the gold outline never doubles as a
55giveaway for one of the hidden positions. (Only an extreme combination, like a
565×5 board with 6 springs, can make this strictly impossible; generation falls
57back gracefully there rather than failing.)
58
85526504 2026-07-01 Pavel Safronov59## Configuring & sharing boards
a7bf123a 2026-07-01 Pavel Safronov60
61Every axis of a board is a control in the panel: **size**, **spring count**, which
36e6a8b9 2026-07-02 Pavel Safronov62sides are **walls**, the **aging** (erosion) amount, **reach**, and the **terrain
63seed**. All of it is captured in a single shareable **code** — a 9-character token
64(e.g. `B0B0bgTL9`) that packs the six axes into 52 bits and base64url-encodes
65them, so the settings aren't legible at a glance (reversible obfuscation, not
66encryption). The code and the controls are two-way views of the same config —
67changing any control rewrites the code, and pasting a code sets every control.
68The reach axis grew the code by a character, so pre-reach 8-character codes no
69longer decode — a stale shared link just rolls a fresh board instead.
a7bf123a 2026-07-01 Pavel Safronov70
71The code also lives in the URL as `?code=<code>`, so a link reproduces the exact
72board. **🎲 Random** rolls a whole new config; the 🎲 next to Terrain rerolls just
73the terrain while keeping the other settings.
74
b9acd697 2026-07-01 Pavel Safronov75Each axis also has a **lock** (🔓/🔒) toggle. Locking an axis pins it: **🎲 Random**
76then rerolls every *other* setting while leaving the locked ones untouched — so you
77can, say, freeze the size and walls and keep rolling only the terrain and springs.
78Locks are a session convenience and aren't part of the shared code.
79
a7bf123a 2026-07-01 Pavel Safronov80## Develop
81
82```bash
83npm install
84npm run dev # Vite dev server
85npm run build # type-check + production build
86npm test # run the model tests (Vitest)
87```
88
85526504 2026-07-01 Pavel Safronov89## Deploy
90
91`.gitlab-ci.yml` builds the app and publishes it to **GitLab Pages** on every push
92to the default branch. Vite is configured with `base: '/basin/'` and builds into
93`public/` (the dir Pages serves), so the live site is
f8e9c765 2026-10-27 Pavel Safronov94<https://apps.fuzzyworld.net/basin/>.
85526504 2026-07-01 Pavel Safronov95
a7bf123a 2026-07-01 Pavel Safronov96## Architecture
97
98The model (typed arrays) is decoupled from the renderer so a tiled/WebGL renderer
99can replace Canvas2D for large boards later.
100
101- `src/model/heightmap.ts` — terrain + per-side edge types (ocean/wall) +
03c2cc12 2026-07-01 Pavel Safronov102 droplet-based hydraulic **erosion** (the aging axis; skipped, and rng-free, at
103 0) + priority-flood depression filling (guarantees every cell drains to an
104 *ocean* edge, seeded only from ocean sides).
a7bf123a 2026-07-01 Pavel Safronov105- `src/model/flow.ts` — the branching watershed flood (single- and multi-spring).
106- `src/model/puzzle.ts` — the board `Config`, its shareable code (encode/decode),
85526504 2026-07-01 Pavel Safronov107 config-driven puzzle generation (with an unambiguous-solution check), and the
108 solution check.
a7bf123a 2026-07-01 Pavel Safronov109- `src/render/renderer.ts` — Canvas2D board + puzzle overlay.
110- `src/game.ts` — puzzle state: exploration probe, locked springs, win detection.