Inspiration
The challenge prompt called for distributed thinking, which initially pointed us toward standard swarm concepts: a shared belief map, an auction system for asset allocation, and information-gain routing. However, analyzing the open-source Dominion simulator fundamentally shifted our approach. By reviewing terrain/course.py and VesselPathPlugin.cc, we identified that the vessel follows a widest-path search, maintaining a 120 m shore clearance and a constant 3.0 m/s speed. This reframed the entire problem which is that instead of searching for 6.5 km of open water, we search along a 1-D arc-length curve.
What it does
Dogwatch is a command and control system that coordinates four ArduPilot vehicles (a quadcopter, a fixed-wing, and two fixed towers) to find and track a shadow vessel in Bellot Strait. It executes a strict control loop per tick: observe, age the belief, fold in sightings, post, decide, and command. It includes:
- MAVLink UDP communication via transport/arena.py to arm, launch, and route assets.
- A vision pipeline that extracts MJPEG frames, detects dark hulls, and projects pixels to precise lat/lon coordinates.
- A belief module maintaining the vessel's probability distribution as a 1D ribbon along the channel.
- A search policy that scores channel segments based on belief, staleness, and travel cost to assign tasks.
- A tracking system that consolidates fixes under a stable identifier for continuous tracking to Dominion's API.
How we built it
We built the system entirely in Python 3.11 using NumPy, pymavlink, and Pillow, intentionally avoiding heavy frameworks, ML models, or external runtime services.
The core structural decision was enforcing a strict transport seam which is that the coordinator takes a WorldObservation and returns a FleetIntent without touching a network socket. This allows the exact same policy to drive both the live arena and our offline simulator, enabling 958 tests to run instantly without network or clock dependencies.
We also isolated all spatial projection logic into a single module, whiteout/geo.py. It is the only code permitted to perform trigonometry on latitudes, a constraint enforced by three AST guards in our test suite to prevent secondary projections.
Challenges we ran into
Ice-filled terrain The camera primarily sees ice, creating dark compact shapes indistinguishable from hulls in a single frame. At 53% ice cover, our detector fired 16 false alarms in 32 frames. We implemented a motion gate based on the vessel's 3.0 m/s speed. Once the aircraft maintained a stable hover, this eliminated false positives completely over 120 live ticks.
Coordinate grid rotations Gazebo poses were relayed in the EPSG:3413 grid, where grid north deviates from true north. Applying a −49.80° convergence rotation reduced ground truth error from 1547 m to 5.7 m. We also had to explicitly bypass the EPSG scale factor k, as the sponsor's setup already corrected for it.
Altitude reference errors Using relative_alt (height above the vehicle's home) caused a tower on a 227 m cliff to report zero altitude, silently discarding its detections. We corrected this to MSL, verifying it to 14 cm against the rendered world.
Silent logic failures Several bugs were mathematically plausible but physically wrong. A belief diffusion rate set to 8.0 m/s erroneously spread probability mass over seven times too much water. Additionally, an assumed 25 km strait length placed 79% of belief mass outside the 6.5 km arena, and unmapped vehicle classes in the telemetry threatened to break log readability.
Accomplishments that we're proud of
- Achieved ground truth accuracy of 5.7 m, validated by clocking the vessel at 2.99 m/s against a configured 3.0.
- Maintained 1.00 coverage across a 300-tick live run and verified our altitude datum to 14 cm.
- Identified edge cases in our own test suite through code mutation, explicitly documenting test limitations rather than overclaiming coverage.
- Successfully armed and launched live ArduPilot vehicles over MAVLink, consistently beating the 3-second arming window to drive the fleet from our custom policy.
What we learned
We learned that deeply understanding the simulation environment is critical; reading the simulator source code simplified a complex 2D spatial search into a highly constrained 1D problem.
We also learned that testing infrastructure must align with the actual evaluation environment. We built systems to optimize against overnight sweeps, only to find the arena locked at SPEEDUP=1 and scored on qualitative criteria like autonomy and collaboration. Finally, we learned that the most dangerous bugs in robotics do not crash the system, they are silent and only surface during rigorous live testing.
What's next
- Achieve a confirmed live detection by overcoming the quadcopter's uncommandable gimbal constraint using our established stand-off geometry.
- Implement active cueing and collaboration, where a tower detection triggers the fixed-wing to confirm and the quadcopter to hold position.
- Complete the internal scorer to accurately evaluate system performance.
Built With
- ardupilot
- canvas
- docker
- gazebo
- github-actions
- html
- javascript
- mavlink
- mypy
- numpy
- opencv
- pillow
- pymavlink
- pytest
- python
- ruff
- scipy
- websockets
Log in or sign up for Devpost to join the conversation.