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.

Drone delivery simulator controlled by Lambda C

Why Drone Control is Lambda C's First Target

Autonomous drone software classically splits into two layers:

This matches Lambda C's core design:

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:

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:

KeyAction
RSwap script — round-trip delivery.c (A→B→A) ⇔ patrol patrol.c (A→B→C→A)
1 / 2 / 3Set cruise altitude to 3 m / 5 m / 10 m
SpaceReset drone position
TToggle trace mode
ESCExit

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:

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:

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 scriptdelivery.c (round-trip) vs patrol.c (patrol).

Why this matters in practice:

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:

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:

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:

Starting from scripts/delivery.c / scripts/patrol.c and writing your own mission script is the fastest way to understand Lambda C.

See Also