Never reference the ground attitude -- a flight-built hull lies on its side

The attitude reference was captured at the first sensor read, on the ground,
where a ship built to fly typically lies over. That made every tilt reading
relative to a meaningless datum, and worse: the hull's natural righting swing
after liftoff was read both as tilt and as rate response, contaminating the
very measurement the test exists to make.

- Attitude reference is now taken in the air, after an explicit righting
  phase that waits for every axis rate to steady (5 s calm, 120 s cap).
  Until then maxTilt() reads 0, so nothing can trip on ground attitude.
- The settled airborne attitude is recorded and reported per axis, with a
  warning if the hull settles far from level even under balanced lift.
- Wizard now says outright that a ship lying over on the ground is fine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-24 00:00:44 +02:00
parent 81e558d04e
commit a94eff9283
2 changed files with 71 additions and 21 deletions

View File

@@ -29,13 +29,18 @@ side drives which balloon, pitch sign, hover level) is measured on the ship by
the **setup wizard**, which the install flow takes you straight into:
1. **Wiring** — the wizard pulses each free side; you say which burner fired.
2. **Sign test** — the ship lifts off by itself, finds its own lift power, then
*rocks* between front and aft pushes. The sign comes from
`rate(+push) rate(push)`, which cancels any constant lean exactly, so a
ship hanging crooked measures fine — that lean **is** the thing being
measured. It then learns the differential trim that levels the ship, saves
it as a warm-start, and descends to hover. No config editing, no freefall.
Takes 24 minutes.
2. **Sign test** — the ship lifts off by itself, finds its own lift power,
waits until buoyancy has righted it and the rates have steadied, and only
*then* takes its attitude reference. It rocks between front and aft pushes;
the sign comes from `rate(+push) rate(push)`, which cancels any constant
lean exactly, so a ship hanging crooked measures fine — that lean **is**
what is being measured. It then learns the differential trim that levels
the ship, saves it as a warm-start, and descends to hover. No config
editing, no freefall. Takes 24 minutes.
A hull built to fly usually lies over on the ground; that is fine and
expected. The ground attitude is never used as a reference — nothing is
measured until the ship is airborne and steady.
After that the ship is ready: `FLY` from the menu or `ENGAGE` from the cockpit
console. Pitch and altitude keep self-learning in flight. The wizard can be

View File

@@ -154,8 +154,12 @@ end
-- mellom de to dyttene kansellerer enhver konstant skjev-drift eksakt. Og fordi vi VEKSLER mellom
-- dyttene gynger skipet rundt utgangspunktet i stedet for aa akkumulere tilt -- selvbegrensende.
--
-- BAKKE-ATTITYDEN BRUKES ALDRI: skipet er bygget for aa FLY, ikke for aa staa, saa paa bakken
-- ligger det gjerne paa siden. Vi venter til oppdriften har reist det opp OG ratene har roet seg
-- foer attityde-referansen settes -- ellers ville skipets egen oppretting blitt lest som respons.
--
-- 1/5 trapper seg luftbaaren fra bunnen (laerer ~hover paa kjoepet, ingen fast liftLevel)
-- 2/5 klatrer kontrollert til trygg maalehoyde (fart-P-hold rundt laert hover)
-- 2/5 klatrer til maalehoyde, lar skipet RETTE SEG OPP, setter attityde-referanse i lufta
-- 3/5 GYNGE-PROBE: front-dytt <-> bak-dytt, 2 sykluser; aksen som svarer blir pitchIndex.
-- Svak respons -> auto sterkere dytt. Maaler ALLE gimbal-akser (byggeretning likegyldig)
-- 4/5 BALANSE: med fortegnet kjent laeres differanse-trimmen som holder skipet vannrett
@@ -196,12 +200,21 @@ local function runSigntest()
if type(a) ~= "table" or type(a[1]) ~= "number" then return nil end
if NAX == 0 then
NAX = math.min(3, #a)
a0 = {}
for i = 1, NAX do axRings[i] = utils.newRing(1.0); a0[i] = a[i] end
for i = 1, NAX do axRings[i] = utils.newRing(1.0) end
end
return a
end
-- Attityde-referansen settes AaLDRI paa bakken: et skip bygget for aa FLY staar ikke stoett,
-- det ligger gjerne paa siden. Den attityden er meningsloes -- og oppdriftens egen oppretting
-- etter avgang ville blitt lest som baade "tilt" og "rate-respons". Vi setter referansen
-- FOERST naar skipet har rettet seg opp i lufta og ratene har roet seg.
local function setAttitudeRef()
if not lastAx then return end
a0 = {}
for i = 1, NAX do a0[i] = lastAx[i] end
end
local function axRates()
local r = {}
for i = 1, NAX do r[i] = axRings[i]:slope() end
@@ -338,13 +351,32 @@ local function runSigntest()
local okC, cErr = recenter("2/5", "initial climb")
if not okC then return bail(abortMsg(cErr)) end
local tSettle = now()
while now() - tSettle < 8 do
velTick(0, 0, "settle0")
hud("2/5", "Settling (learning true hover)...", {
string.format("hover ~%.2f %.0fs left", hover, math.max(0, 8 - (now() - tSettle))),
-- La skipet RETTE SEG OPP i lufta foer vi maaler noe som helst. Et flybygget skrog ligger
-- gjerne paa siden paa bakken; oppdriften reiser det opp av seg selv naar det kommer i lufta.
-- Vi venter til ALLE akse-ratene har roet seg -- og setter FOERST DA attityde-referansen.
local tR, calmR, worst = now(), 0, 0
while now() - tR < 120 and now() < deadline do
velTick(0, 0, "righting")
worst = 0
for i = 1, NAX do worst = math.max(worst, math.abs(axRings[i]:slope())) end
if worst < 0.5 then calmR = calmR + cfg.dt else calmR = 0 end
hud("2/5", "Letting the ship right itself...", {
"(a flight-built hull lies over on the ground;",
" buoyancy levels it once airborne)",
string.format("worst rate %.2f deg/s steady %.1f/5.0 s", worst, calmR),
string.format("hover ~%.2f", hover),
})
if calmR >= 5.0 then break end
local rel = est.altitude - h0
if rel < BAND_LO or rel > BAND_HI then
local okD, dErr = recenter("2/5", "altitude drift")
if not okD then return bail(abortMsg(dErr)) end
end
end
setAttitudeRef() -- NAA er referansen meningsfull: skipets faktiske flyattityde
local settled = {}
for i = 1, NAX do settled[i] = (lastAx and lastAx[i]) or 0 end
-- rate-stabil maaling PER AKSE: hold hoyden (vDes=0) med gitt differanse til ratene roer
-- seg. Returnerer tabell {avgRate per akse}. Lag-bevisst: minHold >= loft-etterslepet.
@@ -476,6 +508,9 @@ local function runSigntest()
term.clear(); term.setCursorPos(1, 1)
print("==== SIGN TEST: SUCCESS ====")
print(string.format("pitch = gimbal axis a%d, sign %+d", axis, sign))
local sline = ""
for i = 1, NAX do sline = sline .. string.format(" a%d %+.0f", i, settled[i]) end
print("airborne attitude:" .. sline)
print(string.format("balance trim %+.2f (saved warm-start)", 2 * levelTrim))
if balanced then
print("Ship levelled out under this trim.")
@@ -483,6 +518,12 @@ local function runSigntest()
print(string.format("NOTE: still %+.0f deg off level -- limited", pitchNow))
print("pitch authority. FLY keeps learning it.")
end
if math.abs(settled[axis]) > 30 then
print("")
print("WARNING: it settled far from level even")
print("with balanced lift -- the hull may not be")
print("righting fully. Check balloon spacing.")
end
print(string.format("hover ~%.1f (saved as baseline)", hover))
print("")
print("Ship is holding hover. Ready to FLY.")
@@ -797,9 +838,12 @@ local function runSetup()
print("Two steps, fully guided:")
print(" 1) Wiring -- map front/aft burner sides")
print(" 2) Sign test -- short auto-flight that")
print(" measures pitch response + hover")
print(" measures pitch response + balance")
print("")
print("Ship must be ON THE GROUND, open sky above.")
print("Just needs open sky above. It's FINE if the")
print("ship lies over on the ground -- it rights")
print("itself airborne, and nothing is measured")
print("until it has.")
print("")
write("[Enter] = start, q = cancel: ")
if read():lower() == "q" then return end
@@ -811,10 +855,11 @@ local function runSetup()
term.clear(); term.setCursorPos(1, 1)
print("==== STEP 2: SIGN TEST ====")
print("")
print("The ship now lifts off BY ITSELF, gives")
print("the front balloon a small push, verifies")
print("the response both ways, and comes back")
print("down to hover. Takes 1-3 minutes.")
print("The ship lifts off BY ITSELF, waits until")
print("it has righted and steadied in the air,")
print("rocks front/aft to find the pitch axis,")
print("learns the trim that levels it, then comes")
print("back down to hover. Takes 2-4 minutes.")
print("")
write("[Enter] = lift off, q = cancel: ")
if read():lower() == "q" then return end