Almost every flight software team has lost a week to a frame error. The signature is always the same: the vehicle behaves correctly in one corner of the envelope and wrongly in another, the maths checks out line by line, and nobody can find the mistake by reading the code.
Every line is correct. The program is not
A frame error is not a typo. Each function does exactly what it says. The gyro driver returns angular rates. The estimator integrates them. The controller compares an attitude against a setpoint. All of it is arithmetically sound.
The defect lives in the space between the functions, and that space has no representation in the code. Three floats leave one module and arrive at the next, and nothing about them records that they were measured relative to the airframe rather than to the ground, or that somebody along the way assumed z pointed up when the convention in use has it pointing down.
Reviewers cannot see it because there is nothing to look at. The reviewer checks the formula, the formula is right, and the review passes.
Four frames, and every pair needs a transform
A small fixed-wing or rotary vehicle typically carries at least four: an earth-centred earth-fixed frame for anything involving position on the globe, a local level frame at the take-off point, the body frame fixed to the airframe, and one sensor frame per instrument that is not perfectly aligned with the airframe.
Four frames means twelve directed transforms. In practice a team implements three or four of them, composes the rest inline, and the compositions are where the errors accumulate. Composition order is not commutative, and a chain written from memory in the middle of a control loop is a chain nobody has checked.
Two conventions are worth settling in writing before the first line is written, because both have a defensible answer and neither is inferable from the code:
- North-east-down or east-north-up for the local level frame. Aerospace usually takes the first, robotics usually takes the second, and teams drawn from both backgrounds will each assume theirs is obvious.
- Whether a rotation matrix rotates the vector or rotates the frame. The two are transposes of each other. Both are called "the rotation matrix" in the literature.
Quaternions have the same problem, more quietly
The Hamilton and JPL conventions differ in the sign of the vector part and in the order of multiplication. Both are in wide use. Both are called quaternions. Code written against one and libraries built against the other will compose without complaint and produce a rotation in the wrong direction.
The failure mode is generous in the worst way: near the identity, small angles behave almost correctly, so bench testing and a gentle first flight look fine. The error grows with angle, which means it shows up during an aggressive manoeuvre, at altitude, away from the people who could diagnose it.
Storage order compounds it. Some libraries store the scalar first, some store it last, and a four-element array offers no clue which it is holding.
Make the frame part of the name, at minimum
The strongest fix is a type system that refuses to add a body-frame vector to a navigation-frame one. In Rust or modern C++ that is a small amount of work and it moves the whole class of defect to compile time.
Where the language will not carry it, naming does most of the job for almost no
cost. A variable called v_body and a function called
ned_from_body make a wrong composition visible in the diff, because
ned_from_body(v_ecef) reads incorrectly even to someone who does not know
the system. That single convention has caught more of these for us than any test.
Name the transform for what it produces and what it consumes, in that order. It makes chains verifiable by eye: the inner label has to match the outer one's input.
Test against a rotation you can do in your head
Frame tests written against the implementation prove that the code does what the code does. The useful ones are the cases where the answer is obvious without any software.
Point the vehicle north and level, and gravity in the body frame should be exactly one g down a single axis, with two zeros. Yaw ninety degrees east and a vector pointing along the nose should end up along the east axis, exactly. Roll one hundred and eighty degrees and the lateral accelerometer should change sign and nothing else should move.
These are not sophisticated tests. That is what makes them work. A test whose expected value was itself computed by the system under test cannot detect a sign error, and most frame test suites we are asked to review are built exactly that way.
Sensor alignment is not a rounding error
The last frame is the one teams skip. An IMU mounted a few degrees off the airframe axis is common, and correcting for it is usually deferred as a refinement.
Three degrees of misalignment puts about five per cent of any longitudinal acceleration onto the lateral channel. During cruise that is a small bias the estimator absorbs. During a sharp pull-up it is a lateral acceleration that was never there, and the controller responds to it.
Measure the alignment once during integration, store it as an explicit transform with the others, and it stops being a mystery term in the flight data later.
Write the frame diagram before the code
One page: the frames, their origins, their axis directions, the handedness, the rotation convention, the quaternion convention, and the storage order. Pin it where everyone can see it and treat it as part of the interface specification rather than documentation.
It takes an afternoon. It is the cheapest week you will ever save.