ReviewOS

also looking at this

stacks/drone

feat: add owned VTOL hover development

#76
Merged chris wants to merge codex/owned-vtol-lift-and-cruise into main
30 files +780 -111

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

.github/workflows/ci.ymlmodified+4-0
Changes to .github/workflows/ci.yml
@@ -142,6 +142,10 @@ jobs:
142142 working-directory: ${{ runner.temp }}
143143 - run: $GITHUB_WORKSPACE/storage/runtime-build/cli/drone controls:simulate --scenario hover --seconds 3
144144 working-directory: ${{ runner.temp }}
145 - run: $GITHUB_WORKSPACE/storage/runtime-build/cli/drone vtol:hover
146 working-directory: ${{ runner.temp }}
147 - run: $GITHUB_WORKSPACE/storage/runtime-build/cli/drone vtol:hover --scenario host-loss
148 working-directory: ${{ runner.temp }}
145149 - run: $GITHUB_WORKSPACE/storage/runtime-build/cli/drone authority:simulate --scenario pi-loss --seconds 4
146150 working-directory: ${{ runner.temp }}
147151 - run: $GITHUB_WORKSPACE/storage/runtime-build/cli/drone drivers:bench --driver ardupilot --drop-first-ack
README.mdmodified+4-0
Changes to README.md
@@ -76,6 +76,10 @@ These profiles use owned raw sensors, estimation, mission policy, controls and a
7676outputs remain inhibited. Read the [shared contract](docs/vehicles.md), [air model](docs/vehicles-air.md),
7777[ground model](docs/vehicles-ground.md) and [surface corrections](docs/vehicles-surface.md) and [subsea pressure/DVL inspection](docs/vehicles-subsea.md).
7878
79`./buddy vtol:hover` separately exercises [VTOL hover development](docs/vehicles-vtol.md):
80vertical takeoff, a nearby hover waypoint and contact-gated landing. Wing transitions, cruise,
81hybrid energy qualification and a selectable VTOL mission workspace remain open.
82
7983## Simulated carried load
8084
8185Characterize a centered or forward point mass, a prescribed separation and native link loss through
app/Commands/VtolHover.tsadded+27-0
Changes to app/Commands/VtolHover.ts
@@ -0,0 +1,27 @@
1import { defineCommand } from '@stacksjs/cli'
2import { print, printError } from '@stacksjs/cli/output'
3import { createVtolHoverProfile, vtolHoverScenarios } from '../../resources/functions/drone/vehicles/vtol'
4import { runVehicleSimulation } from '../../resources/functions/drone/vehicles/runtime'
5
6export default defineCommand({
7 name: 'vtol:hover',
8 description: 'Exercise the fictional lift-and-cruise airframe in hover only; no wing transition or physical control',
9 options: {
10 '--scenario <name>': { description: vtolHoverScenarios.join(', '), default: 'hover' },
11 '--seconds <n>': { description: 'Maximum virtual duration, 2..180 seconds', default: '60' },
12 },
13 handle(options) {
14 try {
15 const result = runVehicleSimulation(createVtolHoverProfile(options.scenario), { scenario: options.scenario, seconds: Number(options.seconds) })
16 const { trace, path, ...report } = result
17 const expectedStatus = options.scenario === 'hover' ? 'completed' : options.scenario === 'host-loss' ? 'host-lost' : 'blocked'
18 const observedFlight = path.some(row => row.truthPositionM[2] < -1 && row.actuators.slice(0, 4).some(n => n > 0))
19 const boardNeutral = result.board.actuators.every(n => n === 0) && result.board.expiresAtMs === null
20 const hoverOnly = path.every(row => row.actuators.slice(4).every(n => n === 0))
21 const demonstrationMatched = result.status === expectedStatus && observedFlight && boardNeutral && hoverOnly
22 print(JSON.stringify({ ...report, scope: 'development-hover-only', transitionsImplemented: false, expectedStatus, demonstrationMatched, observedFlight, boardNeutral, hoverOnly, recordedMissionRows: trace.rows.length, evaluatedPathRows: path.length }, (_key, value) => typeof value === 'bigint' ? String(value) : value, 2))
23 if (!demonstrationMatched) process.exitCode = 1
24 }
25 catch (error) { printError(error instanceof Error ? error.message : String(error)); process.exitCode = 1 }
26 },
27})
bin/drone.tsmodified+2-0
Changes to bin/drone.ts
@@ -50,6 +50,7 @@ import PatrolReplay from '../app/Commands/PatrolReplay'
5050
5151import AntennaBench from '../app/Commands/AntennaBench'
5252import AirSimulate from '../app/Commands/AirSimulate'
53import VtolHover from '../app/Commands/VtolHover'
5354import GroundSimulate from '../app/Commands/GroundSimulate'
5455import SurfaceSimulate from '../app/Commands/SurfaceSimulate'
5556import SubseaSimulate from '../app/Commands/SubseaSimulate'
@@ -126,6 +127,7 @@ PatrolSimulate(app)
126127PatrolReplay(app)
127128AntennaBench(app)
128129AirSimulate(app)
130VtolHover(app)
129131GroundSimulate(app)
130132SurfaceSimulate(app)
131133SubseaSimulate(app)
config/docs.tsmodified+1-0
Changes to config/docs.ts
@@ -76,6 +76,7 @@ const config: BunPressOptions = {
7676 items: [
7777 { text: 'Owned vehicle runtime', link: '/vehicles' },
7878 { text: 'Owned fixed-wing profile', link: '/vehicles-air' },
79 { text: 'VTOL hover development', link: '/vehicles-vtol' },
7980 { text: 'Owned ground vehicle profile', link: '/vehicles-ground' },
8081 { text: 'Owned surface vessel profile', link: '/vehicles-surface' },
8182 { text: 'Owned subsea inspection profile', link: '/vehicles-subsea' },
docs/index.mdmodified+3-0
Changes to docs/index.md
@@ -56,6 +56,9 @@ surface and subsea profiles. [Optional external drivers](drivers.md) have
5656[acknowledged protocol services](driver-protocols.md); scripted peers are distinct
5757from [firmware SITL](sitl.md) and physical testing.
5858
59The separate [VTOL hover development command](vehicles-vtol.md) exercises vertical takeoff,
60hover and landing. Front/cruise/back transitions and the common VTOL mission workspace remain open.
61
5962## Explore a workflow
6063
6164| Workflow | Current development surface | Guide |
docs/roadmap/feature-coverage.mdmodified+1-1
Changes to docs/roadmap/feature-coverage.md
@@ -16,7 +16,7 @@ means there is no native implementation yet. A protocol-compatible transport is
1616| Diagnostics, logs, replay, firmware update and configuration provenance | Owned runtime journal, loss accounting, replay, CLI/viewer and [protected signed preparation, owned candidates, host slots, observed image/session health and fresh-policy rollback](../runtime-updates.md); full mission correlation, key repair, complete storage faults and board install/boot/rollback qualification remain pending | No log/FTP/parameter management | [#9](https://github.com/stacksjs/drone/issues/9) |
1717| Missions, tasks, waypoints, terrain, rally and resume | Owned versioned graph, flat-terrain/fence/rally/energy validation, physics execution, pause/resume, recorded recovery, CLI and stx viewer; correlated durable checkpoints, driver translation and hardware evidence pending | No mission upload or mission-mode control | [#10](https://github.com/stacksjs/drone/issues/10) |
1818| Avoidance, route planning, precision landing, follow and orbit | Owned modeled range detection, inflated route planning, terrain-clearance/RTL/selected-site landing physics; metric follow/orbit goals; physical calibration and dynamic obstacle campaigns pending | No avoidance/landing integration | [#11](https://github.com/stacksjs/drone/issues/11) |
19| Fixed-wing, VTOL, heli, blimp and experimental airframes | Owned fixed-wing aerodynamic trainer with runway takeoff/survey/landing and wind/airspeed-loss tests; VTOL, helicopter, soaring, blimp and experimental families remain planned | Current drivers reject non-quadcopter heartbeats | [#12](https://github.com/stacksjs/drone/issues/12) |
19| Fixed-wing, VTOL, heli, blimp and experimental airframes | Owned fixed-wing runway takeoff/survey/landing and [VTOL hover development](../vehicles-vtol.md) with vertical takeoff/hover/contact-gated landing; VTOL transitions/cruise, hybrid energy, helicopter, soaring, blimp and experimental families remain open | Current drivers reject non-quadcopter heartbeats | [#12](https://github.com/stacksjs/drone/issues/12) |
2020| Rovers, mowing, logistics and materials handling | Owned differential traction/braking, encoder/GNSS odometry, guarded mowing coverage and scoped material-drop simulation; hardware and broader chassis/implements pending | Not exposed | [#13](https://github.com/stacksjs/drone/issues/13) |
2121| Surface vessels, bathymetry, water sampling and harbours | Owned planar hull/current/wind, collision inhibition, corrected single-beam export, scoped sample ACK and energy-return simulation; physical survey/sampling and broader hulls pending | Not exposed | [#14](https://github.com/stacksjs/drone/issues/14) |
2222| Subsea ROV, depth hold, tether and leak handling | Owned eight-thruster 6DOF physics, pressure/DVL local estimation, depth hold, tether/leak supervision and scoped synthetic camera/sonar evidence; hardware, full hydrodynamics and physical inspection pending | Not exposed | [#15](https://github.com/stacksjs/drone/issues/15) |
docs/vehicles-vtol.mdadded+64-0
Changes to docs/vehicles-vtol.md
@@ -0,0 +1,64 @@
1# VTOL hover development
2
3The owned lift-and-cruise model can execute a **hover-only** mission through the shared raw sensor,
4estimator, mission, authority and independent output-lease runtime. It takes off vertically, reaches
5a nearby hover waypoint and lands against fresh modeled contact evidence. Wing transitions and
6cruise control are not implemented. This development profile is separate from the selectable
7profiles in the `/missions` workspace.
8
9```sh
10./buddy vtol:hover
11./buddy vtol:hover --scenario host-loss
12./buddy vtol:hover --scenario imu-loss
13```
14
15The native Stacks command defaults to 60 virtual seconds. It accepts `hover`, `imu-loss`,
16`position-loss`, `vehicle-sensor-loss`, `host-loss`, `rc-override` and `kill`. Faults begin at 3000 ms,
17after takeoff. A report retains the actual `completed`, `blocked`, `host-lost` or `duration-exhausted`
18status. Exit 0 means the selected demonstration matched its expected outcome, observed flight,
19retained neutral pusher/surfaces and ended with neutral board outputs. It does not turn a fault
20into a completed mission. An unmatched outcome, invalid option or insufficient duration exits 1.
21
22The copied standalone CLI registers the same command definition:
23
24```sh
25bun run runtime:cli
26./storage/runtime-build/cli/drone vtol:hover
27```
28
29## Model and measurements
30
31The fictional 2.5 kg body has four vertical rotors, each limited to 12 N, with 60 ms motor response
32and a 120 N/s slew limit. An independent 14 N pusher and the owned trainer's aerodynamic surfaces
33share the rigid body. Rotor allocation preserves collective thrust first, then roll/pitch demand,
34then yaw. No coefficient is measured from a purchased airframe.
35
36The hover demonstration commands the pusher and three surfaces to zero. Calibrated modeled raw
37IMU/GNSS samples feed the owned estimator. The controller receives estimated pose, a current qualified
38raw gyro capture and delayed contact measurements. Plant truth is used for sensor synthesis and
39independent evaluation, never as controller input. Initial north heading and a flat contact plane
40are declared simulation assumptions. Landing uses a 0.1 m below-plane target to obtain finite
41contact; mission completion still requires estimated arrival, low speed, settling and fresh contact.
42
43Contact and paused-ground neutral decisions occur after estimator/rate validation. A stale gyro
44cannot become acceptable because contact is present. Control faults latch for the controller
45lifetime. Each session owns its plant, controller, sensor sequence and returned actuator arrays.
46RC takeover, kill and sensor loss revoke shared authority. Host death leaves its final host snapshot
47stale while the independently ticking modeled board expires the output lease.
48
49The mission's 50 Wh capacity, 1200 W ceiling and 30% reserve are declared budget inputs for this
50fictional hover experiment. They are not identified electrical power, measured endurance or a
51complete hybrid energy model. Neutral outputs during an airborne fault do not establish a safe
52emergency landing. Physical flight commands remain disabled.
53
54## Remaining implementation
55
56[Issue #12](https://github.com/stacksjs/drone/issues/12) remains open for explicit correlated
57transition requests and observed outcomes, front/cruise/back control, airspeed and attitude
58qualification, bounded transition abort/recovery, complete hybrid power/reserve accounting and
59the common mission workspace. Full TECS/soaring, tiltrotor/tailsitter mechanisms, helicopters and
60other airframe families remain separate work. Native board control, measured airframe identification,
61HIL and physical flight require their own evidence.
62
63Read the [shared vehicle contract](vehicles.md), [fixed-wing model](vehicles-air.md) and
64[authority contract](authority.md).
docs/vehicles.mdmodified+3-1
Changes to docs/vehicles.md
@@ -61,7 +61,9 @@ recompute mission replay, reject altered traces and exercise payload uncertainty
6161All evidence is owned physics simulation. The Pico neutral-only board image does not run these
6262TypeScript controllers. Native board execution, external firmware SITL, HIL and measured physical
6363vehicle acceptance remain separate gates. VTOL, helicopter, soaring, blimp and other vehicle
64families remain explicit roadmap work.
64families remain explicit roadmap work. A separate [VTOL hover development command](vehicles-vtol.md)
65now exercises vertical takeoff, a nearby hover waypoint and contact-gated landing. It does not
66implement wing transitions or add a VTOL selection to the common workspace.
6567
6668Read [air](vehicles-air.md), [ground](vehicles-ground.md), [surface](vehicles-surface.md), [subsea](vehicles-subsea.md),
6769[mission semantics](missions.md) and [the replacement matrix](roadmap/feature-coverage.md).
tests/tsconfig.runtime.jsonmodified+1-0
Changes to tests/tsconfig.runtime.json
@@ -75,6 +75,7 @@
7575 "./unit/vtol.test.ts",
7676 "./unit/vtol-control.test.ts",
7777 "./unit/vtol-runtime.test.ts",
78 "./unit/vtol-cli.test.ts",
7879 "./unit/ground.test.ts",
7980 "./unit/surface.test.ts",
8081 "./unit/mission-workspace.test.ts",
tests/unit/vtol-cli.test.tsadded+31-0
Changes to tests/unit/vtol-cli.test.ts
@@ -0,0 +1,31 @@
1import { expect, test } from 'bun:test'
2import { command } from '@stacksjs/testing'
3
4test('native VTOL hover command preserves nominal and lost-host outcomes with explicit development scope', async () => {
5 for (const [scenario, status] of [['hover', 'completed'], ['host-loss', 'host-lost']]) {
6 const result = await command(['vtol:hover', '--scenario', scenario!]).withTimeout(60000)
7 expect(result.exitCode).toBe(0)
8 const report = JSON.parse(result.stdout)
9 expect(report.status).toBe(status)
10 expect(report.scope).toBe('development-hover-only')
11 expect(report.transitionsImplemented).toBe(false)
12 expect(report.physicalFlightEnabled).toBe(false)
13 expect(report.board.actuators).toEqual([0, 0, 0, 0, 0, 0, 0, 0])
14 expect(report.recordedMissionRows).toBeGreaterThan(100)
15 if (scenario === 'host-loss') expect(report.board.fault).toBe('output-lease-expired')
16 else expect(report.mission.reason).toBe('landed')
17 }
18}, 180000)
19
20test('native VTOL command rejects unknown scenarios and preserves insufficient-duration failure', async () => {
21 const invalid = await command(['vtol:hover', '--scenario', 'cruise']).withTimeout(60000)
22 expect(invalid.exitCode).toBe(1)
23 expect(invalid.stdout).toBe('')
24 const short = await command(['vtol:hover', '--seconds', '2']).withTimeout(60000)
25 expect(short.exitCode).toBe(1)
26 const report = JSON.parse(short.stdout)
27 expect(report.status).toBe('duration-exhausted')
28 expect(report.demonstrationMatched).toBe(false)
29 expect(report.mission.state).toBe('running')
30 expect(report.board.actuators).toEqual([0, 0, 0, 0, 0, 0, 0, 0])
31}, 180000)