Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions deploy/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Add these to the existing `boat-sim` environment to enable the demo:
|---|---|---|
| `GEOGRID_API_URL` | `http://bathy:8089/v1/gebco2020` | point depth/routing at the `bathy` service instead of the public OpenTopoData (avoids the 429 quota). Use the address by which the sim reaches `bathy` (service name on a Compose/ECS network, or `localhost` with host networking). |
| `RANDOM_PASSAGES` | `1` | enable random-passage mode |
| `START_LAT` / `START_LON` | `44.7` / `13.1` | **open-water cold-start seed** (decimal degrees). Overrides the SignalK-resume / route-origin start so the boat begins in open water. **Required for random-passage mode** if the persisted/default start is inshore (e.g. the Venice lagoon): a coarse-grid A\* can't route *out* of a lagoon, so the boat would sit there drawing destinations it can never reach. A live hot-restart position still wins (no mid-run teleport). Pick a point inside the `bathy` bbox with open sea room — e.g. `44.7,13.1` (N Adriatic, ~−39 m, W of Istria). |
| `PASSAGE_MIN_NM` | `8` | min passage length (nM). **8–15 is the demo default** (a fresh routed passage roughly every 1.5–2.5 h at ~6 kn); use `40`/`60` for realistic-length passages (~7–10 h each). |
| `PASSAGE_MAX_NM` | `15` | max passage length (nM) |
| `PASSAGE_ARRIVAL_NM` | `1.0` | lay the next passage when within this distance of the destination |
Expand Down Expand Up @@ -104,6 +105,8 @@ services:
SIM_WEB_PORT: "8088"
GEOGRID_API_URL: http://bathy:8089/v1/gebco2020
RANDOM_PASSAGES: "1"
START_LAT: "44.7" # open-water seed (W of Istria); required so the
START_LON: "13.1" # boat doesn't start stuck inshore (e.g. lagoon)
PASSAGE_MIN_NM: "8" # 8-15 nM: a new routed passage every ~1.5-2.5 h
PASSAGE_MAX_NM: "15" # use 40/60 for realistic ~7-10 h passages
AUTOROUTE_MAX_CELLS: "60000"
Expand Down
28 changes: 28 additions & 0 deletions src/yey/boats/simulator/engine/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@
"bilge_pump", "water_pump"]


def _env_start_pos() -> tuple[float, float] | None:
"""Optional fixed cold-start position from START_LAT / START_LON (decimal deg).

When both are set and parseable this overrides the SignalK-resume / route-origin
cold-start position. It exists to seed the boat in open water for the
random-passage demo: a persisted inshore position (e.g. a marina in a lagoon)
can't be autorouted out of at a coarse bathymetry grid, so the boat would sit
there drawing destinations it can never reach. A live caller-supplied start_pos
(hot restart with the last reported position) still wins, so the boat does not
teleport back to the seed mid-run.
"""
lat_s, lon_s = os.environ.get("START_LAT"), os.environ.get("START_LON")
if not lat_s or not lon_s:
return None
try:
return (float(lat_s), float(lon_s))
except ValueError:
print(f"[sim] ignoring invalid START_LAT/START_LON: {lat_s!r}, {lon_s!r}", flush=True)
return None


def build_data_source(settings: Settings):
if settings.weather_source == "signalk":
return SignalKDataSource(settings.signalk_host, settings.signalk_port)
Expand Down Expand Up @@ -131,6 +152,13 @@ async def pipeline(settings: Settings, route, start_pos, report_status) -> None:
sk_sink = chain.active if isinstance(chain.active, SignalKSink) else None
writer = sk_sink.writer if sk_sink else None

if start_pos is None:
env_pos = _env_start_pos()
if env_pos is not None:
start_pos = env_pos
print(f"[sim] start position from START_LAT/START_LON: "
f"({env_pos[0]:.4f}, {env_pos[1]:.4f})", flush=True)

if start_pos is not None:
start_lat, start_lon = start_pos
idx, _ = route.resync_from_position(start_lat, start_lon)
Expand Down
31 changes: 31 additions & 0 deletions tests/test_start_pos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
"""Tests for the START_LAT/START_LON cold-start override (engine/runner.py)."""
from __future__ import annotations

import pytest # type: ignore[import]

from yey.boats.simulator.engine.runner import _env_start_pos # type: ignore[import]


def test_no_env_returns_none(monkeypatch):
monkeypatch.delenv("START_LAT", raising=False)
monkeypatch.delenv("START_LON", raising=False)
assert _env_start_pos() is None # noqa: S101


def test_both_set_returns_tuple(monkeypatch):
monkeypatch.setenv("START_LAT", "44.7")
monkeypatch.setenv("START_LON", "13.1")
assert _env_start_pos() == pytest.approx((44.7, 13.1)) # noqa: S101


def test_partial_env_returns_none(monkeypatch):
monkeypatch.setenv("START_LAT", "44.7")
monkeypatch.delenv("START_LON", raising=False)
assert _env_start_pos() is None # noqa: S101


def test_invalid_env_returns_none(monkeypatch):
monkeypatch.setenv("START_LAT", "not-a-number")
monkeypatch.setenv("START_LON", "13.1")
assert _env_start_pos() is None # noqa: S101
Loading