Repository navigation
falcon-v1.138.0
falcon v1.138.0 — the wasm cascade flies Gazebo, and the 15-second swing is fixed
The published wasm cascade now flies real Gazebo, and the "it starts
swinging after about fifteen seconds" report is fixed at the root.
This bundles the v1.136 → v1.138 arc; those tags were never cut.
The swing
Reported from a Windows/WSL2 bench: "es fängt nach etwa 15 sekunden an zu
schwingen." Reproduced, and it is not drift — it is a limit cycle driven by the
altitude integral, which charged through the entire climb and then spent that
charge as overshoot (NED, negative is up):
t=7.9s true_z -2.14 (target reached) alt_int -6.26 <- peak charge
t=13.9 true_z -2.64 (overshoot) alt_int -3.45
t=39.9 true_z -0.08 (nearly aground) alt_int -5.72
At ki 0.03, ±6 is a ±0.19 thrust swing on a 0.585 hover. The integral now
charges only when the vehicle is not already converging.
Before: 0.62 m error at 16 s, and a 40 s hold ends near the ground. After: four
25 s holds measured 0.02, 0.04, 0.21 and 0.10 m — and alt_int sits at
−0.040 for the whole hold instead of swinging ±6. (Four runs rather than one
because a single green gz run has already fooled us once this cycle.)
Both obvious fixes were measured and rejected: a velocity gate cannot separate
the cases (the climb runs at only 0.1–0.29 m/s), and an error band cannot either
(the thrust-lapse sag the integral must reject is ~1.5 m, larger than the band
that would block the climb). Their product does separate them.
Also in this release
configure(vehicle-config)— nine airframe values a host can install.
Before it, every number was frozen inside the component and no host could
correct them from outside.- ESC telemetry on the seam —
read_motor_rpmhad no field at all, so the
rotor-out FDI in the published component was inert by construction. - Gazebo runs in CI for the first time, with video recorded every run and a
hard gate on the hold error. The wasm and native paths are bit-identical
per-motor across every tick.
Answers to two questions from the embedded track
1. "synth float poles are WIP (#708/#369). Was fehlt dafür noch?"
Those notes are stale, and floats lower fine. #369 (soft-float for all
f32/f64) and #708 (f32.load + i32.reinterpret_f32) were both closed
10–11 July 2026. Verified on synth 0.66.0, --target cortex-m7dp:
vmul.f32 s3, s0, s1
vadd.f32 s4, s3, s2
vdiv.f32 s2, s0, s1
vsqrt.f32 s1, s0
Real hardware FPU, hard-float ABI. So the trivial float path you asked us to
validate passes.
One warning that matters more than the answer
A stale synth silently emits wrong code. Our first attempt used a
four-month-old binary, which compiled the same module with exit 0 and
emitted:
| export | emitted | meaning |
|---|---|---|
faxpy(a,b,c) |
mov r0,r2 ; bx lr |
returns c — no multiply, no add |
fdiv(a,b) |
mov r0,r1 ; bx lr |
returns b |
fsqrt(x) |
bx lr |
returns x |
No FPU instructions, no error, no warning. We nearly reported "floats don't
lower, your route stalls" on the strength of it. So your instinct to validate a
trivial float path is exactly right — and the first thing to validate is the
version. Pin synth through varve rather than a cargo install binary, and
disassemble the result once; an integer function of the same shape is a good
control (ours lowered to a correct mul.w/adds in 10 bytes while the float
one was 4).
What is actually still missing, for our cascade
On synth 0.66.0 / cortex-m7dp, 21 of the cascade's 22 functions lower,
including 1408- and 1468-byte ones. Exactly one declines — the tick itself:
GI-FPU-002: VFP register file exhausted (S0..S15 all live) — f32/f64 register
pressure exceeds the file; the backend retries with VFP spilling (#881) and
this surfaces only if that also fails
Target sensitivity, same input:
| target | functions skipped of 22 |
|---|---|
| cortex-m7dp | 1 (the tick only) |
| cortex-m4f | 3 |
| cortex-m7 | 3 |
| cortex-m3 (no FPU) | 16 |
We tried reducing the pressure from our side by splitting the export into a
forwarder plus an #[inline(never)] body. It does not help — the decline
moves with the body, so the pressure is inherent to the tick (where
FlightCore::step inlines the whole cascade), not to the export boundary.
Filed as synth#1267, including the question of whether per-stage
#[inline(never)] in the guest is the intended remedy.
So: floats are not the blocker. Register pressure in one large tick is.
That is a much narrower risk than "the whole meld+synth route stalls", and it
is visible on day one exactly as you suggested.
Not validated here: the → Renode half. Renode is not installed on this
machine, so we confirmed synth compile --cortex-m only. Someone with Renode
should close that loop before the firmware rewrite goes on the schedule.
2. "Can synth emit a relocatable object or static archive, rather than a linked ELF?"
Yes — a relocatable object. synth compile --relocatable:
int_rel.o: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 — not stripped
00000000 g F .text 0000000a iaxpy
ET_REL, with global function symbols in .text that a linker can see. The
flag's own help is explicit: "Force relocatable object (.o, ET_REL) output even
when wasm has no imports — for linking into a host build system." --link is
the opposite direction (links to firmware ELF via arm-none-eabi-gcc), and
--builtins takes a kiln-builtins .o.
A static archive is not emitted directly — that is arm-none-eabi-ar rcs libfoo.a out.o. One gotcha we hit: use the cross ar. The macOS host ar
accepts the file and produces a malformed archive (ranlib: warning: archive member not a mach-o file).
So the PX4-module route is not gated on output format. It is gated on
GI-FPU-002 for the tick, above.
Known, filed, not fixed
- #403 — the horizontal loop diverges after ~26 s of hold; it was hidden
under the altitude oscillation until that was fixed. This is why a 40 s hold
still ends badly even though the altitude loop is now clean. - #398 — rotor-out recovery does not hold on the gz plant.
- #397 —
//:falcon-cascadehas not built under Bazel since #393, so the
cascade's witness MC/DC coverage is not being produced. - synth#1267 — the cascade tick declines on cortex-m7dp.
Falsification
This release is wrong if a 25 s stationary 2 m hold on the gz falcon-quad,
driven by the published component through the Component Model seam, leaves the
vehicle more than 0.5 m from the commanded altitude — or if the wasm and native
paths ever differ by a single per-motor bit.