Welcome to SimTouch - a particle simulation engine designed for embedded haptic systems. This document provides fast context for AI assistants and new developers at the start of each session.
SimTouch is a dual-phase project that creates organic, physics-driven control signals for haptic actuator arrays:
- Phase 1 (Complete): JavaScript/WebGL2 rapid prototyping environment running in a browser
- Phase 2 (Active): Native C++ implementation for ESP32-S3 autonomous operation
- Phase 3 (Planned): Integration into production haptic suit firmware (
Svibe_Firmware_Slave)
- This file (
START_HERE.md) - Quick orientation (5-minute read) Sim/doc/ARCHITECTURE.md- Phase2 codebase architecture mapSim/doc/GLOSSARY.md- Domain-specific terms and abbreviationsSim/doc/QUICK_REFERENCE.md- Command cheat sheet and troubleshootingSim/doc/migration/migration-plan.md- Comprehensive migration strategy (may be slightly outdated).cursor/rules/project-conventions.mdc- AI agent rules (Phase2 scope, coding standards)
- Source:
Embedded/esp32/Phase2/src/- Application layer (Main.cpp, ConfigWeb.cpp, etc.) - Headers:
Embedded/esp32/Phase2/include/- Simulation modules (SimCore.h, Boundary.h, Turbulence.h, etc.) - Config:
Embedded/esp32/Phase2/platformio.ini- Build configurations for LilyGo & Waveshare targets - Web UI:
Embedded/esp32/Phase2/data/- Self-hosted configuration interface (HTML/CSS/JS)
- JavaScript Sim:
Sim/src/- Original algorithms (for validation & porting reference) - Phase1 Firmware:
Embedded/esp32/Phase1/- Legacy UDP slave mode (deprecated for new work)
┌─────────────────────────────────────────────────────────────┐
│ ESP32-S3 (Autonomous Particle Simulation) │
├─────────────────────────────────────────────────────────────┤
│ Main Loop (Main.cpp) │
│ ├─ Touch Input → TouchForces → SimCore │
│ ├─ IMU Data → ImuForces → SimCore (opt-in) │
│ ├─ SimCore.step() → Physics (60Hz fixed timestep) │
│ │ ├─ Turbulence (noise-driven forces) │
│ │ ├─ Collision (spatial grid 8x8) │
│ │ ├─ Boundary (circular/rectangular enforcement) │
│ │ └─ Gravity (UI or IMU-driven) │
│ ├─ GridModes.compute() → 338 cell values (0-255) │
│ └─ renderGrid() → Display (circular grid, FastLED colors)│
│ │
│ Configuration (ConfigWeb.cpp) │
│ ├─ AsyncWebServer (HTTP) → serves data/* web UI │
│ ├─ AsyncWebSocket (/ws) → binary parameter protocol │
│ └─ SimConfig (81 params, registry-based mapping) │
└─────────────────────────────────────────────────────────────┘
- Simulation: Normalized
[0, 1]space (all physics calculations) - Screen: Pixel space (
240×240Waveshare,480×480LilyGo) - conversion only at render boundary - Grid: Dynamic cell-based output (typically 338 cells for suit compatibility)
- Input → Touch (pixels) converted to normalized coords → Forces applied to particles
- Simulation → 50-200 particles in
[0,1]space, 60Hz physics step - Grid Computation → Particles → proximity/velocity/density modes → cell values
[0-255] - Output → Cell values → display rendering (FastLED palettes) or suit modules
- Indices 50-255: SimTouch parameters (avoid 0-49, reserved for Unity/system commands)
- WebSocket Protocol: Binary
[paramIndex, ...valueBytes]format - Registry:
SimConfig.hkParamRegistry array (type, range, offset metadata)
| Feature | Status | Module |
|---|---|---|
| Particle System | ✅ Working | SimCore.cpp |
| Boundary (Circle/Rect) | ✅ Working | Boundary.cpp |
| Collision Detection | ✅ Working | Collision.cpp |
| Gravity Forces | ✅ Working | GravityForces.cpp |
| Touch Forces | ✅ Working | TouchForces.cpp |
| IMU Forces | ✅ Working (opt-in) | ImuForces.cpp |
| Turbulence | ✅ Working | Turbulence.cpp |
| Grid Rendering | ✅ Working | GridModes.cpp, GridGeometry.cpp |
| Web Configuration | ✅ Working | ConfigWeb.cpp |
| Voronoi Field | 🚧 Scaffold Only | Voronoi.cpp |
| FLIP Fluid | 🚧 Scaffold Only | FluidFLIP.cpp |
| Organic Behaviors | 🚧 Scaffold Only | OrganicBehavior.cpp |
| Modulator (LFO) | 🚧 Scaffold Only | Modulator.cpp |
- Display: ST7701 RGB 480×480 half-circle
- Touch: LilyGo library (onboard capacitive)
- Build:
pio run -e phase2_lilygo
- Display: GC9A01 240×240 round
- Touch: CST816S (I2C)
- Build:
pio run -e phase2_waveshare
- IMU: QMI8658 (6-axis, I2C)
- WiFi: ESP32-S3 AP mode (
ParticleSimulatorSSID) - Flash: 16MB (LittleFS for web UI)
- RAM: 320KB SRAM + 2MB PSRAM
- Read
Sim/doc/migration/migration-plan.md(comprehensive but may be slightly outdated) - Read
Sim/doc/ARCHITECTURE.md(up-to-date Phase2 structure) - Review
.cursor/rules/project-conventions.mdc(coding standards, scope limitations) - Open
Embedded/esp32/Phase2/in PlatformIO
- Simulation Logic: Edit
include/*.handsrc/*.cpp - Configuration: Add params to
SimConfig.hkParamRegistry - Web UI: Edit
data/index.htmlanddata/app.js - Build:
pio run -e phase2_lilygo(orphase2_waveshare) - Upload:
pio run -t upload -e phase2_lilygo - Monitor:
pio device monitor -b 115200
Note: If pio command is not found, use the full path to PlatformIO:
- Windows:
%USERPROFILE%\.platformio\penv\Scripts\platformio.exe - Linux/Mac: Add
~/.platformio/penv/binto PATH or usepython -m platformio
- Connect to WiFi AP:
ParticleSimulator(password:MagicMods) - Open browser:
http://192.168.3.100 - WebSocket auto-connects to
/ws - Parameter changes apply in real-time
- Run JS Sim:
cd Sim && npm run dev→http://localhost:8080/sim.html - Set identical parameters in both JS and ESP32
- Visually compare particle behavior and grid output
- Use UDP path for remote control (Phase 2D feature)
- 320KB SRAM: ~158KB used, ~162KB free
- No dynamic allocation: Use fixed-size arrays,
constexpr, stack allocation - Particle limit: 50-200 particles (tunable via
particleCountparam)
- Use
floatnotdouble(ESP32 has no FPU for double) - Use
sqrtf,cosf,sinf,atan2f(notsqrt,cos, etc.) - Use
constexprfor compile-time constants - All simulation logic in normalized
[0,1]space
- Phase2 only: Work exclusively in
Embedded/esp32/Phase2/ - No Phase1 changes: Phase1 is legacy, reference-only
- No Sim/ code changes: JavaScript Sim is stable, port from it (don't modify)
- Simulation: 60 FPS (60Hz fixed timestep)
- Rendering: 60 FPS (decoupled from sim)
- Frame Budget: ~10ms sim + ~15ms render + ~5ms overhead = ~30ms total (33ms available)
- Cell Count: 338 cells (suit module count compatibility)
- Baud rate:
115200(LilyGo),250000(Waveshare) - FPS stats: Printed every 1s (
sim FPS | render FPS | avg frame ms | cells) - Validation: Phase2A check (particles, touch, boundary) every 2.5s
- Particles stuck/escaping: Check boundary enforcement (
Boundary.cpp) - Low FPS: Reduce
particleCountorcollisionGridSize - WebSocket disconnect: Check WiFi connection,
192.168.3.100reachability - Touch not working: Verify touch driver init in
Graphics.cpp, checkgetTouching()
When starting a new chat session:
- Read this file first for quick orientation
- Check
Sim/doc/ARCHITECTURE.mdfor detailed Phase2 structure - Review recent git changes:
git statusandgit log -5for context - Scan open files in the IDE for user's current focus
- Ask clarifying questions before making assumptions
- Svibe_Firmware_Slave (Phase 3 integration target): Production haptic suit firmware with 338 modules, LVGL UI, Unity UDP control
- JavaScript Sim (Phase 1 reference): Browser-based prototyping environment with WebGL2 rendering
Last Updated: 2026-02-15
Current Phase: Phase 2 (Autonomous ESP32 Implementation)
Primary Developer: Gaia
AI Model Context: Use this as your first-read onboarding document