Wear water on Apple Watch. Discover it on iPhone.
You are looking straight down into water. Tilt the watch and the swell rolls that way. Shake it and the surface stirs, throws a wave in from the edge, and breaks into foam. Tap it and a ripple spreads. Fifteen modes, each a different substance rather than a different palette.
Apple Watch + iPhone · watchOS 10+ · iOS 17+ · SwiftUI + SpriteKit + MapKit
Calm ocean · Breaking surf · Rain shower
A watchOS app that renders live, interactive water with a fragment shader — refraction, caustics, sun glitter, whitecaps and spray — driven by the wrist.
The interesting engineering problem is that watchOS gives third-party apps
almost no way to draw per-pixel. Metal is absent, SwiftUI's shader effects are
unavailable, and Canvas draws shapes rather than pixels. The one route that
works is SpriteKit's SKShader, which accepts GLSL and compiles it to Metal
internally. Everything here follows from that discovery.
See docs/ARCHITECTURE.md for how it works, and docs/DECISIONS.md for why — including the design that was built, rejected and thrown away.
Slosh contains authored water Experiences. A DEBUG live-provider route and the normal Places selection can render San Francisco Bay on a physical Watch or simulator, while the reviewed Real Water catalog now also describes Monterey Bay and Lake Tahoe with explicit partial coverage. The watch picker can select those curated Places; NOAA CO-OPS and USGS NWIS observations stay behind provider-independent Hydro contracts. This is not general geographic discovery; see docs/REAL_WATER.md for coverage and provenance rules. The watch picker exposes compact freshness and exact source provenance while keeping the full-screen water experience free of telemetry.
The iPhone app is the discovery/configuration surface. It browses the same three reviewed Places as a list or geographic MapKit map, opens the shared status and provenance detail, and changes the worn Place only after an explicit Wear this water action. Map pins are canonical water bodies, not NOAA or USGS stations. Arbitrary geographic discovery is not implemented.
| Builds and runs on the watchOS simulator | ✅ |
| All fifteen modes render | ✅ |
| iPhone curated list and geographic map | ✅ |
Automated checks (swift run oceancheck) |
✅ 391 assertions |
| Run on physical hardware | ❌ never |
| Frame rate and battery profiled | ❌ |
| Submitted to the App Store | ❌ |
Two things genuinely require a real watch and are the gate on everything else: the tilt axis and sign, and per-pixel shader performance. Both are detailed in docs/ROADMAP.md. The deterministic device runbook is docs/PHYSICAL_WATCH_VALIDATION.md.
git clone git@github.com:<org>/slosh.git && cd slosh
open Slosh/Slosh.xcodeproj # then runFirst time on this machine you will also need the watchOS platform:
xcodebuild -downloadPlatform watchOSOceanCore/ Swift package — builds and tests without Xcode
Sources/OceanCore/ motion, modes, Hydro domain, no UI framework
Sources/HydroProviders/ NOAA transport and provider-local adapters
Sources/OceanUI/ the shader, the SpriteKit scene, particles
Sources/oceancheck/ existing water behavior checks
Sources/hydrocheck/ deterministic Hydro translation checks
Sources/providercheck/ offline NOAA fixtures and optional live smoke test
Slosh/ watchOS experiential app + iPhone Places browser
docs/ architecture, modes, decisions, economics, roadmap
media/ captures used by the docs
OceanCore imports no UI framework, so the physics and mode catalogue build and
run anywhere Swift does. OceanUI holds the shader and is shared unchanged by
watchOS, iOS and macOS.
cd OceanCore
swift run oceancheck
swift run hydrocheck
swift run providercheckThe provider suite is offline by default. swift run providercheck --live adds
an observational, non-deterministic NOAA smoke test.
391 assertions over flow direction, surge, ripples, rain, lightning, the bowl centre, mode catalogue, physical-validation metrics, and curated Places selection/map contracts, including adversarial geographic-resolution policy.
Why not XCTest. XCTest ships only inside Xcode. Writing the suite as a plain
executable means it runs anywhere the Swift toolchain does — including CI images
without Xcode, and the machine this project started on, where Xcode was present
but unusable. The harness is about eighty lines in
OceanCore/Sources/oceancheck/Check.swift.
Several checks exist to defend the product promise rather than the code: that the restful modes stay restful, that a violent mode answers the wrist harder than a calm one, and that every mode actually has the feature it exists for. A mode whose defining feature is switched off is a recolour of another mode, and the catalogue is tested against its own premise.
The simulator has no motion sensors. SimulatedMotionProvider stands in:
drag to tilt, flick to stir. It is compiled out on real hardware so it can never
shadow a system edge gesture.
There is also no way to script a tap, so debug launch flags exist:
xcrun simctl launch <udid> com.example.Slosh.watchkitapp -themeID aurora
xcrun simctl launch <udid> com.example.Slosh.watchkitapp -startInPicker YESBoth read through @AppStorage, so any settings key works the same way.
The shader failed to compile and SpriteKit fell back to the bare sprite. The reason is always in the log:
xcrun simctl spawn <udid> log show --last 2m \
--predicate 'processImagePath CONTAINS "Slosh"' | grep -A5 "SKShader failed"Three causes, all documented in
docs/ARCHITECTURE.md:
declaring uniforms in the shader source, using inout parameters, or exceeding
Metal's 31-buffer limit.
| Document | What it covers |
|---|---|
| ARCHITECTURE.md | How the water is rendered and driven |
| MODES.md | The fifteen modes, and how to add one |
| DECISIONS.md | The load-bearing calls, including the rewrite |
| ECONOMICS.md | An honest look at whether this can make money |
| ROADMAP.md | The path from here to the App Store |
| CONTRIBUTING.md | Setup, conventions, review expectations |
MIT. See LICENSE.


