Skip to content

Architecture & Engineering Design

This document details the software architecture, physical simulation pipeline, kinematic solver, and mathematical models behind fll-sim.


1. System Architecture

The simulator is structured into six strictly decoupled layers with unidirectional data flow:

flowchart TD
    UserScript["User SPIKE Python Script"] --> SpikeVM["Virtual SPIKE API (Async Sandbox)"]
    SpikeVM --> MotorCtrl["Motor Velocity & PID Controller"]
    MotorCtrl --> RapierPhysics["Rapier3D Physics Engine (60Hz Wasm)"]
    RapierPhysics --> SensorSim["Virtual Sensor Manager (Raycasting)"]
    RapierPhysics --> Viewport3D["Three.js 3D Viewport (WebGL)"]
    SensorSim --> Telemetry["Live Telemetry Stream"]
    Telemetry --> HUD["DOM HUD Dashboard"]
    Viewport3D --> HUD

Module Responsibilities

Layer Path Core Functionality Headless Capable?
CAD Ingestion src/cad/ LDraw parser, Studio .io extractor, Technic catalog, clustering pre-solver Yes
Physics src/physics/ Rapier3D Wasm world, compound bodies, motor joints, 4x8 ft arena walls Yes
Sensors src/sensors/ Raycast ground UV sampling, Gyro IMU quaternion heading extraction Yes
Runtime src/runtime/ Virtual SPIKE Prime API, Python AST transpiler, cooperative abort Yes
Viewport src/view/ Three.js scene, lighting, procedural vinyl mat canvas, mesh synchronization Browser / WebGL
UI src/ui/ DOM HUD, Python code editor, mission presets, live telemetry Browser / DOM

2. Topological Pin & Fastener Clustering

The Challenge of LEGO Physics

A typical FLL competition robot consists of 300 to 600 pieces: * 100+ Technic friction pins (2780, 6558, 32054) * Dozens of liftarms, beams, and frames * Hub, battery, and motors * Wheels and attachments

If each pin and beam is simulated as an independent rigid body connected by physics joints, the constraint solver faces thousands of simultaneous equations. The simulation quickly lags, collapses, or explodes.

The Clustering Pre-Solver Solution

fll-sim analyzes the connectivity graph before handing geometry to Rapier3D: 1. Catalog Classification: Every part is tagged by role: FASTENER_PIN, STRUCTURAL_BEAM, CHASSIS_CORE, MOTOR_STATOR, MOTOR_ROTOR, or WHEEL_RIM. 2. Graph Connected Components: All parts connected by rigid friction pins are merged into an equivalence class. 3. Compound Body Generation: Each connected component becomes one Rapier rigid body equipped with an array of primitive colliders (boxes, cylinders, spheres). 4. Joint Identification: Only active degrees of freedom (axles seated in motor stators, free-spinning wheel hubs) are converted into Rapier 1-DOF Revolute Joints.

As a result, a 400+ part robot collapses into \(\le 4\) rigid bodies: * Body 0: Main Chassis (Hub, battery, frame, sensors, fixed motor stators) * Body 1: Left Drive Wheel * Body 2: Right Drive Wheel * Body 3: Rear Caster Skid


3. Mathematical & Kinematic Formulations

3.1 Wheel Kinematics & Degrees

For an FLL robot with wheel diameter \(D = 56\text{ mm}\): $\(C = \pi \cdot D \approx 17.593\text{ cm}\)$

To drive a linear distance \(d\) in centimeters: $\(\Delta \theta = \left(\frac{d}{\pi \cdot D}\right) \times 360^\circ\)$

For differential drive steering: $\(\text{Speed}_{\text{left}} = V \cdot \left(1 - \frac{2 \cdot \max(0, -S)}{100}\right)\)$ $\(\text{Speed}_{\text{right}} = V \cdot \left(1 - \frac{2 \cdot \max(0, S)}{100}\right)\)$ where \(S \in [-100, 100]\) is the steering value.

3.2 Gyro Yaw Angle Extraction

The robot's heading yaw \(\theta\) is extracted directly from the chassis rigid body quaternion \(q = (x, y, z, w)\): $\(\theta = \text{atan2}\left(2(wy - zx),\, 1 - 2(y^2 + x^2)\right) \times \frac{180}{\pi}\)$

3.3 Sensor UV Projection & Reflected Light

Color sensors cast a downward ray from \((X_0, Y_0, Z_0)\) rotated by the chassis yaw to find the mat intersection point \((X_{\text{mat}}, Z_{\text{mat}})\). The coordinates are mapped to normalized texture coordinates: $\(u = \frac{X_{\text{mat}} + L/2}{L}, \quad v = \frac{Z_{\text{mat}} + W/2}{W}\)$ Reflected light is computed via human perceived luminance: $\(\text{Reflected Light \%} = \text{round}\left(\frac{0.299R + 0.587G + 0.114B}{255} \times 100\right)\)$