Docking control, the forecast that says when the window is open,
the awareness at the berth, and the evidence trail behind all three. One stack — and a
bench that checks the software the way a bus and a boat will.
recorded run · 0.0 sgap —contact angle 2.5°controller PID
The stack
Five parts under one package, plus a hardware tier that replaces the
simulated one when there is a unit on the quay.
Control pilotage.control
A 3-DOF docking simulator — Fossen hull, Kt/Kq propeller, DC motor with a current
limit and a battery of finite internal resistance — with a PID baseline and a
nonlinear MPC that plans against the pontoon as a constraint rather than a cost.
Scored on sixteen bench scenarios and a 330-cell matrix:
3 hulls × 5 propulsion sets × 11 conditions × 2 geometries.
Forecasting pilotage.forecasting
Metocean forecast down to what the berth will actually feel — wind on the beam,
set across the approach, gust structure — and then to scored go/no-go windows, per
berth and per vessel class rather than per port.
A window is a claim with a time on it, and the claim is kept
for scoring against what happened.
Risk pilotage.risk
The safe-window engine, an evidence store and monthly reports. Thresholds and
named risks live in a configuration pack, not in code, so the numbers a decision was
made against can be read without reading the program.
Every refusal is counted and named: a clipped command nobody
recorded is a boat behaving differently from its own log.
Detection pilotage.detection
The port-entrance scene with an AIS cross-check, emitting the safety events an
approach aborts on. Sensors are simulated here and real in the hardware tier, which
emits the same event contract from a Livox unit on the quay.
One role, two implementations, one contract — the abort does
not know which one it came from.
Console pilotage.console
A four-panel dashboard over the three services. No build step, no framework: it is
four HTML panels and a file server, because that is the whole requirement.
Runs from the repository with one command.
The bench SIL · HIL
The plant and the controller on opposite sides of a wire, on a simulated CAN bus
with arbitration, airtime and fault injection. The device under test never sees the
truth and does not know what its actuators did — the boat's situation, before any
hardware is bought.
Generated C held to the model within 2 ULP; every frame's
quantisation inside the contract's own tolerance.
What the bench has already found
Failures a desktop cannot show, because there the measurement and the
prediction are the same instant by construction.
A bus is destroyed by latency long before it drops anything
At 125 kbit/s the worst frame waits 399.73 ms against a
100 ms control period — with no frame lost at all. A bench that
counts losses would call that wire healthy.
The wire takes the last five metres
Beyond five metres it costs nothing. Inside them the estimate error doubles,
6.2 → 12.2 cm, because the improvement the rangefinders are
aboard to give disappears. Quantisation accounts for 0.01 cm of
that; the rest is time, and compensating for the readings' own age recovers it.
Not knowing your actuators gets dearer behind a wire
With a jammed thruster the approach ends 44.4 m off if the
device trusts its own command, and 23.3 m off if it reads the
shaft speed already on the bus. With an exact state the same difference is under a
metre.
Held by gates, not by claims
Every figure here is one a test in the repository holds, and each of
those tests has been watched failing on purpose.
C port
2 ULP
Generated C against the Python model on one step. The C is never hand-edited;
regeneration fails on a diff.
Bus load
33.1 %
Of a 250 kbit/s NMEA 2000 backbone, 760 frames a second, with the boat's own
traffic underneath.
Worst payload age
6.15 ms
Over a 139-second approach: no late cycle in 13 921 and nothing dropped.
Regression
330
Matrix cells, plus sixty gate files over the estimator, the safety layer, the
bus and the log.