Auto-resume flight across chunk unload/reload

When the ship's chunk unloads the CC computer powers off and its redstone
outputs reset to 0, so on return the ship dropped and the pilot had to
re-ENGAGE every time. Flight state now survives the reboot:

- engaged.txt flag written while flying, cleared on pilot STOP.
- On boot, if engaged + profile present: drive the hover baseline
  immediately (closes the fall gap), then runStandby(true) re-enters the
  flight loop automatically with the persisted target -- no manual ENGAGE.
- runFly drives hover before its first sleep so no entry path leaves a
  0-output gap.
- Cleanup no longer lifts a PARKED ship: Ctrl+T out of standby holds hover
  only when engaged, else valves off.
- engaged.txt registered as per-ship state (gitignore, installer keep/reset,
  menu reset, build excludes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-25 17:55:15 +02:00
parent a94eff9283
commit 3e27b3f52b
5 changed files with 84 additions and 17 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ files/*/calib.txt
files/*/sides.txt files/*/sides.txt
files/*/target.txt files/*/target.txt
files/*/settings.txt files/*/settings.txt
files/*/engaged.txt
files/*/scan.txt files/*/scan.txt
files/*/pid_gains.txt files/*/pid_gains.txt
files/*/.installed_* files/*/.installed_*

View File

@@ -63,7 +63,26 @@ Boot chain (autopilot): `startup.lua` → `ap boot` → `autopilot/main.lua`.
Menu any time: `Ctrl+T`, then `ap`. (`stabilizeV2` remains as an alias.) Menu any time: `Ctrl+T`, then `ap`. (`stabilizeV2` remains as an alias.)
Ship state lives on the computer only: `sides.txt`, `calib.txt`, `target.txt`, Ship state lives on the computer only: `sides.txt`, `calib.txt`, `target.txt`,
`settings.txt`. Reset it via menu `p``R`, or when re-running the installer. `settings.txt`, `engaged.txt`. Reset it via menu `p``R`, or when re-running
the installer.
## Surviving chunk unload / reload
When you fly away and the ship's chunk unloads, the CC computer powers off and
its redstone outputs reset to 0 — so on its own the ship would drop the moment
you return. The autopilot handles this:
- While flying it keeps an `engaged.txt` flag. On boot (`startup.lua` runs
automatically when the chunk reloads) it drives the hover baseline
**immediately**, then re-enters the flight loop on its own — no manual
ENGAGE, holding the same target it had.
- A pilot **STOP** clears the flag, so a ship you deliberately stopped stays
stopped across reloads. **HOLD**/**LAND** keep flying, so they resume.
There is still an unavoidable ~12 s gap while the computer boots; on this slow
ship that is harmless. If the computer comes back *off* after a reload (some
server setups), right-click it once — CC then runs `startup.lua` and the
auto-resume takes over.
## Updating the code ## Updating the code

View File

@@ -17,7 +17,7 @@ declare -A ROLE_SRC=(
) )
# per-skip tilstand + utdaterte verktoey: shippes ALDRI (installeren rydder dem ogsaa) # per-skip tilstand + utdaterte verktoey: shippes ALDRI (installeren rydder dem ogsaa)
EXCLUDES=(calib.txt sides.txt target.txt settings.txt scan.txt pid_gains.txt EXCLUDES=(calib.txt sides.txt target.txt settings.txt engaged.txt scan.txt pid_gains.txt
stabilize.lua diag.lua probe.lua scan.lua manifest) stabilize.lua diag.lua probe.lua scan.lua manifest)
# rolle-spesifikt: cockpit-koden hoerer ikke hjemme paa autopilot-PCen # rolle-spesifikt: cockpit-koden hoerer ikke hjemme paa autopilot-PCen
declare -A ROLE_EXCLUDES=( declare -A ROLE_EXCLUDES=(

View File

@@ -97,6 +97,20 @@ local function profileComplete()
return loadCalib().signMeasured == true return loadCalib().signMeasured == true
end end
-- ENGASJERT-flagg: finnes filen <=> autopiloten FLYR aktivt (etter ENGAGE/FLY, foer STOP).
-- Dette er kjernen i restart-overlevelsen: en chunk-unload slaar av datamaskinen, og reboot
-- nullstiller redstone -> skipet ville falle. Ligger flagget der ved boot, gjenopptar vi flukten
-- automatisk (og holder hover umiddelbart), i stedet for aa vente i standby paa manuell ENGAGE.
local ENGAGED_FILE = "engaged.txt"
local function setEngaged(on)
if on then
local f = fs.open(ENGAGED_FILE, "w"); f.write("1"); f.close()
elseif fs.exists(ENGAGED_FILE) then
fs.delete(ENGAGED_FILE)
end
end
local function isEngaged() return fs.exists(ENGAGED_FILE) end
-- live brukerinnstillinger (justeres fra pilot-konsollen), persistert. Syntetiske noekler -> cfg. -- live brukerinnstillinger (justeres fra pilot-konsollen), persistert. Syntetiske noekler -> cfg.
local SETTINGS_FILE = "settings.txt" local SETTINGS_FILE = "settings.txt"
local function saveSettings(t) local f = fs.open(SETTINGS_FILE, "w"); f.write(textutils.serialise(t)); f.close() end local function saveSettings(t) local f = fs.open(SETTINGS_FILE, "w"); f.write(textutils.serialise(t)); f.close() end
@@ -713,6 +727,11 @@ local function runFly()
print(""); print("[Enter]"); read(); return print(""); print("[Enter]"); read(); return
end end
-- driv hover FOERST -- lukker fall-gapet (spesielt paa auto-resume etter reboot der
-- redstone kom tilbake paa 0) foer loopen og status-broadcasten kommer i gang.
driveValves(calib.uHoverFront, calib.uHoverRear)
setEngaged(true) -- markér at vi FLYR -> overlever chunk-reload (auto-resume ved boot)
local launchAlt = sensors.readAltitude() or 0 local launchAlt = sensors.readAltitude() or 0
local target = loadTarget() or (launchAlt + cfg.takeoffOffset) local target = loadTarget() or (launchAlt + cfg.takeoffOffset)
local est = Estimator.new(cfg) local est = Estimator.new(cfg)
@@ -739,6 +758,8 @@ local function runFly()
elseif msg.cmd == "set" then applySetting(msg.key, msg.value, true) elseif msg.cmd == "set" then applySetting(msg.key, msg.value, true)
elseif msg.cmd == "stop" then stopReq = true end elseif msg.cmd == "stop" then stopReq = true end
end end
-- pilot-STOP er en eksplisitt "slutt aa fly" -> ryd engasjert-flagget saa en senere reload
-- IKKE gjenopptar av seg selv. (Ctrl+T beholder flagget: da vil du typisk fortsette senere.)
-- vent ~dt sekunder MEN behandle innkommende kommandoer underveis (ikke-blokkerende styring) -- vent ~dt sekunder MEN behandle innkommende kommandoer underveis (ikke-blokkerende styring)
local function serve(secs) local function serve(secs)
local timer = os.startTimer(secs) local timer = os.startTimer(secs)
@@ -823,7 +844,10 @@ local function runFly()
}) })
serve(cfg.dt) -- vent dt + behandle pilot-kommandoer (erstatter sleep) serve(cfg.dt) -- vent dt + behandle pilot-kommandoer (erstatter sleep)
if stopReq then print("Pilot STOP -- holding hover, back to standby."); break end if stopReq then
setEngaged(false) -- eksplisitt stopp -> ikke auto-resume etter neste reload
print("Pilot STOP -- holding hover, back to standby."); break
end
end end
end end
@@ -898,7 +922,21 @@ local function awaitCmd(secs)
end end
end end
local function runStandby() local function runStandby(autoResume)
link.open()
-- AUTO-RESUME: fl...y da chunken unloadet -> gjenoppta UMIDDELBART (ingen fall, ingen manuell
-- ENGAGE). Bare paa boot-inngangen; manuell "standby" fra menyen gjor det ikke.
if autoResume and isEngaged() and fs.exists(CALIB_FILE) then
term.clear(); term.setCursorPos(1, 1)
print("==== RESUMING FLIGHT ====")
print("Autopilot was flying before the chunk")
print("reloaded -- re-engaging automatically.")
print("(Pilot STOP to stop; it will NOT resume")
print(" after a stop.)")
sleep(0.6)
runFly() -- blokkerer til STOP / Ctrl+T
local c = loadCalib(); driveValves(c.uHoverFront, c.uHoverRear)
end
while true do while true do
-- re-proev modem hver runde: fanger et modem som plugges til mens vi staar i standby -- re-proev modem hver runde: fanger et modem som plugges til mens vi staar i standby
local hasModem = link.open() local hasModem = link.open()
@@ -939,6 +977,11 @@ end
-- Foerste boot uten komplett profil -> tilby wizarden. Timeout -> standby, slik at en ubemannet -- Foerste boot uten komplett profil -> tilby wizarden. Timeout -> standby, slik at en ubemannet
-- reboot (chunk-reload) aldri blir staaende og vente paa tastetrykk. -- reboot (chunk-reload) aldri blir staaende og vente paa tastetrykk.
local function runBoot() local function runBoot()
-- ALLER FOERST: fl...y vi da chunken unloadet? Driv hover UMIDDELBART saa skipet ikke rekker
-- aa falle mens vi tegner skjerm / venter. runStandby(true) gjenopptar selve reguleringen.
if isEngaged() and fs.exists(CALIB_FILE) then
local c = loadCalib(); driveValves(c.uHoverFront, c.uHoverRear)
end
if not profileComplete() then if not profileComplete() then
term.clear(); term.setCursorPos(1, 1) term.clear(); term.setCursorPos(1, 1)
print("==== AIRSHIP AUTOPILOT ====") print("==== AIRSHIP AUTOPILOT ====")
@@ -959,7 +1002,7 @@ local function runBoot()
end end
end end
end end
runStandby() runStandby(true) -- true = auto-resume flukt hvis vi fløy da chunken unloadet
end end
-- ============================ PROFIL / MENY ============================ -- ============================ PROFIL / MENY ============================
@@ -1015,7 +1058,7 @@ local function menu()
term.clear(); term.setCursorPos(1, 1); showProfile() term.clear(); term.setCursorPos(1, 1); showProfile()
print("\nR = reset SHIP PROFILE (new ship), Enter = back"); local a = read():lower() print("\nR = reset SHIP PROFILE (new ship), Enter = back"); local a = read():lower()
if a == "r" then if a == "r" then
for _, f in ipairs({ CALIB_FILE, SIDES_FILE, TARGET_FILE, SETTINGS_FILE }) do for _, f in ipairs({ CALIB_FILE, SIDES_FILE, TARGET_FILE, SETTINGS_FILE, ENGAGED_FILE }) do
if fs.exists(f) then fs.delete(f) end if fs.exists(f) then fs.delete(f) end
end end
print("Ship profile reset -- setup wizard on next boot."); print("[Enter]"); read() print("Ship profile reset -- setup wizard on next boot."); print("[Enter]"); read()
@@ -1060,20 +1103,24 @@ local function runMode(mode, a2, a3)
end) end)
-- opprydding: aktive driv-modus -> hold hover (aldri etterlat drivende uten beskjed); -- opprydding: aktive driv-modus -> hold hover (aldri etterlat drivende uten beskjed);
-- monitor styrer ingenting (la utgangene staa). -- monitor styrer ingenting (la utgangene staa).
local function holdHoverOut()
if fs.exists(CALIB_FILE) then
local c = loadCalib(); driveValves(c.uHoverFront, c.uHoverRear) -- hold, ikke null (aldri stup)
else
allOff()
end
end
if mode == "manual" or mode == "wiring" then if mode == "manual" or mode == "wiring" then
allOff() allOff()
elseif mode == "signtest" or mode == "setup" then elseif mode == "signtest" or mode == "setup" then
-- Ctrl+T midt i sign-testen: luftbaaren -> hold hover (ALDRI frittfall); paa bakken -> alt av -- Ctrl+T midt i sign-testen: luftbaaren -> hold hover (ALDRI frittfall); paa bakken -> alt av
if signtestAirborne then if signtestAirborne then holdHoverOut() else allOff() end
local c = loadCalib(); driveValves(c.uHoverFront, c.uHoverRear) elseif mode == "cal" or mode == "fly" or mode == "observe" then
else holdHoverOut() -- disse fl...yr aktivt -> hold hover ved avslutning
allOff() elseif mode == "boot" or mode == "standby" then
end -- Ctrl+T ut av standby: hold hover KUN hvis vi faktisk fløy (engasjert). Et parkert skip
elseif mode == "cal" or mode == "fly" or mode == "observe" or mode == "standby" or mode == "boot" then -- paa bakken skal IKKE begynne aa varme brennere og lette av seg selv.
if fs.exists(CALIB_FILE) then if isEngaged() then holdHoverOut() else allOff()
local c = loadCalib(); driveValves(c.uHoverFront, c.uHoverRear) -- hold, ikke null (aldri stup)
else
allOff() -- aldri floeyet under vaar kontroll -> skipet staar paa bakken; ikke varm brennere
end end
end end
if not ok and err ~= "Terminated" then print("ERROR: " .. tostring(err)); print("[Enter]"); read() end if not ok and err ~= "Terminated" then print("ERROR: " .. tostring(err)); print("[Enter]"); read() end

View File

@@ -20,7 +20,7 @@ local BASE = "https://gt.zyon.no/Stian/airship-autopilot/raw/branch/main"
local ROLES = { "autopilot", "cockpit" } local ROLES = { "autopilot", "cockpit" }
-- per-skip tilstand: roeres aldri automatisk, men tilbys nullstilt ved install -- per-skip tilstand: roeres aldri automatisk, men tilbys nullstilt ved install
local STATE_FILES = { "sides.txt", "calib.txt", "target.txt", "settings.txt" } local STATE_FILES = { "sides.txt", "calib.txt", "target.txt", "settings.txt", "engaged.txt" }
-- kjente filer fra eldre versjoner som ikke lenger hoerer til rollen (engangs-rydding; -- kjente filer fra eldre versjoner som ikke lenger hoerer til rollen (engangs-rydding;
-- videre oppdateringer ryddes automatisk via installasjons-loggen .installed_<role>) -- videre oppdateringer ryddes automatisk via installasjons-loggen .installed_<role>)