Skiff combines a headless six-degree-of-freedom Rust simulation with an optional React and Three.js cockpit. The server owns vessel state and physics. The browser provides visualization and controls, and can feed forces from its cloth-sail simulation back into the authoritative hull model.
Important
Skiff is an active simulation and research project. It is not certified navigation, autopilot, or vessel-control software.
- Server-authoritative physics — approximately 20 Hz, six-degree-of-freedom Lagoon 450S dynamics with hulls, rudders, skegs, engines, sail forces, stability states, waves, grounding, and bathymetry.
- Headless operation — the vessel continues sailing without a browser connected.
- Interactive 3D cockpit — React 19, React Three Fiber, Three.js, Zustand, HUD telemetry, helm and engine controls, environmental controls, and a Verlet cloth sail.
- Graceful sail-model fallback — fresh browser cloth forces can drive the boat; stale input blends back to the server coefficient model instead of freezing or dropping to zero.
- Routing and validation tools — isochrone routing, boat-profile tests, polar sweeps, and a terminal state monitor.
- External integrations — optional Meridian weather, Signal K telemetry, OpenCPN route guidance, and a native Model Context Protocol endpoint.
flowchart LR
subgraph Browser["Browser · React / R3F"]
UI[HUD and controls]
Cloth[Cloth-sail solver]
end
subgraph Server["Rust server · source of truth"]
API[Axum HTTP API and MCP]
Physics[20 Hz vessel physics]
Sail[Coefficient sail fallback]
World[Environment, land, and depth]
end
UI -->|control and environment| API
API --> Physics
Physics -->|state snapshot| UI
Cloth -->|body-frame sail wrench| API
Sail --> Physics
World --> Physics
Meridian[Meridian weather] --> API
SignalK[Signal K / OpenCPN] <--> API
Agent[MCP client] <--> API
CLI[Router, profile tests, polar sweep] --> Library[Shared Rust library]
Library --> Physics
The Rust process is authoritative for pose, velocity, stability, fuel, environment, grounding, and route guidance. A browser may submit an aerodynamic sail wrench while connected. If that wrench becomes stale, the server smoothly returns to its coefficient sail model.
For the underlying contracts and design rationale, read:
media/skiff-architecture.md— approachable architecture deep diveplan/overarching_architecture.md— authoritative engineering conventionsrunbook_pi.md— Raspberry Pi and production deployment runbook
- A recent stable Rust toolchain with Rust 2024 edition support
- Node.js 22 and npm
- Optional: Docker for a containerized build
- Optional: Signal K and Meridian credentials for live integrations
Build the browser client, then start the Rust server:
git clone https://github.com/DeepBlueDynamics/skiff.git
cd skiff
cd web
npm install
npm run build
cd ..
cargo run --bin skiffOpen:
- Simulator:
http://localhost:18081/ - Health check:
http://localhost:18081/healthz - MCP endpoint:
http://localhost:18081/mcp
The default port is 18081. Set SKIFF_PORT to override it locally, or PORT on platforms such as Cloud Run.
Run the backend and Vite development server in separate terminals.
Terminal 1 — Rust server
cargo run --bin skiffTerminal 2 — browser client
cd web
npm install
npm run devOpen http://localhost:5173/. Vite proxies simulator API requests to the Rust server on port 18081.
Useful checks:
# Rust tests
cargo test
# Frontend typecheck and production bundle
cd web && npm run build
# Terminal telemetry dashboard
python web/monitor_sim.py
# Generate a headless polar report
cargo run --release --bin polar_sweepThe polar sweep writes reports/polar_report.md.
The included multi-stage image builds both the browser client and the release Rust binary.
docker build -t skiff .
docker run --rm \
-e PORT=8080 \
-p 8080:8080 \
skiffOpen http://localhost:8080/.
Default base URL: http://localhost:18081
| Method | Path | Purpose |
|---|---|---|
GET |
/healthz |
Liveness check |
GET |
/v1/sim/state |
Full authoritative simulator snapshot |
POST |
/v1/sim/control |
Helm, trim, reef, engines, traveler, and mass controls |
POST |
/v1/sim/environment |
Manual wind, current, and wave conditions |
POST |
/v1/sim/position |
Set latitude and longitude; land targets snap to nearby water |
POST |
/v1/sim/reset |
Reset the vessel state, with an optional initial heading |
POST |
/v1/sim/sail_wrench |
Submit a cloth-derived body force and torque |
POST |
/v1/sim/course |
Engage or release heading or track hold |
POST |
/v1/sim/sail |
Furl or deploy the sail model |
POST |
/v1/sim/refuel |
Refill both fuel tanks |
POST |
/v1/auth/token |
Install a user token for live Meridian data |
POST |
/v1/auth/logout |
Clear the live-data token and live-weather status |
POST |
/mcp |
Native streamable-HTTP MCP transport |
Control request example
curl -X POST http://localhost:18081/v1/sim/control \
-H 'content-type: application/json' \
-d '{
"helm": 0.0,
"sail_trim": 0.76,
"reef": 0.0,
"thrust_port": 0.0,
"thrust_stbd": 0.0,
"mass_scale": 1.0,
"traveler": 0.0,
"fuel_burn_max_lph": 9.0
}'Environment request example
curl -X POST http://localhost:18081/v1/sim/environment \
-H 'content-type: application/json' \
-d '{
"wind_speed_mps": 5.0,
"wind_to_deg": 150.0,
"current_speed_mps": 0.55,
"current_to_deg": 85.0,
"wave_height_m": 1.0,
"wave_period_s": 7.0,
"wave_to_deg": 290.0,
"manual": true
}'Flow directions use to-direction: the direction toward which the wind, current, or waves travel.
Cloth-sail wrench example
{
"seq": 123,
"f_body": [1200.0, 450.0, -80.0],
"tau_body": [300.0, -120.0, 1600.0]
}The body frame is +X forward, +Y starboard, and +Z down. The server accepts fresh cloth input and blends back to the coefficient model when updates stop.
MCP is built into the Rust server at /mcp; no separate Python bridge or process is required.
Example Claude CLI registration:
claude mcp add skiff \
--transport http \
http://localhost:18081/mcpAvailable tools:
| Tool | Purpose |
|---|---|
get_state |
Read authoritative vessel and environment telemetry |
set_control |
Set helm, trim, reef, traveler, mass scale, and engine inputs |
set_environment |
Apply manual wind, current, and wave conditions |
set_position |
Move the vessel to a latitude and longitude |
reset |
Reset the simulation |
set_course |
Set heading hold, track hold, or release course control |
set_sail |
Furl or deploy the sail |
set_engines |
Set or release the shared two-engine thrust override |
refuel |
Refill the vessel's fuel tanks |
Run the isochrone router between two coordinates:
cargo run --bin skiff-cli -- route \
--origin "12.00,-61.76" \
--dest "12.25,-61.50" \
--depart "2026-08-01T12:00:00Z" \
--profile castoff-compatible \
--step 900 \
--horizon 72h \
--out route.jsonThe current CLI route command uses the built-in constant environment provider. Treat it as a routing and geometry harness rather than a live-weather voyage planner.
Inspect predicted boat performance for a single condition:
cargo run --bin skiff-cli -- profile-test \
--profile castoff-compatible \
--wind 10kt \
--current 0kt@0Run a headless true-wind-speed × true-wind-angle sweep with trim optimization:
cargo run --release --bin polar_sweep| Variable | Default | Purpose |
|---|---|---|
SKIFF_PORT |
18081 |
Preferred local HTTP bind port |
PORT |
unset | Platform-provided fallback port |
RUST_LOG |
info |
Rust tracing filter, for example skiff=debug |
SIGNALK_HOST |
unset | Signal K server host |
SIGNALK_TOKEN |
unset | Signal K authentication token |
MERIDIAN_URL |
https://meridian.deepbluedynamics.com |
Meridian API base URL |
MERIDIAN_USER_TOKEN |
unset | Optional initial user JWT for live Meridian weather |
A browser login can install a Meridian user token at runtime through POST /v1/auth/token; it does not require restarting the simulator.
skiff/
├── src/
│ ├── main.rs # Axum server, simulation loop, integrations
│ ├── cli.rs # Route and boat-profile CLI
│ ├── mcp.rs # Native streamable-HTTP MCP endpoint
│ ├── cat_physics.rs # Lagoon 450S force model and integration
│ ├── world.rs # Land mask, grounding, and bathymetry
│ ├── boat/ # Boat profiles, polars, leeway, wave penalties
│ ├── env/ # Environment providers and interpolation
│ ├── route/ # Isochrone routing
│ ├── sim/ # Headless simulator state and stepping
│ └── bin/ # Additional binaries, including polar_sweep
├── web/ # Vite, React, R3F, Three.js, and Zustand client
├── models/Lagoon_450S/ # Source vessel assets
├── media/ # Public architecture documentation
├── plan/ # Detailed engineering contracts and design work
├── Dockerfile # Multi-stage production image
└── runbook_pi.md # Raspberry Pi deployment runbook
The following rules are load-bearing across the Rust and browser implementations:
- Rust is the source of truth for physics and vessel state.
- Internal calculations use SI units.
- Fields named
*_to_degdescribe the direction a flow travels toward. - Body-frame forces use
+Xforward,+Ystarboard, and+Zdown. - Sails consume apparent wind; water foils consume through-water velocity.
- A stale browser wrench must decay toward the coefficient sail model, never remain frozen.
Do not change coordinate frames, force signs, wind semantics, or sail attachment contracts without first updating plan/overarching_architecture.md and the corresponding tests.
Before opening a pull request:
cargo test
cd web && npm run buildKeep the server authoritative, preserve headless operation, and add tests for changes to units, coordinates, force signs, routing behavior, or API contracts.
Skiff is currently version 0.1.0. Interfaces and physical models may change as validation improves. The root README intentionally focuses on setup and operation; detailed design decisions and experimental work belong in media/ and plan/.
MIT, as declared in Cargo.toml.