Skip to content

schroedermatthew/fatp-drone

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fatp-drone · FeatureManager

A dependency-resolving feature flag system built on FAT-P.

The premise: feature flags in real systems are not booleans. They have dependencies, implications, conflicts, and safety constraints. Tracking all of that by hand — checking whether enabling GPU rendering also enables the shader compiler, whether HDR conflicts with the old display mode, whether toggling a subsystem OFF would orphan everything that depended on it — is how you get bugs. FeatureManager makes it automatic, validated, and transactional.

Five relationship types. Transitive dependency resolution. Cycle detection with path reporting. Observer pattern with RAII lifetime management. JSON round-trip serialization. GraphViz DOT export. Pluggable thread-safety policy. A Preempts relationship for override scenarios — enabling the source forcibly disables the target, cascades through its reverse-dependency closure, and latches inhibit so nothing can sneak back on. And a forceExclusive operation for e-stop semantics — blanks the entire feature state atomically before enabling the target, so no MutuallyExclusive or Conflicts constraint can block it.

CI


How it was built

FeatureManager was designed and implemented by two AI models working collaboratively: Claude (Anthropic) and ChatGPT (OpenAI). No human wrote any of the code, tests, CI configuration, or documentation.

The Fat-P development guidelines — covering coding standards, naming conventions, test structure, benchmark methodology, CI workflow, documentation style, and AI operational behavior — were transferred from the parent FAT-P library and adapted to this project. Both AI models worked from those guidelines throughout. The guidelines are AI-to-AI communication: written by AI instances to constrain future AI instances. The human who initiated the Fat-P project has never read them.

This is not pair-programming in the human sense. It is two AI models with different strengths doing genuine cross-review: one proposes, the other challenges, the guidelines arbitrate. The output — 44 unit tests, 144 integration tests across the drone demo suites, a CI pipeline, a WebAssembly simulator, full documentation — was produced in a single session and was compliant with Fat-P standards from the start. Nothing was retrofitted.

The distinction from the fatp-ecs project is the division of work: fatp-ecs was built autonomously by Claude alone, with adversarial AI review afterward. fatp-drone was built collaboratively from the start, with ChatGPT participating in design and review throughout rather than in a separate review pass.

The human's role, as with the rest of the Fat-P project, is directional: accept, reject, flag. The architecture originated with the AI pair. The methodology originated in the guidelines. The guidelines originated with earlier AI instances that needed to leave instructions for later ones.


Relationships

Type Semantics Symmetric?
Requires Enabling A auto-enables B. Disabling B blocked while A is on. No
Implies Enabling A auto-enables B (softer than Requires). No
Conflicts A and B cannot both be enabled. Yes (auto)
MutuallyExclusive Group constraint — only one member can be enabled at a time. Yes (auto)
Preempts Enabling A force-disables B and its entire reverse-dependency closure. Latches inhibit: B cannot re-enable while A is on. No
forceExclusive(A) Blanks all feature states atomically, then enables A and its Requires/Implies closure. No constraint can block it. Used for e-stop semantics.

Demo: fatp-drone

The included drone demo shows FeatureManager driving a real control-systems problem — 22 subsystems with hard safety constraints, live state enforcement, and a full vehicle lifecycle state machine.

The key point: every dependency and constraint is declared explicitly in code, once. There is no implicit knowledge scattered across enable/disable call sites. The graph is the source of truth; FeatureManager enforces it everywhere.

Try it — WebAssembly simulator

The drone demo compiles to WebAssembly and runs entirely in the browser. No server, no install — the same C++ binary that passes the CI test suite, packed into a single HTML file.

→ Download fatp-drone.html — save the file, then open it locally in any browser. No upload, no server needed.

The simulator exposes the full CommandParser::execute() interface with a GUI that explains the system's rules as you interact with them:

  • Self-explaining failures — when ARM fails, the missing subsystems pulse red in the left panel and a checklist shows exactly which of the 6 requirements are unmet.
  • Guard previews — hover any action button to see exactly what's missing or confirm it's ready, before you click.
  • Dependency cascade — enabling a subsystem that unlocks others via Requires/Implies edges triggers a purple cascade animation on the newly-reachable nodes and edges in the live graph.
  • Typed telemetry log — log delta lines are parsed into typed entries (✓ enabled, ○ disabled, ◈ state change, ⚠ safety, ✗ error) with per-type color coding.
  • Live state machine — transition arrows turn green (reachable) or red (blocked) in real time based on current state and subsystem conditions.
  • Timeline strip — a scrubable event bar accumulates every enable/disable/state/safety event as a colored pip. Hover any pip to see the label.
  • Quick-arm shortcuts — one-click "Enable All Arm Requirements" button, and flight mode picker chips for instant mode switching.

The WASM binary is built from the same source as the CLI demo using clang-18 --target=wasm32-wasi. A thin JS bridge exposes execute_command_json, get_state_name, and the FeatureManager JSON export. No Emscripten runtime — just a 1.1 MB .wasm file instantiated directly via WebAssembly.instantiate.

To build and embed:

# Build WASM (requires clang-18 + wasi-sysroot)
./build.sh

# Embed binary into the HTML template
python embed_wasm.py fatp-drone.wasm fatp-drone-wasm-template.html fatp-drone-latest.html

The graph in code

void registerRelationships()
{
    using namespace drone::subsystems;
    using FR = fat_p::feature::FeatureRelationship;

    // Power chain
    requireOk(mManager.addRelationship(kESC,      FR::Requires, kBatteryMonitor), "ESC->BatteryMonitor");
    requireOk(mManager.addRelationship(kMotorMix, FR::Requires, kESC),            "MotorMix->ESC");

    // Safety
    requireOk(mManager.addRelationship(kFailsafe, FR::Requires, kBatteryMonitor), "Failsafe->BatteryMonitor");
    requireOk(mManager.addRelationship(kFailsafe, FR::Requires, kRCReceiver),     "Failsafe->RCReceiver");

    // Flight mode sensor requirements.
    // NOTE: Flight modes are MutuallyExclusive — they cannot chain via Requires
    // (AltHold cannot Require Stabilize since they conflict with each other).
    // Each mode independently declares the sensors it needs.

    requireOk(mManager.addRelationship(kStabilize,  FR::Requires, kIMU),       "Stabilize->IMU");
    requireOk(mManager.addRelationship(kStabilize,  FR::Requires, kBarometer), "Stabilize->Barometer");

    requireOk(mManager.addRelationship(kAltHold,    FR::Requires, kIMU),       "AltHold->IMU");
    requireOk(mManager.addRelationship(kAltHold,    FR::Requires, kBarometer), "AltHold->Barometer");

    requireOk(mManager.addRelationship(kPosHold,    FR::Requires, kIMU),       "PosHold->IMU");
    requireOk(mManager.addRelationship(kPosHold,    FR::Requires, kBarometer), "PosHold->Barometer");
    requireOk(mManager.addRelationship(kPosHold,    FR::Requires, kGPS),       "PosHold->GPS");

    requireOk(mManager.addRelationship(kAutonomous, FR::Requires, kIMU),            "Auto->IMU");
    requireOk(mManager.addRelationship(kAutonomous, FR::Requires, kBarometer),      "Auto->Barometer");
    requireOk(mManager.addRelationship(kAutonomous, FR::Requires, kGPS),            "Auto->GPS");
    requireOk(mManager.addRelationship(kAutonomous, FR::Requires, kDatalink),       "Auto->Datalink");
    requireOk(mManager.addRelationship(kAutonomous, FR::Requires, kCollisionAvoid), "Auto->CollisionAvoid");
    requireOk(mManager.addRelationship(kAutonomous, FR::Implies,  kCollisionAvoid), "Auto=>CollisionAvoid");

    requireOk(mManager.addRelationship(kRTL, FR::Requires, kIMU),       "RTL->IMU");
    requireOk(mManager.addRelationship(kRTL, FR::Requires, kBarometer), "RTL->Barometer");
    requireOk(mManager.addRelationship(kRTL, FR::Requires, kGPS),       "RTL->GPS");

    // EmergencyStop has no Preempts edges. Shutdown is handled at call time by
    // triggerEmergencyStop() -> forceExclusive(), which atomically clears all feature
    // states before enabling EmergencyStop. The re-enable latch lives in enableSubsystem().
}

void registerGroups()
{
    requireOk(mManager.addGroup(kGroupSensors,
                  {kIMU, kGPS, kBarometer, kCompass, kOpticalFlow, kLidar}), "addGroup Sensors");

    requireOk(mManager.addGroup(kGroupPower,
                  {kBatteryMonitor, kESC, kMotorMix}), "addGroup Power");

    requireOk(mManager.addGroup(kGroupComms,
                  {kRCReceiver, kTelemetry, kDatalink}), "addGroup Comms");

    // MutuallyExclusive: adds Conflicts between every pair of flight modes
    requireOk(mManager.addMutuallyExclusiveGroup(kGroupFlightModes,
                  {kManual, kStabilize, kAltHold, kPosHold, kAutonomous, kRTL}),
              "addMutuallyExclusiveGroup FlightModes");

    requireOk(mManager.addGroup(kGroupSafety,
                  {kGeofence, kFailsafe, kCollisionAvoid, kEmergencyStop}), "addGroup Safety");
}

The graph rendered

Exported via mgr.exportDependencyGraph()toDot(), rendered by Graphviz. Solid edges: Requires. Dashed: Implies. Dotted: MutuallyExclusive. (Preempts edges are supported by FeatureManager but unused in this graph — EmergencyStop uses forceExclusive() instead.)

Drone subsystem dependency graph

What the enforcement looks like

> enable PosHold
  GPS enabled (auto)
  IMU enabled (auto)
  Barometer enabled (auto)
  PosHold enabled

> enable Stabilize
  Error: PosHold is mutually exclusive with Stabilize

> emergency engine-failure
  PosHold disabled
  GPS, IMU, Barometer disabled
  EmergencyStop enabled
  Latch active: no flight mode can re-enable until reset

> takeoff
  Error: no flight mode is active

> graph
  digraph FeatureGraph { ... }   (live export — paste into graphviz.online)

> json
  { "features": { ... } }       (save / restore full state)

Vehicle lifecycle: Preflight → Armed → Flying → Landing → Armed (or → Emergency → Preflight). Every transition is guard-protected: arming checks IMU, Barometer, BatteryMonitor, ESC, MotorMix, RCReceiver; takeoff requires an active flight mode; emergency is always available.


Tests

44 unit tests across logic, observer, serialization, and forceExclusive coverage. The drone demo adds 144 more across SubsystemManager (44), VehicleStateMachine (38), TelemetryLog (17), and DroneCore integration (45) suites. Each suite includes adversarial wrong-state probes and stress/fuzz sequences.


How FAT-P makes this possible

FeatureManager is built from FAT-P components, each solving a specific part of the problem:

FAT-P Component Role in FeatureManager
Expected All operation results — no exceptions in domain logic
FlatSet Cache-friendly sorted-vector relationship storage
FastHashMap O(1) feature lookup, desiredState planning map
JsonLite Full graph serialization / deserialization
Factory Callback registry for serializable validation keys
ValueGuard Scoped state changes with automatic rollback
EnumPlus FeatureRelationship enum with string round-trip for JSON
ConcurrencyPolicies Pluggable thread-safety (SingleThreaded / Mutex / SharedMutex)
Stringify Error message construction

The components weren't designed for a feature manager. They were designed to be useful individually. FeatureManager is what happens when you compose them.


Building

Header-only. Requires C++20 and FAT-P.

# Tests
cmake -B build -DFATP_INCLUDE_DIR=/path/to/FatP
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failure

# Header self-containment check
g++ -std=c++20 -I/path/to/FatP test_FeatureManager_HeaderSelfContained.cpp

CMake options

Option Default Description
FATP_INCLUDE_DIR auto-detect Path to FAT-P include directory
FATP_FM_BUILD_TESTS ON Build test executables
FATP_FM_BUILD_BENCHMARKS OFF Build benchmark suite
FATP_FM_BUILD_DRONE_DEMO ON Build fatp-drone demo

Performance

Operation Complexity Notes
isEnabled() O(1) FastHashMap lookup
enable() / disable() O(d × log n) d = dependency depth
validate() O(n × d × log n) Full graph traversal
addFeature() O(log n) Map insertion
addRelationship() O(log r) FlatSet insertion
toJson() O(n) Feature iteration
fromJson() O(n) Parse + factory lookup
Memory per feature ~574 bytes With 5 relationships, FlatSet storage

The plan/commit transaction model used for enable() means no partial state is ever visible and there is no rollback code — if planning fails, nothing was mutated.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors