Drone Demo
Lambda C was designed primarily for autonomous drone and robotics control. This page uses the bundled demos/raylib_drone/ to show how embedded control software is structured with Lambda C.

Why Drone Control is Lambda C's First Target
Autonomous drone software classically splits into two layers:
- Airframe / HAL layer — Low-level C that owns motors, sensors, power, armed state. Subject to safety certification; changes carefully.
- Autopilot / mission layer — Expresses intent like "take off, fly to B, drop the package, return to A." Easy to verify in isolation, but changes constantly as routes and policies evolve per deployment.
This matches Lambda C's core design:
- The lower layer (HAL) lives in C host code — driving the MCU directly, validating calibrated/armed limits, saturating or rejecting requests.
- The upper layer (mission) lives in Lambda C script — deployed as
.lcbcbytecode, swappable via signed OTA.
The script expresses what it wants; the host's FFI decides whether that's physically feasible. This is the same separation found in aerospace and automotive control. Lambda C is built specifically to drive this boundary cleanly at ~100 ns per load-time-linked FFI call.
What the Bundled Demo Does
Pick up a package at point A, deliver to B, return to A (with ENABLE_POINT_C = 1 it extends to a triangle route A → B → C → A).
IDLE → TAKEOFF → FLY_TO_B → HOVER_B → DESCEND_B → DROP
│
▼
TAKEOFF_B → (FLY_TO_C → HOVER_C → DESCEND_C → WAIT_C → TAKEOFF_C)?
│
▼
FLY_TO_A → HOVER_A → DESCEND_A → LANDED
What's implemented:
- Mission state machine — two scripts:
scripts/delivery.c(round-trip) andscripts/patrol.c(patrol) — 11 states (up to 17 with the triangle route) - Altitude / position control — proportional control via
altitude_hold()andfly_toward() - 3D visualization with raylib — see drone behavior in real time
- Script swap — press
Rto hot-swap the two behavior scripts (VM state survives); editing and saving also auto-reloads - Trace mode — press
Tfor per-instruction execution log - Watchdog — abort execution after 16 ms per frame (
lcvm_set_watchdog_frame(16000))
A telemetry HUD on the top-left shows altitude, position, throttle, attitude, ARMED state, mission state, package status, and the selected route. The top-right shows script status and FPS.
Controls:
| Key | Action |
|---|---|
R | Swap script — round-trip delivery.c (A→B→A) ⇔ patrol patrol.c (A→B→C→A) |
1 / 2 / 3 | Set cruise altitude to 3 m / 5 m / 10 m |
Space | Reset drone position |
T | Toggle trace mode |
ESC | Exit |
Pressing R makes the host point the SDK at a different source file and reload it, swapping the drone to a completely different behavior script (swap_script() in main_sdk.c). Both are delivery missions — pick up at A, drop at B — differing only in the route: direct (A→B→A) or via point C (A→B→C→A). Editing a script in your editor and saving also auto-reloads it; 1/2/3 are convenience keys that patch the cruise altitude in place (see script_patcher.h).
Layer 1: The Script Expresses Mission Intent
delivery.c knows nothing about physics. It only states intent through FFIs like drone_get_altitude() and drone_set_throttle():
/* FFI declarations — what the host provides */
double drone_get_time();
double drone_get_altitude();
double drone_get_x();
double drone_get_z();
void drone_set_arm(int armed);
void drone_set_throttle(double throttle);
void drone_set_pitch(double pitch);
void drone_set_roll(double roll);
void drone_set_led(int r, int g, int b);
/* Mission state machine */
switch (g_mission_state) {
case 1: /* TAKEOFF — climb to cruise altitude */
drone_set_throttle(altitude_hold(CRUISE_ALT));
if (at_altitude(CRUISE_ALT)) g_mission_state = 2;
break;
case 2: /* FLY_TO_B — head to delivery point */
drone_set_throttle(altitude_hold(CRUISE_ALT));
fly_toward(POINT_B_X, POINT_B_Z);
if (at_position(POINT_B_X, POINT_B_Z)) {
g_hover_start_time = drone_get_time();
g_mission_state = 3;
}
break;
/* ... */
}
This script:
- Compiles to ~5 KB of bytecode
- Has no GC so it always finishes within a bounded time budget
- Has no closures, no dynamic types — behavior is fully predictable
- Can be modified without recompiling the host C code
Layer 2: The Host Owns Physics and Feasibility
The host side (main_sdk.c and drone_api.c) holds the physics simulation and FFI implementations. On a real airframe the same place holds the motor driver, IMU, and armed-state verification logic.
/* The two callbacks the SDK requires */
static void on_init(LcvmState *vm, void *user) {
/* Frame watchdog: 60 fps ≈ 16 ms */
lcvm_set_watchdog_frame(16000);
/* Register all FFI (auto-generated from drone_ffi.h) */
lcvm_register_ffi_all();
}
static void on_frame(LcvmState *vm, float dt, void *user) {
UserState *state = (UserState *)user;
state->sim_time += dt;
drone_set_time(state->sim_time);
/* Propagate throttle/pitch/roll written by the script to physics */
drone_physics_update(dt);
}
The FFI implementations on the host side are where feasibility is enforced. For example:
drone_set_throttle(double)clamps the script's value into[0.0, 1.0]drone_set_arm(int)(on a real airframe) checks GPS and battery before flipping the armed flag- Sensor-read FFIs return calibrated physical quantities
If the script asks for throttle 2.0, the HAL refuses or saturates. The script has no unbounded authority — that is Lambda C's safety boundary.
FFI: The Narrow Bridge Between Intent and Execution
The whole drone demo runs on just 12 FFI functions:
/* drone_ffi.h — only 12 functions */
double drone_get_time();
double drone_get_altitude();
double drone_get_x();
double drone_get_z();
void drone_set_arm(int armed);
void drone_set_mission_state(int state);
void drone_set_has_package(int has);
void drone_set_throttle(double throttle);
void drone_set_pitch(double pitch);
void drone_set_roll(double roll);
void drone_set_yaw(double yaw);
void drone_set_led(int r, int g, int b);
That's enough to describe the full delivery mission. lcvmc --gen-ffi drone_ffi.h autogenerates the registration function lcvm_register_ffi_all(), so hand-written FFI boilerplate is essentially zero.
At runtime, names are resolved to IDs at load time, so even when the script calls these hundreds of times per frame, dispatch stays at ~100 ns each.
Hot Reload: Edit the Mission While the Drone Is Flying
With the demo running, open scripts/delivery.c and edit a value such as the cruise altitude, then save:
double CRUISE_ALT = 5.0; /* try changing to 8.0 and saving */
On save the SDK detects the change, lcvmc runs automatically, the new .lcbc is loaded, and the in-flight drone moves at the new altitude on the next frame. VM state (position, velocity, armed flag) is preserved. Pressing R additionally hot-swaps to a whole different script — delivery.c (round-trip) vs patrol.c (patrol).
Why this matters in practice:
- During field testing, you cut the code-build-restart-arm-wait-for-condition cycle
- In production, the same machinery becomes signed
.lcbcOTA delivery - The certified host C code stays untouched while mission logic updates safely
Simulator SDK: Apply the Same Design to Your Domain
The drone demo is a reference implementation of the Lambda C Simulator SDK (lib/lcvm_sim.h). The SDK provides:
- Script loading and re-linking
- Auto-compile-and-hot-reload (
lcvm_sim_reload()) - Safe recovery on errors via
setjmp/longjmp - File-watch background reloads
You implement just two callbacks:
lcvm_sim_config_t cfg = {
.script_path = "mission.c",
.enable_hot_reload = 1,
.on_init = on_init, /* register FFIs */
.on_frame = on_frame, /* physics + render */
.user_data = &state,
};
lcvm_sim_ctx_t *sim = lcvm_sim_create(&cfg);
while (running) {
lcvm_sim_update(sim, dt);
}
Drones are one application; the same pattern lifts cleanly to:
- Industrial robots — script commands joint angles and gripper state, host owns IK and collision checking
- HVAC / water heaters — script drives setpoints and valve states, host owns the thermal model and safety interlocks
- Warehouse AGVs — script handles routing, host owns SLAM and motor control
See Examples for callback templates in each domain.
Try It
cd demos/raylib_drone
make
./drone_emu_sdk # drone_emu_sdk.exe on Windows
# or, from the repo root: .\run_delivery.ps1
Once running:
Rto swap between round-trip (delivery.c) and patrol (patrol.c) — the host loads a different script and the in-flight drone follows the new route on the next frame1/2/3to change cruise altitude to 3, 5, or 10 m- Edit
scripts/delivery.cin your editor and save — it auto-reloads and applies in flight Tto toggle trace mode — the instruction stream prints to stdoutSpaceto reset drone position — the mission continuesESCto quit
Starting from scripts/delivery.c / scripts/patrol.c and writing your own mission script is the fastest way to understand Lambda C.
See Also
- Features — full catalog of VM, SDK, and compiler capabilities
- Architecture — 4-byte instructions, FFI mechanics, hot-reload internals
- Operating Envelope — trust boundaries and sandbox design
- Examples — PID control, state machines, FFI patterns
- Getting Started — embedding the VM into your own host