Water-droplet v1 + patch resolution over a DEM (PROPOSAL — pending Stephen's confirmation) (Agentscript Webgpu)

**Note** from Bead: Agentscript Webgpu · [canonical source](https://redfish.acequia.io/guerin/.agents/f4ef67b7-001f-412c-ba90-f93ceba92bda/2026-06-17/notes/02-water-droplet-v1-and-patch-resolution.md) · session 2026-06-17 · discussion: Talk: Agentscript Webgpu

> Opened 2026-06-17 from Stephen's directive (verbatim in the chat log). First running test of the > agentscript-webgpu port. **This note is a restatement-for-confirmation, not a ratified decision** — the > near-side cull rule (§3) has one ambiguity flagged for Stephen.

## DECISIONS (confirmed 2026-06-17, via the interactive form) - **Gridless v1.** No patch grid in v1 — droplets are turtles sampling the DEM directly (patches/water field deferred to v1.5). Supersedes §3's "focal shell" patch-grid framing. - **Downhill = continuous Sobel gradient (B), not pick-lowest-neighbor (D8).** Each droplet samples DEM height at a 3×3 Horn/Sobel stencil spaced `h` and steps along `−∇h` (continuous direction). D8 (A) / D-infinity (C) deferred to v1.5 once the patch water-field exists (flow accumulation / pooling). - **`h` = a single global scrubable value** (the Sobel tap spacing = the resolution knob). View-center- raycast and per-turtle-distance `h` are later modes. - **Flats/pits caveat:** where `∇h ≈ 0` a droplet stalls → add a small jitter / random-walk fallback (the soft-constraint move) until the water field exists. - **Editor:** CodeMirror 6 (pending only final confirm). **Contacts:** moved to the `contacts` bead `e47cb39d` (no longer in this bead).

## The model First integrated test = a **water-droplet model on the Terrarium DEM**. It is the natural first combined exercise of two AgentSets at once: - **Patches** carry the **elevation field** sampled from AWS Terrarium DEM tiles (`s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png`, decode `(r·256 + g + b/256) − 32768`, the same source `ants-in-taos` used) plus a **water-depth** field. - **Turtles** are the **droplets**, running downhill (steepest-descent / `uphill`-negated over the patch elevation), depositing/!carrying water — a clean turtles×patches coupling, rendered in taos-engine.

## Elevation + slope/aspect on the GPU — taos-engine already bakes the Sobel Stephen's question: can a turtle get elevation-here cheaply on the GPU, and apply a local Sobel for slope and aspect? **Yes to both — and the engine already did the Sobel.** Read from real source (`Documents/src/taos-engine`): - **`TerrainSystem`** (`src/terrain/terrain_system.ts`) holds a GPU **heightmap atlas** (`_heightmapTex` + view + sampler, paged virtual-texture). `setHeightmap({origin, extent, heightScale, floorHeight})` (`HeightmapOptions:134-147`) maps **world-XZ → atlas uv**: `origin` = world XZ of texel (0,0), `extent` = world span, `height_world = atlas.r * heightScale + floorHeight`. - The atlas is **RGBA and pre-packs the normal** (`shaders/terrain/terrain.wgsl:115-125`): `.r` = normalized height, `.g` = normal **x**, `.b` = normal **z**; `ny = sqrt(1 − nx² − nz²)`. The DEM bake pass (`geo/raster_dem_bake.ts`) ran the gradient once at bake time — `.g/.b` **is** the Sobel. **Elevation-here is one tap:** `elevation = s.r * heightScale + floorHeight` (the `sample_height` fetch). **Slope/aspect: the turtle runs its OWN 3×3 Sobel on the height `.r` — at ITS resolution.** (Correction, 2026-06-17, Stephen's catch: I first said the baked atlas normal `.g/.b` makes a 3×3 redundant — wrong. The baked normal is fixed at the **engine's bake/render scale**; a droplet needs slope/aspect at **its own scale**, and that scale **is the Sobel tap spacing `h`**.) So the turtle samples `.r` at a 3×3 stencil spaced `h` around its (x,z) and applies the **GIS-standard Horn (1981) operator** (= a `1,2,1`-weighted Sobel, what ESRI/GDAL use): - `dz/dx = ((z₂ + 2z₅ + z₈) − (z₀ + 2z₃ + z₆)) / (8h)` - `dz/dy = ((z₆ + 2z₇ + z₈) − (z₀ + 2z₁ + z₂)) / (8h)` - `slope = atan(√(dz/dx² + dz/dy²))`, `aspect = atan2(dz/dy, −dz/dx)`, **downhill** `= −(dz/dx, dz/dy)`. ~6–9 texture taps, cheap. **`h` is the resolution knob** — the per-turtle Sobel spacing *replaces* the patch-grid resolution debate (§3 below). This is **independent of the mutable-water question**: the Sobel is the base "local slope here" operator either way; a water-depth field is a *separate, additive* layer (§ "do we need patches"). The baked normal `.g/.b` stays a free fallback when engine-scale is acceptable. (Also noted: `geo/water.wgsl` + `water_feature.ts` exist — prior-art water *surface*, distinct from our ABM droplet physics.)

## The core problem Stephen raised: oblique views have non-uniform resolution In an oblique 3-D camera, a fixed world-space patch grid has wildly varying **screen-space** size: foreground patches are huge, distant ones sub-pixel; and the DEM is **tiled per slippy zoom level**, so the foreground wants a high zoom (fine tiles) and the distance a low zoom (coarse tiles). ### Recommendation — separate the three resolutions; keep the SIM grid single-resolution in v1 Three things get conflated; pull them apart: 1. **Simulation resolution** — the patch grid the water physics runs on. **Must be one uniform grid** (neighbor stencils, mass conservation, and flow routing all assume uniform spacing — `notes/01` §4). Making the *sim grid itself* multi-resolution breaks the physics at resolution seams (fine→coarse flow needs flux/area matching — a real problem, not v1). 2. **DEM source resolution** — which tile zoom(s) we fetch elevation from (discrete). 3. **Display resolution** — screen pixels per patch, which varies across the frame (perspective). The honest strategies for the oblique case, increasing in cost: - **A — single uniform grid sized at the view-center** (recommended for v1): one zoom, one patch size, clamp the footprint by near/far thresholds. Physics is trivially correct. Foreground is over-sampled, distance under-sampled; the clamp throws away the wasteful tail. - **B — view-aligned / ground-projected grid** (patches ~uniform on screen): needs a per-patch area Jacobian in the physics → **defer**. - **C — nested clipmap / quadtree rings keyed to tile zoom** (the scalable answer for wide vistas): multi-res seams need flux matching → **v2+**. → **Go with A for v1.** Name B and C as the upgrade path. Don't build multi-resolution physics yet.

## v1 spec (as I understand Stephen's directive) 1. **Grid fills the screen, ~960 patches wide × aspect-derived height** ("approx 960×whatever"). At 16:9 that is ≈ 960×540 ≈ 520k patches — fine for WebGPU compute (this is the point of the GPU port). 2. **Base resolution from the view-center raycast.** Raycast the camera-center ray to the DEM; the ground point's distance/scale sets **meters-per-patch** (so ~960 patches span the visible width *at the center*) and selects the **Terrarium zoom** whose ground-sample-distance best matches. 3. **Min/max screen-size falloff (the cull band).** Each patch has a screen-space pixel size that grows toward the foreground and shrinks toward the distance. Render only where that size is within `[min, max]`: - **below min** (too far / sub-pixel) → don't render or simulate (wasteful + aliasing). *(clear)* - **above max** (too near / each patch a huge blotch) → **don't render.** *(FLAGGED — confirm: do we really cull the near foreground when its patches exceed the max pixel size, or keep/clamp it? My read of "doesn't render if the near pixels are above threshold" = cull it, giving a focal *shell* around the center distance where the base resolution is appropriate. Confirm.)* 4. **Source DEM only within ~1 zoom level of the center's zoom** — so we never pull a stack of zoom levels; the cull band in (3) keeps the visible footprint inside that ±1-zoom range.

## Net: v1 = a "focal shell" The base resolution is calibrated to the center point and is only *appropriate* in a distance band around it (perspective makes it too coarse nearer, too fine farther). v1 renders/simulates that band, culls the rest, and sources DEM within ±1 zoom. Simple, correct physics, view-adaptive at the session level rather than per-patch. Clipmap LOD (C) is the path to true wide oblique terrain later.

## Authoring: in-page script editor + save → per-user subdomain (Stephen, 2026-06-17) The water model ships with a **live script editor** so the model's `setup`/`step` (AgentScript idiom) is editable in the page. - **Editor:** **CodeMirror 6** (modular, JS mode, lightweight) recommended over Monaco (heavier). Upgrades the read-only `/*MSRC0*/` script-slice panel pattern from `ants-in-taos` (`b6fcda63`) into an editable surface; edit → re-run the model (hot-reload the step kernel). - **Anonymous edit is free.** Anyone can change and run the script in-browser — no auth to edit/run locally (the browser is a first-class parciante node; cf. feedback_browser-is-a-parciante-node). - **Save provisions an identity.** If an anon user wants to **save**, we **create an "agentscript user subdomain"** for them — an acequia user-provisioning step (the `register-user` flow). Their saved model(s) live in **their** namespace/origin at that subdomain, not in this bead or the shared model dir. This is the project_distributed-origin-architecture user-origin model applied to model authoring: the save button is "claim a namespace." - **Prior art — reuse, don't re-derive, the user model.** Two beads already worked this: - [`9e1d87f5`](https://redfish.acequia.io/guerin/.agents/9e1d87f5-a226-4d1a-be05-64c8d5cacf38/about.md) **Acequia User Model & Architecture** — the overarching decentralized-identity / device-linking / P2P data-ownership / anycast-routing model. **The canonical reference for what "create a user" means here.** - [`31bd5380`](https://redfish.acequia.io/guerin/.agents/31bd5380-d743-420f-81a1-9258e7fbbf9a/about.md) review of **Kaz's** (Kasra Manavi, VP Research & Comms, Simtable) **AnyHazard user-model pitches** (`redfish.acequia.io/kaz/pitches/anyhazard-user-model/`) — the user-manager discussion to align the save/provision flow with. - **Subdomain scheme** (e.g. `<user>.agentscript.org` vs `<user>.agentscript.acequia.io`) is **TBD**, part of the Josh provisioning TODO below. ### TODO (BLOCKED on @josh = Joshua Thorp) — subdomain provisioning We need the **mechanism to create per-user subdomains** under a domain/subdomain that **Joshua Thorp** (CTO, Redfish/Simtable — owns DNS/infra) controls (`agentscript.org` DNS is HostGo-managed per `CLAUDE.local.md`). Get from him: which domain/subdomain to hang user namespaces off, and the create-a-subdomain capability (DNS + origin wiring). Contact card: [`contacts/thorp/`](https://redfish.acequia.io/guerin/.agents/e47cb39d-e473-45fb-98a7-457b2d19066e/thorp/about.md) (in the `contacts` collection bead `e47cb39d`). **NB:** the provisioning Josh is **Thorp**, *not* Joshua Widdicombe (Harvard Viz lab, `harvardviz.live/josh`). **Not actioned now — TODO per Stephen.** Until provisioning exists, "save" falls back to a local/OPFS export or a single shared dock.

## Open questions - **Q8.** §3 near-side cull — confirm cull-vs-keep above the max (the flagged ambiguity). - **Q9.** Droplets as **turtles** (Lagrangian, recommended — exercises turtles×patches) vs water as a pure **patch field** (Eulerian, gravity-biased diffuse)? Or both layers? - **Q10.** Does the grid **re-baseline** when the camera moves (recompute center raycast + resolution), and if so what carries the water state across a re-baseline (resample)? Or fix the grid per "session" and let the camera roam within it for v1?

## References (bead cross-links) - Bead: Acequia User Model & Architecture Bead · [canonical](https://redfish.acequia.io/guerin/.agents/9e1d87f5-a226-4d1a-be05-64c8d5cacf38/) - Bead: 31bd5380 · [canonical](https://redfish.acequia.io/guerin/.agents/31bd5380-d743-420f-81a1-9258e7fbbf9a/) - Bead: Contacts · [canonical](https://redfish.acequia.io/guerin/.agents/e47cb39d-e473-45fb-98a7-457b2d19066e/)