Jarvis
Provides optional local LLM support for analyze and free-form interpretation in the Jarvis engineering engine.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@JarvisDiseña un dron para carga de 2 kg con autonomía de 30 minutos."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Jarvis
v0.6.20 tagged tip · T12 FOLLOW ★ · T11 arm UX ★ · basic mando + Safety latch + FOLLOW CLOSED · ontology explain @ v0.6.0 · bloque 0.5 historical @ v0.5.44
Deterministic engineering engine for designing physical systems with AI-assisted natural language.
Jarvis is aerial-first (drones and related vehicles): you describe goals and components in Spanish; calculation and simulation stay rule-based and auditable. The model may interpret — it does not invent the physics.
Read the one-page contract: VISION.md.
Fase C (platform scaffold) in plain language: what “scaffold” means and what each new package is for — ARCHITECTURE.md §1a.
Quick start
git clone https://github.com/mcuestamunoz/Jarvis.git
cd Jarvis
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev,mcp]"CLI chat (optional: local Ollama for analyze / free-form interpret):
python -m jarvis.main --chat
# or
jarvis --chatPizarra (visor 3D + Continuity spatial assembly; huecos de arquitectura = slots, no BOM; mutación = CLI / Continuity / Situar drag):
jarvis boardTests:
pytestMCP server (Cursor / MCP clients):
python -m jarvis.adapters.mcp.serverWorkspace projects live under workspace/ (override with JARVIS_WORKSPACE_ROOT).
Ollama defaults: JARVIS_OLLAMA_BASE_URL, JARVIS_OLLAMA_MODEL (see src/jarvis/config.py).
Related MCP server: COMSOL MCP Server
What v0.5.30 includes
Fase C · C32 (B1-fase-c-mcu-spi-hal-stub) — the SPI port got a name, still not the gyro:
New
native/flight_control/include/jarvis/fc/spi.hpp/src/spi.cpp:SpiBytePort— an abstract base with a virtual destructor exposingtransfer(tx, rx, n) -> size_t(copies up tonbytes from TX into RX, in order, never blocks) — andLoopbackSpi, the only implementation: RX = TX, default capacity256.A transfer past capacity returns a short count rather than growing unbounded — same overflow policy
LoopbackUart(C28) already uses. UnlikeLoopbackUart's own persistent FIFO,LoopbackSpicarries no state between calls — a real SPI transfer is a single synchronous exchange, not a stream.Zero SPI registers, zero CMSIS, zero chip-select/NSS GPIO, zero IRQ/DMA anywhere in either file.
The desk FC's own gyro (when later wired) is an ICM42688P on SPI — cited only as desk identity in a comment, never in real code: no
WHO_AM_Iread, no register map, no sample.dshot.hpp/dshot.cpp/dshot.py(C31),hello_led.h/hello_led.c/stub_main.cpp(C30), anduart.hpp/uart.cpp(C28) all stay byte-identical — idle is still only PC13, no SPI poll inmain.SimulatedImuHal(C3) untouched.MCU SPI stub != chip SPI != gyro live != flying. A named SPI byte port exists; an in-memory loopback implements it.
Package
0.5.30· taggedv0.5.30· suite 3649 · hostctest60/60 — ★ ACCEPT CLOSED — reviewNext: C33 CLOSED @
v0.5.31.
What v0.5.31 includes
Fase C · C33 (B1-fase-c-spi-scripted-slave) — a second SpiBytePort: canned RX, not TX echo, still not the gyro:
Extends the existing
native/flight_control/include/jarvis/fc/spi.hpp/src/spi.cpp(no new file) withScriptedSpi, a secondSpiBytePortimplementation —LoopbackSpi(C32) stays byte-behavior-unchanged.ScriptedSpi(canned_rx)fillsrx[0..accepted)from a pre-loaded byte script, never fromtx— this is how a test pretends "a device answered" without any real chip.set_next_rx(...)reprograms the script for subsequent transfers; eachtransfer(...)call fills RX from the start of the currently-loaded script (a fixed canned response, not a consuming stream across calls).Same overflow policy as
LoopbackSpi/LoopbackUart: a script shorter than the requestednreturns a short count, and the untouched tail ofrxis left exactly as the caller passed it in.Zero SPI registers, zero CMSIS, zero chip-select/NSS GPIO, zero IRQ/DMA, zero CRSF/ELRS tokens anywhere in
native/.WHO_AM_I/ICM42688Pappear only in comments (e.g. "a future gyro test could load0x47here"), never in real code.stub_main.cpp,hello_led.h/hello_led.c(C30),dshot.hpp/dshot.cpp/dshot.py(C31), anduart.hpp/uart.cpp(C28) all stay byte-identical — no SPI poll inmain.Scripted SPI != gyro live != chip SPI != flying. A canned-RX test double exists. Nothing here reads a real device.
Package
0.5.31· taggedv0.5.31· suite 3667 · hostctest66/66 — ★ ACCEPT CLOSED — reviewNext: C34 CLOSED @
v0.5.32.
What v0.5.32 includes
Fase C · C34 (B1-fase-c-spi-scripted-gyro-probe) — a client of SpiBytePort, not a gyro driver:
New
native/flight_control/include/jarvis/fc/spi_probe.hpp/src/spi_probe.cpp:probe_rx(SpiBytePort& port, uint8_t* rx, size_t n) -> size_t— sendsndummy zero TX bytes (never a register address) and returns whateverport.transfer(...)moves intorx.n == 0returns0.spi.hpp/spi.cpp(C32/C33) get zero edits — the port stays the port,spi_probe.*is the first caller.On
ScriptedSpi(C33) loaded with a placeholder fixture byte,probe_rxreturns that byte — proving the RX path end-to-end. OnLoopbackSpi(C32),probe_rxreturns all-zeros (the dummy TX echoed back) — proving the client is port-shaped, not tied to one implementation.Same overflow policy as the port itself: a script shorter than
nreturns a short count, untouched tail ofrxleft exactly as passed in.Zero SPI registers, zero CMSIS, zero chip-select/NSS GPIO, zero IRQ/DMA, zero CRSF/ELRS tokens anywhere in
native/.WHO_AM_I/ICM42688P/register address0x75do not appear anywhere inspi_probe.*, not even in a comment — the fixture byte itself lives in tests only, never in library code.stub_main.cpp,hello_led.h/hello_led.c(C30),dshot.hpp/dshot.cpp/dshot.py(C31),uart.hpp/uart.cpp(C28), andloop.hpp/loop.cpp/loop.py(C3/C4) all stay byte-identical — no probe poll inmain, no IMU-into-stepwiring.Scripted gyro probe != gyro live != chip SPI != WHO_AM_I != flying. A byte-probe client exists. Nothing here reads a real device or claims an identity register.
Package
0.5.32· taggedv0.5.32· suite 3679 · hostctest72/72 — ★ ACCEPT CLOSED — reviewNext: Taller CSS
B1-geometry-taller-css-cuboid-facesREADY — six faces on a thin plate (visor, not CAD). Cola: standoff points. DShot wire / C30 DFU parked until bench.
What v0.5.33 includes
Fase C · C35 (B1-fase-c-step-failsafe-hold-ticks) — tests only: many ticks, still not flying:
No production code changed.
loop.py/loop.hpp/loop.cpp,rc_hold.hpp/rc_hold.cpp,crsf_failsafe.py, andspi_probe.hpp/spi_probe.cppare all byte-unchanged — this Buy only chains the EXISTINGControlLoop::step/FlightControlLoop.stepandRcHoldWatch/CrsfRcHoldWatch+failsafe_loop_inputsmore times, and in a new combination.New tests (Python + 4 new Catch2 cases in
test_loop.cpp): 1000step()ticks with a canned level IMU sample (accel ≈ (0,0,-9.81), gyro zeros) and no plant — every tick's four motor forces stay finite and in[0, 1].A stale hold watch (never noted, or past the 0.5 s timeout) feeds
failsafe_loop_inputs(t)into that samestep()—collectivepassed in is0, forces still finite and in[0, 1]. NoEscOutput/GPIO call anywhere in the new tests."Hold" means feeding
level_setpointfor those N ticks, notAutonomyVerb.HOLDreaching execution —submit_command(HOLD)still returnsexecution="not_attempted"under the defaultRejectAllSafetyGate, re-asserted explicitly.probe_rx(C34) is not used as an IMU source anywhere in these tests — the canned IMU sample is a plain struct literal, same as every prior Buy on this axis.Many ticks != flying != 6-DoF. Failsafe -> step != motors cut != HOLD executed. A thousand ticks of
step()on canned IMU, and a stale-RC path into the samestep(), both exist. Nothing here is a flying plant, a motor cut, or an executed autonomy command.Package
0.5.33· taggedv0.5.33· suite 3691 · hostctest76/76 — ★ ACCEPT CLOSED — reviewNext: Taller CSS cylinder faces ★ ACCEPT CLOSED @
v0.5.35. D2 docs LANDED @ package0.5.36(awaiting Cursor review + Engineer ★ ACCEPT, no tag yet). Cola: standoff points. DShot wire / C30 DFU parked until bench.
What v0.5.34 includes
Geometry / visor · B1-geometry-taller-css-cuboid-faces — six box faces stop exploding on a thin plate, still not CAD:
UI/CSS visor fix, not a Fase C flight-software Buy. New pure helper
ui/spatial-board/src/cuboidFaces.ts—cuboidFaceLayout(w, d, h)— returns six{name, width, height, left, top, transform}entries;Solid3D.tsx'sboxbranch now maps them onto the six.sb-solid__facenodes instead of hardcoding six transform strings inline.Root cause: each face sat at the
.sb-solid__faceCSS defaultleft: 0; top: 0.front/backmatch the wrapper's ownw x hsize so they were never wrong, butleft/right(widthd) andtop/bottom(heightd) do not — withtransform-origin: 50% 50%(never overridden), an off-center face rotates about the wrong point and swings out, worst on a thin plate.Fix: center every face first (
left: (w-fw)/2,top: (h-fh)/2), then rotate, thentranslateZ(half-extent along that face's own normal). Still exactly six.sb-solid__facenodes — no seventh, no CAD, no fit verdict.Verified against the MY5 top-plate fixture (
161x42x2mm,pxPerMm 0.5->80.5x21x1px):top/bottomcenter totranslateZ(0.5px),left/righttotranslateZ(40.25px), and no face's transform string containstranslateX/translateY— centering lives entirely inleft/top, never in the transform.Cylinder/disk branches in
Solid3D.tsx, the projector, thegeometryDTO, andspatial_board.pyall stay byte-unchanged —git diff --statempty.Visor cuboid != CAD != fit != extra parts != a 2D card's own origin. Six CSS faces that meet on a thin declared box exist. Nothing here is a machined plate, a fit verdict, or a seventh part.
Package
0.5.34· taggedv0.5.34· UI vitest 136/136 ·tsc --noEmitclean · Python suite 3691 (unchanged — no new Python tests) — ★ ACCEPT CLOSED (Engineer Taller smoke 2026-09-25) — reviewNext: Taller CSS cylinder faces ★ ACCEPT CLOSED @
v0.5.35. D2 docsB1-docs-truth-sync-after-c35READY. Cola: standoff points.
What v0.5.35 includes
Geometry / visor · B1-geometry-taller-css-cylinder-faces — caps + 16 slats stop exploding on a short/tall cylinder, still not CAD:
UI/CSS visor fix, not a Fase C flight-software Buy — same fix, same class of bug, as the cuboid Buy that shipped @
v0.5.34. New pure helperui/spatial-board/src/cylinderFaces.ts—cylinderSolidLayout(diameterPx, heightPx)— returns{caps, slats}: 2 caps and 16 side slats, each{width, height, left, top, transform};Solid3D.tsx'scylinderbranch now maps them onto the.sb-solid__disk-face/.sb-solid__facenodes instead of hardcoding the transforms inline.Root cause: each
D x Dcap sat at the CSS defaultleft: 0; top: 0on aD x Hwrapper — correct only whenH == D. Withtransform-origin: 50% 50%(never overridden), a short prop hub (H << D) or a tall standoff-shaped post (H >> D) rotates each cap about the wrong point and it swings off axis. The 16 side slats already re-centered horizontally before this Buy — that part was already correct and is reused unchanged, just relocated into the new helper.Fix: center each cap first (
left: 0,top: (H-D)/2— negative whenH < D), then rotate, thentranslateZ(H/2)— nevertranslateYagain after centering. Still exactly 2 caps and 16 slats (N frozen) — no 17th face, no invented diameter or height.Verified against a short-hub fixture (Ø130.72 x 6.8mm -> px
65.36x3.4, captop=-30.98,translateZ(1.7px)) and a tall-post fixture (Ø6 x 30mm -> px3x15, captop=6,translateZ(7.5px)) — both directions of the bug, not just the thin-plate case. No cap or slat transform string containstranslateX/translateY.cuboidFaces.ts(the box helper), the disk branch,SCENE3D.pxPerMm, the projector, thegeometryDTO, andspatial_board.pyall stay byte-unchanged —git diff --statempty on every one of them.Visor cylinder != CAD != fit != extra parts != round metal != a standoff hole pattern. 2 CSS caps + 16 CSS slats that meet on a declared Ø x H exist. Nothing here is a turned standoff, a prop hub from the mill, or a fit verdict.
Package
0.5.35· taggedv0.5.35· UI vitest 142/142 ·tsc --noEmitclean · Python suite 3691 (unchanged — no new Python tests) — ★ ACCEPT CLOSED (Engineer Taller smoke 2026-09-25) — reviewNext: D2 docs LANDED @ package
0.5.36(awaiting Cursor review + Engineer ★ ACCEPT, nov0.5.36tag yet) —B1-docs-truth-sync-after-c35· report. Cola: standoff points.
What v0.5.36 includes (landed, awaiting Cursor review + Engineer ★ ACCEPT — no v0.5.36 tag yet)
Docs · B1-docs-truth-sync-after-c35 — maps and READMEs now name the same tip as ARCHITECTURE.md:
Docs-only Buy — no
src/,ui/, orlibrary/product edits. A Phase 0 inventory (.jes/artifacts/inventory_docs_truth_sync_after_c35_b0.md, one row per in-scope file) was written and gated before any Phase 1 edit, per the IC's own STOP-before-bulk-edits rule.docs/system_map/README.mdandJARVIS_SYSTEM_MAP.md's own "Fase C" section stopped naming tipv0.5.3/suite3236(a snapshot from C1-C5, 2026-09-20) as current — both now name tipv0.5.35, live suite/UI/ctest counts, and point atARCHITECTURE.md§1c /PLATFORM_CAPABILITY_VISION.md§13.CONNECTIONS.mdgets one new changelog paragraph (C6-C35 + Taller CSS, no new C-xxx) after its existing C1-C5 entry — the canonical registry table itself is untouched, still ending at C-113.The interactive canvas (
jarvis-system-map.canvas.tsx) andDIAGRAMS.mdboth stopped implying "no C++/CMake tree" or "C++ (future IC)" as a current fact — a C++ host+MCU-cross-compile tree has existed undernative/flight_control/since C13. No fake SPI/stepgraph nodes were added — this is prose-only.native/flight_control/README.md's own layout list now namesdshot.hpp,spi.hpp,spi_probe.hpp, and their test files (previously stopped at C28'suart.hpp); C30's two "not yet tagged" mentions are corrected to "★ ACCEPT CLOSED @ tagv0.5.28" (the tag exists —git tag -l v0.5.28); new honesty paragraphs for C31-C35 were added matching this README's own established per-Buy style.docs/USER_GUIDE_CRAFT_MONTAGE.md§8.4 gets one short Spanish paragraph: a declared box is one six-face prism, a declared cylinder is one body (2 caps + 16 slats), a thin plate or short hub is still one solid — not extra parts, not CAD, not a fit verdict. No MY5 mm, no standoff ×8 anywhere in it.docs/system_map/00_entry/ENTRY_MAP.md's visor projector row gets one clause naming Taller CSS cuboid + cylinder @v0.5.35, still C-094/C-113 class, no new C-xxx.Left untouched, verified clean:
VISION.md,docs/PROJECT_CONTINUITY.md,docs/ENGINEERING_READINESS_VISION.md,docs/BUGS.md/FASE_LLM.md/CODE_AUDIT_CORE.md(already carryHISTORICALbanners),docs/IMPLEMENTATION_TASKS.md's closed archive sections,ARCHITECTURE.md§1c's own historical C3 quote, and 22 otherdocs/*.mdfiles with zero stale Fase C references (grep-verified, listed in the inventory).Docs tip != flying != gyro live != DShot pin != new C-xxx != standoff ×8 shipped. Maps and READMEs that name the same tagged tip as
ARCHITECTURE.mdexist. Nothing here is a new craft connection, firmware on the desk, or the MY5/standoff catalog claimed as shipped.Package
0.5.36· Python suite 3691 (unchanged) · UI vitest 142/142 · hostctest76/76 — landed, awaiting Cursor review + Engineer ★ ACCEPT, nov0.5.36tag yet — reportNext: awaiting Engineer ★ ACCEPT on this docs Buy. Cola: standoff points.
What v0.5.37 includes
Fase C · C36 (B1-fase-c-sim-6dof-plant) — a toy body that can move through space, not just tilt:
New
ToyQuad6DofPlant(Pythonplant.py+ C++plant.hpp/plant.cpp) beside unchangedToyQuadAttitudePlant(C11) — purely additive; C11 tilt-recovery smoke still green.Translation:
thrust_body = (0,0,thrust_gain*sum(forces))·a_world = R(q)·(thrust/mass)+g· semi-implicit Euler · ENUg=(0,0,-9.81).ImuSamplestays C11-shaped (gravity in body + gyro — no specific force). Pose ontrue_position_m/true_velocity_mpsonly. Plant stays outsideloop.step.6-DoF toy ≠ flying ≠ product aero ≠ MY5 truth.
Package
0.5.37· taggedv0.5.37· suite 3696 · hostctest82/82 — ★ ACCEPT CLOSED — reviewNext: C37 ★ ACCEPT CLOSED @
v0.5.38.
What v0.5.38 includes
Fase C · C37 (B1-fase-c-mag-yaw-rung) — yaw stops being only gyro drift:
MagSample+SimulatedMagHal(Py+C++) — caller-supplied true q; toy horizontal world field; HAL never owns a plant.ComplementaryAttitudeEstimator.update(sample, mag=None)— mag optional;mag=None≡ C7; yaw-only world-frame correction when present.RC_CH_YAWunlocked (RC_MAX_YAW_RAD=π); roll/pitch/throttle unchanged. Mag fusion outsideloop.step.Sim mag ≠ live mag ≠ flying.
Package
0.5.38· taggedv0.5.38· suite 3706 · hostctest88/88 — ★ ACCEPT CLOSED — reviewNext: C38 ★ ACCEPT CLOSED @
v0.5.39. Cola: C39 position loop (sim). Assistant PARKED. Silicon parked.
What v0.5.39 includes (★ ACCEPT CLOSED)
Fase C · C38 (B1-fase-c-altitude-loop) — collective stops being only the RC stick or a fixed constant:
New
AltitudeSample+SimulatedAltitudeHal(Pythonsim_altitude_hal.py+ C++sim_altitude_hal.hpp/.cpp) — a direct altitude port, not a fake ISA pressure/meteorology model:read_altitude(true_z_m, t_s)returns the caller-supplied true height directly. This HAL never owns or secretly consults a plant.New
AltitudeController(Pythonaltitude_controller.py+ C++altitude_controller.hpp/.cpp) — one law only, run outsideFlightControlLoop.step:collective = clip(hover_bias + kp*(z_des_m - altitude_m) - kd*vz_mps, 0, 1).hover_biasdefaults to the toy hover point that actually balancesToyQuad6DofPlant's own default gravity/thrust constants (mass_kg*g/(4*thrust_gain)≈0.1226) — not the IC's suggestedhover_collective()(0.5), which was verified empirically to badly mismatch this plant's own physics and never settle near the setpoint.vz_mpsis caller-supplied (the smoke passesToyQuad6DofPlant.true_velocity_mps[2]directly), not internally integrated.Deliberately named
sim_altitude_hal.py/altitude_controller.py, notaltitude.py— this codebase already shipsattitude.py(the C7 estimator); "altitude"/"attitude" differ by one letter, avoided the same way in file names, class names (noAltitudeSetpointnext to the existingAttitudeSetpoint— a plainz_des_mfloat is used instead), and the C++ headers.New smoke
run_altitude_loop_smokechainsalt.compute(...)→loop.step(...)→plant.step(...)exactly as this Buy's own IC requires — starting atz=0withz_des_m=2.0, verified converging monotonically (no overshoot) to within0.07mof the setpoint over 500 steps (5 seconds sim time).Sim altitude != live baro/ToF chip. z→collective in RAM != altitude hold in air. A simulated height measurement drives thrust so the toy plant moves in
z. Nothing here reads a real sensor or claims any real vehicle holds altitude.Package
0.5.39· taggedv0.5.39· suite 3714 · hostctest93/93 — ★ ACCEPT CLOSED — reviewNext: C39 ★ ACCEPT CLOSED @
v0.5.40. Cola: C40 autonomy executor (sim). Assistant PARKED. Silicon parked.
What v0.5.40 includes (★ ACCEPT CLOSED)
Fase C · C39 (B1-fase-c-position-loop) — horizontal motion stops being only open-loop tilt or luck:
New
PositionSample+SimulatedPositionHal(Pythonsim_position_hal.py+ C++sim_position_hal.hpp/.cpp) — a direct ENU port, not a NMEA/WGS84/optical-flow stack:read_position(true_x_m, true_y_m, t_s)returns the caller-supplied true horizontal position (East/North) directly. Optionalz_mallowed, unused by the controller. This HAL never owns or secretly consults a plant.New
PositionSetpoint(x_m, y_m)+PositionController(Pythonposition_controller.py+ C++position_controller.hpp/.cpp) — one law only, run outsideFlightControlLoop.step:pitch_rad = clip(kp*(x_des-x) - kd*vx, -max_tilt_rad, max_tilt_rad),roll_rad = clip(-(kp*(y_des-y) - kd*vy), -max_tilt_rad, max_tilt_rad), composed into anAttitudeSetpointquaternion with yaw held at0.max_tilt_raddefaults toRC_MAX_TILT_RAD(pi/6, 30 degrees, C25).Signs derived and proven: a positive pitch rotates body +Z thrust onto positive world X (East) — no sign flip on the pitch term; a positive roll rotates body +Z thrust onto negative world Y (South) — hence the sign flip on the roll term. Verified with a direct closed-loop test: East-only setpoint moves
true_position_m[0]up, North-only setpoint movestrue_position_m[1]up, with no cross-axis coupling.No naming-collision concern this Buy (unlike C38's
altitude/attitude) —PositionSetpoint/PositionController/PositionSample/SimulatedPositionHalare used directly, typed, per the IC's own preference.New smoke
run_position_loop_smokechainspos.compute(...)→alt.compute(...)(C38's ownAltitudeController, unchanged) →loop.step(...)→plant.step(...)— starting at(0,0,0)withx_des_m=5.0, y_des_m=0.0, z_des_m=2.0, verified horizontal distance shrinking from5.0mto under0.25mover 1000 steps (10 seconds sim time).Sim position != live GPS/flow chip. xy→tilt in RAM != position hold in air != GO_TO executed. An ENU point in RAM != a house map. Nothing here reads a real sensor or claims any real vehicle holds position, and this Buy does not implement the
AutonomyVerb.GO_TOexecutor (that stays C40, a separate, future Buy) —propose_command(GO_TO)still hitsRejectAllSafetyGate.Package
0.5.40· taggedv0.5.40· suite 3723 · hostctest101/101 — ★ ACCEPT CLOSED — reviewNext: C40 ★ ACCEPT CLOSED @
v0.5.41. Cola: C41 safety-sim policy. Assistant PARKED. Silicon parked.
What v0.5.41 includes (★ ACCEPT CLOSED)
Fase C · C40 (B1-fase-c-autonomy-executor) — autonomy verbs stop being only labels that RejectAll:
New
SimAutonomyExecutor(Pythonflight_software/autonomy/sim_executor.py+ C++sim_autonomy_executor.hpp/.cpp) — a separate, sim-only driver, not a change to the C4 command surface:propose_command/submit_command/AutonomySubmissionResultare completely untouched, andexecutionis never set to"executed"anywhere in this module.Reuses, never reimplements: every
tick(verb, params, dt_s)call chainsPositionController.compute(C39) →AltitudeController.compute(C38) →FlightControlLoop.step(C24) →plant.step(C36) — the same four-call chainrun_position_loop_smokealready used, now a stateful per-verb driver.HOLD freezes a
PositionSetpointat the current (or caller-supplied) xy + z the first tick it's ticked, then holds that frozen point every subsequent tick. GO_TO requires finitex_m/y_m;z_moptional, defaulting to the altitude frozen at the first GO_TO tick (not a new invented cruise-altitude constant). LAND freezes xy the same way HOLD does and ratchetsz_des_mdownward by a documentedland_rate_mps(default0.5 m/s) toward a documented floorz_land_m(default0.0) — not a claim of touchdown gear. Any other verb raises.Respects C39 N2 (z may droop under tilt): tests do not require altitude glued to setpoint during an aggressive
GO_TOchase;HOLDasserts both xy and z stay within a documented bound only after settling;LANDasserts z strictly decreases toward the floor.Verified:
GO_TOshrinks horizontal distance to(3.0, 0.0)from3.0mto under0.1mover 1500 steps (withz_m=2.0also converging);HOLD(after that convergence) keeps a 500-tick window within0.5mon every axis;LAND(from a convergedz≈2.0) brings z under0.5mover 1000 steps.verb -> setpoints in RAM != execute on copper. Sim HOLD/LAND/GO_TO != flying != Safety allow.
propose_command/submit_commandthrough the defaultRejectAllSafetyGatestill returnsreject/not_attemptedfor all three verbs — this Buy does not touch Safety (that's C41).Package
0.5.41· taggedv0.5.41· suite 3732 · hostctest105/105 — ★ ACCEPT CLOSED — reviewNext: C41 ★ ACCEPT CLOSED @
v0.5.42. Cola: C42 ICM client. Assistant PARKED. Silicon parked.
What v0.5.42 includes (★ ACCEPT CLOSED)
Fase C · C41 (B1-fase-c-safety-sim-policy) — Safety can say yes to the same three verbs the sim executor understands:
ArmedAllowlistSafetyGate(C17) widened, not replaced: its allow-list grows from{HOLD, LAND}to{HOLD, LAND, GO_TO}— one gate class, still opt-in, still starting disarmed, still parsingautonomy:{verb}:{id}. No second, competingSimAutonomyAllowlistSafetyGatewas added — the IC's own "prefer one clear gate story" default.default_safety_gate()is unchanged — still alwaysRejectAllSafetyGate. NoAllowAllSafetyGateexists anywhere undersrc/.Allow still never means execute:
submit_command'sallowbranch staysexecution="not_implemented"for all three verbs — this gate is never called fromSimAutonomyExecutor.tick(C40), and that executor is never called fromsubmit_commandor fromsafety.py. The two APIs stay entirely separate call paths.AuthoritySignalstill never flipsallow— re-verified forGO_TOspecifically.C17's own pre-existing tests were disclosed-retargeted (not weakened):
GO_TOmoved from the "rejected" list to the "allowed" list intests/test_fase_c_safety_real_policy_b1.py, since the allow-list itself changed by design.allow != execute != flying. sim allowlist != copper arm != motors. GO_TO allow != GO_TO in air !=
SimAutonomyExecutor.tick.Package
0.5.42· taggedv0.5.42· suite 3741 — ★ ACCEPT CLOSED — reviewNext: C42 ★ ACCEPT CLOSED @
v0.5.43. Cola: C43 craft↔FS bind. Assistant PARKED until C43 CLOSED. Silicon parked.
What v0.5.43 includes (★ ACCEPT CLOSED)
Fase C · C42 (B1-fase-c-icm-register-client) — the placeholder probe becomes a named device transaction:
New
read_who_am_i(SpiBytePort&) -> WhoAmIResult(C++ only,native/flight_control/include/jarvis/fc/icm42688p.hpp+.cpp) — a second, named client ofSpiBytePortsitting besideprobe_rx(C34, kept, unmodified).Datasheet citation: TDK InvenSense ICM-42688-P (DS-000347) —
WHO_AM_Iregister at address0x75, expected value0x47. Verified via web search cross-referenced against the open-source PX4-Autopilot driver's own register header, since every direct datasheet PDF fetch attempted this session returned HTTP 403 — that corroboration path is disclosed in the header comment rather than a fabricated page/table citation.Transaction: the standard InvenSense-family 2-byte full-duplex read —
TX[0] = 0x75 | 0x80,TX[1]= dummy,RX[1]= value — sent throughSpiBytePort::transfer, no bypass (verified with a recording test double, not just a real-port smoke).WhoAmIResult{value, matches_expected, bytes_transferred}— a short transfer (exhaustedScriptedSpifixture) is always a documented mismatch, never a silent success;LoopbackSpi(which only ever echoes TX) is verified to never falsely claim a match.No second register was added — a disclosed scope choice (one well-corroborated register beats a second, weakly-sourced one), not a silent omission.
This is a C++-native Buy, same axis as C32-C34 — no Python
SpiBytePort/client exists or was added;tests/test_fase_c_icm_register_client_b1.py's own checks are structural (freeze-diffs, forbidden-token greps, CMake wiring), matching C34's own established pattern.ICM register client on ScriptedSpi != chip SPI1. WHO_AM_I in RAM != gyro live != samples in step. Datasheet cite != lab measurement on copper.
Package
0.5.43· taggedv0.5.43· suite 3750 · hostctest111/111 — ★ ACCEPT CLOSED — reviewNext: C43 ★ ACCEPT CLOSED @
v0.5.44. Software-month CLOSED. Assistant DC discuss.
What v0.5.44 includes (★ ACCEPT CLOSED)
Fase C · C43 (B1-fase-c-craft-fs-bind) — the software-month tip can finally name which craft it's about:
New
bind_profile_to_craft_identity(profile, craft_sku, library=None) -> BoundVehicleProfile(src/jarvis/vehicle_profiles/bind.py) — a one-way, read-only bind:vehicle_profilesreads craft identity viaComponentLibrary(jarvis.knowledge.library, the existing sole reader oflibrary/JSON); craft packages (core/adapters/Continuity/CLI/Board) still never importjarvis.flight_software/jarvis.vehicle_profiles— a directed seam, not a two-way bridge.BoundVehicleProfileis a small wrapper type (profile+craft_sku/craft_manufacturer/craft_model/craft_size_class_inch), not an extension ofVehicleProfileitself — the C3 schema (extra="forbid", pinned by 40+ Buys' own fixtures) stays completely untouched;load_smoke_profile()/smoke_quad_hal_imuare unaffected.New fixture
this_quad.json(same C3-shaped declaration fields assmoke_quad_hal_imu.json— id/display_name/vehicle_class/rung/notes, no geometry/mass of its own) binds, via the new helper, to the desk's ownhglrc_my5_5incatalog frame (library/frames/_datos.json) — verified mirroring that row'smanufacturer="HGLRC"/model="MY5"/size_class_inch=5.0verbatim.An unknown SKU raises
KeyError— the same errorComponentLibrary.get_framealready raises, not a second, competing "not found" type — never a silent empty bind.Read-only, verified: the bind never writes
library/frames/_datos.json(mtime/content unchanged across a bind call) or any craft workspace file.Profile reads craft != Continuity drives firmware. Craft identity in RAM != flash != arm != flying. Directed seam != craft imports flight_software. No Continuity turn was added that flashes, arms, or submits into
flight_software;submit_command/propose_commandare never imported bybind.py.Package
0.5.44· taggedv0.5.44· suite 3759 — ★ ACCEPT CLOSED — reviewNext: after ★ ACCEPT, this closes the software-month arc — Assistant design-contract discussion may begin (still needs its own ★ to implement). Silicon parked.
What v0.5.29 includes
Fase C · C31 (B1-fase-c-dshot-encode-stub) — the DShot 16-bit frame, in RAM, still not a pin:
New
src/jarvis/flight_software/flight_control/dshot.py+native/flight_control/include/jarvis/fc/dshot.hpp/src/dshot.cpp:encode_dshot_frame(throttle, telemetry=False) -> uint16—value = (throttle << 1) | telemetry,checksum = (value ^ (value>>4) ^ (value>>8)) & 0xF,frame = (value << 4) | checksum,throttlea required[0, 2047]integer (out of range raises a typed error).Vectors verified in both languages:
throttle=0 -> 0x0000,throttle=48 -> 0x0606,throttle=2047 -> 0xFFEE.Special range documented, not implemented: DShot's own protocol reserves
0..47as commands (beep, 3D, etc.) — this module encodes the 11-bit field as given, no command table.Optional
encode_motor_forces_dshot(forces) -> 4x uint16linearly maps force[0,1]onto throttle48..2047(never0..2047) — a parallel path;encode_motor_forces(PWM-µs, C10/C14) andEscOutput/SimulatedEscSink(C26) both stay byte-unchanged.mcu/hello_led.h/hello_led.c/stub_main.cpp(C30) stay byte-identical — idle is still PC13, not DShot.DShot150/300/600 appear only as cited protocol names in comments, never as a claimed timer period or GPIO toggle rate.
DShot encode != pin != motors != flying. A 16-bit DShot packet computed in software exists. Nothing here makes an ESC see a waveform.
Package / tag
v0.5.29· suite 3635 · hostctest55/55 — reviewNext: C32 CLOSED @
v0.5.30. C33B1-fase-c-spi-scripted-slaveREADY.
What v0.5.28 includes
Fase C · C30 (B1-fase-c-mcu-flash-observable) — a DFU-able LED image exists; it has not been seen on the desk:
Closes the two residuals C29 B1 left uncorrected in code: (a)
stub_main's idle loop was an emptywhile (true)— a successfulReset_Handlerlooked identical to a dead board; (b) CMake had no dependency onlinker_cortex_m4.ld, so editing it never triggered a relink (C29's own test workaround deleted the.elffirst — not a real fix).(a) New
native/flight_control/mcu/hello_led.h/hello_led.c: barevolatileMMIO (no CMSIS, no HAL) toggling PC13, cited from Betaflight's own unified target for this FC (HGLR-HGLRCF405V2.config,resource LED 1 C13) — deliberately not PA8 (that target's ownresource MOTOR 6 A08) and not PB1 (LED_STRIP). Register addresses cited from RM0090, transcribed by hand. The busy-wait between toggles is explicitly uncalibrated — a visible flicker at reset-default HSI 16 MHz, never a claimed millisecond period.(b)
set_property(TARGET fc_mcu_stub.elf APPEND PROPERTY LINK_DEPENDS .../linker_cortex_m4.ld)inCMakeLists.txt— verified by touching the.ldand rebuilding without deleting the prior.elf: the.elf's mtime advances, confirming a genuine relink.A
POST_BUILDstep producesfc_mcu_stub.bin(load address0x08000000, unchanged from C29) for USB DFU — documented innative/flight_control/README.mdalongside the restore procedure (reflash target HGLRCF405V2 from Betaflight Configurator) and the props-off/battery-off warning.uart.hpp/crsf_serial.pystay byte-identical;startup_cortex_m4.c/syscalls_stub.c/C16's own toolchain flags too. No NVIC/EXTI/IRQ/DMA, no motor-pin write anywhere.Flashed LED blink != flying != DShot != USART live != Betaflight HGLRCF405V2. An image the Engineer can DFU onto the desk F405 whose idle loop toggles the cited status LED now exists; CMake will relink if the linker script changes.
Package / tag
v0.5.28· suite 3619 · hostctest51/51 — review. Report §11: not flashed on desk.Next: C31
B1-fase-c-dshot-encode-stubREADY (DShot frame in RAM, not pin). C30 DFU smoke parked until bench.
What v0.5.27 includes
Fase C · C29 B1 (B1-fase-c-silicon-cited-flash-map) — the linker now cites the desk MCU's own datasheet, still not flashed:
C29 B0 (investigation) recommended parking until the Engineer named an MCU — on 2026-09-24 they did: desk stack HGLRC F460 6S V1, FC SKU HGLRC F405 8S V1, MCU line STM32F405 (printed in that FC's own manual). This B1 replaces C18's disclosed fiction with that citation.
native/flight_control/mcu/linker_cortex_m4.ldnow usesFLASH1024Kat0x08000000/RAM128Kat0x20000000(SRAM1+SRAM2 contiguous) — taken from ST RM0090 Table 3 (STM32F405xx/07xx, "Memory map"), not from the HGLRC manual itself (which names the MCU line but prints no ORIGIN/LENGTH numbers).CCM RAM (
0x10000000, 64 KiB per RM0090) is deliberately excluded from theMEMORYblock — folding it into a flat RAM region would be its own undisclosed simplification.The linker's own honesty comment cites both the desk identity (HGLRC) and the map source (RM0090), plus the disclosed residual: the HGLRC manual doesn't print the STM32F405's exact order-code suffix, but RM0090 Table 3's figures apply to the whole xx/07xx line regardless.
mcu/stub_main.cppwas relinked and verified (readelf -l:VirtAddr 0x08000000, entry point0x8000045) but stays byte-unchanged — no new peripheral was touched.cmake/toolchains/arm-none-eabi.cmake(C16's own CPU flags) also stays byte-unchanged.No CMSIS, no STM32Cube, no OpenOCD/J-Link anywhere in the touched files.
Cited FLASH map != flashed != boots on FC != Betaflight HGLRCF405V2. The linker now uses ST's own published addresses for this MCU, but that remains a compile/link-time fact on the Mac, never a claim about the HGLRC stack itself.
Package / tag
v0.5.27· suite 3598 · hostctest51/51 — reviewNext: Engineer pick among parked axes — board flash · GPIO/DShot wire · craft↔FS
What v0.5.26 includes
Fase C · C28 (B1-fase-c-mcu-uart-hal-stub) — the MCU side got a UART-shaped port, still not a chip peripheral:
New
native/flight_control/include/jarvis/fc/uart.hpp+src/uart.cpp:UartBytePort— an abstract base with a virtual destructor exposingread(dst, n)/write(src, n)(neither blocks nor throws on a full/empty port) — andLoopbackUart, the only implementation: an in-memory FIFO (default capacity256).A write past remaining capacity returns a short count (refuses the extra bytes) rather than growing unbounded or overwriting already-queued bytes — a chosen, tested overflow policy.
Zero USART registers, zero CMSIS, zero
IOSSIOSPEED/termios, zero IRQ/DMA anywhere in either file — grep-verified in real code.mcu/stub_main.cppstill does not referenceUartBytePort/LoopbackUart— no UART poll loop was added toReset_Handler/main; the.elfstill links against the ARM toolchain when present.capabilities/crsf_serial.py(Mac host serial, C22/C23) stays byte-identical — this Buy is C++-only on the MCU tree, no Python UART driver added.The C21-C27 lock of zero CRSF/ELRS mentions anywhere under
native/, even in comments, holds — re-verified tree-wide (proactive grep on the new header; this Buy ID contains no protocol tokens, so no rewrite was required).MCU UART stub != chip USART != Darwin baud != live ELRS. A named byte port exists; an in-memory loopback implements it. Nothing here is a USART talking to a receiver, the Mac's own
IOSSIOSPEEDmoved onto the chip, or ExpressLRS running on the MCU.Package / tag
v0.5.26· suite 3584 · hostctest51/51 — reviewNext (one front at a time): C29 B1 silicon + cited FLASH map — IC
What v0.5.25 includes
Fase C · C27 (B1-fase-c-crsf-stream-timeout-failsafe) — stale sticks stop being treated as live, still not a motor cut:
New
src/jarvis/capabilities/crsf_failsafe.py— a sixth separate module (never folded intoradio.py;git diffconfirmsradio.py/crsf_stub.py/crsf_dual_role.py/crsf_stream.py/crsf_serial.py/intent.py/safety.pyall byte-unchanged).CrsfRcHoldWatchis an age watch, not a parser:note_rc(now_s)records the last time a valid0x16frame arrived;evaluate(now_s)/is_stale(now_s)compare that againsttimeout_s(default0.5s, illustrative, not sourced from any real ExpressLRS product spec) —age_s <= timeout_sis fresh,age_s > timeout_sis stale, never-noted is stale withreason="never".Every method takes
now_sas a caller-supplied argument — this module never callstime.time()as its own source of truth. Anow_searlier than the last noted time raises a typedValueError.failsafe_loop_inputs(t_s)returns C8's ownlevel_setpoint(t_s)pluscollective=0.0— C25's ownRcLoopInputsreused unchanged — and never callsFlightControlLoop.step,EscOutput.apply_forces, or anySafetyGate.evaluate.The optional
feed_and_note_rc(...)helper calls C21's ownassembler.feed(data)unchanged, notes only on a completed0x16frame, and never callsingest_stream_bytes— C21's own default helper stays untouched.C++ twin:
native/flight_control/include/jarvis/fc/rc_hold.hpp+src/rc_hold.cpp, added tojarvis_fc, deliberately protocol-agnostic — no radio-link protocol name anywhere in that tree, even in comments (same C21-C26 lock, re-verified tree-wide).C20's
CrsfDualRolePolicyand C25'smap_rc_to_loop_inputsare untouched and not imported here.Timeout failsafe != motors cut != live ELRS != Safety allow. An age watch exists; after
0.5s without a noted RC sample, sticks are no longer treated as live; the recommended inputs are level attitude plus zero collective.Package / tag
v0.5.25· suite 3572 · hostctest45/45 — reviewNext (one front at a time): C28 MCU UART HAL stub — IC
What v0.5.24 includes
Fase C · C26 (B1-fase-c-esc-output-hal) — the ESC output got a named port, still not a pin:
esc.py/esc.hpp(C10/C14's own module) gainEscOutput— Pythonabc.ABC, C++ abstract base with a virtual destructor — exposingapply_forces(forces: MotorForceCommand) -> EscApplyResultplusarm()/disarm()/armed(identical semantics to C10).SimulatedEscSinkis-aEscOutputin both languages. Its C10apply(EscPwmCommand)path and arming behavior stay byte-identical —apply_forcesis a thin wrapper:encode_motor_forces(forces)thenapply(cmd).The
esc.cppdiff is purely additive — verified line-by-line: zero lines removed/changed, four lines added.esc.py/esc.hpp/esc.cppare the only rung files touched this Buy;filter/attitude/controller/rate_torque/mixer/plantall staygit diff --statempty.The mixer still speaks forces only —
mixer.py/mixer.hppgain no PWM/DShot/pin knowledge, grep-verified in real code.stepstill never callsapply/apply_forces/SimulatedEscSink—loop.py/loop.hpp/loop.cppstay byte-unchanged, re-verified explicitly.Only one implementation ships:
SimulatedEscSink. No GPIO sink, no unimplemented pin class, no DShot this Buy.EscOutput HAL != pin != motors != DShot. A named port exists; the simulated sink implements it; the mixer still does not know the wire protocol.
Package / tag
v0.5.24· suite 3553 · hostctest39/39 — reviewNext (one front at a time): C27 CRSF stream-timeout failsafe — IC
What v0.5.23 includes
Fase C · C25 (B1-fase-c-rc-setpoint) — RC channels got a language toward the tick, still not flying:
New
src/jarvis/flight_software/flight_control/rc_setpoint.py:map_rc_to_loop_inputs(channels, *, t_s) -> RcLoopInputs— an illustrative AETR map (not a real TX model): roll/pitch/throttle = channel indices0/1/2; the yaw channel is unused this Buy (no magnetometer anywhere in this tree, so no absolute heading a yaw stick could honestly command).CRSF_CH_MIN/CRSF_CH_MID/CRSF_CH_MAX=172/992/1811(the same illustrative 11-bit convention already used around C20's policy). Throttle maps linearly ontocollective ∈ [0, 1], clipped — mid-stick gives≈0.5003, not exactly0.5(documented, not rounded away).Roll/pitch deflection is measured from
992, scaled to reach exactlyπ/6(30°) at either endpoint, clipped beyond it, composed intoq_body_to_world_desiredvia the standard body 3-2-1 Euler-to-quaternion formula with yaw fixed at0.C++ twin:
native/flight_control/include/jarvis/fc/rc_setpoint.hpp+src/rc_setpoint.cpp, added tojarvis_fc, same thresholds/formula,std::vector<int>in place of any CRSF-shaped type — the C21-C23 lock of zero CRSF/ELRS mentions anywhere undernative/stays intact (even in comments).Optional
step_with_rc(loop, sample, channels)maps then calls C24's ownFlightControlLoop.stepunchanged —loop.py/loop.hpp/loop.cppall byte-unchanged; never calls a plant,SimulatedEscSink, orSafetyGate.evaluate.C20's
CrsfDualRolePolicy(aux → Authoritykill) untouched and not imported here.radio.pystill has no stick API.default_safety_gate()unchanged.RC->setpoint != flying != sticks drive motors != Safety allow != yaw lock. A deterministic, documented map from already-decoded channel units to the two arguments
stepalready accepted — no pilot flies anything, no motor spins, no heading-hold exists.Package / tag
v0.5.23· suite 3538 · hostctest36/36 — reviewNext (one front at a time): C27 CRSF stream-timeout failsafe — IC
What v0.5.22 includes
Fase C · C24 (B1-fase-c-control-loop-tick) — the control loop got a name, still not flying:
New
src/jarvis/flight_software/flight_control/loop.py:FlightControlLoop.step(sample, setpoint, collective) -> ControlTickResult— extracts the body C11/C13 already ran inlined into one named tick:filter_sample -> estimator.update -> controller.compute -> bridge.convert -> mixer.mix, exactly the existing order, calling each existing rung's own unmodified method. No new math.C++ twin:
native/flight_control/include/jarvis/fc/loop.hpp+src/loop.cpp, added tojarvis_fc, same order, same existing classes.stepdoes not call the plant, read a real HAL, or write a pin — the caller still suppliesImuSampleand consumesMotorForceCommand/EscPwmCommanditself.dtis not an argument (no 1 kHz ISR claim);AttitudeSetpoint/collectiveare plain arguments (no RC decoding — mapping sticks to them is C25).run_controlled_flight_sim_smoke(Python, C11) andfc_closed_loop_smoke(C++, C13) are refactored to callstepinstead of inlining the chain — verified bit-identical to the pre-refactor behavior:15° → 0.252°in 200 steps, no gain retuned.mcu/stub_main.cppstill has noControlLoop/step(control cycle — noReset_Handlerspin.radio.py/crsf_*.py/intent.py/safety.py/autonomy/all byte-unchanged.default_safety_gate()unchanged.Named control tick != flying != MCU ISR != motors != RC sticks. One named cycle, IMU+setpoint+collective in, four motor forces out, reusing C6-C12/C13 unchanged; plant, ESC pin, and RC mapping remain later cola.
Package / tag
v0.5.22· suite 3520 · hostctest31/31 — reviewNext (one front at a time): C27 CRSF stream-timeout failsafe — IC
What v0.5.21 includes
Fase C · C23 (B1-fase-c-crsf-host-baud) — Darwin host baud 420000, still not a live link:
Extends C22's own
src/jarvis/capabilities/crsf_serial.py— no sixth module — with opt-in Darwin host baud configuration:configure_host_baud(fd, baud=420000)/CrsfHostSerialIngress.configure_baud(...).On
sys.platform == "darwin", applies raw 8N1 termios (disabling canonical mode and the CR/NL translations that would corrupt binary CRSF) then issues the realIOSSIOSPEEDioctl — the request number derived from the_IOW('T', 2, speed_t)macro, not copied frompyserialor any library; independently verified to equal0x80085402.On any other platform it fails closed with a typed
CrsfHostSerialError— no LinuxTCSETS2/BOTHER, no Windows serial stack.attach_fd/attach_pathstill never auto-configure baud — C22's own "open = give me bytes" contract is unchanged, re-verified by re-running C22's own test suite unmodified.Every PASS requires no hardware: the Darwin success path is proven entirely via a mocked
fcntl.ioctl; the one unmocked ioctl call runs against a real POSIXptyand is asserted to fail (a pty is not a UART) — that failure is the honest, expected outcome, not something skipped around.A successful ioctl is a host OS configuration fact, never proof a receiver exists. No
pyserialdependency was added.radio.py/crsf_stub.py/crsf_dual_role.py/crsf_stream.py/intent.py/safety.pyall byte-unchanged.default_safety_gate()unchanged.Host baud 420000 != live ELRS != RX connected != UART driver != Safety allow. Darwin can be asked to clock an attached FD at an ELRS-typical rate, proven without hardware via ioctl mock, nothing more.
Package file / tag
v0.5.21· suite 3504 — reviewNext (one front at a time): C27 CRSF stream-timeout failsafe — IC
What v0.5.20 includes
Fase C · C22 (B1-fase-c-crsf-host-serial) — CRSF host serial ingest, still not a live link:
New
src/jarvis/capabilities/crsf_serial.py— a fifth separate module (never folded intoradio.py;git diffconfirmsradio.py/crsf_stub.py/crsf_dual_role.py/crsf_stream.py/intent.py/safety.pyall byte-unchanged).CrsfHostSerialIngresspulls bytes from an already-open host FD (attach_fd, caller-owned, never closed by this module) or an opt-in device path (attach_path, ingress-owned, closed on.close());poll(...)performs exactly one non-blocking read then feeds C21's ownCrsfByteStreamAssembler, unchanged — no background thread, no "connected" flag, no/dev/cu.*auto-scan.Every PASS in this Buy's own test suite uses a POSIX
ptyas its loopback — no physical receiver or USB serial adapter is required, verified by running the full suite with nothing plugged in.No
pyserialdependency was added, and 420000 baud (the rate a real ELRS link runs at) is not configured anywhere — opening a path here means "give me bytes from this node," not "I configured an ELRS receiver"; custom-baud configuration is explicitly deferred to a later, platform-specific IC.A real pty gotcha was found and disclosed while testing: POSIX ptys default to canonical (line-buffered) mode, which silently held binary CRSF bytes back from a reader until a newline appeared — fixed entirely in the test harness (
tty.setraw(...)), not in the shipped module, which has no terminal-mode logic at all.Optional
poll_and_ingest(...)helper reuses C20'singest_rc_channels(...)unchanged for any completed0x16frames.RadioIntentAdapter.parse(...)still raisesNotImplementedError.default_safety_gate()unchanged.Host serial ingest != live ELRS != "RX connected" != UART driver != Safety allow. A pull-based FD/path reader proving bytes can be pulled from a host source and reassembled by C21's own assembler, verified entirely via
ptyloopback, nothing more.Tag
v0.5.20· suite 3485 — reviewNext (one front at a time — Engineer picks): deepen policy · board flash · craft↔FS · baud 420000
What v0.5.19 includes
Fase C · C21 (B1-fase-c-crsf-byte-stream) — CRSF byte-stream assembler, still not a UART:
New
src/jarvis/capabilities/crsf_stream.py— a fourth separate module (never folded intoradio.py;git diffconfirmsradio.py/crsf_stub.py/crsf_dual_role.py/intent.py/safety.pyall byte-unchanged).CrsfByteStreamAssembler.feed(data: bytes) -> list[CrsfFrame]reassembles frames from bytes delivered in arbitrary chunks (the shape a UART delivers data in) by slicing exactframe_len + 2candidate windows and handing them, unmodified, to C19's ownparse_crsf_frame— no second CRC8/envelope implementation (verified: no0xD5anywhere in the module).Incomplete candidates wait in a bounded leftover buffer (default cap
256bytes); invalid complete windows (bad CRC, or a declaredframe_lenoutside the plausible[2, 64]range) are never raised to the caller — the assembler drops exactly one byte and resyncs, looping until it finds a valid frame or exhausts the buffer.Optional
ingest_stream_bytes(...)helper reuses C20'singest_rc_channels(...)unchanged for any completed0x16frames — C20's policy (one aux channel, one threshold,AuthorityKind="kill"only) is neither deepened nor reconfigured here.Zero I/O anywhere in the module — no serial/socket/pty/USB/
open(), no class named likeSerial/UartPort.RadioIntentAdapter.parse(...)still raisesNotImplementedError, even fed real assembled frames.default_safety_gate()unchanged.Byte-stream assembler != UART open != live ELRS != a pilot link != Safety allow. A pure in-memory buffer proving bytes delivered in chunks reassemble into the same frames C19 already parses from a complete buffer, nothing more.
Tag
v0.5.19· suite 3465 — reviewNext (one front at a time — Engineer picks): host serial ingest · deepen policy · board flash · craft↔FS
What v0.5.18 includes
Fase C · C20 (B1-fase-c-crsf-dual-role-bridge) — CRSF decode → dual-role bridge, still not a link:
New
src/jarvis/capabilities/crsf_dual_role.py— a third separate module (never folded intoradio.py;git diffconfirmsradio.py/intent.py/safety.pyall byte-unchanged).Bridges C19's decoded
CrsfRcChannelsinto C5's typedRadioStubFrame/RadioDualRoleResultunder one documented, deterministic policy:CrsfDualRolePolicy(one aux channel index + threshold, illustrative defaults channel4/1500, not sourced from any real hardware) — at/above threshold emitsAuthorityKind="kill"only (Authority-only, never Intent); below threshold, returnsNone.Optional
link_statsenrichment folds LQ/RSSI/SNR intoRadioStubFrame.noteswithout ever changing whether Authority fires.ingest_rc_channels(...)optionally routes a produced frame throughSimulatedRadioIngress.ingest(...).Authority from this bridge stays trace-only relative to Safety — explicitly tested: wiring the bridge's own
AuthoritySignal.idinto aSafetyRequest.authority_signal_idstill yieldsrejecton bothRejectAllSafetyGateand an armedArmedAllowlistSafetyGate;default_safety_gate()is unchanged.RadioIntentAdapter.parse(...)still raisesNotImplementedError, even fed a real bridge-produced frame. The bridge never callssubmit_commandor imports the autonomy surface.No I/O anywhere in the module — no serial/socket/pty/USB/subprocess.
Bridge != live ELRS != a pilot link != Safety allow. A deterministic, documented map from already-decoded bytes to a typed dual-role frame, nothing more.
Tag
v0.5.18· suite 3444 — reviewNext (one front at a time — Engineer picks): UART stream · deepen policy · board flash · craft↔FS
What v0.5.17 includes
Fase C · C19 (B1-fase-c-crsf-link-stub) — first CRSF byte-fixture link stub (ELRS-shaped, host-only):
New
src/jarvis/capabilities/crsf_stub.py— a separate module from C5's ownradio.pyon purpose (its no-decode-API lock stays byte-unchanged, confirmed viagit diff).Parses the CRSF envelope (
[device_addr][frame_len][type][payload][crc8], CRC8 poly0xD5overtype+payload) from checked-in fixture bytes intoCrsfFrame, then decodes0x16RC_CHANNELS_PACKED(16 × 11-bit channels) and0x14LINK_STATISTICS(RSSI/LQ/SNR fields).Truncated frames and bad-CRC frames both raise a typed
CrsfParseError— no silent partial success — verified against four checked-in.binfixtures undertests/fixtures/crsf/.Zero I/O anywhere in the module — no serial/socket/pty/USB/subprocess, confirmed by inspecting real code with comments/docstrings stripped.
RadioIntentAdapter.parse(...)still raisesNotImplementedErroreven when fed real CRSF fixture bytes;default_safety_gate()andArmedAllowlistSafetyGateare untouched.Nothing decoded here reaches
SimulatedRadioIngress, autonomysubmit_command, or anySafetyGate. No CRSF/ELRS token anywhere undernative/.Fixture CRSF parse != live ELRS != a pilot link != a CRSF driver product. This module can decode a byte sequence, nothing more: no receiver is "connected," no air protocol (RF/binding/telemetry) is implemented, no pilot's sticks drive anything.
Tag
v0.5.17· suite 3428 — reviewNext (one front at a time — Engineer picks after C20 ACCEPT): UART stream · deepen policy · board flash · craft↔FS
What v0.5.16 includes
Fase C · C18 (B1-fase-c-cpp-mcu-freestanding-elf) — first freestanding linked MCU .elf:
New
native/flight_control/mcu/— generic Cortex-M4 linker script (FLASHat0x00000000/RAMat0x20000000, the ARM-architected generic Code/SRAM regions, not any vendor's remapped boot address; 256 KiB/64 KiB illustrative sizes, explicitly fictional), a 16-entry ARMv7-M vector table +Reset_Handlerstartup, minimal newlib syscall stubs (no semihosting, no real I/O), and a thin entry point that links realjarvis_fccode.Reuses C16's own toolchain file unchanged — produces
fc_mcu_stub.elfalongside the existinglibjarvis_fc.a.A real link was performed and verified:
readelf -hshowsMachine: ARM,Type: EXEC, a real entry point, soft-float ABI;nmconfirmsjarvis::fc::ImuLowPassFilter::filter_sampleandencode_motor_forcesare linked in as defined (not merely referenced) symbols.C++ exceptions kept enabled (option (a) over disabling them) — the rung sources'
throw std::invalid_argument(...)calls are untouched; the C++ runtime resolves via the toolchain's own libstdc++/newlib plus this Buy's own syscall stubs.A real build-system gap was hit and fixed, disclosed: the
.cstartup/syscall sources were silently never compiled (CXX-onlyproject()) until C was added as a project language — the linker'scannot find entry symbol Reset_Handlerwarning is gone after the fix.Host build re-verified unaffected:
cteststill 28/28 green.No GPIO/flash/OpenOCD/vendor BSP anywhere (grep-verified).
default_safety_gate()unchanged; autonomysubmit_commandstill always rejects. Does not touch Continuity, orchestrator IDLE, Board, orlibrary/.Freestanding
.elf!= flashed / != boots on hardware / != motors / != GPIO. A linked, inspectable, host-only ARM executable, nothing more; this image has never run on any board.Tag
v0.5.16· suite 3412 — reviewNext (one front at a time — Engineer picks after C19 ACCEPT): board flash · craft↔FS · deepen link stub
What v0.5.15 includes
Fase C · C17 (B1-fase-c-safety-real-policy) — first real (non-RejectAll) Safety policy gate:
src/jarvis/capabilities/safety.py—ArmedAllowlistSafetyGate: opt-in, starts disarmed (always rejects, reason"disarmed"), and once explicitlyarm()ed allows onlyHOLD/LAND(parsed fromsubmit_command's ownautonomy:{verb}:{id}action-id shape); any other verb or a malformedaction_idis rejected too ("verb_not_allowed"/"unparseable_action_id"— neither reuses RejectAll's"not_implemented").default_safety_gate()is byte-unchanged —git diffconfirms zero lines touched inRejectAllSafetyGateor the factory function; the shipped product default remains "reject everything."evaluate()never readsauthority_signal_idat all — Authority (C5) stays trace-only, unable to flip a decision through this or any gate.submit_command'sallowbranch, unreachable in shipped code since C4, already resolved toexecution="not_implemented"— zero changes were needed toflight_software/autonomy/surface.py/types.py; verified at runtime viatyping.get_args(ExecutionState) == {"not_attempted", "not_implemented"}.New
smoke_policy_gate_hold_and_land()helper demonstrates the armed allow path end-to-end (both verbsallow,executionstill"not_implemented").No
AllowAllSafetyGateanywhere insrc/, noSimulatedEscSink/GPIO coupling, no craft/CLI wiring.Policy allow != flying / != executed autonomy / != hardware Safety / != "safe to fly". A real, opt-in, narrowly-scoped software gate now exists alongside the unchanged RejectAll default; no gate shipped in
src/can ever returnexecution="executed".Tag
v0.5.15· suite 3403 — reviewNext (one front at a time — Engineer picks): MCU freestanding
.elf· link (ELRS) · craft↔FS
What v0.5.14 includes
Fase C · C16 (B1-fase-c-cpp-mcu-cross-compile) — first MCU cross-compile scaffold, a compile-time proof only:
New
native/flight_control/cmake/toolchains/arm-none-eabi.cmake—CMAKE_SYSTEM_NAME Generic, generic Cortex-M4 (-mcpu=cortex-m4 -mthumb -mfloat-abi=soft, not a claim about any specific board's silicon).Separate build (
build/flight_control_mcu) produces onlylibjarvis_fc.afor that triple — gated entirely via CMake (no#ifdefin any rung source), so Catch2/the unit-test binary/both smokes never build on the MCU path.A real cross-build was performed and verified:
arm-none-eabi-objdumpconfirmsfile format elf32-littlearm, architecture: armv7e-mon the produced archive — genuine target code.A real toolchain-completeness problem was hit and disclosed: the bare Homebrew
arm-none-eabi-gccformula has no bundlednewlib/libstdc++and fails to compile<optional>; the working build used the xPackarm-none-eabi-gccv15.2.1-1.1 release instead. The new pytest wrapper handles both outcomes — skips with a specific install hint rather than hard-failing when a compiler is present but incomplete.Host build re-verified unaffected:
cteststill 28/28 green (unit suite + both smokes).No GPIO/flash/OpenOCD/vendor BSP anywhere — STM32Cube, CMSIS device packs, ChibiOS, FreeRTOS, PX4, ArduPilot all absent (grep-verified).
default_safety_gate()unchanged; autonomysubmit_commandstill always rejects. Does not touch Continuity, orchestrator IDLE, Board, orlibrary/.MCU cross-compile != flashed / != flying / != firmware runs on a flight controller / != GPIO-verified. A compile-time proof the steel-ladder sources build freestanding for a Cortex-M4-class target, nothing more; no board has run this code.
Tag
v0.5.14· suite 3389 — reviewNext (one front at a time — Engineer picks): Safety-real · MCU freestanding
.elf· link · craft↔FS
What v0.5.13 includes
Fase C · C15 (B1-fase-c-cpp-unit-tests) — a real C++ unit-test framework, deepening host verification:
Catch2 v3, pinned to release tag
v3.7.1(commitfa43b77429ba76c462b1898d6cd2f2d7a9416b14), fetched via CMakeFetchContent— network needed once at first configure, none needed after (offline story documented innative/flight_control/README.md).New
native/flight_control/tests/— 26TEST_CASEs / ~494 assertions, at least one per steel rung: filter, attitude (including a C11 Amendment A regression guard), controller (sign-check on the dominant axis), rate_torque, mixer (documented X-geometry sign check), esc.ctestnow runs 28 entries total: the 26 unit cases (individually discovered viacatch_discover_tests) plus both pre-existing smoke binaries — all green.Behavior freeze honored exactly:
git diff --staton every pre-existing rung source (filter.cpp…esc.cpp/plant.cpp/all headers) is empty — this Buy added coverage only, no bug exposed, no Engineer-call needed.Both smoke binaries remain unmodified;
fc_closed_loop_smokere-verified byte-identical:15° → 0.252°in 200 steps.No GPIO/pigpio/
/dev/mem/serial/DShot/socket anywhere in the new test sources (grep-verified).default_safety_gate()unchanged; autonomysubmit_commandstill always rejects. Does not touch Continuity, orchestrator IDLE, Board, orlibrary/.C++ unit tests != flying / != MCU verification / != production-hardened / != algorithm change. A host desktop
ctestrun with a real framework instead of two hand-rolled smoke mains, nothing more.Tag
v0.5.13· suite 3380 — reviewNext (one front at a time — Engineer picks): MCU cross-compile · Safety-real · link · craft↔FS
What v0.5.12 includes
Fase C · C14 (B1-fase-c-cpp-esc-pwm-stub) — steel-ladder parity for the ESC/PWM encoding stub:
native/flight_control/include/jarvis/fc/esc.hpp+src/esc.cpp— ports Python C10'sencode_motor_forces/SimulatedEscSinkinto the C++ tree: same linear force→PWM-µs map (force=0 → min_us,force=1 → max_us, default1000–2000,min_us < max_usenforced), same in-memorySimulatedEscSink(armedstartsfalse;apply(cmd)always records the command,applied=trueonly while armed, otherwiseapplied=false/reason="disarmed").New, separate
fc_esc_pwm_smokeexecutable (18 checks, all passing) — kept apart from the C13 tip smoke on purpose, so that one stays focused.fc_closed_loop_smoke(C13's own tip) re-verified byte-identical:15° → 0.252°in 200 steps, unaffected by this Buy.No GPIO/pigpio/
/dev/mem/serial/DShot/Oneshot/Multishot/socket anywhere in the new sources (grep-verified).Python
esc.pyuntouched — re-verified with the same before/after values.default_safety_gate()unchanged; autonomysubmit_commandstill always rejects. Does not touch Continuity, orchestrator IDLE, Board, orlibrary/.C++ ESC stub != hardware write / != ESC online / != motors spinning. Closes the C++ tree's module parity with the Python wooden ladder (filter/attitude/controller/rate_torque/mixer/esc, plus the plant tip).
Tag
v0.5.12· suite 3368 — reviewNext (one front at a time — Engineer picks): MCU cross-compile · deepen C++ tests · real Safety · real link · craft↔FS
What v0.5.11 includes
Fase C · C13 (B1-fase-c-cpp-flight-control-scaffold) — the first material C++ scaffold for flight_control:
New
native/flight_control/tree (outsidesrc/jarvis/, locked path) — C++17, host-only CMake ≥ 3.16 build. Not MCU firmware, not a board bring-up, no cross-compile requirement in this Buy.Mirrors the Python wooden ladder module-for-module:
filter/attitude/controller/rate_torque/mixer/plant, same formulas, same ENU convention, same honesty notes — including the C11 Amendment A accel-correction sign fix from day one.fc_closed_loop_smokeexecutable (also runnable viactest) runs the same closed-loop tip: seeded at 15° tilt, the true tilt error recovers to 0.252° after 200 steps — matching the Python ladder's own number, though bit-identity was not required by the IC.No GPIO/pigpio/
/dev/mem/serial/DShot/socket anywhere in the tree (grep-verified) — pure host math and stdout. No PX4/ArduPilot vendored.Python
flight_software/package untouched except a docstring pointer — it remains the design guide and the craft platform; this Buy does not delete or replace it.default_safety_gate()unchanged; autonomysubmit_commandstill always rejects. Does not touch Continuity, orchestrator IDLE, Board, orlibrary/.C++ scaffold != flying / != hardware flight controller / != firmware on any board / != replacing Python craft SoT. A host desktop build proving the same algorithmic ladder in a second language, nothing more.
Tagged
v0.5.11on Engineer ACCEPT (suite 3358) — reviewNext (one front at a time): deepen C++ parity · MCU cross-compile · Safety-real · link · craft↔FS — Engineer prioritizes
What v0.5.10 includes
Fase C · C12 (B1-fase-c-rate-torque-bridge) — closes the rate ≠ torque honesty gap C9/C11 left open on purpose:
Same Engineer scaffold discipline: "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." No C++ tree, no CMake created in this Buy.
src/jarvis/flight_software/flight_control/rate_torque.py—LinearRateTorqueBridge.convert(rates: BodyRateCommand) -> BodyTorqueCommand: one feedforward map only (tau_i = gain_i * omega_cmd_iper axis, scalar or per-axis gains, each finite and> 0) — not a cascaded rate PID (nokp * (omega_cmd - omega_measured)term, no integral/derivative state)BodyTorqueCommand.tau_bodyis explicitly normalized/dimensionless, torque-like — never claimed as Newton-metres of any real vehicleQuadXMixer.mix(collective, torques: BodyTorqueCommand)is migrated: it no longer accepts a bareBodyRateCommandat all — no silent dual API (passing a rate directly raisesAttributeError, not a quiet misinterpretation)C11's closed-loop tip re-verified through the bridge and unchanged:
15° → 0.252°in 200 steps, identical to before migration — no gain retune needed, since the bridge's defaultgain=1.0is a mathematical no-op relative to the mixer's pre-migration direct pass-throughdefault_safety_gate()unchanged — the bridge is not actuation; autonomysubmit_commandstill always rejectsDoes not touch Continuity, orchestrator IDLE, Board, or
library/Bridge != rate loop product / != physical N·m / != flying. A single named feedforward step exists now instead of an implicit rate-as-torque assumption
Tagged
v0.5.10on Engineer ACCEPT (suite 3348) — reviewNext: ★ C13 C++ scaffold IC (material change of the wooden ladder)
What v0.5.9 includes
Fase C · C11 (B1-fase-c-controlled-flight-sim-tip) — closes the C0 §7 wooden-ladder tip: toy closed-loop sim, no hardware:
Same Engineer scaffold discipline: "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." No C++ tree, no CMake created.
src/jarvis/flight_software/flight_control/plant.py—ToyQuadAttitudePlant: toy, attitude-only, explicitly-not-product-physics dynamics.step(forces: MotorForceCommand, *, dt_s) -> ImuSampleadvances from C9 forces (not PWM) and emits anImuSampleconsistent with its own true attitude — the C3→C10 chain now runs as a real closed looprun_controlled_flight_sim_smoke(): from a documented 15° initial tilt, true tilt error drops below 2° within 200 steps.run_open_loop_baseline_smoke(): the same plant with zero correction stays at a constant 15° — no passive righting, proving the closed loop does real workRate ≠ torque remains open: C9's mixer still treats body rate as its mix channel; this plant does not silently insert a rate→torque controller — it uses its own, separately-documented toy force→angular-acceleration map
Also fixed, disclosed: a real sign bug in C7's
ComplementaryAttitudeEstimator(taggedv0.5.5, ACCEPT CLOSED) — its accel correction had the cross-product argument order reversed, converging estimates away from the true tilt for any non-level input (confirmed even at 0.1°; every pre-existing C7 test only fed already-level accel, so this was never exercised). One-line fix + a new regression test in C7's own test filedefault_safety_gate()unchanged — advancing the plant is not actuation; autonomysubmit_commandstill always rejectsDoes not touch Continuity, orchestrator IDLE, Board, or
library/Sim closed-loop tip @ 0.5.9 != flying / != hardware-verified flight / != physics-accurate sim. No motor spins, no real vehicle exists
Tagged
v0.5.9on Engineer ACCEPT (suite 3330) — review
What v0.5.8 includes
Fase C · C10 (B1-fase-c-esc-pwm-stub-rung) — sixth flight_control rung: ESC/PWM command encoding stub, in-memory sink only:
Same Engineer scaffold discipline: "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." No C++ tree, no CMake created.
src/jarvis/flight_software/flight_control/esc.py—encode_motor_forces(forces, *, min_us=1000, max_us=2000) -> EscPwmCommand: linear map,force=0 → min_us,force=1 → max_us;min_us/max_usmust be finite withmin_us < max_usExactly one encoding — classic PWM-in-µs only; no DShot/Oneshot/Multishot shipped alongside it
SimulatedEscSink—armedstartsFalse;apply(cmd)always records the command in memory but only reportsapplied=Truewhile armed (applied=False,reason="disarmed"otherwise) — noRPi.GPIO,pigpio,/dev/mem, serial, or socket I/O anywhereNot wired to C4: no auto-routing of
AutonomyVerb.HOLDrun_esc_pwm_smoke()— full C3→C10 pipeline smoke path, disarmed by default (reuses the existingsmoke_quad_hal_imuprofile, no schema change)default_safety_gate()unchanged — encoding/recording a PWM command is not actuation; autonomysubmit_commandstill always rejectsDoes not touch Continuity, orchestrator IDLE, Board, or
library/ESC/PWM stub @ 0.5.8 != hardware ESC / != flying. No pin, port, or socket exists anywhere, and no motor is claimed to spin
Tagged
v0.5.8on Engineer ACCEPT (suite 3315) — review
What v0.5.7 includes
Fase C · C9 (B1-fase-c-mixer-rung) — fifth flight_control rung: motor allocation, four numbers only:
Same Engineer scaffold discipline: "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." No C++ tree, no CMake created.
src/jarvis/flight_software/flight_control/mixer.py—QuadXMixer: one documented quadrotor-X layout (motors0..3= FR/FL/RL/RR, 45° off body axes) via a fixed linear allocation matrixmix(collective, rates: BodyRateCommand) -> MotorForceCommand—collectiveclamped to[0, 1]; output is four[0, 1]-clamped, dimensionless motor force numbersHonesty-critical simplification, stated explicitly: C8's
BodyRateCommand.omega_body_rad_sis a body rate, not a true body torque — this B1 mixer treats it directly as roll/pitch/yaw mix channels to teach allocation geometry, without claiming rate ≡ torque physically and without inventing a second controller to bridge that gaproll_scale/pitch_scale/yaw_scale(default0.05each) must be finite and>= 0; a0disables that channelExactly one layout — no
+/H/Y6/octo mixing matrix shipped alongside itNot wired to C4: no auto-routing of
AutonomyVerb.HOLDhover_collective()andrun_mixer_smoke()— tests/smoke only, no hover-thrust or hardware claim (reuses the existingsmoke_quad_hal_imuprofile, no schema change)default_safety_gate()unchanged — mixing is not actuation; autonomysubmit_commandstill always rejectsDoes not touch Continuity, orchestrator IDLE, Board, or
library/Mixer stub @ 0.5.7 != ESC / != flying. No PWM, DShot, ESC UART, or GPIO exists anywhere, and no motor is claimed to spin
Tagged
v0.5.7on Engineer ACCEPT (suite 3297)
What v0.5.6 includes
Fase C · C8 (B1-fase-c-attitude-controller-rung) — fourth flight_control rung: attitude controller, body-rate output only:
Same Engineer scaffold discipline: "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." No C++ tree, no CMake created.
src/jarvis/flight_software/flight_control/controller.py—PdAttitudeController:omega_cmd = kp * e_rot - kd * omega_measured(kpdefault6.0, must be> 0;kddefault0.6, must be>= 0; both finite)e_rotis the body-frame small-angle rotation vector from the C7AttitudeStatetoward anAttitudeSetpoint, extracted from the shortest-path error quaternion;omega_measuredisAttitudeState.omega_body_rad_s(damping term)Exactly one controller — no cascaded rate PID, LQR, MPC, or INDI shipped alongside it
compute(setpoint, state) -> BodyRateCommand— body-rate number only: no motor thrust, no mixer matrix, no PWM/ESC, no collective-thrust channel, no position/velocity loopNot wired to C4:
AutonomyVerb.HOLDis never auto-routed into this controller; the module never callssubmit_commandlevel_setpoint(t_s)— identity-quaternion setpoint for tests/smoke only, no hover-thrust claimrun_attitude_controller_smoke()— pipeline smoke path (reuses the existingsmoke_quad_hal_imuprofile, no schema change)default_safety_gate()unchanged — computing a rate command is not actuation; autonomysubmit_commandstill always rejectsDoes not touch Continuity, orchestrator IDLE, Board, or
library/Controller stub @ 0.5.6 != flying / != motor commands. Mixer and ESC remain future ICs, each its own front
Tagged
v0.5.6on Engineer ACCEPT (suite 3280)
What v0.5.5 includes
Fase C · C7 (B1-fase-c-attitude-estimation-rung) — third flight_control rung: attitude estimation, sim-only, one algorithm:
Same Engineer scaffold discipline: "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." No C++ tree, no CMake created.
src/jarvis/flight_software/flight_control/attitude.py—ComplementaryAttitudeEstimator: gyro integration fused with accel-derived tilt via a small-angle proportional correction (gainconstructor param, default0.02, rejects values outside(0, 1])Exactly one algorithm — explicitly not Mahony/Madgwick/EKF/UKF/MEKF by name: no bias/integral state, no gradient descent, no covariance propagation
update(sample: ImuSample) -> AttitudeState— unit quaternion(w, x, y, z)mapping body →enuworld frame (locked), plus body angular rateHard cut: no magnetometer, no GPS/baro, no online gyro-bias learning, no position/velocity — yaw is gyro-integrated only, with no absolute heading reference
Reuses C6's
ImuLowPassFilter/ImuSampledirectly — no parallel filter reimplemented inside the estimatorread_attitude(hal, filt, estimator)pipesSimulatedImuHal→ImuLowPassFilter→ estimator;vehicle_profiles.run_hal_imu_attitude_smoke()is the pytest-visible smoke path (reuses the existingsmoke_quad_hal_imuprofile, no schema change)Known simulator limitation:
SimulatedImuHalisn't attitude-aware — it always emits a fixed-direction gravity vector, so this Buy's tests validate against synthetic in-memoryImuSamplesequences with known tilts, not solely via the shared sim HALdefault_safety_gate()unchanged — estimation is not actuation; autonomysubmit_commandstill always rejectsDoes not touch Continuity, orchestrator IDLE, Board, or
library/Attitude stub @ 0.5.5 != flight-verified attitude / != controlled flight. Controller, mixer, and ESC remain future ICs, each its own front
Tagged
v0.5.5on Engineer ACCEPT (suite 3264)
What v0.5.4 includes
Fase C · C6 (B1-fase-c-imu-filtering-rung) — second flight_control rung: IMU filtering, sensing post-process only:
Same Engineer scaffold discipline: "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." No C++ tree, no CMake created.
src/jarvis/flight_software/flight_control/filter.py—ImuLowPassFilter: deterministic first-order EMA (alphaconstructor param, default0.2, rejects values outside(0, 1]), applied per-axis toaccel_mps2/gyro_rad_sfilter_sample(raw: ImuSample) -> ImuSamplereuses C3'sImuSampleverbatim — no parallel type. First sample after construction/reset()seeds the filter unsmoothedSensing post-process, not estimation: no quaternion, no Euler angles, no Madgwick/Mahony/EKF output — that belongs to a later, separate estimation-class rung
read_filtered(hal, filt)pipesSimulatedImuHal.read_imu()through the filter;vehicle_profiles.run_hal_imu_filter_smoke()is the pytest-visible smoke pathdefault_safety_gate()unchanged — filtering is not actuation, noallowrequired; autonomysubmit_commandstill always rejectsDoes not touch Continuity, orchestrator IDLE, Board, or
library/Filter rung @ 0.5.4 != attitude / != controlled flight. State estimation, attitude/rate/position control, mixer, and ESC remain future ICs
Tagged
v0.5.4on Engineer ACCEPT (suite 3249)
What v0.5.3 includes
Fase C · C4 + C5 as one ACCEPT block (Engineer 2026-09-20) — package/tag v0.5.3. Intermediate tag v0.5.2 was never cut (see docs truth-sync).
C4 — autonomy command surface (B1-fase-c-autonomy-surface)
Same Engineer scaffold discipline: "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." No C++ tree, no CMake created.
Opens
src/jarvis/flight_software/autonomy/—AutonomyVerb(TAKEOFF/HOLD/GO_TO/FOLLOW/RETURN_HOME/LAND/PATROL),propose_command(),submit_command()Every
submit_commandcall goes throughSafetyGate.evaluate(...)first — withRejectAllSafetyGate,HOLD/LANDalwaysoutcome=reject/execution="not_attempted"Even a test-local fake
allowgate cannot makeexecutionbecome"executed"— resolves to"not_implemented"No
flight_software/autonomy/executor.py; C3 IMU rung unchanged; registry still emptyCommand surface != flyable autonomy
C5 — radio dual-role stub (B1-fase-c-radio-dual-role)
src/jarvis/capabilities/radio.py—RadioStubFrame→SimulatedRadioIngress→RadioDualRoleResult(Intentand/orAuthoritySignal)RadioIntentAdapter.parse(...)still raisesNotImplementedError(live path refuse)Optional
SafetyRequest.authority_signal_id— RejectAll unchanged; authority never implies allowDual-role stub != live ELRS — no CRSF/ELRS decode, no serial I/O, no radio→autonomy auto-submit
Does not touch Continuity, orchestrator IDLE, Board, or
library/Tagged
v0.5.3on Engineer ACCEPT (suite 3236)
What v0.5.1 includes
Fase C · C3 (B1-fase-c-first-fc-rung) — first flight_control rung, sensing-only:
Engineer amendment (on top of the C3 IC): "Python scaffold / sim only — production flight_control runtime is C++ (future IC)." Everything here is a Python platform scaffold, not the production flight controller; the real
flight_controlruntime/firmware will be C++ in later Buys with its own IC. No C++ tree, no CMake created in this Buy.Opens
src/jarvis/flight_software/flight_control/andsrc/jarvis/vehicle_profiles/on disk for the first time — exactly one rung: HAL + simulated IMU sample acquisition (ImuHal,SimulatedImuHal,ImuSample)No filtering, no state estimation, no attitude/rate/position controller, no mixer, no ESC/PWM, no autonomy verbs — none of it exists in this package yet
SimulatedImuHalis deterministic (sameseed→ same sample sequence) and never touches real hardware, a bus, or a network socketOne pytest smoke
VehicleProfile(smoke_quad_hal_imu) +run_hal_imu_smoke()invehicle_profiles/— not bound to any craft workspace/BOM/catalog SKUNaming split (honesty-critical): craft catalog
flight_controller(a BOM part) andflight_software.flight_control(this control spine) are different systems of record — never conflateddefault_safety_gate()unchanged — still alwaysRejectAllSafetyGate; noAllowAllSafetyGateanywhere insrc/Does not touch Continuity, orchestrator IDLE, Board, or
library/First rung stub @ 0.5.1 != controlled flight. Estimation/control/mixer/ESC/autonomy remain future ICs (C4+)
Tagged
v0.5.1on Engineer ACCEPT (see.jes/artifacts/implementation_review_fase_c_first_fc_rung_b1.md)
What v0.5.0 includes
Fase C · C1 scaffold (B1-fase-c-capability-registry-scaffold) — schemas only, no runtime:
src/jarvis/capabilities/— typedCapabilityRecord/ProviderRecord/SkillRecord(Pydantic) + aCapabilityRegistrywith a query-only API (list / get-by-id / "who offers capability X?")CapabilityRegistry.load_default()is always empty — 0 capabilities, 0 providers, 0 skills. No path in the product marks flight/actuation asavailable; theavailabilityenum only offersstub/not_implementedin C1No execution path: no method or field named
execute/dispatch/command_esc; nothing here turns a record into a motor/ESC/autonomy commandDoes not touch Continuity, orchestrator IDLE, Board, or
library/— craft SoT stays thev0.4.3surfaceScaffold @ 0.5.0 != Flight Software shipped. At C1,
flight_software/did not exist yet; C3 @v0.5.1opens the first Python scaffold rung (production FC runtime remains C++ / future IC)Git tag
v0.5.0/checkpoint-fase-c-capability-registry(Engineer ACCEPT 2026-09-20)
What v0.4.3 includes (tag)
Last pre–Fase C checkpoint (closeout note):
Everything in v0.4.2 (Fase M mission craft), plus:
D1 docs/` truth-sync @ code · obsolete labeled
U1 Board Taller 3D default · Grafo tab · inspector + mount chain to plate · piece chips
IDLE first-acquire catalog scoped to
vtx/cameras(FN-009 / terrestrial wizard preserved)
Live suite 3166 · UI 132
Next: Fase C @
0.5.0on first Engineer ★ Buy
What v0.4.2 included
Mission craft software checkpoint (Fase M CLOSED — M7 closeout):
Continuity mission intent + wizard vigilancia nudge + SYSTEM_DEFINITION B routing
Mission
mass_g→ AUW; Continuity ladder mount + autonomía objetivo;power_wdeclare + Phoenix catalog Wlibrary/camerasPhoenix 2 +library/vtxZeus 800 — fluid catalog help-choose / rebind / mass mirror (RF mW ≠ DC W)BOM
[sku]display for cameras / FC / sensors; craft-montage user guide syncLive suite 3165 · UI 105
What v0.4.1 included
Everything in v0.4.0 (Continuity spatial assembly), plus Board Situar:
C-113 — Scene3D drag →
board_pose_bridge→ sameset_component_declared_box_posewriter as CLIdeclara…Free camera while situating (no forced cenital); screen-plane drag follows the cursor; Shift = profundidad (Y)
Situar UX: larger pane, zoom range, live preview
Standoff count gate B4-min (
count==4→ corners; else omit)
Live suite 2669 · UI 80
Locks unchanged: Prop/Energy = HD-004 wall; System Optimization deferred until pain; screening AABB ≠ fit VERIFIED
What's landed between v0.4.1 and v0.4.2 (now tagged)
The craft montage + mission craft arc — empty project → montaje honesto + identidad/masa/montaje/potencia/VTX sin inventar física:
Checklist family, estimated-temporary dims, arm/disk Visor, FC/sensors library envelopes
Mission payload identity → cameras/radio → Continuity ladder → catalog seeds
See
docs/IMPLEMENTATION_TASKS.mdand USER_GUIDE_CRAFT_MONTAGE.md
Next
Tip tagged v0.6.0 — ontology explain CLOSED (spine solid+audited, ONTOLOGY_CROSSWALKS.md). See close note.
PRIORIDAD: Assistant 0.6.1+ — ★ DC-assistant-placement → intelligence/ scaffold → R2 retrieve → terminal canal. Silicon parked — bench note.
Parked (bags/lab): C30 DFU smoke · plate-box · Path N · HD-* · more camera/radio SKUs · Board inspector polish · GPIO/DShot wire · Linux baud.
See docs/IMPLEMENTATION_TASKS.md.
Docs
Doc | Role |
Product contract (non-technical) | |
v1 usable — when a project is “done” | |
A' — Situation / Evidence / Next useful step | |
How the system is built | |
System Map — connections & authority | |
Command-level walk: empty project → montaje honesto en Board | |
Roadmap, gaps, software/product debt | |
Physics debt gated on T1/T2 lab (ESC η, battery C-rate, sag, OP→consumo) | |
Living CLI findings register (G9–G20) | |
Deeper product / flow reference |
Tags
v0.5.21 / checkpoint-fase-c-crsf-host-baud — Fase C C23: Darwin IOSSIOSPEED 420000 + raw 8N1 on C22 FD; suite 3504 · UI 132.v0.5.20 / checkpoint-fase-c-crsf-host-serial — Fase C C22: CRSF host serial ingest (pty); suite 3485 · UI 132.v0.5.11 / checkpoint-fase-c-cpp-scaffold — Fase C C13: first C++ flight_control host scaffold; suite 3358 · UI 132.v0.5.10 / checkpoint-fase-c-rate-torque — Fase C C12: rate→torque honesty bridge; suite 3348 · UI 132.v0.5.9 / checkpoint-fase-c-sim-tip — Fase C C11: toy closed-loop wooden-ladder tip (+ C7 accel sign fix); suite 3330 · UI 132.v0.5.8 / checkpoint-fase-c-esc-pwm — Fase C C10: force→PWM µs + SimulatedEscSink; suite 3315 · UI 132.v0.5.7 / checkpoint-fase-c-mixer — Fase C C9: quad-X mixer; suite 3297 · UI 132.v0.5.6 / checkpoint-fase-c-controller — Fase C C8: PD attitude → body-rate command; suite 3280 · UI 132.v0.5.5 / checkpoint-fase-c-attitude — Fase C C7: complementary attitude estimation rung; suite 3264 · UI 132.v0.5.4 / checkpoint-fase-c-imu-filter — Fase C C6: IMU EMA/low-pass filter rung; suite 3249 · UI 132.v0.5.3 / checkpoint-fase-c-autonomy-radio — Fase C C4+C5 one block: autonomy command surface + radio dual-role stub; suite 3236 · UI 132. (No v0.5.2 tag — see truth-sync note.)v0.5.1 / checkpoint-fase-c-first-fc-rung — Fase C C3: first flight_control rung (HAL + simulated IMU), flight_software/+vehicle_profiles/ opened; Python scaffold, production FC runtime is C++ (future IC); suite 3206 · UI 132.v0.5.0 / checkpoint-fase-c-capability-registry — Fase C open: empty Capability Registry scaffold; suite 3181 · UI 132.v0.4.3 / checkpoint-board-taller-3d — pre–Fase C CLOSED: docs truth-sync + Board Taller 3D; suite 3166 · UI 132.v0.4.2 / checkpoint-fase-m-mission-craft — Fase M CLOSED: mission mass + Continuity ladder + cameras/VTX seeds + B routing; suite 3165 · UI 105.v0.4.1 / checkpoint-board-situar — Board Situar drag→pose (C-113) + free camera + standoff count gate; suite 2669 · UI 80.v0.4.0 / checkpoint-continuity-spatial-assembly — Continuity spatial assembly (situar el mapa); suite 2652.v0.3.8 / checkpoint-spatial-board-projector — ship spatial_board.py (was gitignored); /workspace/ ignore.v0.3.7 / checkpoint-structure-representation-closed — Structure representation arc closed (catalog→parts→rebind→plates); suite 2294.v0.3.6 / checkpoint-experimental-prop-energy-closed — experimental prop/energy/Structure A/fail-routing construction closed; knowledge-parity phase starts.v0.3.5 / checkpoint-phase25-hover-energy — Phase 2.5 honest hover-regime autonomy.v0.3.4 / checkpoint-motor-op-voltage-coherence — motor OP voltage gate + DSE live params.v0.3.3 / checkpoint-validation-case-regression-gate — Validation Case probe/docs.v0.3.2 / checkpoint-deferred-queue-cd — Deferred Queue C+D.v0.3.1 / checkpoint-next-engineering-block — G24-A + P2-2 OP bridge.v0.3.0 / checkpoint-propeller-catalog-bind — propeller help-choose → exact OP.v0.2.0 — H1–H4 handoffs closed; System Map at 0 RED.v0.1.0-prototype — first functional cut.
This server cannot be deployed
Maintenance
Related MCP Connectors
Official DevSpeak MCP server — translate technical text into formal specs from any AI IDE or agent
MCP server for aerospace calculations: orbital mechanics, ephemeris, DSN operations, ...
- mcpOAuthcom.gibsonai
GibsonAI MCP server: manage your databases with natural language
MCP server for generating rough-draft project plans from natural-language prompts.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server for creating, modifying, and analyzing KiCAD schematic files using natural language.20MIT
- AlicenseBqualityDmaintenanceMCP Server for COMSOL Multiphysics simulation automation via AI agents.781MIT
- AlicenseNot gradedqualityBmaintenanceA local MCP server that enables AI agents to create and edit parametric CAD models through natural language, using a validated operation graph that compiles to real geometry.1MIT
- AlicenseAqualityBmaintenanceMCP server providing embedded engineering calculators and code generators as tools for AI agents, enabling precise, deterministic embedded math and C code generation.2974 npmMIT