ESP32 S3 Devkit C1 Arduino IDE Projects and Showcases

This will be the single thread where I will cover all of my ESP32 S3 N8R8 Devkit C1 firmware or hardware projects.

Part 1: ESP32 S3 Devkit C1 Arduino IDE Sanity Check

So you have just received your brand new ESP32 S3, and want to get started in Arduino IDE.

But you are confused with the spoilt choice of USB and UART connection.
Or you are stuck in sketch uploading, and the device not responding.

In any case, I have faced the same issues, and have managed to get inside it's brains finally.

So I share the details to provide a tested reference so that you can figure out whether there is actually something wrong with your hardware or connections, and reduce some of the frustrating load so that you can begin working on your planned projects ASAP.

=================================================

Arduino IDE version: 2.3.6
ESP32 board: ESP32 S3 Rev2 WROOM1 N8R8 Devkit C1
Port type used: UART port of ESP32 C1
Board Package: ESP32 3.3.0 by Espressif Systems
IDE Board: ESP32S3 Dev Module

=================================================
[It is recommended to start with UART first if you are a beginner, as it does not involve manually throwing the device into boot loading reset (as will be the case if USB is used.]

Make sure that your setup matches exactly this, if you have a the same Devkit C1 NxRx board. Double check the values, as other than clock speed, most of the IDE initiated fields/options will be misconfigured, or disabled.
Additional Note: E.g. N8R8 means:
N8 = 8MB flash
R8 = 8MB PSRAM (OPI for tested MC board)

// NO CODE
/*
//EDIT: For Additional clarity and any other ESP32 S3 boards: 
If you have ESP32 S3 board, you should have one of the two following 
configurations, use the "PSRAM" and and "Flash Size" from the above 
"Tools" menu accordingly:

 Nx: In built flash x MB (Ideally your "x" value in "Flash Size".)
 Rx: In built PSRAM (SPI accessed via SPI0 and SPI1 of the ESP32 S3 WROOM MC)
     All models don't have PSRAM, and thus, an Rx value. In such case,
     Keep "PSRAM" as disabled.
     If your board is of S3 NxRx series, it has an 
     Octal Peripheral Interfaced Pseudo Static Random Access memory (OPI PSRAM).
      Octal for: 8 data lines connected directly for faster and parallel
      connection to the PSRAM unit.   
*/

[NB: Readers with NxRx module can keep PSRAM disabled if needed in power constrained scenarios
Don't confuse Rx PSRAM as the main RAM for the MC. All ESP32 S3 have an internal 512KB S(tatic)RAM]

IMPORTANT: Whenever Sanity Checking for the first time, make sure that the ""Core Debug Level" is on Verbose, to catch any stray mistakes. Use warn or any other higher level of debug only when sanity is assured.

Make sure that PSRAM used is OPI (For Rev2 Devkit C1, but always check with your manufacturer for accurate info).

If everything else is setup here properly, you will be properly guided by the debug outputs themselves if there is anything wrong in your setup.
(e.g: for my case, I had the PSRAM setup to QSPI, which was causing the sketch to fail silently after upload, only came to know about it after I turned on Core Debug level, which remained disabled by default).

The examples I used to test:
In Arduino IDE:
File ─> Examples ─> ESP32 ─> ChipID > GetChipID.ino
..............................................└─> GPIO > BlinkRGB.ino

For the LED sketch, make sure that your defined LED pin is correct. For usual case (and the tested board), the RGB is at GPIO Pin 38.

Also make sure to uncomment the RGB brightness define on the top of the sketch, which is commented out by default.
In short, you should include these two preprocessing defines if you have exactly the same board:

#define RGB_BUILTIN 38        // Board-specific: RGB LED on GPIO 38
#define RGB_BRIGHTNESS 64     // Default brightness (max 255)

You can also try this sketch which is a rework on the available BlinkRGB example but enhanced for the current board.
BlinkRGB.ino sketch:
ESP32 S3 BlinkRGB.ino Advanced Example Sketch

Otherwise, do make sure that the cable is proper and supports data. A good expensive cable is essential for any USB communication with almost any MC.

For further information and USB port support, you can refer to these two resources on which I relied. I will update the post later when I actually test out a sketch over the USB port with tested results.

Sanity Check for Uploading Code to DevKitC-1 Using Arduino IDE (Solved)
#1 - How to Program ESP32-S3 with Arduino IDE + CP2102 Driver & WS2812/SK6812 RGB LED Setup

If there is anything anyone would like to add from their experience, or this works for any other ESPS3 boards as well, please feel free to add to the thread.

Happy LED-ing.

Additional Reads and Resources:
ESP32 S3 Official Datasheet - Espressif Systems
ESP32 S3 Devkit C1 Official Schematics
ESP32 S3 Devkit C1 Official Website

1 Like

Thanks for sharing!

1 Like

ESP32 S3 Sanity Check Part 2: 1.8 ST7735 TFT SPI 128*160

Assuming that the board has been properly set up, now you have one of those cheap KMR 1.8 128x160 (BLACKTAB) ST7735 driven displays to try out.

I was able to get everything running smoothly with TFT_eSPI display library for ST7735.

Following are the briefs from my experience.

Tested for:

Inside the Arduino IDE

Press Ctrl + Shift + I to open Library Manager

>Install TFT_eSPI by @bodmer (Tested with v2.5.43)

>Press Alt + Ctrl + K to open the Sketch Folder/Directory

>Navigate to β€œlibrariesβ€œ folder

>Find TFT_eSPI folder there

>Go inside, and update the User_Setup.h file there as directed in the following thread.

  This is the header file that will tell the eSPI driver 
about the hardware configurations, so that it’s action 
matches the hardware expectations.

KET SETTINGS to change in the TFT_eSPI header

*    - #define ST7735_DRIVER

*    - #define ST7735_BLACKTAB (or REDTAB/GREENTAB as needed)

*    - Pin definitions: CS=10, DC=4, RST=5, BL=6, 
MOSI=11, SCLK=12

*    - #define USE_HSPI_PORT

*    - #define SPI_FREQUENCY 10000000 (10MHz) // Tested freq

Working and tested header file:

//                            USER DEFINED SETTINGS
//   Set driver type, fonts to be loaded, pins used and SPI control method etc
//
//                       EDIT THE PIN NUMBERS IN THE LINES FOLLOWING TO SUIT YOUR ESP32 SETUP                       

// ##################################################################################
//
// Section 1. Call up the right driver file and any options for it
//
// ##################################################################################

// Define to disable all #warnings in library (can be put in User_Setup_Select.h)
//#define DISABLE_ALL_LIBRARY_WARNINGS

// Tell the library to use the ESP32 SPI port 1 (VSPI)
#define USER_SETUP_ID 25

#define ST7735_DRIVER      // Define additional parameters below for this display

// For ST7735, ST7789 and ILI9341 ONLY, define the colour order IF the blue and red are swapped on your display
// Try ONE option at a time to find the correct colour order for your display

//  #define TFT_RGB_ORDER TFT_RGB  // Colour order Red-Green-Blue
//  #define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red

// For ST7735 ONLY
// Define the type of display and tab colour for 128x160 displays
#define ST7735_BLACKTAB
// #define ST7735_REDTAB
// #define ST7735_GREENTAB
// #define ST7735_GREENTAB2
// #define ST7735_GREENTAB3
// #define ST7735_GREENTAB128    // For 128x128 display
// #define ST7735_GREENTAB160x80 // For 160x80 display (BGR, inverted, 26 offset)

// ##################################################################################
//
// Section 2. Define the pins that are used to interface with the display here
//
// ##################################################################################

// For ESP32-S3 Dev board with ST7735 display
// Use dedicated SPI pins for better compatibility

#define TFT_MISO -1  // Not connected for this project
#define TFT_MOSI 11  // ESP32-S3 Hardware SPI MOSI (MUST use hardware SPI pins)
#define TFT_SCLK 12  // ESP32-S3 Hardware SPI SCLK (MUST use hardware SPI pins)
#define TFT_CS   10  // Chip select control pin
#define TFT_DC    4  // Data Command control pin
#define TFT_RST   5  // Reset pin (could connect to RST pin)
#define TFT_BL    6  // LED back-light (Use -1 to disable pin in case of no PWM backlight control need

// ESP32-S3 specific: Force use of hardware SPI port
#define USE_HSPI_PORT

// ##################################################################################
//
// Section 3. Define the fonts that are to be used here
//
// ##################################################################################

// Comment out the #defines below with // to stop that font being loaded
// The ESP32 and ESP8266 have plenty of memory so commenting out fonts is not
// normally necessary. If all fonts are loaded the extra FLASH usage is ~15Kbytes.
// To save FLASH space only enable the fonts you need!

#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel high font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel high font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8  // Font 8. Large 75 pixel high font needs ~3256 bytes in FLASH, only characters 1234567890:-.
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

// Comment out the #define below to stop the SPIFFS filing system and smooth font code being loaded
// this will save ~20kbytes of FLASH
#define SMOOTH_FONT

// ##################################################################################
//
// Section 4. Other options
//
// ##################################################################################

// Reduced SPI frequency for ESP32-S3 compatibility
//#define SPI_FREQUENCY  10000000  // 10MHz - reduced for S3 stability
#define SPI_FREQUENCY  27000000  // Try this if 10MHz works
//#define SPI_FREQUENCY  40000000  // Maximum to use SPIFFS
//#define SPI_FREQUENCY  80000000  // Comment out if using SPIFFS or other functions that access the FLASH File system

// Optional reduced SPI frequency for reading TFT
#define SPI_READ_FREQUENCY  2000000  // 2MHz for reading

// The ESP32 has 2 free SPI ports i.e. VSPI and HSPI, the VSPI is the default.
// If the VSPI port is in use and pins are not accessible (e.g. TTGO T-Beam)
// then uncomment the following line:
//#define USE_HSPI_PORT

// Comment out the following #define if "SPI Transactions" do not need to be
// supported. When commented out the code size will be smaller and sketches will
// run slightly faster, so leave it commented out unless you need it!
// Transaction support is needed to work with SD library but not needed with TFT_SdFat
// Transaction support is required if other SPI devices are connected.
#define SUPPORT_TRANSACTIONS

Here is the connection layout. Bypass capacitor for the circuit and limiting resistors for the backlight are optional for regulated 3v3 [provided it is powered from the VOUT (3V3) marked pin) of Devkit C1, Devkit C1 handles it to a functional level]. If used, recommended values are 10ΞΌF and 220Ξ© respectively. Also ceramic capacitors are recommended for any effective noise reduction or decoupling. Use electrolytic only in the non availability of ceramic.

 * HARDWARE CONNECTIONS:
 * ST7735 -> ESP32-S3
 * VCC    -> 3.3V
 * GND    -> GND
 * CS     -> GPIO 10
 * RESET  -> GPIO 5
 * A0/DC  -> GPIO 4
 * SDA    -> GPIO 11 (MOSI)
 * SCK    -> GPIO 12 (SCLK)
 * LED    -> GPIO 6 (Backlight) (Optional)
 * 
 * NeoPixel -> ESP32-S3 DevKit C1
 * Data   -> GPIO 48 (Built-in RGB LED)

Great! Now that you are setup, try any of the eSPI examples. For example:
File > Examples > TFT_eSPI > 128 x 160 > TFT_Clock_Digital.ino

Happy computing

Your two or more topics on the same or similar subject have been merged.

Please do not duplicate your questions as doing so wastes the time and effort of the volunteers trying to help you as they are then answering the same thing in different places.

Please create one topic only for your question and choose the forum category carefully. If you have multiple questions about the same project then please ask your questions in the one topic as the answers to one question provide useful context for the others, and also you won’t have to keep explaining your project repeatedly.

Repeated duplicate posting could result in a temporary or permanent ban from the forum.

Could you take a few moments to Learn How To Use The Forum

It will help you get the best out of the forum in the future.

Thank you.

1 Like

Noted @UKHeliBob. Thank you for the heads up and the merge.
As for the forum rule I have gone through it the very first day I opened the account.

I just did not connect it with this problem before your remark. Makes sense, and my life easier as well.

Footnote: I just needed to see your posts as a reference. Now I have a clear format in my head.

1 Like

ESP32 S3 Sanity Check Part 3: Comprehensive test

Now that you are setup, and have achieved some β€œsanityβ€œ, here is a comprehensive test suite which fully checks the device graphics and memory with assembled common algorithms and testing methods.

/**
 * Core1D Automation Labs Presents:
 * ESP32-S3 Advanced TFT_eSPI ST7735 Comprehensive Test & Benchmark Master Suite
 * Real-Time Hardware Diagnostics Engine, Memory Verification & Boot Statistics
 * 
 * Version: 1.8.1 "TFT Raster Test Suite"
 * Designed by: Sir Ronnie from Core1D Automation Labs
 * License: MIT License + Core1D Labs Commercial Usage Rights
 * Target: ESP32-S3 DevKit C1 N8R8 with 1.8" ST7735 SPI TFT Display (128x160)
 *         Built-in NeoPixel RGB LED (GPIO 38)
 * 
 * HARDWARE CONNECTIONS:
 * =====================
 * 
 * ST7735 1.8" TFT Display (128x160 Portrait Mode):
 * β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 * β”‚  ST7735 Pin    β”‚  ESP32-S3 DevKit C1 Pin   β”‚
 * │────────────────┼───────────────────────────│
 * β”‚  VCC           β”‚  3V3                      β”‚
 * β”‚  GND           β”‚  GND                      β”‚
 * β”‚  CS            β”‚  GPIO 10 (FSPICS0)        β”‚
 * β”‚  RESET/RST     β”‚  GPIO 5  (TFT_RST)        β”‚
 * β”‚  A0/DC         β”‚  GPIO 4  (TFT_DC)         β”‚
 * β”‚  SDA/MOSI      β”‚  GPIO 11 (FSPID)          β”‚
 * β”‚  SCK/SCLK      β”‚  GPIO 12 (FSPICLK)        β”‚
 * β”‚  LED (BL)      β”‚  GPIO 6  (Optional BL)    β”‚
 * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 * 
 * Built-in NeoPixel RGB LED:
 * β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 * β”‚  NeoPixel      β”‚  ESP32-S3 DevKit C1       β”‚
 * │────────────────┼───────────────────────────│
 * β”‚  Data Signal   β”‚  GPIO 38 (Built-in)       β”‚
 * β”‚  VCC           β”‚  Internal 3V3             β”‚
 * β”‚  GND           β”‚  Internal GND             β”‚
 * β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 * 
 * HARDWARE ASCII SCHEMATIC:
 * ========================
 * 
 *    ESP32-S3 DevKit C1 N8R8               ST7735 1.8" TFT Display
 *  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 *  β”‚                             β”‚         β”‚                         β”‚
 *  β”‚  [USB-C]              3V3  β—β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β— VCC                    β”‚
 *  β”‚                             β”‚         β”‚                         β”‚
 *  β”‚  [WiFi Antenna]             β”‚         β”‚                         β”‚
 *  β”‚  [Bluetooth 5.0 LE]         β”‚         β”‚                         β”‚
 *  β”‚                             β”‚         β”‚                         β”‚
 *  β”‚                    GPIO 11 β—β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β— SDA (MOSI)             β”‚
 *  β”‚                    GPIO 12 β—β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β— SCK (SCLK)             β”‚
 *  β”‚                    GPIO 10 β—β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β— CS                     β”‚
 *  β”‚                     GPIO 5 β—β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β— RST                    β”‚
 *  β”‚                     GPIO 4 β—β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β— DC (A0)                β”‚
 *  β”‚                     GPIO 6 β—β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β— LED (Optional)         β”‚
 *  β”‚                             β”‚         β”‚                         β”‚
 *  β”‚                       GND  β—β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β— GND                    β”‚
 *  β”‚                             β”‚         β”‚                         β”‚
 *  β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚         β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
 *  β”‚    β”‚ ESP32-S3-WROOM-1β”‚      β”‚         β”‚ β”‚  128x160 Portrait   β”‚ β”‚
 *  β”‚    β”‚   N8R8 Module   β”‚      β”‚         β”‚ β”‚   65K Colors        β”‚ β”‚
 *  β”‚    β”‚   240MHz Dual   β”‚      β”‚         β”‚ β”‚   SPI Interface     β”‚ β”‚
 *  β”‚    β”‚   Core + RISC-V β”‚      β”‚         β”‚ β”‚   ST7735 Driver     β”‚ β”‚
 *  β”‚    β”‚   8MB Flash     β”‚      β”‚         β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
 *  β”‚    β”‚   8MB PSRAM     β”‚      β”‚         β”‚                         β”‚
 *  β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 *  β”‚                             β”‚                        β”‚
 *  β”‚  ● GPIO 38 (NeoPixel RGB)   β”‚                        β”‚
 *  β”‚    [WS2812B Built-in LED]   β”‚                        β”‚
 *  β”‚                             β”‚                        β”‚
 *  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
 *                β”‚                                        β”‚
 *                └─── [ Xtensa LX7 Dual-Core @ 240MHz ] β”€β”€β”˜
 *                      [ WiFi 802.11 b/g/n + BLE 5.0 ]
 * 
 * TFT_eSPI LIBRARY SETUP INSTRUCTIONS:
 * =====================================
 * 1. Install TFT_eSPI library (v2.5.4+) by Bodmer via Arduino Library Manager
 * 2. Navigate to: Documents/Arduino/libraries/TFT_eSPI/
 * 3. Edit User_Setup.h with these tested settings:
 * 
 *    // Driver Selection
 *    #define ST7735_DRIVER
 *    #define ST7735_BLACKTAB      // or REDTAB/GREENTAB based on your display
 *    
 *    // Pin Definitions for ESP32-S3
 *    #define TFT_CS    10         // Chip select control pin
 *    #define TFT_DC     4         // Data Command control pin
 *    #define TFT_RST    5         // Reset pin (could connect to ESP32 RST)
 *    #define TFT_BL     6         // LED back-light (optional)
 *    
 *    // Hardware SPI pins (ESP32-S3 FSPI)
 *    #define TFT_MOSI  11         // SPI Master Out Slave In
 *    #define TFT_SCLK  12         // SPI Serial Clock
 *    
 *    // SPI Configuration
 *    #define USE_FSPI_PORT        // Use FSPI port on ESP32-S3
 *    #define SPI_FREQUENCY  27000000  // 27MHz for optimal performance
 *    #define SPI_READ_FREQUENCY  20000000  // 20MHz for read operations
 *    #define SPI_TOUCH_FREQUENCY  2500000  // Not used but required
 *    
 *    // Font Configuration
 *    #define LOAD_GLCD            // Font 1: Original Adafruit 8 pixel font
 *    #define LOAD_FONT2           // Font 2: Small 16 pixel high font
 *    #define LOAD_FONT4           // Font 4: Medium 26 pixel high font
 *    #define LOAD_FONT6           // Font 6: Large 48 pixel high font
 *    #define LOAD_GFXFF           // FreeFonts
 *    #define SMOOTH_FONT          // Enable anti-aliased fonts
 * 
 * 4. Arduino IDE Settings:
 *    - Board: "ESP32S3 Dev Module"
 *    - CPU Frequency: "240MHz (WiFi)"
 *    - Flash Size: "8MB (64Mb)"
 *    - PSRAM: "OPI PSRAM"
 *    - Partition Scheme: "8M with spiffs (3MB APP/1.5MB SPIFFS)"
 *    - Core Debug Level: "Info"
 *    - Arduino Runs On: "Core 1"
 *    - Events Run On: "Core 1"
 * 
 */

/**
 * ESP32-S3 TFT_eSPI ST7735 Comprehensive Sanity Test & Benchmark Suite V1.5
 * ============================================================
 * 
 * SETUP INSTRUCTIONS FOR TFT_eSPI:
 * 1. Install TFT_eSPI library (v2.5.4+) by Bodmer via Library Manager
 * 2. Navigate to: Documents/Arduino/libraries/TFT_eSPI/
 * 3. Replace User_Setup.h with provided configuration
 * 4. Key settings in User_Setup.h:
 *    - #define ST7735_DRIVER
 *    - #define ST7735_BLACKTAB (or REDTAB/GREENTAB as needed)
 *    - Pin definitions: CS=10, DC=4, RST=5, BL=6, MOSI=11, SCLK=12
 *    - #define USE_HSPI_PORT
 *    - #define SPI_FREQUENCY 10000000 (10MHz)
 * 5. Use Arduino IDE 2.2.1 or ESP32 board package v2.0.14 for best compatibility
 * 
 * HARDWARE CONNECTIONS:
 * ST7735 -> ESP32-S3
 * VCC    -> 3.3V
 * GND    -> GND
 * CS     -> GPIO 10
 * RESET  -> GPIO 5
 * A0/DC  -> GPIO 4
 * SDA    -> GPIO 11 (MOSI)
 * SCK    -> GPIO 12 (SCLK)
 * LED    -> GPIO 6 (Backlight)
 * 
 * NeoPixel -> ESP32-S3 DevKit C1
 * Data   -> GPIO 48 (Built-in RGB LED)
 * 
 * FEATURES:
 * - Hardware diagnostics and memory verification
 * - Built-in NeoPixel RGB LED control
 * - Advanced animations (plasma, matrix rain, starfield)
 * - Comprehensive RAM/Flash/PSRAM testing
 * - Chip ID and metadata display
 */

#include <TFT_eSPI.h>
#include <SPI.h>
#include <Adafruit_NeoPixel.h> // To drive the RGB LED
#include <esp_chip_info.h>
#include <esp_flash.h>
#include <soc/soc.h>
#include <soc/rtc_cntl_reg.h>
#include <Preferences.h>  // For non-volatile storage

// NeoPixel Configuration (ESP32-S3 DevKit C1 built-in RGB LED)
constexpr uint8_t NEOPIXEL_PIN            = 38; // Devkit C1
constexpr uint8_t NEOPIXEL_COUNT          = 1;
constexpr uint8_t NEOPIXEL_MAX_BRIGHTNESS = 2;  // Range: 0 - 255.

// Hardware test constants
constexpr size_t MEMORY_TEST_CHUNK_SIZE = 1024;
constexpr uint32_t MEMORY_TEST_PATTERN  = 0xDEADBEEF;

// Animation constants
constexpr uint8_t PLASMA_SCALE    = 11;
constexpr uint8_t MATRIX_DROPS    = 17;
constexpr uint8_t STARFIELD_STARS = 200;

// Boot counter constants
constexpr const char* BOOT_COUNTER_NAMESPACE = "boot_stats";
constexpr const char* BOOT_COUNT_KEY         = "boot_count";
constexpr const char* SUCCESSFUL_CYCLES_KEY  = "cycles_ok";

// Create instances
TFT_eSPI tft = TFT_eSPI();
Adafruit_NeoPixel neopixel(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
Preferences preferences;

// Benchmark variables
unsigned long frameCount    = 0;
unsigned long lastFPSUpdate = 0;
float currentFPS            = 0.0;
size_t initialFreeHeap      = 0;
size_t videoBufferSize      = 0;

// Boot tracking variables
uint32_t bootCount        = 0;
uint32_t successfulCycles = 0;
bool bootCounterAvailable = false;

// Animation variables
float angle                  = 0.0;
int waveOffset               = 0;
unsigned long lastAnimUpdate = 0;
uint8_t plasmaPhase          = 0;
int matrixDrops[MATRIX_DROPS];
struct Star {
  float x, y, z;
} stars[STARFIELD_STARS];

// Hardware info structure
struct HardwareInfo {
  esp_chip_info_t chipInfo;
  uint32_t chipId;
  uint32_t flashSize;
  uint32_t psramSize;
  size_t freeHeap;
  size_t totalHeap;
  size_t freePsram;
  size_t totalPsram;
};

// Test colors array
constexpr uint16_t testColors[] = {
  TFT_RED, TFT_GREEN, TFT_BLUE, TFT_YELLOW, 
  TFT_MAGENTA, TFT_CYAN, TFT_WHITE, TFT_ORANGE,
  TFT_PINK, TFT_GREENYELLOW, TFT_SKYBLUE, TFT_VIOLET
};

// NeoPixel colors
constexpr uint32_t neoPixelColors[] = {
  0xFF0000,  // Red
  0x00FF00,  // Green
  0x0000FF,  // Blue
  0xFFFF00,  // Yellow
  0xFF4500,  // Orange Red
  0x9400D3,  // Violet
  0x00FFFF,  // Cyan
  0xFF1493,  // Deep Pink
  0x32CD32,  // Lime Green
  0x8A2BE2,  // Blue Violet
  0xFF6347,  // Tomato
  0x4169E1   // Royal Blue
};

// Timing control
struct TestTiming {
  unsigned long startTime;
  unsigned long duration;
  bool isRunning;
};

void initializeBootCounter() {
  Serial.println(F("\n[BOOT COUNTER INITIALIZATION]"));
  Serial.println(F("----------------------------------------"));
  
  // Initialize preferences for non-volatile storage
  bootCounterAvailable = preferences.begin(BOOT_COUNTER_NAMESPACE, false);
  
  if (bootCounterAvailable) {
    // Read current boot statistics
    bootCount        = preferences.getUInt(BOOT_COUNT_KEY, 0);
    successfulCycles = preferences.getUInt(SUCCESSFUL_CYCLES_KEY, 0);
    
    // Increment boot count
    bootCount++;
    preferences.putUInt(BOOT_COUNT_KEY, bootCount);
    
    Serial.println(F(" Boot counter initialized successfully"));
    Serial.printf("  Total boots: %lu\n", bootCount);
    Serial.printf("  Successful test cycles: %lu\n", successfulCycles);
    Serial.printf("  Success rate: %.1f%%\n", 
                  bootCount > 0 ? (float)successfulCycles * 100 / bootCount : 0.0);
    
    // Display boot info on screen
    tft.setTextColor(TFT_GREEN, TFT_BLACK);
    tft.setCursor(2, 15);
    tft.printf("Boot #%lu", bootCount);
    tft.setCursor(2, 25);
    tft.printf("Cycles: %lu", successfulCycles);
    if (bootCount > 1) {
      tft.setCursor(2, 35);
      tft.printf("Rate: %.1f%%", (float)successfulCycles * 100 / bootCount);
    }
    
  } else {
    Serial.println(F("βœ— Boot counter not available (Preferences failed)"));
    Serial.println(F("  Continuing without boot tracking..."));
    
    tft.setTextColor(TFT_YELLOW, TFT_BLACK);
    tft.setCursor(2, 15);
    tft.println(F("Boot tracking"));
    tft.setCursor(2, 25);
    tft.println(F("unavailable"));
  }
  
  delay(2000); // Show boot info for 2 seconds
}

void recordSuccessfulCycle() {
  if (bootCounterAvailable) {
    successfulCycles++;
    preferences.putUInt(SUCCESSFUL_CYCLES_KEY, successfulCycles);
    
    Serial.printf(" Recorded successful cycle #%lu\n", successfulCycles);
    Serial.printf(" Success rate: %.1f%%\n", (float)successfulCycles * 100 / bootCount);
  }
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  
  Serial.println(F("\n============================================================"));
  Serial.println(F("ESP32-S3 TFT_eSPI ST7735 ADVANCED COMPREHENSIVE TEST SUITE"));
  Serial.println(F("With Advanced NeoPixel Integration and Boot Tracking"));
  Serial.println(F("============================================================"));
  
  // Initialize NeoPixel with startup sequence
  neopixel.begin();
  neopixel.setBrightness(NEOPIXEL_MAX_BRIGHTNESS);
  neoPixelStartupSequence();
  
  // Record initial memory state
  initialFreeHeap = ESP.getFreeHeap();
  Serial.printf("Initial Free Heap: %zu bytes\n", initialFreeHeap);
  
  // Initialize display
  Serial.println(F("\n[1/13] DISPLAY INITIALIZATION TEST"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColor(0x0000FF); // Blue for initialization
  
  tft.init();
  tft.setRotation(0);  // Portrait mode (Currently everything is static set for portrait aspect ratios only, ideal practice for embedded systems)
  
  Serial.printf("Display Resolution: %d x %d\n", tft.width(), tft.height());
  Serial.println(F("βœ“ TFT_eSPI initialization successful!"));
  
  // Calculate approximate video buffer size
  videoBufferSize = tft.width() * tft.height() * 2; // 16-bit color
  Serial.printf("Video Buffer Size: %zu bytes (%.1f KB)\n", 
                videoBufferSize, videoBufferSize / 1024.0);
  
  // Clear screen
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_CYAN, TFT_BLACK);
  tft.setCursor(2, 2);
  tft.println(F("Boot Tracking Init"));
  
  // Initialize boot counter (test 2/13)
  Serial.println(F("\n[2/13] BOOT COUNTER INITIALIZATION"));
  setNeoPixelColor(0x9400D3); // Violet for boot tracking
  initializeBootCounter();
  
  setNeoPixelColor(0x00FF00); // Green - ready for tests
  
  // Run comprehensive tests
  runHardwareDiagnostics();      // 3/13
  runEnhancedMemoryVerificationTest(); // 4/13
  runColorAccuracyTest();        // 5/13
  runGeometricDrawingTest();     // 6/13
  runTextRenderingTest();        // 7/13
  runEnhancedAdvancedAnimationTests(); // 8/13
  runAnimationTest();            // 9/13 (FIXED)
  runMemoryStressTest();         // 10/13
  runPerformanceBenchmark();     // 11/13
  runSpriteTest();               // 12/13 (Drawing performance test)
  runEnhancedNeoPixelTest();     // 13/13

  Serial.println(F("\n============================================================"));
  Serial.println(F("ALL TESTS COMPLETED SUCCESSFULLY!"));
  Serial.println(F("============================================================"));
  
  // Clear screen completely before celebration
  tft.fillScreen(TFT_BLACK);
  delay(500);
  
  // Success celebration with clear display
  neoPixelCelebrationSequence();
  
  // Show "ALL TESTS PASSED" message clearly
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setTextSize(2);
  tft.setCursor(5, 40);
  tft.println(F("ALL TESTS"));
  tft.setCursor(20, 60);
  tft.println(F("PASSED!"));
  
  tft.setTextSize(1);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setCursor(5, 90);
  tft.println(F("13/13 tests successful"));
  tft.setCursor(5, 100);
  tft.printf("Memory used: %d KB", (initialFreeHeap - ESP.getFreeHeap())/1024);
  tft.setCursor(5, 110);
  tft.printf("Display: %dx%d", tft.width(), tft.height());
  
  // Show this for 3 seconds
  delay(3000);
  
  // Final system check with clean display
  runFinalSystemCheck();
  
  Serial.println(F("\n============================================================"));
  Serial.println(F("ENTERING DEMO CYCLE"));
  Serial.println(F("============================================================"));
  
  // Init starfield for demo mode
  initializeStarfield();
}

void neoPixelStartupSequence() {
  // Startup rainbow sweep
  for (int i = 0; i < 12; i++) {
    setNeoPixelColor(neoPixelColors[i]);
    delay(150);
  }
  
  // Breathing effect
  for (int cycle = 0; cycle < 3; cycle++) {
    for (int brightness = 0; brightness <= NEOPIXEL_MAX_BRIGHTNESS; brightness += 2) {
      neopixel.setBrightness(brightness);
      setNeoPixelColor(0x4000FF);
      delay(30);
    }
    for (int brightness = NEOPIXEL_MAX_BRIGHTNESS; brightness >= 0; brightness -= 2) {
      neopixel.setBrightness(brightness);
      setNeoPixelColor(0x4000FF);
      delay(30);
    }
  }
  
  neopixel.setBrightness(NEOPIXEL_MAX_BRIGHTNESS);
}

void neoPixelCelebrationSequence() {
  // Rainbow celebration
  for (int cycle = 0; cycle < 3; cycle++) {
    for (int i = 0; i < 12; i++) {
      setNeoPixelColor(neoPixelColors[i]);
      delay(100);
    }
  }
  
  // Final flash
  for (int i = 0; i < 6; i++) {
    setNeoPixelColor(0xFFFFFF); // White
    delay(100);
    setNeoPixelColor(0x000000); // Off
    delay(100);
  }
  
  setNeoPixelColor(0x0000FF); // Blue for demo mode
}

void setNeoPixelColor(uint32_t color) {
  neopixel.setPixelColor(0, color);
  neopixel.show();
}

void setNeoPixelColorSmooth(uint32_t color, int transitionTime = 200) {
  // Get current color (simplified - assumes we know the previous color)
  static uint32_t lastColor = 0x000000;
  
  uint8_t r1 = (lastColor >> 16) & 0xFF;
  uint8_t g1 = (lastColor >> 8) & 0xFF;
  uint8_t b1 = lastColor & 0xFF;
  
  uint8_t r2 = (color >> 16) & 0xFF;
  uint8_t g2 = (color >> 8) & 0xFF;
  uint8_t b2 = color & 0xFF;
  
  int steps = transitionTime / 10;
  for (int i = 0; i <= steps; i++) {
    uint8_t r = r1 + ((r2 - r1) * i / steps);
    uint8_t g = g1 + ((g2 - g1) * i / steps);
    uint8_t b = b1 + ((b2 - b1) * i / steps);
    
    uint32_t intermediateColor = (r << 16) | (g << 8) | b;
    setNeoPixelColor(intermediateColor);
    delay(10);
  }
  
  lastColor = color;
}

TestTiming createTestTimer(unsigned long durationMs) {
  TestTiming timer;
  timer.startTime = millis();
  timer.duration = durationMs;
  timer.isRunning = true;
  return timer;
}

bool isTestRunning(TestTiming& timer) {
  if (!timer.isRunning) return false;
  
  if (millis() - timer.startTime >= timer.duration) {
    timer.isRunning = false;
    return false;
  }
  return true;
}

float getTestProgress(const TestTiming& timer) {
  if (!timer.isRunning) return 1.0;
  return (float)(millis() - timer.startTime) / timer.duration;
}

void runHardwareDiagnostics() {
  Serial.println(F("\n[2/12] HARDWARE DIAGNOSTICS"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0x00FFFF); // Cyan for diagnostics
  
  HardwareInfo hw = {};
  
  // Get chip info
  esp_chip_info(&hw.chipInfo);
  hw.chipId = ESP.getChipModel() ? (uint32_t)ESP.getEfuseMac() : 0;
  
  // Get mem info
  hw.freeHeap   = ESP.getFreeHeap();
  hw.totalHeap  = ESP.getHeapSize();
  hw.freePsram  = ESP.getFreePsram();
  hw.totalPsram = ESP.getPsramSize();
  
  // Get flash size
  esp_flash_get_size(NULL, &hw.flashSize);
  
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_CYAN, TFT_BLACK);
  tft.setTextSize(1);
  tft.setCursor(2, 2);
  tft.println(F("Hardware Diagnostics"));
  
  // Diagnostic display
  TestTiming timer = createTestTimer(6000); // 6 seconds for diagnostics
  int displayStep = 0;
  
  while (isTestRunning(timer)) {
    unsigned long stepTime = (millis() - timer.startTime) / 500;
    if (stepTime != displayStep) {
      displayStep = stepTime;
      
      // Pulse NeoPixel during diagnostics
      int brightness = NEOPIXEL_MAX_BRIGHTNESS * (0.5 + 0.5 * sin(millis() * 0.01));
      neopixel.setBrightness(brightness);
      setNeoPixelColor(0x00FFFF);
      
      switch (displayStep % 4) {
        case 0:
          // Display chip info
          tft.fillRect(2, 15, 124, 50, TFT_BLACK);
          tft.setTextColor(TFT_WHITE, TFT_BLACK);
          tft.setCursor(2, 15);
          tft.printf("Model: %s", ESP.getChipModel());
          tft.setCursor(2, 25);
          tft.printf("Cores: %d", hw.chipInfo.cores);
          tft.setCursor(2, 35);
          tft.printf("Revision: %d", hw.chipInfo.revision);
          tft.setCursor(2, 45);
          tft.printf("CPU: %d MHz", ESP.getCpuFreqMHz());
          break;
          
        case 1:
          // Mem info
          tft.fillRect(2, 15, 124, 50, TFT_BLACK);
          tft.setCursor(2, 15);
          tft.printf("Flash: %lu MB", hw.flashSize / (1024*1024));
          tft.setCursor(2, 25);
          tft.printf("Heap: %zu/%zu KB", hw.freeHeap/1024, hw.totalHeap/1024);
          tft.setCursor(2, 35);
          tft.printf("PSRAM: %zu/%zu KB", hw.freePsram/1024, hw.totalPsram/1024);
          break;
          
        case 2:
          // Features
          tft.fillRect(2, 15, 124, 50, TFT_BLACK);
          tft.setTextColor(TFT_YELLOW, TFT_BLACK);
          tft.setCursor(2, 15);
          tft.printf("WiFi: %s", (hw.chipInfo.features & CHIP_FEATURE_WIFI_BGN) ? "YES" : "NO");
          tft.setCursor(2, 25);
          tft.printf("BLE: %s", (hw.chipInfo.features & CHIP_FEATURE_BLE) ? "YES" : "NO");
          tft.setCursor(2, 35);
          tft.printf("IEEE802.15.4: %s", (hw.chipInfo.features & CHIP_FEATURE_IEEE802154) ? "YES" : "NO");
          break;
          
        case 3:
          // Progress indicator
          tft.fillRect(2, 15, 124, 50, TFT_BLACK);
          tft.setTextColor(TFT_GREEN, TFT_BLACK);
          tft.setCursor(2, 15);
          tft.println("Scanning hardware...");
          
          // Progress bar
          float progress = getTestProgress(timer);
          int barWidth   = (int)(120 * progress);
          tft.drawRect(2, 30, 120, 8, TFT_WHITE);
          tft.fillRect(3, 31, barWidth-1, 6, TFT_GREEN);
          break;
      }
    }
    delay(1000);
  }
  
  neopixel.setBrightness(NEOPIXEL_MAX_BRIGHTNESS);
  setNeoPixelColor(0x00FF00); // Green = success
  
  Serial.println(F("Hardware Information:"));
  Serial.printf("  Chip: %s (Rev %d)\n", ESP.getChipModel(), hw.chipInfo.revision);
  Serial.printf("  Cores: %d, CPU: %d MHz\n", hw.chipInfo.cores, ESP.getCpuFreqMHz());
  Serial.printf("  Flash: %lu bytes (%.1f MB)\n", hw.flashSize, hw.flashSize / (1024.0*1024.0));
  Serial.printf("  Heap: %zu/%zu bytes\n", hw.freeHeap, hw.totalHeap);
  Serial.printf("  PSRAM: %zu/%zu bytes\n", hw.freePsram, hw.totalPsram);
  Serial.println(F("Hardware diagnostics completed"));
  
  delay(1000);
}

void runEnhancedMemoryVerificationTest() {
  Serial.println(F("\n[3/12] ENHANCED MEMORY VERIFICATION TEST"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0xFF4500); // Orange for memory testing
  
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_ORANGE, TFT_BLACK);
  tft.setCursor(2, 2);
  tft.println(F("Memory Verification"));
  
  TestTiming timer = createTestTimer(8000); // 8 seconds for thorough memory test
  
  size_t totalTested = 0;
  size_t errors      = 0;
  
  // Test heap memory in chunks
  const size_t freeHeap  = ESP.getFreeHeap();
  const size_t testSize  = (freeHeap / 4 < 32768) ? (freeHeap / 4) : 32768;
  const size_t chunkSize = 256; // Small chunks for visual feedback
  
  tft.setCursor(2, 15);
  tft.printf("Testing %zu bytes", testSize);
  
  // Visual memory map
  const int mapWidth  = 120;
  const int mapHeight = 60;
  const int mapStartY = 40;
  
  tft.drawRect(2, mapStartY, mapWidth, mapHeight, TFT_WHITE);
  tft.setCursor(2, mapStartY + mapHeight + 5);
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  
  uint8_t* testBuffer = (uint8_t*)malloc(testSize);
  
  if (testBuffer) {
    size_t chunksTotal = testSize / chunkSize;
    
    while (isTestRunning(timer) && totalTested < testSize) {
      size_t currentChunk = totalTested  / chunkSize;
      size_t chunkStart   = currentChunk * chunkSize;
      size_t chunkEnd     = (chunkStart  + chunkSize < testSize) ? (chunkStart + chunkSize) : testSize;
      
      // NeoPixel color based on test phase
      if (totalTested < testSize / 3) {
        setNeoPixelColor(0xFF0000); // Red = writing phase
      } else if (totalTested < 2 * testSize / 3) {
        setNeoPixelColor(0xFFFF00); // Yellow = verification phase
      } else {
        setNeoPixelColor(0x00FF00); // Green = final phase
      }
      
      // Write test pattern to current chunk
      for (size_t i = chunkStart; i < chunkEnd; i++) {
        testBuffer[i] = (uint8_t)(i ^ 0xAA);
      }
      
      // Verify test pattern in current chunk
      for (size_t i = chunkStart; i < chunkEnd; i++) {
        if (testBuffer[i] != (uint8_t)(i ^ 0xAA)) {
          errors++;
        }
        totalTested++;
      }
      
      // Update visual memory map
      int mapX            = (currentChunk * mapWidth) / chunksTotal;
      int mapY            = mapStartY + 1;
      uint16_t chunkColor = errors > 0 ? TFT_RED : TFT_GREEN;
      
      // Draw memory chunk status
      tft.fillRect(2 + mapX, mapY, (mapWidth / chunksTotal) > 1 ? (mapWidth / chunksTotal) : 1, mapHeight - 2, chunkColor);
      
      // Update status text
      tft.fillRect(2, 110, 124, 30, TFT_BLACK);
      tft.setCursor(2, 110);
      tft.setTextColor(errors > 0 ? TFT_RED : TFT_GREEN, TFT_BLACK);
      tft.printf("Tested: %zu/%zu", totalTested, testSize);
      tft.setCursor(2, 120);
      tft.printf("Errors: %zu", errors);
      tft.setCursor(2, 130);
      tft.printf("Progress: %.1f%%", (float)totalTested * 100 / testSize);
      
      delay(50); // Slow down for perception
    }
    
    free(testBuffer);
    
    // Test PSRAM (if available)
    if (ESP.getPsramSize() > 0) {
      setNeoPixelColor(0x9400D3); // Violet for PSRAM test
      
      const size_t freePsram     = ESP.getFreePsram();
      const size_t psramTestSize = (freePsram / 4 < 65536) ? (freePsram / 4) : 65536;
      uint8_t* psramBuffer       = (uint8_t*)ps_malloc(psramTestSize);
      
      if (psramBuffer) {
        size_t psramErrors = 0;
        
        tft.fillRect(2, 140, 124, 20, TFT_BLACK);
        tft.setCursor(2, 140);
        tft.setTextColor(TFT_VIOLET, TFT_BLACK);
        tft.printf("Testing PSRAM...");
        
        // Test PSRAM with visual feedback
        for (size_t i = 0; i < psramTestSize; i += 1024) {
          size_t chunkEnd = min(i + 1024, psramTestSize);
          
          // Write pattern
          for (size_t j = i; j < chunkEnd; j++) {
            psramBuffer[j] = (uint8_t)(j ^ 0x55);
          }
          
          // Verify pattern
          for (size_t j = i; j < chunkEnd; j++) {
            if (psramBuffer[j] != (uint8_t)(j ^ 0x55)) {
              psramErrors++;
            }
          }
          
          // Update PSRAM progress
          if (i % 8192 == 0) {
            tft.fillRect(2, 150, 124, 10, TFT_BLACK);
            tft.setCursor(2, 150);
            tft.printf("PSRAM: %.1f%%", (float)i * 100 / psramTestSize);
          }
          
          delay(1);
        }
        
        free(psramBuffer);
        
        tft.fillRect(2, 140, 124, 20, TFT_BLACK);
        tft.setCursor(2, 140);
        tft.setTextColor(psramErrors > 0 ? TFT_RED : TFT_GREEN, TFT_BLACK);
        tft.printf("PSRAM: %zu errors", psramErrors);
        
        errors += psramErrors;
      }
    }
  }
  
  // Final result
  setNeoPixelColor(errors > 0 ? 0xFF0000 : 0x00FF00); // Red if errors, Green if success
  
  Serial.printf("Enhanced memory verification: %zu bytes tested, %zu errors\n", totalTested, errors);
  delay(2000);
}

void runEnhancedAdvancedAnimationTests() {
  Serial.println(F("\n[4/12] ADVANCED ANIMATION TESTS"));
  Serial.println(F("----------------------------------------"));
  
  // Plasma effect with NeoPixel sync
  runEnhancedPlasmaEffect();
  
  // Matrix rain effect with reactive NeoPixel
  runEnhancedMatrixRain();
  
  // Starfield effect with speed-reactive NeoPixel
  runEnhancedStarfield();
  
  Serial.println(F("Advanced animations completed"));
}

void runEnhancedPlasmaEffect() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_PURPLE, TFT_BLACK);
  tft.setCursor(2, 2);
  tft.println(F("Plasma"));
  
  TestTiming timer = createTestTimer(5000); // Guaranteed 5 seconds
  
  while (isTestRunning(timer)) {
    float progress = getTestProgress(timer);
    
    // NeoPixel color synced to plasma animation
    uint8_t neoR = (uint8_t)(128 + 127 * sin(plasmaPhase * 0.1));
    uint8_t neoG = (uint8_t)(128 + 127 * sin(plasmaPhase * 0.15));
    uint8_t neoB = (uint8_t)(128 + 127 * sin(plasmaPhase * 0.2));
    uint32_t neoColor = (neoR << 16) | (neoG << 8) | neoB;
    setNeoPixelColor(neoColor);
    
    for (int x = 0; x < tft.width(); x += 2) {
      for (int y = 20; y < tft.height(); y += 2) {
        int color1 = (int)(128 + 127 * sin((x + plasmaPhase) / 16.0));
        int color2 = (int)(128 + 127 * sin((y + plasmaPhase) / 8.0));
        int color3 = (int)(128 + 127 * sin(sqrt((x-64)*(x-64) + (y-80)*(y-80)) / 8.0 + plasmaPhase));
        
        uint8_t r = (color1 + color3) / 2;
        uint8_t g = (color2 + color3) / 2;
        uint8_t b = (color1 + color2) / 2;
        
        uint16_t color = tft.color565(r, g, b);
        tft.drawPixel(x, y, color);
      }
    }
    
    // Progress indicator
    int progressBar = (int)(120 * progress);
    tft.drawRect(2, 12, 120, 4, TFT_WHITE);
    tft.fillRect(2, 12, progressBar, 4, TFT_GREEN);
    
    plasmaPhase += 4;
    delay(50);
  }
}

void runEnhancedMatrixRain() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setCursor(2, 2);
  tft.println(F("Matrix"));
  
  // Initialize drops
  for (int i = 0; i < MATRIX_DROPS; i++) {
    matrixDrops[i] = random(-50, 0);
  }
  
  TestTiming timer = createTestTimer(5000); // Guaranteed 5 seconds
  int frameCounter = 0;
  
  while (isTestRunning(timer)) {
    frameCounter++;
    
    // NeoPixel pulses with matrix intensity
    uint8_t intensity = (uint8_t)(128 + 127 * sin(frameCounter * 0.2));
    uint32_t matrixGreen = (0x00 << 16) | (intensity << 8) | 0x00;
    setNeoPixelColor(matrixGreen);
    
    tft.fillRect(0, 15, tft.width(), tft.height()-15, TFT_BLACK);
    
    for (int i = 0; i < MATRIX_DROPS; i++) {
      int x = i * (tft.width() / MATRIX_DROPS) + 2;
      
      // Draw trail
      for (int j = 0; j < 8; j++) {
        int y = matrixDrops[i] - j * 8;
        if (y >= 15 && y < tft.height()) {
          uint8_t brightness = 255 - j * 32;
          uint16_t color = tft.color565(0, brightness, 0);
          char c = random(33, 127);
          tft.setTextColor(color, TFT_BLACK);
          tft.setCursor(x, y);
          tft.write(c);
        }
      }
      
      matrixDrops[i] += 8;
      if (matrixDrops[i] > tft.height()) {
        matrixDrops[i] = random(-50, -8);
      }
    }
    
    // Progress indicator
    float progress  = getTestProgress(timer);
    int progressBar = (int)(120 * progress);
    tft.drawRect(2, 12, 120, 2, TFT_WHITE);
    tft.fillRect(2, 12, progressBar, 2, TFT_GREEN);
    
    delay(100);
  }
}

void runEnhancedStarfield() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setCursor(2, 2);
  tft.println(F("FTL"));
  
  TestTiming timer = createTestTimer(5000); // Guaranteed 5 seconds
  int speedLevel   = 1;
  
  while (isTestRunning(timer)) {
    float progress = getTestProgress(timer);
    
    // Increase speed over time
    speedLevel = (int)(1 + progress * 20);
    
    // NeoPixel color represents warp speed
    uint8_t blue       = 255;
    uint8_t white      = (uint8_t)(progress * 255);
    uint32_t warpColor = (white << 16) | (white << 8) | blue;
    setNeoPixelColor(warpColor);
    
    tft.fillRect(0, 15, tft.width(), tft.height()-15, TFT_BLACK);
    
    for (int i = 0; i < STARFIELD_STARS; i++) {
      // Move star towards viewer with increasing speed
      stars[i].z -= speedLevel;
      if (stars[i].z <= 0) {
        stars[i].x = random(-1000, 1000);
        stars[i].y = random(-1000, 1000);
        stars[i].z = 1000;
      }
      
      // Project to screen coordinates
      int sx = (int)(stars[i].x / stars[i].z * 100) + tft.width()/2;
      int sy = (int)(stars[i].y / stars[i].z * 100) + tft.height()/2;
      
      if (sx >= 0 && sx < tft.width() && sy >= 15 && sy < tft.height()) {
        uint8_t brightness = 255 - (uint8_t)(stars[i].z / 4);
        uint16_t color     = tft.color565(brightness, brightness, brightness);
        tft.drawPixel(sx, sy, color);
        
        // Draw star trails at high speed
        if (speedLevel > 10) {
          int prevX = (int)(stars[i].x / (stars[i].z + speedLevel) * 100) + tft.width()/2;
          int prevY = (int)(stars[i].y / (stars[i].z + speedLevel) * 100) + tft.height()/2;
          if (prevX >= 0 && prevX < tft.width() && prevY >= 15 && prevY < tft.height()) {
            tft.drawLine(prevX, prevY, sx, sy, color);
          }
        }
      }
    }
    
    // Speed indicator
    tft.fillRect(2, 12, 120, 10, TFT_BLACK);
    tft.setCursor(2, 12);
    tft.printf("Warp %d", speedLevel);
    
    delay(20);
  }
}

void runEnhancedNeoPixelTest() {
  Serial.println(F("\n[13/13] ADVANCED NEOPIXEL RGB LED TEST"));
  Serial.println(F("----------------------------------------"));
  
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_MAGENTA, TFT_BLACK);
  tft.setCursor(2, 2);
  tft.println(F("Enhanced NeoPixel"));
  
  TestTiming timer = createTestTimer(8000); // 8 seconds for comprehensive test
  
  // FIXED: Color cycle test with proper index matching
  for (int i = 0; i < 12 && isTestRunning(timer); i++) {
    // Use modulo to ensure we don't exceed neoPixelColors array size
    int neoIndex = i % (sizeof(neoPixelColors) / sizeof(neoPixelColors[0]));
    setNeoPixelColorSmooth(neoPixelColors[neoIndex], 300);
    
    tft.fillRect(2, 20 + (i % 6)*15, 60, 10, TFT_BLACK);
    tft.setCursor(2, 20 + (i % 6)*15);
    tft.setTextColor(testColors[i % 12], TFT_BLACK);  // Ensure we stay within testColors bounds
    tft.printf("Color %d", i+1);
    
    // Draw color sample on screen that matches the NeoPixel
    // Convert NeoPixel color to 16-bit TFT color for accurate display
    uint32_t neoColor = neoPixelColors[neoIndex];
    uint8_t r         = (neoColor >> 16) & 0xFF;
    uint8_t g         = (neoColor >> 8) & 0xFF;
    uint8_t b         = neoColor & 0xFF;
    uint16_t tftColor = tft.color565(r, g, b);
    
    tft.fillRect(70, 20 + (i % 6)*15, 50, 10, tftColor);
    
    delay(250);
  }
  
  if (isTestRunning(timer)) {
    // Breathing effect with multiple colors
    tft.fillRect(2, 110, 124, 30, TFT_BLACK);
    tft.setCursor(2, 110);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.println(F("Breathing Effects"));
    
    uint32_t breatheColors[] = {0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00};
    
    for (int colorIdx = 0; colorIdx < 4 && isTestRunning(timer); colorIdx++) {
      tft.setCursor(2, 120);
      tft.printf("Breathing: %d/4", colorIdx + 1);
      
      for (int cycle = 0; cycle < 2 && isTestRunning(timer); cycle++) {
        // Breathe in
        for (int brightness = 0; brightness <= NEOPIXEL_MAX_BRIGHTNESS && isTestRunning(timer); brightness += 2) {
          neopixel.setBrightness(brightness);
          setNeoPixelColor(breatheColors[colorIdx]);
          
          // Visual breathing indicator
          int barHeight = (brightness * 20) / NEOPIXEL_MAX_BRIGHTNESS;
          tft.fillRect(90, 140 - barHeight, 10, barHeight,             testColors[colorIdx * 3]);
          tft.fillRect(90, 120,             10, 140 - 120 - barHeight, TFT_BLACK);
          
          delay(40);
        }
        
        // Breathe out
        for (int brightness = NEOPIXEL_MAX_BRIGHTNESS; brightness >= 0 && isTestRunning(timer); brightness -= 2) {
          neopixel.setBrightness(brightness);
          setNeoPixelColor(breatheColors[colorIdx]);
          
          // Visual breathing indicator
          int barHeight = (brightness * 20) / NEOPIXEL_MAX_BRIGHTNESS;
          tft.fillRect(90, 140 - barHeight, 10, barHeight,             testColors[colorIdx * 3]);
          tft.fillRect(90, 120,             10, 140 - 120 - barHeight, TFT_BLACK);
          
          delay(40);
        }
      }
    }
  }
  
  // Rainbow effect
  if (isTestRunning(timer)) {
    tft.fillRect(2, 120, 80, 20, TFT_BLACK);
    tft.setCursor(2, 120);
    tft.println(F("Rainbow Effect"));
    
    unsigned long rainbowStart = millis();
    while (isTestRunning(timer) && (millis() - rainbowStart < 3000)) {
      for (int hue = 0; hue < 360 && isTestRunning(timer); hue += 5) {
        // Convert HSV to RGB
        float h = hue / 60.0;
        float c = 1.0;
        float x = c * (1.0 - abs(fmod(h, 2.0) - 1.0));
        float r1, g1, b1;
        
        if (h < 1) { r1 = c; g1 = x; b1 = 0; }
        else if (h < 2) { r1 = x; g1 = c; b1 = 0; }
        else if (h < 3) { r1 = 0; g1 = c; b1 = x; }
        else if (h < 4) { r1 = 0; g1 = x; b1 = c; }
        else if (h < 5) { r1 = x; g1 = 0; b1 = c; }
        else { r1 = c; g1 = 0; b1 = x; }
        
        uint8_t r = (uint8_t)(r1 * 255);
        uint8_t g = (uint8_t)(g1 * 255);
        uint8_t b = (uint8_t)(b1 * 255);
        
        uint32_t rainbowColor = (r << 16) | (g << 8) | b;
        setNeoPixelColor(rainbowColor);
        
        // Rainbow progress bar that matches NeoPixel color
        int progress = (hue * 100) / 360;
        tft.fillRect(2, 130, progress, 8, tft.color565(r, g, b));
        
        delay(30);
      }
    }
  }
  
  // Reset to full brightness
  neopixel.setBrightness(NEOPIXEL_MAX_BRIGHTNESS);
  setNeoPixelColor(0x00FF00); // Green for success
  
  Serial.println(F("Enhanced NeoPixel test completed"));
  delay(1000);
}

void runColorAccuracyTest() {
  Serial.println(F("\n[5/12] COLOR ACCURACY TEST"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0xFF1493); // Deep pink for color testing
  
  TestTiming timer = createTestTimer(4000); // 4 seconds guaranteed
  
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextSize(1);
  tft.setCursor(5, 5);
  tft.println(F("Color Accuracy Test"));
  
  // Color bars with NeoPixel sync
  for(int i = 0; i < 12 && isTestRunning(timer); i++) {
    int y = 20 + (i * 10);
    tft.fillRect(10, y, 60, 8, testColors[i]);
    tft.setCursor(75, y);
    tft.setTextColor(testColors[i], TFT_BLACK);
    tft.printf("Color %d", i+1);
    
    // Sync NeoPixel to current color being tested
    if (i < 8) {
      setNeoPixelColor(neoPixelColors[i]);
    }
    
    delay(200);
  }
  
  // RGB gradient test
  if (isTestRunning(timer)) {
    tft.setCursor(5, 140);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.println(F("RGB Gradient:"));
    
    for(int x = 0; x < 128 && isTestRunning(timer); x += 4) {
      uint16_t color = tft.color565(x*2, (127-x)*2, (x/2));
      tft.drawFastVLine(x, 150, 8, color);
      
      // Update NeoPixel to match gradient
      if (x % 16 == 0) {
        uint32_t neoGradient = ((x*2) << 16) | (((127-x)*2) << 8) | (x/2);
        setNeoPixelColor(neoGradient);
      }
      delay(10);
    }
  }
  
  setNeoPixelColor(0x00FF00); // Green for success
  Serial.println(F("Color accuracy test completed"));
  
  // Wait for remaining time
  while (isTestRunning(timer)) {
    delay(100);
  }
}

void runGeometricDrawingTest() {
  Serial.println(F("\n[6/12] GEOMETRIC DRAWING TEST"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0x32CD32); // Lime green for geometry
  
  TestTiming timer = createTestTimer(4000); // 4 seconds guaranteed
  
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setCursor(5, 5);
  tft.println(F("Geometric Shapes"));
  
  unsigned long startTime = millis();
  
  // Animated line drawing
  for(int i = 0; i < 8 && isTestRunning(timer); i++) {
    tft.drawLine(20 + i*2, 20, 80 + i*2, 60, testColors[i % 12]);
    setNeoPixelColor(neoPixelColors[i % 12]);
    delay(200);
  }
  
  // Rectangles with animation
  if (isTestRunning(timer)) {
    setNeoPixelColor(0xFF0000); // Red for rectangles
    tft.drawRect(10, 70, 40, 30, TFT_RED);
    delay(300);
    
    setNeoPixelColor(0x00FF00); // Green for filled rectangle
    tft.fillRect(60, 70, 40, 30, TFT_GREEN);
    delay(300);
  }
  
  // Circles with animation
  if (isTestRunning(timer)) {
    setNeoPixelColor(0x0000FF); // Blue for circle outline
    tft.drawCircle(30, 120, 15, TFT_BLUE);
    delay(300);
    
    setNeoPixelColor(0xFFFF00); // Yellow for filled circle
    tft.fillCircle(80, 120, 15, TFT_YELLOW);
    delay(300);
  }
  
  // Triangles with animation
  if (isTestRunning(timer)) {
    setNeoPixelColor(0xFF00FF); // Magenta for triangle outline
    tft.drawTriangle(10, 140, 30, 155, 50, 140, TFT_MAGENTA);
    delay(300);
    
    setNeoPixelColor(0x00FFFF); // Cyan for filled triangle
    tft.fillTriangle(70, 140, 90, 155, 110, 140, TFT_CYAN);
    delay(300);
  }
  
  unsigned long drawTime = millis() - startTime;
  
  setNeoPixelColor(0x00FF00); // Green for success
  Serial.printf("Geometric drawing completed in %lu ms\n", drawTime);
  
  // Wait for remaining time
  while (isTestRunning(timer)) {
    delay(100);
  }
}

void runTextRenderingTest() {
  Serial.println(F("\n[7/12] TEXT RENDERING TEST"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0x4169E1); // Royal blue for text
  
  TestTiming timer = createTestTimer(4000); // 4 seconds guaranteed
  
  tft.fillScreen(TFT_BLACK);
  
  unsigned long startTime = millis();
  
  // Different text sizes with NeoPixel feedback
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextSize(1);
  tft.setCursor(5, 5);
  tft.println(F("Size 1: Hello World!"));
  setNeoPixelColor(0xFFFFFF); // White for text
  delay(400);
  
  if (isTestRunning(timer)) {
    tft.setTextSize(2);
    tft.setCursor(5, 25);
    tft.println(F("Size 2"));
    setNeoPixelColor(0xFFFF00); // Yellow for larger text
    delay(400);
  }
  
  if (isTestRunning(timer)) {
    tft.setTextSize(1);
    tft.setCursor(5, 50);
    tft.println(F("Text Performance Test"));
    delay(400);
  }
  
  // Colored text with NeoPixel sync
  constexpr const char* testTexts[]  = {"RED",    "GREEN",   "BLUE",   "YELLOW",   "PINK"  };
  constexpr uint16_t textColors[]    = {TFT_RED,  TFT_GREEN, TFT_BLUE, TFT_YELLOW, TFT_PINK};
  constexpr uint32_t neoTextColors[] = {0xFF0000, 0x00FF00,  0x0000FF, 0xFFFF00,   0xFF1493};
  
  for(int i = 0; i < 5 && isTestRunning(timer); i++) {
    tft.setTextColor(textColors[i], TFT_BLACK);
    tft.setCursor(5, 70 + i*15);
    tft.println(testTexts[i]);
    setNeoPixelColor(neoTextColors[i]);
    delay(300);
  }
  
  // Performance text rendering
  if (isTestRunning(timer)) {
    setNeoPixelColor(0x8A2BE2); // Blue violet for performance test
    for(int i = 0; i < 10 && isTestRunning(timer); i++) {
      tft.setTextColor(TFT_WHITE, TFT_BLACK);
      tft.setCursor(70, 70 + i*8);
      tft.printf("Line %d", i);
      delay(100);
    }
  }
  
  unsigned long textTime = millis() - startTime;
  
  setNeoPixelColor(0x00FF00); // Green for success
  Serial.printf("Text rendering completed in %lu ms\n", textTime);
  
  // Wait for remaining time
  while (isTestRunning(timer)) {
    delay(100);
  }
}

void runAnimationTest() {
  Serial.println(F("\n[9/13] ANIMATION PERFORMANCE TEST"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0xFF6347); // Tomato for animation
  
  TestTiming timer = createTestTimer(6000); // 6 seconds guaranteed
  
  tft.fillScreen(TFT_BLACK);
  
  // Define clear layout zones to avoid conflicts
  // Zone 1: Header (0-15)
  // Zone 2: Status info (16-35) 
  // Zone 3: Animation area (36-120)
  // Zone 4: Progress/FPS info (121-160)
  
  // Header zone
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setCursor(5, 5);
  tft.println(F("Animation Test"));
  
  unsigned long animStart = millis();
  int frames = 0;
  
  while(isTestRunning(timer)) {
    // Clear only the animation zone (36-120) to avoid flicker
    tft.fillRect(0, 36, 128, 84, TFT_BLACK);
    
    // Bouncing ball animation with NeoPixel sync
    float t = (millis() - animStart) / 1000.0;
    int x   = 64 + 50 * sin(t * 2);
    int y   = 70 + 25 * sin(t * 3); // Centered in animation zone
    
    // Draw bouncing ball
    tft.fillCircle(x, y, 8, TFT_RED);
    
    // Sync NeoPixel to ball position
    uint8_t neoIntensity = (uint8_t)(128 + 127 * sin(t * 4));
    uint32_t ballColor   = (neoIntensity << 16) | (0x00 << 8) | 0x00;
    setNeoPixelColor(ballColor);
    
    // Rotating line in animation zone
    float angle = t   * 180;
    int x1      = 64  + 35 * cos(radians(angle));
    int y1      = 100 + 15 * sin(radians(angle)); // Lower in animation zone
    tft.drawLine(64, 100, x1, y1, TFT_GREEN);
    
    // Frame counter
    frames++;
    
    // FIXED: Status display in dedicated zone (121-160) - no overlap
    if (frames % 30 == 0) {
      float currentFPS = frames / ((millis() - animStart) / 1000.0);
      
      // Clear status zone
      tft.fillRect(5, 121, 118, 39, TFT_BLACK);
      
      // FPS display
      tft.setTextColor(TFT_YELLOW, TFT_BLACK);
      tft.setCursor(5, 125);
      tft.printf("FPS: %.1f", currentFPS);
      
      // Frame count
      tft.setCursor(5, 135);
      tft.printf("Frames: %d", frames);
      
      // Time remaining
      float timeRemaining = 6.0 - t;
      tft.setCursor(5, 145);
      tft.printf("Time: %.1fs", timeRemaining);
    }
    
    // FIXED: Progress bar in status zone (not overlapping animation)
    float progress = t / 6.0;
    if (progress > 1.0) progress = 1.0;
    int progressWidth = (int)(110 * progress);
    
    tft.drawRect(5, 155, 110,           4, TFT_WHITE);
    tft.fillRect(5, 155, progressWidth, 4, TFT_GREEN);
    
    delay(16); // ~60 FPS target
  }
  
  float avgFPS = frames / 6.0;
  setNeoPixelColor(0x00FF00); // Green for success
  
  // FIXED: Clear result display in status zone
  tft.fillRect(5, 121, 118, 39, TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setCursor(5, 125);
  tft.printf("ANIMATION COMPLETE");
  tft.setCursor(5, 135);
  tft.printf("Avg FPS: %.1f", avgFPS);
  tft.setCursor(5, 145);
  tft.printf("Total frames: %d", frames);
  
  Serial.printf("Animation test: %d frames in 6s (%.1f FPS)\n", frames, avgFPS);
  delay(2000); // Show results for 2 seconds
}

void runMemoryStressTest() {
  Serial.println(F("\n[9/12] MEMORY STRESS TEST"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0xFF4500); // Orange red for stress test
  
  TestTiming timer = createTestTimer(5000); // 5 seconds guaranteed
  
  size_t beforeTest = ESP.getFreeHeap();
  
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setCursor(5, 5);
  tft.println(F("Memory Stress Test"));
  
  // Fill screen multiple times with different patterns
  for(int pattern = 0; pattern < 5 && isTestRunning(timer); pattern++) {
    // NeoPixel indicates current pattern
    setNeoPixelColor(neoPixelColors[pattern * 2]);
    
    for(int y = 0; y < tft.height() && isTestRunning(timer); y += 4) {
      for(int x = 0; x < tft.width(); x += 4) {
        uint16_t color = testColors[(x + y + pattern) % 12];
        tft.fillRect(x, y, 4, 4, color);
      }
      
      // Update memory info every few lines
      if (y % 40 == 0) {
        tft.fillRect(5, 20 + pattern*10, 118, 8, TFT_BLACK);
        tft.setCursor(5, 20 + pattern*10);
        tft.printf("Pattrn %d - Free: %zu", pattern+1, ESP.getFreeHeap());
      }
      delay(5);
    }
    
    delay(200);
  }
  
  size_t afterTest = ESP.getFreeHeap();
  int memoryDiff = beforeTest - afterTest;
  
  setNeoPixelColor(memoryDiff < 1000 ? 0x00FF00 : 0xFFFF00); // Green if low usage, Yellow if higher
  
  Serial.printf("  Memory test completed\n");
  Serial.printf("  Before: %zu bytes, After: %zu bytes\n", beforeTest, afterTest);
  Serial.printf("  Memory usage: %d bytes\n", memoryDiff);
  
  // Wait for remaining time
  while (isTestRunning(timer)) {
    delay(100);
  }
}

void runPerformanceBenchmark() {
  Serial.println(F("\n[10/12] PERFORMANCE BENCHMARK"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0x9400D3); // Violet for performance
  
  TestTiming timer = createTestTimer(5000); // 5 seconds guaranteed
  
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setCursor(5, 5);
  tft.println(F("Performance Test"));
  
  // Pixel drawing benchmark
  setNeoPixelColor(0xFF0000); // Red for pixel test
  unsigned long startTime = millis();
  for(int i = 0; i < 1000 && isTestRunning(timer); i++) {
    int x = random(0, tft.width());
    int y = random(0, tft.height());
    uint16_t color = random(0x0000, 0xFFFF);
    tft.drawPixel(x, y, color);
    
    if (i % 100 == 0) {
      tft.fillRect(5, 20, 100, 8, TFT_BLACK);
      tft.setCursor(5, 20);
      tft.printf("Pixels: %d/1000", i);
    }
  }
  unsigned long pixelTime = millis() - startTime;
  
  // Rectangle drawing benchmark
  if (isTestRunning(timer)) {
    setNeoPixelColor(0x00FF00); // Green for rectangle test
    startTime = millis();
    for(int i = 0; i < 100 && isTestRunning(timer); i++) {
      int x = random(0, tft.width()-20);
      int y = random(0, tft.height()-20);
      int w = random(5, 20);
      int h = random(5, 20);
      uint16_t color = random(0x0000, 0xFFFF);
      tft.fillRect(x, y, w, h, color);
      
      if (i % 10 == 0) {
        tft.fillRect(5, 35, 100, 8, TFT_BLACK);
        tft.setCursor(5, 35);
        tft.printf("Rects: %d/100", i);
      }
    }
    unsigned long rectTime = millis() - startTime;
    
    // Circle drawing benchmark
    if (isTestRunning(timer)) {
      setNeoPixelColor(0x0000FF); // Blue for circle test
      startTime = millis();
      for(int i = 0; i < 50 && isTestRunning(timer); i++) {
        int x = random(10, tft.width()-10);
        int y = random(10, tft.height()-10);
        int r = random(3, 10);
        uint16_t color = random(0x0000, 0xFFFF);
        tft.fillCircle(x, y, r, color);
        
        if (i % 5 == 0) {
          tft.fillRect(5, 50, 100, 8, TFT_BLACK);
          tft.setCursor(5, 50);
          tft.printf("Circles: %d/50", i);
        }
      }
      unsigned long circleTime = millis() - startTime;
      
      // Display results
      tft.fillRect(5, 70, 118, 40, TFT_BLACK);
      tft.setCursor(5, 70);
      tft.setTextColor(TFT_GREEN, TFT_BLACK);
      tft.println("Benchmark Results:");
      tft.setCursor(5, 80);
      tft.printf("Pixels: %.1f/ms", 1000.0/pixelTime);
      tft.setCursor(5, 90);
      tft.printf("Rects: %.1f/ms", 100.0/rectTime);
      tft.setCursor(5, 100);
      tft.printf("Circles: %.1f/ms", 50.0/circleTime);
      
      Serial.printf("  Performance benchmarks:\n");
      Serial.printf("  1000 pixels: %lu ms (%.1f pixels/ms)\n",   pixelTime,  1000.0/pixelTime);
      Serial.printf("  100 rectangles: %lu ms (%.1f rects/ms)\n", rectTime,   100.0/rectTime);
      Serial.printf("  50 circles: %lu ms (%.1f circles/ms)\n",   circleTime, 50.0/circleTime);
    }
  }
  
  setNeoPixelColor(0x00FF00); // Green for success
  
  // Wait for remaining time
  while (isTestRunning(timer)) {
    delay(100);
  }
}

// Sprite implementation were throwing core dumps. Lost the old function in the refactoring processes. 
// A simpler drawing test, until the sprite problem is figured out.
void runSpriteTest() {
  Serial.println(F("\n[11/12] ADVANCED DRAWING PERFORMANCE TEST"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0x00FFFF); // Cyan for drawing test
  
  TestTiming timer = createTestTimer(4000); // 4 seconds guaranteed
  
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setCursor(5, 5);
  tft.println(F("Drawing Performance"));
  
  Serial.println(F("Running advanced drawing performance tests"));
  
  size_t freeHeap = ESP.getFreeHeap();
  Serial.printf("Avail mem: %zu bytes\n", freeHeap);
  
  // Test 1: Rectangle blitting performance
  if (isTestRunning(timer)) {
    setNeoPixelColor(0xFF0000); // Red for rectangle test
    
    unsigned long rectStart = millis();
    int rectangles = 0;
    
    tft.setCursor(5, 20);
    tft.println(F("Rectangle Blit Tst"));
    
    while (isTestRunning(timer) && (millis() - rectStart < 1000)) {
      int x = random(0, tft.width() - 16);
      int y = random(30, tft.height() - 16);
      int w = random(8, 17);
      int h = random(8, 17);
      uint16_t color = testColors[rectangles % 12];
      
      tft.fillRect(x, y, w, h, color);
      rectangles++;
      
      if (rectangles % 10 == 0) {
        tft.fillRect(5, 35, 100, 10, TFT_BLACK);
        tft.setCursor(5, 35);
        tft.printf("Rects: %d", rectangles);
      }
    }
    
    unsigned long rectTime = millis() - rectStart;
    Serial.printf("Rectangle performance: %d rects in %lu ms (%.1f rects/ms)\n", 
                  rectangles, rectTime, (float)rectangles / rectTime);
  }
  
  // Test 2: Line drawing performance
  if (isTestRunning(timer)) {
    setNeoPixelColor(0x00FF00); // Green for line test
    
    tft.fillRect(0, 50, tft.width(), 30, TFT_BLACK);
    tft.setCursor(5, 50);
    tft.println(F("Line Drawing Test"));
    
    unsigned long lineStart = millis();
    int lines = 0;
    
    while (isTestRunning(timer) && (millis() - lineStart < 1000)) {
      int x1 = random(0, tft.width());
      int y1 = random(65, tft.height());
      int x2 = random(0, tft.width());
      int y2 = random(65, tft.height());
      uint16_t color = testColors[lines % 12];
      
      tft.drawLine(x1, y1, x2, y2, color);
      lines++;
      
      if (lines % 10 == 0) {
        tft.fillRect(5, 80, 100, 10, TFT_BLACK);
        tft.setCursor(5, 80);
        tft.printf("Lines: %d", lines);
      }
    }
    
    unsigned long lineTime = millis() - lineStart;
    Serial.printf("Line performance: %d lines in %lu ms (%.1f lines/ms)\n", 
                  lines, lineTime, (float)lines / lineTime);
  }
  
  // Test 3: Pattern copying simulation
  if (isTestRunning(timer)) {
    setNeoPixelColor(0x0000FF); // Blue for pattern test
    
    tft.fillRect(0, 95, tft.width(), 25, TFT_BLACK);
    tft.setCursor(5, 95);
    tft.println(F("Pattern Copy Test"));
    
    // Create simple 16x16 pattern using direct draw
    const int patternSize = 16;
    
    for (int pos = 0; pos < 4 && isTestRunning(timer); pos++) {
      int destX = 10 + (pos % 2) * 50;
      int destY = 110 + (pos / 2) * 25;
      
      // "Copy" pattern by redrawing it
      unsigned long copyStart = millis();
      
      // Draw checkerboard pattern
      for (int py = 0; py < patternSize; py++) {
        for (int px = 0; px < patternSize; px++) {
          uint16_t color = ((px/4 + py/4) % 2) ? testColors[pos*2] : testColors[pos*2+1];
          tft.drawPixel(destX + px, destY + py, color);
        }
      }
      
      unsigned long copyTime = millis() - copyStart;
      
      setNeoPixelColor(neoPixelColors[pos * 2]);
      
      tft.fillRect(5, 140, 118, 20, TFT_BLACK);
      tft.setCursor(5, 140);
      tft.printf("Pattern %d: %lu ms", pos+1, copyTime);
      tft.setCursor(5, 150);
      tft.printf("(%d pixels copied)", patternSize * patternSize);
      
      Serial.printf("Pattern %d copy: %lu ms for %d pixels (%.2f pixels/ms)\n", 
                    pos+1, copyTime, patternSize*patternSize, 
                    (float)(patternSize*patternSize) / copyTime);
      
      delay(500);
    }
  }
  
  // Test 4: Memory usage simulation
  if (isTestRunning(timer)) {
    setNeoPixelColor(0xFFFF00); // Yellow for memory test
    
    tft.fillRect(5, 140, 118, 20, TFT_BLACK);
    tft.setCursor(5, 140);
    tft.println(F("Memory Usage Check"));
    
    size_t currentHeap = ESP.getFreeHeap();
    size_t usedMemory = freeHeap - currentHeap;
    
    tft.setCursor(5, 150);
    tft.printf("Used: %zu bytes", usedMemory);
    
    Serial.printf("Memory usage during test: %zu bytes\n", usedMemory);
    Serial.printf("Memory efficiency: %.1f%% free\n", (float)currentHeap / freeHeap * 100);
    
    delay(1000);
  }
  
  setNeoPixelColor(0x00FF00); // Green for success
  
  tft.fillRect(5, 140, 118, 20, TFT_BLACK);
  tft.setCursor(5, 140);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.println(F("Drawing Tests OK"));
  tft.setCursor(5, 150);
  tft.println(F("(Sprite avoided)"));
  
  Serial.println(F("βœ“ Advanced drawing performance test completed"));
  Serial.println(F("  Note: Sprite functionality skipped due to memory constraints"));
  
  // Wait for remaining time
  while (isTestRunning(timer)) {
    delay(100);
  }
}

void runFinalSystemCheck() {
  Serial.println(F("\nFINAL SYSTEM STATUS"));
  Serial.println(F("----------------------------------------"));
  
  setNeoPixelColorSmooth(0x32CD32); // Lime green for final check
  
  size_t currentFreeHeap = ESP.getFreeHeap();
  int memoryUsed = initialFreeHeap - currentFreeHeap;
  
  Serial.printf("System Status:\n");
  Serial.printf("  CPU Frequency: %d MHz\n", ESP.getCpuFreqMHz());
  Serial.printf("  Initial Free Heap: %zu bytes\n", initialFreeHeap);
  Serial.printf("  Current Free Heap: %zu bytes\n", currentFreeHeap);
  Serial.printf("  Memory Used by Tests: %d bytes\n", memoryUsed);
  Serial.printf("  Display Buffer Size: %zu bytes\n", videoBufferSize);
  Serial.printf("  Memory Efficiency: %.1f%%\n", 
                (float)currentFreeHeap / initialFreeHeap * 100);
  
  // Display final status on screen
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setTextSize(1);
  tft.setCursor(5, 5);
  tft.println(F("ALL TESTS PASSED!"));
  
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setCursor(5, 25);
  tft.printf("Free Heap: %zu", currentFreeHeap);
  tft.setCursor(5, 35);
  tft.printf("Used: %d bytes", memoryUsed);
  tft.setCursor(5, 45);
  tft.printf("Display: %dx%d", tft.width(), tft.height());
  tft.setCursor(5, 55);
  tft.printf("Efficiency: %.1f%%", (float)currentFreeHeap / initialFreeHeap * 100);
  
  // Success animation
  for (int i = 0; i < 3; i++) {
    setNeoPixelColor(0x00FF00); // Green
    delay(200);
    setNeoPixelColor(0x000000); // Off
    delay(200);
  }
  setNeoPixelColor(0x00FF00); // Final green
  
  Serial.println(F("All tests completed successfully!"));
}

// Helper functions for better starfield and other effects
void initializeStarfield() {
  for (int i = 0; i < STARFIELD_STARS; i++) {
    stars[i].x = random(-1000, 1000);
    stars[i].y = random(-1000, 1000);
    stars[i].z = random(1, 1000);
  }
}

void runEnhancedWaveDemo() {
  // Multi-layered sine wave pattern with NeoPixel sync
  static int wavePhase1 = 0;
  static int wavePhase2 = 0;
  static int wavePhase3 = 0;
  
  // Clear wave area
  tft.fillRect(0, 110, 128, 50, TFT_BLACK);
  
  // Layer 1: Primary wave
  for(int x = 0; x < 128; x += 1) {
    int y1 = 130 + 15 * sin(radians(x * 3 + wavePhase1));
    if(y1 >= 110 && y1 < 160) {
      uint16_t waveColor1 = testColors[(x/8 + wavePhase1/20) % 12];
      tft.drawPixel(x, y1, waveColor1);
    }
  }
  
  // Layer 2: Secondary wave
  for(int x = 0; x < 128; x += 2) {
    int y2 = 135 + 10 * sin(radians(x * 5 + wavePhase2));
    if(y2 >= 110 && y2 < 160) {
      uint16_t waveColor2 = testColors[(x/12 + wavePhase2/30) % 12];
      tft.drawPixel(x, y2, waveColor2);
    }
  }
  
  // Layer 3: Tertiary wave
  for(int x = 0; x < 128; x += 3) {
    int y3 = 140 + 8 * sin(radians(x * 7 + wavePhase3));
    if(y3 >= 110 && y3 < 160) {
      uint16_t waveColor3 = testColors[(x/16 + wavePhase3/40) % 12];
      tft.drawPixel(x, y3, waveColor3);
    }
  }
  
  // Rotating indicator with trail
  angle += 3.0;
  if(angle >= 360) angle = 0;
  
  int centerX = 110, centerY = 140;
  
  // Draw trail
  for (int i = 1; i <= 5; i++) {
    float trailAngle = angle - i * 15;
    int tx = centerX + (20 - i * 2) * cos(radians(trailAngle));
    int ty = centerY + (20 - i * 2) * sin(radians(trailAngle));
    uint8_t brightness = 255 - i * 40;
    uint16_t trailColor = tft.color565(brightness, 0, 0);
    tft.drawPixel(tx, ty, trailColor);
  }
  
  // Main indicator
  int x1 = centerX + 18 * cos(radians(angle));
  int y1 = centerY + 18 * sin(radians(angle));
  
  tft.fillCircle(centerX, centerY, 20, TFT_BLACK);
  tft.drawCircle(centerX, centerY, 19, TFT_WHITE);
  tft.drawLine(centerX, centerY, x1, y1, TFT_RED);
  tft.fillCircle(centerX, centerY, 2, TFT_WHITE);
  
  // NeoPixel synced to primary wave amplitude
  uint8_t waveIntensity = (uint8_t)(128 + 127 * sin(radians(wavePhase1 * 2)));
  uint32_t waveNeoColor = (waveIntensity << 16) | (0x00 << 8) | (255 - waveIntensity);
  setNeoPixelColor(waveNeoColor);
  
  wavePhase1 += 4;
  wavePhase2 += 6;
  wavePhase3 += 8;
}

void runEnhancedSpinningShapesDemo() {
  static float shapeAngle1 = 0;
  static float shapeAngle2 = 0;
  static float pulseFactor = 0;
  
  // Clear animation area
  tft.fillRect(0, 110, 128, 50, TFT_BLACK);
  
  // Spinning triangle with pulsing
  int cx1 = 32, cy1 = 135;
  float pulseSize = 18 + 8 * sin(radians(pulseFactor));
  
  for (int i = 0; i < 3; i++) {
    float a = shapeAngle1 + i * 120;
    int x   = cx1 + pulseSize * cos(radians(a));
    int y   = cy1 + pulseSize * sin(radians(a));
    tft.fillCircle(x, y, 4, testColors[i * 2]);
    
    // Connect lines
    if (i < 2) {
      float nextA = shapeAngle1 + (i + 1) * 120;
      int nextX   = cx1 + pulseSize * cos(radians(nextA));
      int nextY   = cy1 + pulseSize * sin(radians(nextA));
      tft.drawLine(x, y, nextX, nextY, testColors[i * 2]);
    } else {
      // Close the triangle
      float firstA = shapeAngle1;
      int firstX   = cx1 + pulseSize * cos(radians(firstA));
      int firstY   = cy1 + pulseSize * sin(radians(firstA));
      tft.drawLine(x, y, firstX, firstY, testColors[i * 2]);
    }
  }
  
  // Spinning square with rotation trail
  int cx2 = 96, cy2 = 135;
  
  // Draw rotation trail
  for (int trail = 1; trail <= 3; trail++) {
    float trailAngle = shapeAngle2 - trail * 20;
    for (int i = 0; i < 4; i++) {
      float a             = trailAngle + i * 90;
      int x               = cx2 + (18 - trail * 3) * cos(radians(a));
      int y               = cy2 + (18 - trail * 3) * sin(radians(a));
      uint8_t alpha       = 255 - trail * 60;
      uint16_t trailColor = tft.color565(alpha/3, alpha/3, alpha);
      tft.drawPixel(x, y, trailColor);
    }
  }
  
  // Main spinning square
  for (int i = 0; i < 4; i++) {
    float a = shapeAngle2 + i * 90;
    int x   = cx2 + 15 * cos(radians(a));
    int y   = cy2 + 15 * sin(radians(a));
    tft.fillRect(x-2, y-2, 4, 4, testColors[i+4]);
    
    // Connect squares with lines
    int nextI   = (i + 1) % 4;
    float nextA = shapeAngle2 + nextI * 90;
    int nextX   = cx2 + 15 * cos(radians(nextA));
    int nextY   = cy2 + 15 * sin(radians(nextA));
    tft.drawLine(x, y, nextX, nextY, testColors[i+4]);
  }
  
  // NeoPixel color based on shape rotation
  uint8_t rotR           = (uint8_t)(128 + 127 * sin(radians(shapeAngle1)));
  uint8_t rotG           = (uint8_t)(128 + 127 * sin(radians(shapeAngle2)));
  uint8_t rotB           = (uint8_t)(128 + 127 * sin(radians(pulseFactor)));
  uint32_t rotationColor = (rotR << 16) | (rotG << 8) | rotB;
  setNeoPixelColor(rotationColor);
  
  shapeAngle1 += 4;
  shapeAngle2 += 6;
  pulseFactor += 8;
}

void runEnhancedBouncingBallsDemo() {
  static float ball1X   = 20, ball1Y = 125, ball1VX = 2.2, ball1VY = 1.8;
  static float ball2X   = 60, ball2Y = 145, ball2VX = -1.8, ball2VY = -2.2;
  static float ball3X   = 100, ball3Y = 135, ball3VX = -2.5, ball3VY = 1.5;
  static int trailIndex = 0;
  static struct BallTrail {
    int x, y;
    uint8_t life;
  } trails1[10], trails2[10], trails3[10];
  
  // Clear animation area
  tft.fillRect(0, 110, 128, 50, TFT_BLACK);
  
  // Update ball 1 with enhanced physics
  ball1X += ball1VX;
  ball1Y += ball1VY;
  if (ball1X <= 5 || ball1X >= 123) {
    ball1VX = -ball1VX * 0.98; // Energy loss
    ball1X  = constrain(ball1X, 5, 123);
  }
  if (ball1Y <= 115 || ball1Y >= 155) {
    ball1VY = -ball1VY * 0.98; // Energy loss
    ball1Y  = constrain(ball1Y, 115, 155);
  }
  
  // Update ball 2
  ball2X += ball2VX;
  ball2Y += ball2VY;
  if (ball2X <= 5 || ball2X >= 123) {
    ball2VX = -ball2VX * 0.98;
    ball2X  = constrain(ball2X, 5, 123);
  }
  if (ball2Y <= 115 || ball2Y >= 155) {
    ball2VY = -ball2VY * 0.98;
    ball2Y  = constrain(ball2Y, 115, 155);
  }
  
  // Update ball 3
  ball3X += ball3VX;
  ball3Y += ball3VY;
  if (ball3X <= 5 || ball3X >= 123) {
    ball3VX = -ball3VX * 0.98;
    ball3X  = constrain(ball3X, 5, 123);
  }
  if (ball3Y <= 115 || ball3Y >= 155) {
    ball3VY = -ball3VY * 0.98;
    ball3Y  = constrain(ball3Y, 115, 155);
  }
  
  // Add to trails
  trails1[trailIndex] = {(int)ball1X, (int)ball1Y, 50};
  trails2[trailIndex] = {(int)ball2X, (int)ball2Y, 50};
  trails3[trailIndex] = {(int)ball3X, (int)ball3Y, 50};
  trailIndex = (trailIndex + 1) % 10;
  
  // Draw trails
  for (int i = 0; i < 10; i++) {
    if (trails1[i].life > 0) {
      uint8_t alpha = trails1[i].life * 5;
      tft.drawPixel(trails1[i].x, trails1[i].y, tft.color565(alpha, 0, 0));
      trails1[i].life--;
    }
    if (trails2[i].life > 0) {
      uint8_t alpha = trails2[i].life * 5;
      tft.drawPixel(trails2[i].x, trails2[i].y, tft.color565(0, 0, alpha));
      trails2[i].life--;
    }
    if (trails3[i].life > 0) {
      uint8_t alpha = trails3[i].life * 5;
      tft.drawPixel(trails3[i].x, trails3[i].y, tft.color565(0, alpha, 0));
      trails3[i].life--;
    }
  }
  
  // Draw balls with glow effect
  for (int r = 8; r >= 4; r--) {
    uint8_t alpha = (8 - r) * 60;
    tft.drawCircle((int)ball1X, (int)ball1Y, r, tft.color565(255,   alpha, alpha));
    tft.drawCircle((int)ball2X, (int)ball2Y, r, tft.color565(alpha, alpha, 255  ));
    tft.drawCircle((int)ball3X, (int)ball3Y, r, tft.color565(alpha, 255,   alpha));
  }
  
  // Solid center
  tft.fillCircle((int)ball1X, (int)ball1Y, 3, TFT_RED  );
  tft.fillCircle((int)ball2X, (int)ball2Y, 3, TFT_BLUE );
  tft.fillCircle((int)ball3X, (int)ball3Y, 3, TFT_GREEN);
  
  // NeoPixel reflects average ball energy
  float totalEnergy    = (abs(ball1VX) + abs(ball1VY) + abs(ball2VX) + abs(ball2VY) + abs(ball3VX) + abs(ball3VY)) / 6.0;
  uint8_t energyLevel  = (uint8_t)(totalEnergy * 40);
  uint32_t energyColor = (energyLevel << 16) | (energyLevel << 8) | (255 - energyLevel);
  setNeoPixelColor(energyColor);
}

void runEnhancedFireworksDemo() {
  static struct Particle {
    float x, y, vx, vy, ax, ay;
    uint16_t color;
    uint8_t life, maxLife;
    uint8_t type; // 0=normal, 1=sparkler, 2=tracer
  } particles[30];
  static bool initialized            = false;
  static unsigned long lastExplosion = 0;
  static int explosionCount          = 0;
  
  if (!initialized || millis() - lastExplosion > 3000) {
    // Create new explosion with different types
    int centerX = random(25, 103);
    int centerY = random(120, 145);
    explosionCount++;
    
    for (int i = 0; i < 30; i++) {
      particles[i].x = centerX;
      particles[i].y = centerY;
      
      // Vary explosion patterns
      float angle = random(0, 360);
      float speed = random(10, 40) / 10.0;
      
      if (explosionCount % 3 == 0) {
        // Circular explosion
        particles[i].vx = speed * cos(radians(angle));
        particles[i].vy = speed * sin(radians(angle));
      } else if (explosionCount % 3 == 1) {
        // Upward fountain
        particles[i].vx = random(-20, 20) / 10.0;
        particles[i].vy = -speed;
      } else {
        // Directional burst
        float burstAngle = random(-45, 45);
        particles[i].vx  = speed * cos(radians(burstAngle));
        particles[i].vy  = speed * sin(radians(burstAngle)) - 1;
      }
      
      particles[i].ax    = 0;
      particles[i].ay    = 0.15; // gravity
      particles[i].color = testColors[random(0, 12)];
      particles[i].life  = particles[i].maxLife = random(30, 80);
      particles[i].type  = random(0, 3);
    }
    
    initialized   = true;
    lastExplosion = millis();
    
    // Explosion flash on NeoPixel
    setNeoPixelColor(0xFFFFFF);
    delay(50);
  }
  
  // Clear animation area
  tft.fillRect(0, 110, 128, 50, TFT_BLACK);
  
  int activeParticles = 0;
  
  // Update and draw particles
  for (int i = 0; i < 30; i++) {
    if (particles[i].life > 0) {
      activeParticles++;
      
      // Physics update
      particles[i].vx += particles[i].ax;
      particles[i].vy += particles[i].ay;
      particles[i].x  += particles[i].vx;
      particles[i].y  += particles[i].vy;
      particles[i].life--;
      
      // Add air resistance for sparklers
      if (particles[i].type == 1) {
        particles[i].vx *= 0.99;
        particles[i].vy *= 0.99;
      }
      
      if (particles[i].x >= 0 && particles[i].x < 128 && 
          particles[i].y >= 110 && particles[i].y < 160) {
        
        // Different rendering based on type
        if (particles[i].type == 0) {
          // Normal particle
          uint8_t alpha = (particles[i].life * 255) / particles[i].maxLife;
          uint16_t fadedColor = tft.color565(
            ((particles[i].color >> 11) * alpha)         >> 8,
            (((particles[i].color >> 5) & 0x3F) * alpha) >> 8,
            ((particles[i].color & 0x1F) * alpha)        >> 8
          );
          tft.drawPixel((int)particles[i].x, (int)particles[i].y, fadedColor);
        } else if (particles[i].type == 1) {
          // Sparkler - draw with trail
          tft.fillCircle((int)particles[i].x, (int)particles[i].y, 1, particles[i].color);
          
          // Trail effect
          int prevX = (int)(particles[i].x - particles[i].vx);
          int prevY = (int)(particles[i].y - particles[i].vy);
          if (prevX >= 0 && prevX < 128 && prevY >= 110 && prevY < 160) {
            tft.drawLine((int)particles[i].x, (int)particles[i].y, prevX, prevY, particles[i].color);
          }
        } else {
          // Tracer - bright line
          int prevX = (int)(particles[i].x - particles[i].vx * 2);
          int prevY = (int)(particles[i].y - particles[i].vy * 2);
          if (prevX >= 0 && prevX < 128 && prevY >= 110 && prevY < 160) {
            tft.drawLine((int)particles[i].x, (int)particles[i].y, prevX, prevY, TFT_WHITE);
          }
          tft.drawPixel((int)particles[i].x, (int)particles[i].y, particles[i].color);
        }
      }
    }
  }
  
  // NeoPixel fades from explosion brightness
  uint8_t fadeLevel = 255 * activeParticles / 30;
  uint32_t fadeColor = (fadeLevel << 16) | (fadeLevel << 8) | (fadeLevel >> 1);
  setNeoPixelColor(fadeColor);
}

void loop() {
  // Single demo cycle > record boot index > restart loop
  static bool testsCompleted          = false;
  static unsigned long demoStartTime  = 0;
  static uint8_t demoMode             = 0;
  static unsigned long lastModeChange = 0;
  static bool demoInitialized         = false;
  static bool finalCheckDisplayed     = false;
  
  // Test completion flags sanity check (i.e. if the test is not complete, it is still initial phase).
  if (!testsCompleted) {
    testsCompleted      = true;
    demoStartTime       = millis();
    demoInitialized     = false;
    finalCheckDisplayed = false;
    return;
  }
  
  // Initialize demo cycle
  if (!demoInitialized) {
    Serial.println(F("\n============================================================"));
    Serial.println(F("STARTING DEMO CYCLE (40 seconds total)"));
    Serial.println(F("============================================================"));
    
    demoInitialized = true;
    lastModeChange  = millis();
    demoMode        = 0;
    frameCount      = 0;
    lastFPSUpdate   = millis();
    
    // Clear screen for demo
    tft.fillScreen(TFT_BLACK);
    
    // Demo start NeoPixel sequence
    for (int i = 0; i < 3; i++) {
      setNeoPixelColor(0x00FF00); // Green
      delay(100);
      setNeoPixelColor(0x000000); // Off
      delay(100);
    }
  }
  
  unsigned long currentTime = millis();
  unsigned long demoElapsed = currentTime - demoStartTime;
  
  // FIXED: Show final system check in the last 5 seconds (35-40s) before reboot sequence
  if (demoElapsed >= 35000 && !finalCheckDisplayed) {
    Serial.println(F("\n============================================================"));
    Serial.println(F("PREPARING FOR REBOOT - FINAL STATUS"));
    Serial.println(F("============================================================"));
    
    // Clear screen and show final system status
    tft.fillScreen(TFT_BLACK);
    
    size_t currentFreeHeap = ESP.getFreeHeap();
    int memoryUsed = initialFreeHeap - currentFreeHeap;
    
    Serial.printf("Pre-reboot System Status:\n");
    Serial.printf("  CPU Frequency: %d MHz\n", ESP.getCpuFreqMHz());
    Serial.printf("  Current Free Heap: %zu bytes\n", currentFreeHeap);
    Serial.printf("  Memory Used: %d bytes\n", memoryUsed);
    Serial.printf("  Demo runtime: %.1fs\n", demoElapsed/1000.0);
    
    // Display pre-reboot status
    tft.setTextColor(TFT_CYAN, TFT_BLACK);
    tft.setTextSize(1);
    tft.setCursor(5, 5);
    tft.println(F("PRE-REBOOT STATUS"));
    
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.setCursor(5, 25);
    tft.printf("Demo: %.1fs complete", demoElapsed/1000.0);
    tft.setCursor(5, 35);
    tft.printf("Free Heap: %zu KB", currentFreeHeap/1024);
    tft.setCursor(5, 45);
    tft.printf("Memory Used: %d KB", memoryUsed/1024);
    
    tft.setTextColor(TFT_GREEN, TFT_BLACK);
    tft.setCursor(5, 65);
    tft.println(F("βœ“ All systems nominal"));
    tft.setCursor(5, 75);
    tft.println(F("βœ“ Ready for reboot"));
    
    setNeoPixelColor(0x00FF00); // Green for ready
    delay(3000);
    
    finalCheckDisplayed = true;
    Serial.println(F("Pre-reboot status check completed"));
  }
  
  // FIXED: Check if demo cycle is complete (40 seconds: 4 modes Γ— 10 seconds each)
  if (demoElapsed >= 40000) {
    Serial.println(F("\n============================================================"));
    Serial.println(F("DEMO CYCLE COMPLETE - RECORDING SUCCESS AND REBOOTING"));
    Serial.println(F("============================================================"));
    
    // Record successful cycle completion
    recordSuccessfulCycle();
    
    // Display reboot message on screen (this comes AFTER final system check)
    tft.fillScreen(TFT_BLACK);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.setTextSize(2);
    tft.setCursor(10, 20);
    tft.println(F("CYCLE"));
    tft.setCursor(5, 40);
    tft.println(F("COMPLETE"));
    
    tft.setTextSize(1);
    tft.setTextColor(TFT_YELLOW, TFT_BLACK);
    tft.setCursor(10, 70);
    tft.println(F("REBOOTING..."));
    
    // Display boot statistics if available
    if (bootCounterAvailable) {
      tft.setTextColor(TFT_GREEN, TFT_BLACK);
      tft.setCursor(5, 90);
      tft.printf("Total Boots: %lu", bootCount);
      tft.setCursor(5, 100);
      tft.printf("Successful: %lu", successfulCycles);
      tft.setCursor(5, 110);
      tft.printf("Success: %.1f%%", (float)successfulCycles * 100 / bootCount);
      
      Serial.printf("Final Statistics:\n");
      Serial.printf("  Total boots: %lu\n", bootCount);
      Serial.printf("  Successful cycles: %lu\n", successfulCycles);
      Serial.printf("  Success rate: %.1f%%\n", (float)successfulCycles * 100 / bootCount);
    }
    
    // Reboot countdown with NeoPixel
    for (int countdown = 5; countdown > 0; countdown--) {
      tft.fillRect(5, 125, 100, 15, TFT_BLACK);
      tft.setTextColor(TFT_RED, TFT_BLACK);
      tft.setCursor(5, 125);
      tft.printf("Reboot in %d...", countdown);
      
      // Flash NeoPixel during countdown
      setNeoPixelColor(0xFF0000); // Red
      delay(200);
      setNeoPixelColor(0x000000); // Off
      delay(300);
      setNeoPixelColor(0xFF0000); // Red
      delay(200);
      setNeoPixelColor(0x000000); // Off
      delay(300);
    }
    
    // Final reboot indication
    tft.fillRect(5, 125, 100, 15, TFT_BLACK);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.setCursor(5, 125);
    tft.println(F("REBOOTING NOW!"));
    
    // Final NeoPixel sequence
    for (int i = 0; i < 10; i++) {
      setNeoPixelColor(0xFFFFFF); // White flash
      delay(50);
      setNeoPixelColor(0x000000); // Off
      delay(50);
    }
    
    // Close preferences properly
    if (bootCounterAvailable) {
      preferences.end();
    }
    
    Serial.println(F("Initiating ESP32 restart..."));
    delay(500);
    
    // Restart the ESP32
    ESP.restart();
  }
  
  // Check to ensure demo triggering matches with final test phase
  if (demoElapsed < 35000) {
    // Change demo mode every 10 seconds
    if (currentTime - lastModeChange > 8750) { // 35s / 4 modes = 8.75s per mode
      demoMode       = (demoMode + 1) % 4;
      lastModeChange = currentTime;
      tft.fillScreen(TFT_BLACK);
      
      Serial.printf("Demo mode %d/4 starting (%.1fs elapsed)\n", 
                    demoMode + 1, demoElapsed / 1000.0);
      
      // Mode change NeoPixel indication
      for (int i = 0; i < 3; i++) {
        setNeoPixelColor(neoPixelColors[demoMode * 2]);
        delay(100);
        setNeoPixelColor(0x000000);
        delay(100);
      }
    }
    
    // Update FPS calculation every second
    if(currentTime - lastFPSUpdate >= 1000) {
      currentFPS = frameCount * 1000.0 / (currentTime - lastFPSUpdate);
      frameCount = 0;
      lastFPSUpdate = currentTime;
      
      // Display system info in TOP area (y=20-50)
      tft.fillRect(5, 20, 118, 30, TFT_BLACK);
      tft.setTextColor(TFT_YELLOW, TFT_BLACK);
      tft.setCursor(5, 25);
      tft.printf("FPS: %.1f", currentFPS);
      tft.setCursor(5, 35);
      tft.printf("Heap: %zu KB", ESP.getFreeHeap()/1024);
      
      // Mode and progress info 
      unsigned long modeElapsed   = currentTime - lastModeChange;
      unsigned long modeRemaining = (8750 - modeElapsed) / 1000;
      tft.setCursor(5, 45);
      tft.printf("Time: %lus (%.1fs total)", modeRemaining, demoElapsed / 1000.0);
      
      // Progress bar in upper area
      int progressWidth = (int)(110 * demoElapsed / 35000); // 35s total demo time
      tft.drawRect(5, 55, 110, 4, TFT_WHITE);
      tft.fillRect(5, 55, progressWidth, 4, TFT_GREEN);
      
      // NeoPixel status indicator
      if (currentFPS > 50) {
        setNeoPixelColor(0x00FF00); // Green - excellent
      } else if (currentFPS > 30) {
        setNeoPixelColor(0xFFFF00); // Yellow - good
      } else if (currentFPS > 15) {
        setNeoPixelColor(0xFF4500); // Orange - moderate
      } else {
        setNeoPixelColor(0xFF0000); // Red - low
      }
    }
    
    // Demo mode display in header area (y=5-15)
    tft.fillRect(5, 5, 118, 12, TFT_BLACK);
    tft.setTextColor(TFT_CYAN, TFT_BLACK);
    tft.setCursor(5, 5);
    
    switch (demoMode) {
      case 0:
        tft.printf("Wave Patterns [%d/4]", demoMode + 1);
        runEnhancedWaveDemo();
        break;
      case 1:
        tft.printf("Spinning Shapes [%d/4]", demoMode + 1);
        runEnhancedSpinningShapesDemo();
        break;
      case 2:
        tft.printf("Bouncing Balls [%d/4]", demoMode + 1);
        runEnhancedBouncingBallsDemo();
        break;
      case 3:
        tft.printf("Fireworks [%d/4]", demoMode + 1);
        runEnhancedFireworksDemo();
        break;
    }
    
    frameCount++;
  }
  
  delay(16); // ~60 FPS target (Practical achievable: ~40 FPS)
}

PS: This is a single core sketch, Dual core Sanity Check Firmware is under development.

1 Like

ESP32 S3 Sanity Check Part 4: Dual Screen Dual Core Double Buffer

Now that/if you are setup with the ESP32 S3 N8R8 and ST7735 128x160 SPI display, you might be wondering what more can be done.

How about, using two 128X160 ST7735 SPI displays?
And to top it off, what if each display is dedicated to each core?

Well, as they say, two is always better than one.

Here is a test suite to run on this hardware setup, with ASCII schematic and further brief explanations of the principles used. Using Adafruit GFX and ST3775 Libraries.

/**
 * @file ESP32-S3_DualScreen_Demo.ino
 * @brief ESP32-S3 Dual Core Dual Screen Graphics Demo using Adafruit GFX Library
 * @version 2.0
 * @date 2025-08-22
 * 
 * @author Sir Ronnie from Core 1D Automation Labs
 * @license MIT License
 * 
 * @section description Description
 * This project demonstrates advanced dual-core programming on ESP32-S3 with two ST7735 TFT displays.
 * Each CPU core controls one screen independently, showcasing true parallel processing capabilities.
 * Features double buffering, thread-safe SPI communication, and smooth 60 FPS animations.
 * 
 * @section hardware Hardware Requirements
 * - ESP32-S3 DevKit C1 (or compatible with PSRAM)
 * - 2x ST7735 128x160 TFT displays (1.8" recommended)
 * - Breadboard and jumper wires
 * - 5V/3.3V power supply (USB sufficient for testing)
 * 
 * @section wiring Wiring Diagram
 * @code
 *                ESP32-S3 N8 R8 DevKit C1
 *                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 *                   β”‚                     β”‚
 *    ╔─────────────── GPIO 11 (MOSI)  ────┼───────────────────────────────────╗
 *    ║─────────────── GPIO 12 (SCLK)  ────┼───────────────────────────────────║ 
 *    β•‘              β”‚                     β”‚                                   β•‘
 *    β•‘              β”‚ GPIO 10 (CS1)   ────┼─────┐                             β•‘
 *    β•‘              β”‚ GPIO 4  (DC1)   ────┼───┐ β”‚                             β•‘
 *    β•‘              β”‚ GPIO 5  (RST1)  ────┼─┐ β”‚ β”‚                             β•‘
 *    β•‘              β”‚                     β”‚ β”‚ β”‚ β”‚                             β•‘
 *    β•‘              β”‚ GPIO 15 (CS0)   ────┼─┼─┼─┼─────┐                       β•‘
 *    β•‘              β”‚ GPIO 7  (DC0)   ────┼─┼─┼─┼───┐ β”‚                       β•‘
 *    β•‘              β”‚ GPIO 6  (RST0)  ────┼─┼─┼─┼─┐ β”‚ β”‚                       β•‘
 *    β•‘              β”‚                     β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚                       β•‘
 *    β•‘              β”‚ 3.3V ───────────────┼─┼─┼─┼─┼─┼─┼─── [VCC (Common 3v3)] β•‘
 *    β•‘              β”‚ GND ────────────────┼─┼─┼─┼─┼─┼─┼─── [GND (Common)]     β•‘
 *    β•‘              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚                       β•‘
 *    β•‘                                 RST1<β”˜ β”‚ β”‚ β”‚ β”‚ β”‚                       β•‘
 *    β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   ST7735 #0         DC1<β”˜ β”‚ β”‚ β”‚ β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β•‘                ST7735 #1
 *    ║───── SCK     β”‚   (LEFT SCREEN)       CS1<β”˜ β”‚ β”‚ β”‚    β”‚ SCK        β”œβ”€β”€β”€β”€β”€β•‘                (RIGHT SCREEN) 
 *    β•šβ”€β”€β”€β”€β”€ MOSI    β”‚   Core 0 Control            β”‚ β”‚ β”‚    β”‚ MOSI       β”œβ”€β”€β”€β”€β”€β•    β”Œ>GPIO 10     Core 1 Control
 *         β”‚ CS   β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”Όβ”€β”˜    β”‚ CS      β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œ>GPIO 4
 *         β”‚ DC   β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”˜      β”‚ DC      β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œ>GPIO 5
 *         β”‚ RST  β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚ RST     β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 *         β”‚ VCC  ───┼─────     [Common 3v3]     ───────────┼ VCC        β”‚
 *         β”‚ GND  ───┼─────     [Common GND]     ───────────┼ GND        β”‚
 *         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 * 
 *         Core 0 (LEFT)                                    Core 1 (RIGHT)
 *         β”œβ”€ Basic Colors                                  β”œβ”€ Color Cycle  
 *         β”œβ”€ Bouncing Ball                                 β”œβ”€ Moving Lines
 *         β”œβ”€ Rainbow Bars                                  β”œβ”€ Color Noise
 *         β”œβ”€ Geometric Shapes                              β”œβ”€ Pixel Rain
 *         └─ Text Test                                     └─ Pattern Test
 * @endcode
 * 
 * @section features Key Features
 * True dual-core processing (Core 0 + Core 1)
 * Thread-safe SPI communication with mutex
 * Double buffering for flicker-free graphics  
 * ~60 FPS butter animations on both screens
 * PSRAM utilization for large frame buffers
 * Memory-efficient canvas rendering
 * Automatic test cycling every 60 seconds
 * Real-time performance monitoring
 * 
 * @section libraries Required Libraries
 * Install these libraries through Arduino IDE Library Manager:
 * - Adafruit GFX Library (>=1.11.0)
 * - Adafruit ST7735 and ST7789 Library (>=1.9.0)
 * 
 * @section performance Performance Expectations
 * - Frame Rate: ~60 FPS per screen
 * - Memory Usage: ~65KB RAM + 82KB PSRAM
 * - SPI Speed: 40MHz (optimized for ST7735)
 * - CPU Usage: ~30% per core during complex animations
 * 
 * @section license License Information
 * MIT License
 * 
 * Copyright (c) 2025 Sir Ronnie, Core 1D Automation Labs
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * @section adafruit_license Adafruit License
 * This project uses Adafruit GFX Library and ST7735 Library:
 * 
 * Copyright (c) 2012 Adafruit Industries. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED.
 */

// ============================================================================
// LIBRARY INCLUDES - Essential Graphics Libraries
// ============================================================================

#include <Adafruit_GFX.h>    // Core graphics library - provides drawing functions
#include <Adafruit_ST7735.h> // ST7735 TFT display driver - handles display communication  
#include <SPI.h>             // SPI communication protocol - connects to displays

// ============================================================================
// HARDWARE PIN CONFIGURATION - ESP32-S3 DevKit C1
// ============================================================================
// Using constexpr for compile-time constants
// Use constexpr as a replacement for #define whenever possible, to have compile time evaluation

// Screen 1 (RIGHT) - Controlled by Core 1
constexpr uint8_t SCREEN1_CS    = 10;   // Chip Select - tells display when data is for it
constexpr uint8_t SCREEN1_RST   = 5;    // Reset pin - restarts the display
constexpr uint8_t SCREEN1_DC    = 4;    // Data/Command - tells display if data is command or pixel data

// Screen 0 (LEFT) - Controlled by Core 0  
constexpr uint8_t SCREEN0_CS    = 15;   // Chip Select for left display
constexpr uint8_t SCREEN0_RST   = 6;    // Reset pin for left display
constexpr uint8_t SCREEN0_DC    = 7;    // Data/Command for left display

// Shared SPI pins - both displays use same data lines (saved pins + hard SPI support)
constexpr uint8_t SHARED_MOSI   = 11;   // Master Out Slave In - sends data to displays
constexpr uint8_t SHARED_SCLK   = 12;   // Serial Clock - synchronizes data transmission

// ============================================================================
// DISPLAY CONFIGURATION CONSTANTS
// ============================================================================

constexpr uint16_t       SCREEN_WIDTH = 128;          // ST7735 display width in pixels
constexpr uint16_t      SCREEN_HEIGHT = 160;         // ST7735 display height in pixels
constexpr uint16_t   TEXT_AREA_HEIGHT = 35;         // Reserved space at top for test names
constexpr uint16_t        DRAW_AREA_Y = TEXT_AREA_HEIGHT;      // Where drawing area starts
constexpr uint16_t   DRAW_AREA_HEIGHT = SCREEN_HEIGHT - TEXT_AREA_HEIGHT; // Available drawing height
constexpr unsigned long TEST_DURATION = 60000;              // How long each test runs (60 seconds)

// ============================================================================
// DISPLAY AND GRAPHICS OBJECTS
// ============================================================================

// Create display objects - these handle low-level display communication
Adafruit_ST7735 screen0(SCREEN0_CS, SCREEN0_DC, SCREEN0_RST);  // Left screen for Core 0
Adafruit_ST7735 screen1(SCREEN1_CS, SCREEN1_DC, SCREEN1_RST);  // Right screen for Core 1

// Double buffer canvases - draw in memory first, then transfer to screen (prevents flicker)
// GFXcanvas16 = 16-bit color depth (65,536 colors) - good balance of quality and memory
GFXcanvas16 canvas0(SCREEN_WIDTH, SCREEN_HEIGHT);  // Memory buffer for left screen
GFXcanvas16 canvas1(SCREEN_WIDTH, SCREEN_HEIGHT);  // Memory buffer for right screen

// ============================================================================
// MULTI-THREADING SYNCHRONIZATION
// ============================================================================

// Task handles - references to our two CPU core tasks
TaskHandle_t Core0Task;  // Handle for Core 0 task (left screen)
TaskHandle_t Core1Task;  // Handle for Core 1 task (right screen)

// Mutex (mutual exclusion) - prevents both cores from using SPI at same time
// Think of it like a bathroom key - only one person can use it at a time!
SemaphoreHandle_t spiMutex;

// ============================================================================
// TEST STATE MANAGEMENT
// ============================================================================

/**
 * @brief Structure to track the state of each core's test execution
 * 
 * This keeps track of what test is running, timing, and update flags
 * for each CPU core independently.
 */
struct TestState {
  uint8_t       currentTest     = 0;        // Which test is currently running (0-4)
  unsigned long testStartTime   = 0; // When current test started (for auto-switching)
  uint32_t      frameCount      = 0;        // Count frames for performance monitoring  
  bool          needsUpdate     = true;        // Flag: does screen need refreshing?
  bool          forceFullRedraw = true;    // Flag: force complete screen redraw?
};

// Create separate state tracking for each core
TestState core0State;  // State for Core 0 (left screen)
TestState core1State;  // State for Core 1 (right screen)

// ============================================================================
// COLOR PALETTE - 16-bit RGB565 Format
// ============================================================================
// RGB565 packs colors into 16 bits: 5 bits red, 6 bits green, 5 bits blue
// This gives us 65,536 possible colors with efficient memory usage

const uint16_t colors[] = {
  ST77XX_BLACK, ST77XX_BLUE,    ST77XX_RED,    ST77XX_GREEN,      // Basic colors
  ST77XX_CYAN,  ST77XX_MAGENTA, ST77XX_YELLOW, ST77XX_WHITE,     // More basic colors
  0x0010,       0x0200,         0x8000,        0x8010,          // Dark shades (custom RGB565 values)
  0xFBE0,       0x07E0,         0xF81F,        0x7BEF          // Bright shades (custom RGB565 values)
};

// ============================================================================
// TEST NAMES FOR DISPLAY
// ============================================================================

// Test names for Core 0 (left screen) - simple graphics
const char* core0Tests[] = {
  "Basic Colors",      // Simple color fills and transitions
  "Bouncing Ball",     // Physics simulation with collision detection
  "Rainbow Bars",      // Scrolling color bands
  "Geometric Shapes",  // Moving circles and rectangles
  "Text Display"       // Typography and text effects
};

// Test names for Core 1 (right screen) - more advanced effects
const char* core1Tests[] = {
  "Color Cycle",      // Smooth color transitions with concentric circles
  "Moving Lines",     // Scrolling line patterns
  "Color Noise",      // Mathematical noise effect (sine/cosine waves)
  "Pixel Rain",       // Falling pixels simulation (like Matrix effect)
  "Pattern Test"      // Dynamic checkerboard patterns
};

// ============================================================================
// FUNCTION DECLARATIONS - Forward declarations for compiler
// ============================================================================

// Core task functions - these run continuously on each CPU core
void Core0Loop(void* pvParameters);
void Core1Loop(void* pvParameters);

// Canvas update functions - decide which test to run
void updateCanvas0();
void updateCanvas1();

// Utility function for drawing test headers
void drawTestHeader(GFXcanvas16& canvas, const char* core, const char* test);

// Core 0 test functions - simpler effects for learning
void core0_basicColors    (GFXcanvas16& canvas);
void core0_bouncingBall   (GFXcanvas16& canvas);
void core0_rainbowBars    (GFXcanvas16& canvas);
void core0_geometricShapes(GFXcanvas16& canvas);
void core0_textDisplay    (GFXcanvas16& canvas);

// Core 1 test functions - more advanced effects
void core1_colorCycle     (GFXcanvas16& canvas);
void core1_movingLines    (GFXcanvas16& canvas);
void core1_colorNoise     (GFXcanvas16& canvas);
void core1_pixelRain      (GFXcanvas16& canvas);
void core1_patternTest    (GFXcanvas16& canvas);

// ============================================================================
// SETUP FUNCTION - Runs once at startup
// ============================================================================

/**
 * @brief Main setup function - initializes hardware and starts dual-core tasks
 * 
 * This function runs once when the ESP32 boots up. It:
 * 1. Initializes serial communication for debugging
 * 2. Checks for PSRAM (external memory for better performance)
 * 3. Sets up SPI communication protocol
 * 4. Creates and starts tasks on both CPU cores
 */
void setup() {
  // Initialize serial communication for debugging output
  Serial.begin(115200);  // 115200 baud rate - fast enough for real-time monitoring
  delay(2000);           // Give serial monitor time to connect
  
  // Print startup banner
  Serial.println("ESP32-S3 Dual Screen with Adafruit GFX - Simplified");
  Serial.println("===================================================");
  
  // Check for PSRAM (external RAM) - improves performance with large graphics
  if (psramFound()) {
    Serial.printf("PSRAM found! Free: %.1f MB\n", ESP.getFreePsram() / 1024.0 / 1024.0);
  } else {
    Serial.println("WARNING: PSRAM not found. Performance may be reduced.");
    // Note: Code will still work without PSRAM, just slower
  }
  
  // Create mutex for thread-safe SPI access
  // This prevents both cores from trying to talk to displays simultaneously
  spiMutex = xSemaphoreCreateMutex();
  
  // Initialize SPI bus with our pin configuration
  // -1 for MISO (not used) and SS (we handle CS manually)
  SPI.begin(SHARED_SCLK, -1, SHARED_MOSI, -1);
  
  Serial.println("Creating dual-core tasks...");
  
  // Create task for Core 0 (left screen)
  // Parameters: function, name, stack size, parameters, priority, handle, core number
  xTaskCreatePinnedToCore(Core0Loop, "Core0Task", 8192, NULL, 2, &Core0Task, 0);
  delay(1000);  // Brief delay to let Core 0 initialize first
  
  // Create task for Core 1 (right screen) 
  xTaskCreatePinnedToCore(Core1Loop, "Core1Task", 8192, NULL, 2, &Core1Task, 1);
  
  Serial.println("Adafruit GFX dual-core system initialized!");
}

// ============================================================================
// MAIN LOOP - Runs continuously on Core 1 (monitoring and reporting)
// ============================================================================

/**
 * @brief Main monitoring loop - provides performance feedback
 * 
 * This runs on Core 1 alongside the graphics task. It monitors both cores
 * and reports performance statistics every 3 seconds. This helps with
 * debugging and performance optimization.
 */
void loop() {
  static unsigned long lastReport = 0;
  
  // Report performance statistics every 3 seconds
  if (millis() - lastReport > 3000) {
    Serial.println("\n=== PERFORMANCE REPORT ===");
    Serial.printf("Core 0 - Test: %s, Frames: %d (%.1f FPS)\n", 
                  core0Tests[core0State.currentTest % 5], 
                  core0State.frameCount, 
                  core0State.frameCount / 3.0);  // FPS = frames / 3 seconds
    Serial.printf("Core 1 - Test: %s, Frames: %d (%.1f FPS)\n", 
                  core1Tests[core1State.currentTest % 5], 
                  core1State.frameCount, 
                  core1State.frameCount / 3.0);
    Serial.printf("Free Heap: %d bytes, Free PSRAM: %d bytes\n", 
                  ESP.getFreeHeap(), ESP.getFreePsram());
    
    // Reset frame counters for next measurement period
    core0State.frameCount = 0;
    core1State.frameCount = 0;
    lastReport = millis();
  }
  
  delay(100);  // Don't hog CPU time - this is just monitoring
}

// ============================================================================
// CORE 0 TASK - Screen 0 (simple effects)
// ============================================================================

/**
 * @brief Core 0 main loop - runs continuously on CPU Core 0
 * @param pvParameters Unused (required by FreeRTOS task signature)
 * 
 * This task handles the left screen and demonstrates fundamental graphics concepts.
 */
void Core0Loop(void* pvParameters) {
  Serial.printf("Core 0 starting on core: %d\n", xPortGetCoreID());
  
  // Initialize left screen (Screen 0) with thread safety
  if (xSemaphoreTake(spiMutex, portMAX_DELAY) == pdTRUE) {
    Serial.println("Core 0: Initializing LEFT screen...");
    
    // Initialize ST7735 display
    screen0.initR(INITR_BLACKTAB);  // Use black tab variant settings
    screen0.setRotation(0);         // Portrait orientation
    screen0.fillScreen(ST77XX_BLACK);
    
    // Display startup test pattern to verify connections
    screen0.fillScreen(ST77XX_RED);   delay(300);  // Flash red
    screen0.fillScreen(ST77XX_GREEN); delay(300);  // Flash green  
    screen0.fillScreen(ST77XX_BLUE);  delay(300);  // Flash blue
    screen0.fillScreen(ST77XX_BLACK);               // Return to black
    
    Serial.println("Core 0: LEFT screen ready!");
    xSemaphoreGive(spiMutex);  // Release SPI for other core to use
  }
  
  // Initialize canvas buffer and timing
  canvas0.fillScreen(ST77XX_BLACK);
  core0State.testStartTime = millis();
  
  // Main render loop - runs forever
  while (true) {
    // Check if it's time to switch to next test (every 60 seconds)
    if (millis() - core0State.testStartTime >= TEST_DURATION) {
      core0State.currentTest++;          // Move to next test
      core0State.testStartTime   = millis(); // Reset timer
      core0State.forceFullRedraw = true;     // Force complete redraw
      core0State.needsUpdate     = true;     // Mark as needing screen update
      Serial.printf("Core 0 -> %s\n", core0Tests[core0State.currentTest % 5]);
    }
    
    // Update the canvas with current test graphics
    updateCanvas0();
    
    // Transfer canvas to physical screen if changes were made
    if (core0State.needsUpdate) {
      // Try to get exclusive SPI access (timeout after 50ms)
      if (xSemaphoreTake(spiMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
        // This is the magic! One function call transfers entire canvas to screen
        screen0.drawRGBBitmap(0, 0, canvas0.getBuffer(), SCREEN_WIDTH, SCREEN_HEIGHT);
        xSemaphoreGive(spiMutex);  // Release SPI for other core
      }
      core0State.needsUpdate = false;  // Mark as updated
    }
    
    core0State.frameCount++;  // Count frame for performance monitoring
    vTaskDelay(pdMS_TO_TICKS(16)); // Target ~60 FPS (16ms per frame)
  }
}

// ============================================================================
// CORE 1 TASK - Screen 1 (advanced effects)
// ============================================================================

/**
 * @brief Core 1 main loop - runs continuously on CPU Core 1  
 * @param pvParameters Unused (required by FreeRTOS task signature)
 * 
 * This task handles the right screen and demonstrates advanced graphics effects.
 * Advanced Effects have been reserved on Core1, as only Arduino Core lives on Core 1, while Core 0 is dedicated for 
 * It shows more complex mathematical and visual concepts.
 */
void Core1Loop(void* pvParameters) {
  Serial.printf("Core 1 starting on core: %d\n", xPortGetCoreID());
  delay(500);  // Brief delay to offset from Core 0 initialization
  
  // Initialize right screen (Screen 1) 
  if (xSemaphoreTake(spiMutex, portMAX_DELAY) == pdTRUE) {
    Serial.println("Core 1: Initializing RIGHT screen...");
    
    screen1.initR(INITR_BLACKTAB);
    screen1.setRotation(0);
    screen1.fillScreen(ST77XX_BLACK);
    
    // Different test pattern to distinguish from Core 0
    screen1.fillScreen(ST77XX_YELLOW);  delay(300);
    screen1.fillScreen(ST77XX_CYAN);    delay(300);  
    screen1.fillScreen(ST77XX_MAGENTA); delay(300);
    screen1.fillScreen(ST77XX_BLACK);
    
    Serial.println("Core 1: RIGHT screen ready!");
    xSemaphoreGive(spiMutex);
  }
  
  canvas1.fillScreen(ST77XX_BLACK);
  core1State.testStartTime = millis();
  
  // Main render loop - identical structure to Core 0 but different tests
  while (true) {
    if (millis() - core1State.testStartTime >= TEST_DURATION) {
      core1State.currentTest++;
      core1State.testStartTime   = millis();
      core1State.forceFullRedraw = true;
      core1State.needsUpdate     = true;
      Serial.printf("Core 1 -> %s\n", core1Tests[core1State.currentTest % 5]);
    }
    
    updateCanvas1();
    
    if (core1State.needsUpdate) {
      if (xSemaphoreTake(spiMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
        screen1.drawRGBBitmap(0, 0, canvas1.getBuffer(), SCREEN_WIDTH, SCREEN_HEIGHT);
        xSemaphoreGive(spiMutex);
      }
      core1State.needsUpdate = false;
    }
    
    core1State.frameCount++;
    vTaskDelay(pdMS_TO_TICKS(16));
  }
}

// ============================================================================
// CANVAS UPDATE DISPATCHERS - Route to correct test function
// ============================================================================

/**
 * @brief Dispatch Core 0 canvas updates to appropriate test function
 * 
 * This acts as a router, calling the correct test function based on 
 * the current test index. Using modulo (%) ensures we cycle through tests.
 */
void updateCanvas0() {
  switch (core0State.currentTest % 5) {
    case 0: core0_basicColors    (canvas0); break;       // Color fundamentals
    case 1: core0_bouncingBall   (canvas0); break;      // Physics simulation  
    case 2: core0_rainbowBars    (canvas0); break;     // Color animation
    case 3: core0_geometricShapes(canvas0); break;    // Shape drawing
    case 4: core0_textDisplay    (canvas0); break;   // Typography
  }
}

/**
 * @brief Dispatch Core 1 canvas updates to appropriate test function
 */
void updateCanvas1() {
  switch (core1State.currentTest % 5) {
    case 0: core1_colorCycle (canvas1); break;            // Advanced color effects
    case 1: core1_movingLines(canvas1); break;           // Line animations
    case 2: core1_colorNoise (canvas1); break;          // Mathematical effects
    case 3: core1_pixelRain  (canvas1); break;         // Particle simulation
    case 4: core1_patternTest(canvas1); break;        // Pattern generation
  }
}

// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================

/**
 * @brief Draw header section with core name and test name
 * @param canvas The graphics canvas to draw on
 * @param core Core identifier ("CORE 0" or "CORE 1")  
 * @param test Current test name
 * 
 * Creates a consistent header area at the top of each screen showing
 * which core is running and what test is currently active.
 */
void drawTestHeader(GFXcanvas16& canvas, const char* core, const char* test) {
  // Clear header area with black background
  canvas.fillRect(0, 0, SCREEN_WIDTH, TEXT_AREA_HEIGHT, ST77XX_BLACK);
  
  // Draw core name in large white text
  canvas.setTextColor(ST77XX_WHITE);
  canvas.setTextSize(2);  // 2x normal size
  
  // Auto-center the text using getTextBounds()
  int16_t x1, y1;
  uint16_t w, h;
  canvas.getTextBounds(core, 0, 0, &x1, &y1, &w, &h);
  canvas.setCursor((SCREEN_WIDTH - w) / 2, 5);  // Center horizontally
  canvas.print(core);
  
  // Draw test name in smaller yellow text
  canvas.setTextColor(ST77XX_YELLOW);
  canvas.setTextSize(1);  // Normal size
  canvas.getTextBounds(test, 0, 0, &x1, &y1, &w, &h);
  canvas.setCursor((SCREEN_WIDTH - w) / 2, 22);
  canvas.print(test);
}

// ============================================================================
// CORE 0 TESTS - Simple Graphics Demonstrations
// ============================================================================

/**
 * @brief Test 1: Basic Colors - Demonstrates color fundamentals
 * @param canvas Graphics canvas to draw on
 * 
 * This test cycles through different colors, showing:
 * - RGB565 color format usage
 * - Rectangle drawing functions
 * - Color palette management
 * - Frame-based animation timing
 */
void core0_basicColors(GFXcanvas16& canvas) {
  static int colorIndex     = 0;      // Current color in palette
  static int lastColorIndex = -1; // Track changes to avoid unnecessary redraws
  
  // Only redraw if color changed or forced full redraw
  if (core0State.forceFullRedraw || colorIndex != lastColorIndex) {
    canvas.fillScreen(ST77XX_BLACK);
    drawTestHeader(canvas, "CORE 0", "Basic Colors");
    
    // Get two contrasting colors from palette  
    uint16_t color1 = colors[colorIndex % 16];
    uint16_t color2 = colors[(colorIndex + 8) % 16];  // Offset for contrast
    
    // Draw nested rectangles to show color relationships
    canvas.fillRect( 0, DRAW_AREA_Y,      SCREEN_WIDTH,      DRAW_AREA_HEIGHT,      color1);
    canvas.fillRect(10, DRAW_AREA_Y + 10, SCREEN_WIDTH - 20, DRAW_AREA_HEIGHT - 20, color2);
    
    core0State.needsUpdate     = true;
    lastColorIndex             = colorIndex;
    core0State.forceFullRedraw = false;
  }
  
  // Change color every 60 frames (1 second at 60 FPS)
  static int frameCount = 0;
  if (++frameCount >= 60) {
    colorIndex++;
    frameCount = 0;
  }
}

/**
 * @brief Test 2: Bouncing Ball - Physics simulation
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Position and velocity vectors
 * - Collision detection with screen boundaries
 * - Circle drawing functions
 * - Real-time physics animation
 * - Dynamic color changes on collision
 */
void core0_bouncingBall(GFXcanvas16& canvas) {
  static int ballX = 64, ballY = 80;  // Ball position (starts at center)
  static int velX  =  3, velY  =  4;      // Ball velocity (pixels per frame)
  static int colorIndex = 0;          // Color changes when ball bounces
  
  // Always update for smooth animation (no optimization here.)
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 0", "Bouncing Ball");
  
  // Draw ball as two circles - colored outer, white center for 3D effect
  canvas.fillCircle(ballX, ballY, 10, colors[colorIndex % 16]);  // Outer circle
  canvas.fillCircle(ballX, ballY,  6, ST77XX_WHITE           );  // Inner highlight
  
  // Update ball position (add velocity to position)
  ballX += velX;
  ballY += velY;
  
  // Collision detection - bounce off walls and change color
  if (ballX <= 15 || ballX >= SCREEN_WIDTH - 15) {  // Hit left or right wall
    velX = -velX;    // Reverse horizontal direction
    colorIndex++;    // Change color on bounce
  }
  if (ballY <= DRAW_AREA_Y + 15 || ballY >= SCREEN_HEIGHT - 15) {  // Hit top or bottom
    velY = -velY;    // Reverse vertical direction  
    colorIndex++;    // Change color on bounce
  }
  
  core0State.needsUpdate = true;  // Always need screen update for animation
}

/**
 * @brief Test 3: Rainbow Bars - Scrolling color animation
 * @param canvas Graphics canvas to draw on
 * 
 * This test shows:
 * - Loop-based drawing patterns
 * - Color palette cycling
 * - Scrolling animations with modulo arithmetic
 * - Horizontal rectangle drawing
 */
void core0_rainbowBars(GFXcanvas16& canvas) {
  static int offset     = 0;          // Scroll position
  static int lastOffset = -1;     // Optimization: track changes
  
  // Only redraw when scroll position changes
  if (core0State.forceFullRedraw || offset != lastOffset) {
    canvas.fillScreen(ST77XX_BLACK);
    drawTestHeader(canvas, "CORE 0", "Rainbow Bars");
    
    const int barHeight = 8;  // Height of each color bar
    
    // Draw horizontal bars across the screen
    for (int y = DRAW_AREA_Y; y < SCREEN_HEIGHT; y += barHeight) {
      // Calculate color index based on position + scroll offset
      uint16_t color = colors[((y - DRAW_AREA_Y)/barHeight + offset) % 16];
      canvas.fillRect(0, y, SCREEN_WIDTH, barHeight, color);
    }
    
    core0State.needsUpdate     = true;
    lastOffset                 = offset;
    core0State.forceFullRedraw = false;
  }
  
  // Scroll every 10 frames for smooth but not too fast movement
  static int frameCount = 0;
  if (++frameCount >= 10) {
    offset++;
    frameCount = 0;
  }
}

/**
 * @brief Test 4: Geometric Shapes - Moving shapes with trigonometry
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Trigonometric functions (sin, cos) for circular motion
 * - Multiple object animation
 * - Shape drawing (rectangles and circles)
 * - Boundary checking and constraint functions
 */
void core0_geometricShapes(GFXcanvas16& canvas) {
  static int frame = 0;  // Animation frame counter
  
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 0", "Geometric Shapes");
  
  int centerY = DRAW_AREA_Y + DRAW_AREA_HEIGHT/2;  // Vertical center of drawing area
  
  // Draw 4 shapes moving in circular patterns
  for (int i = 0; i < 4; i++) {
    int       size = 12 + i * 8;  // Each shape gets progressively larger
    uint16_t color = colors[(frame/4 + i * 4) % 16];  // Different color per shape
    
    // Calculate position using trigonometry for circular motion
    int x =      64 + cos(frame * 0.15 + i) * 30;  // Horizontal orbit
    int y = centerY + sin(frame * 0.15 + i) * 20;  // Vertical orbit (smaller)
    
    // Keep shapes on screen (constrain function limits values to range)
    x = constrain(x,               size/2, SCREEN_WIDTH  - size/2);
    y = constrain(y, DRAW_AREA_Y + size/2, SCREEN_HEIGHT - size/2);
    
    // Alternate between rectangles and circles for variety
    if (i % 2 == 0) {
      canvas.fillRect(x - size/2, y - size/2, size, size, color);
    } else {
      canvas.fillCircle(x, y, size/2, color);
    }
  }
  
  frame++;  // Advance animation
  core0State.needsUpdate = true;
}

/**
 * @brief Test 5: Text Display - Typography and text effects
 * @param canvas Graphics canvas to draw on
 * 
 * This test shows:
 * - Text rendering at different sizes
 * - Color-changing text effects
 * - Font sizing and positioning
 * - Multi-line text layouts
 */
void core0_textDisplay(GFXcanvas16& canvas) {
  static int     textFrame = 0;
  static int lastTextFrame = -1;
  
  // Only redraw when text frame changes (every 30 animation frames)
  if (core0State.forceFullRedraw || textFrame != lastTextFrame) {
    uint16_t bgColor = colors[textFrame % 16];  // Cycling background color
    canvas.fillScreen(bgColor);
    
    // Get contrasting text colors
    uint16_t color1 = colors[(textFrame +  5) % 16];
    uint16_t color2 = colors[(textFrame + 10) % 16];
    uint16_t color3 = colors[(textFrame + 15) % 16];
    
    // Large title text
    canvas.setTextColor(color1);
    canvas.setTextSize(3);  // 3x normal size
    canvas.setCursor(15, 40);
    canvas.print("CORE 0");
    
    // Medium subtitle
    canvas.setTextColor(color2);
    canvas.setTextSize(2);  // 2x normal size
    canvas.setCursor(5, 80);
    canvas.print("ADAFRUIT");
    
    // Small descriptive text
    canvas.setTextColor(color3);
    canvas.setTextSize(1);  // Normal size
    canvas.setCursor(10, 110);
    canvas.print("GFX Library");
    
    // Bottom text in white for contrast
    canvas.setTextColor(ST77XX_WHITE);
    canvas.setCursor(25, 130);
    canvas.print("Core1D Auto Labs");
    
    core0State.needsUpdate = true;
    lastTextFrame = textFrame;
    core0State.forceFullRedraw = false;
  }
  
  // Change text colors every 30 frames (0.5 seconds at 60 FPS)
  static int frameCount = 0;
  if (++frameCount >= 30) {
    textFrame++;
    frameCount = 0;
  }
}

// ============================================================================
// CORE 1 TESTS - Advanced Graphics Demonstrations  
// ============================================================================

/**
 * @brief Test 1: Color Cycle - Advanced color transitions with geometry
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Smooth color transitions
 * - Concentric circle patterns
 * - Color theory and complementary colors
 * - Geometric design principles
 */
void core1_colorCycle(GFXcanvas16& canvas) {
  static int colorIndex = 0;
  static int lastColorIndex = -1;
  
  if (core1State.forceFullRedraw || colorIndex != lastColorIndex) {
    canvas.fillScreen(colors[colorIndex % 16]);  // Dynamic background color
    drawTestHeader(canvas, "CORE 1", "Color Cycle");
    
    int centerY = DRAW_AREA_Y + DRAW_AREA_HEIGHT/2;
    
    // Draw concentric circles with mathematically related colors
    // Outer ring - thick circle outline
    canvas.drawCircle(64, centerY, 45, colors[(colorIndex +  4) % 16]);
    canvas.drawCircle(64, centerY, 44, colors[(colorIndex +  4) % 16]);
    
    // Middle ring - complementary color
    canvas.drawCircle(64, centerY, 30, colors[(colorIndex +  8) % 16]);
    canvas.drawCircle(64, centerY, 29, colors[(colorIndex +  8) % 16]);
    
    // Center - filled circle with triadic color
    canvas.fillCircle(64, centerY, 15, colors[(colorIndex + 12) % 16]);
    
    core1State.needsUpdate     = true;
    lastColorIndex             = colorIndex;
    core1State.forceFullRedraw = false;
  }
  
  // Slower color change for dramatic effect
  static int frameCount = 0;
  if (++frameCount >= 40) {
    colorIndex++;
    frameCount = 0;
  }
}

/**
 * @brief Test 2: Moving Lines - Scrolling line patterns
 * @param canvas Graphics canvas to draw on
 * 
 * This test shows:
 * - Continuous scrolling animation
 * - Line drawing functions
 * - Modulo arithmetic for wrapping
 * - Multi-thickness line effects
 */
void core1_movingLines(GFXcanvas16& canvas) {
  static int linePos = 0;  // Scroll position
  
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 1", "Moving Lines");
  
  // Draw multiple scrolling lines with different colors
  for (int i = 0; i < 15; i++) {
    int y = (linePos + i * 10) % DRAW_AREA_HEIGHT + DRAW_AREA_Y;  // Wrap around
    
    // Only draw if line is in visible area
    if (y >= DRAW_AREA_Y && y < SCREEN_HEIGHT - 3) {
      uint16_t color = colors[i % 16];
      // Draw thick lines (3 pixels high) for better visibility
      canvas.drawFastHLine(0,   y, SCREEN_WIDTH, color);
      canvas.drawFastHLine(0, y+1, SCREEN_WIDTH, color);
      canvas.drawFastHLine(0, y+2, SCREEN_WIDTH, color);
    }
  }
  
  linePos += 2;  // Scroll speed: 2 pixels per frame
  core1State.needsUpdate = true;
}

/**
 * @brief Test 3: Color Noise - Mathematical wave effects
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Mathematical functions (sine, cosine) for visual effects
 * - Color noise algorithm (simplified plasma-like effect)
 * - Block-based rendering for performance
 * - Color mapping from mathematical values
 */
void core1_colorNoise(GFXcanvas16& canvas) {
  static int frame = 0;  // Animation time
  
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 1", "Color Noise");
  
  // Use blocks instead of individual pixels for better performance
  const int blockSize = 8;
  
  for (int y = DRAW_AREA_Y; y < SCREEN_HEIGHT; y += blockSize) {
    for (int x = 0; x < SCREEN_WIDTH; x += blockSize) {
      // Color noise formula: combine sine waves at different frequencies
      float value = sin(x * 0.2 + frame * 0.1) + cos(y * 0.2 + frame * 0.15);
      
      // Convert mathematical result (-2 to +2) to color index (0-15)
      uint8_t colorIndex = (int)(value * 4 + 8) % 16;
      canvas.fillRect(x, y, blockSize, blockSize, colors[colorIndex]);
    }
  }
  
  frame++;  // Advance animation time
  core1State.needsUpdate = true;
}

/**
 * @brief Test 4: Pixel Rain - Particle simulation system
 * @param canvas Graphics canvas to draw on
 * 
 * This test shows:
 * - Particle system programming
 * - Array-based object management
 * - Random number generation
 * - Variable-speed animation
 * - Dynamic object recycling
 */
void core1_pixelRain(GFXcanvas16& canvas) {
  // Particle system: array of falling "stars"
  static struct { int x, y, speed; } stars[25];  // 25 falling particles
  static bool initialized = false;
  
  // Initialize star positions on first run
  if (!initialized) {
    for (int i = 0; i < 25; i++) {
      stars[i].x     = random(SCREEN_WIDTH);           // Random horizontal position
      stars[i].y     = random(DRAW_AREA_Y, SCREEN_HEIGHT); // Random vertical start
      stars[i].speed = 1 + (i % 4);                // Speed 1-4 pixels per frame
    }
    initialized = true;
  }
  
  canvas.fillScreen(ST77XX_BLACK);
  drawTestHeader(canvas, "CORE 1", "Pixel Rain");
  
  // Update and draw each star
  for (int i = 0; i < 25; i++) {
    stars[i].y += stars[i].speed;  // Move star down
    
    // Reset star at top when it goes off bottom
    if (stars[i].y >= SCREEN_HEIGHT) {
      stars[i].x     = random(SCREEN_WIDTH);
      stars[i].y     = DRAW_AREA_Y;
      stars[i].speed = 1 + random(4);  // Random new speed
    }
    
    // Choose color based on speed (faster = brighter)
    uint16_t color = (stars[i].speed == 1) ? 0x4208 :      // Dim gray
                     (stars[i].speed == 2) ? 0x8410 :      // Medium gray
                     (stars[i].speed == 3) ? ST77XX_WHITE : // Bright white
                     colors[i % 8];                         // Colored
    
    // Draw star as vertical streak (length based on speed)
    canvas.fillRect(stars[i].x, stars[i].y, 2, stars[i].speed + 1, color);
  }
  
  core1State.needsUpdate = true;
}

/**
 * @brief Test 5: Pattern Test - Dynamic geometric patterns
 * @param canvas Graphics canvas to draw on
 * 
 * This test demonstrates:
 * - Procedural pattern generation
 * - Nested loop algorithms
 * - Boolean logic for pattern creation
 * - Grid-based graphics
 * - Time-based pattern evolution
 */
void core1_patternTest(GFXcanvas16& canvas) {
  static int     patternFrame = 0;
  static int lastPatternFrame = -1;
  
  // Only redraw when pattern changes (every 15 frames for smooth transition)
  if (core1State.forceFullRedraw || patternFrame != lastPatternFrame) {
    canvas.fillScreen(ST77XX_BLACK);
    drawTestHeader(canvas, "CORE 1", "Pattern Test");
    
    // Create dynamic checkerboard with smooth transitions
    const int squareSize = 12;  // Size of each pattern square
    
    for (int y = DRAW_AREA_Y; y < SCREEN_HEIGHT; y += squareSize) {
      for (int x = 0; x < SCREEN_WIDTH; x += squareSize) {
        // Complex boolean logic creates evolving checkerboard
        bool isColored = ((x/squareSize + (y-DRAW_AREA_Y)/squareSize + patternFrame/15) % 2) == 0;
        
        // Choose color: either from palette or dark gray
        uint16_t color = isColored ? colors[(patternFrame/15 + (x+y)/30) % 16] : 0x2104;
        canvas.fillRect(x, y, squareSize, squareSize, color);
      }
    }
    
    core1State.needsUpdate     = true;
    lastPatternFrame           = patternFrame;
    core1State.forceFullRedraw = false;
  }
  
  // Pattern evolution speed
  static int frameCount = 0;
  if (++frameCount >= 15) {
    patternFrame++;
    frameCount = 0;
  }
}

Have a nice week!

External References:
For S3 Devkit C1
#168 ESP32 Dual Core on Arduino IDE including Data Passing and Task Synchronization by Andreas Spiess

For ESP32
ESP32 with FreeRTOS (Arduino IDE) – Getting Started: Create Tasks

1 Like

Good morning.

For the last few days I was experimenting and testing various ST7735 displays with an ESP32 S3 Devkit C1 N8R8, and also testing optimizing tricks and under the hood working as I read along about them.

From that endeavour, my first step to a long term goal has come to fruition.

I have managed to assemble together my own graphics library implementation, designed to drive two BACKTAB ST7735 displays efficiently (more displays will be added soon, starting from all the displays I own), using as much techniques i could learn about from LX7 specific optimizations and a slightly better knowledge of C++ in general and the β€œunder the hoodβ€œ working of Arduino AVR GCC Compiler.

My focus was to deliver a perceptive and undisputed smooth animation (Frame State Transitions) rather than brute chasing FPS.

It may not provide much, but from what it does, mostly they have been robust tested.

I have not started to make a GitHub repo yet, but it seems like the time might be coming soon if I work on this further.

I only have Devkit C1 with me so far, so I only could test it there. If anyone else would like to test it, feel free to do so and even modify and use it in whichever project you like, provided this niche hardware setup somehow comes to be of use.

This is still a work in progress and patches and updates for different screen are soon to come.
First small goal is to cover all ST7735 lineup. And improve on it as per my project needs.

Presenting the Beta release of:

TFTFriend - ESP32 ST7735 Duo BLACKTAB GFX Library

Arduino SPI

Hardware Requirement:

  • ESP32 (S3 N8R8 Devkit C1 tested)
  • Two ST7735 BLACKTAB SPI displays with common MOSI and SCK bus line.

 /*
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚                    CONNECTION SUMMARY TABLE                         β”‚
     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
     β”‚ Function        β”‚ ESP32-S3 GPIO   β”‚ Display #1      β”‚ Display #2    β”‚
     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
     β”‚ SPI Clock       β”‚ GPIO 12         β”‚ SCL Pin         β”‚ SCL Pin       β”‚
     β”‚ SPI Data        β”‚ GPIO 11         β”‚ SDA Pin         β”‚ SDA Pin       β”‚
     β”‚ Chip Select 1   β”‚ GPIO 15         β”‚ CS Pin          β”‚ N/C           β”‚
     β”‚ Chip Select 2   β”‚ GPIO 10         β”‚ N/C             β”‚ CS Pin        β”‚
     β”‚ Data/Command 1  β”‚ GPIO 7          β”‚ DC Pin          β”‚ N/C           β”‚
     β”‚ Data/Command 2  β”‚ GPIO 4          β”‚ N/C             β”‚ DC Pin        β”‚
     β”‚ Reset 1         β”‚ GPIO 6          β”‚ RST Pin         β”‚ N/C           β”‚
     β”‚ Reset 2         β”‚ GPIO 5          β”‚ N/C             β”‚ RST Pin       β”‚
     β”‚ Power (+3.3V)   β”‚ 3V3             β”‚ VCC Pin         β”‚ VCC Pin       β”‚
     β”‚ Ground          β”‚ GND             β”‚ GND Pin         β”‚ GND Pin       β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
PS: For no manual control for the display Backlight, treat LED+ as VCC and LED- as ground. Do refer to respective datasheet for 5v5 compatibility. the used BLACKTAB display supports 5v5 logic as well.  
*/

Library Files:

ST7735DualEngine.h:

/**************************************************************
* (Core1D) TFTFriend - ESP32S3 ST7735Duo Graphics Engine
* Advanced Dual-Display Graphics Library for ESP32 Family
* --------------------------------------------------------
* Version: 2.1 Tensilica LX7 Optimized
* Platform: ESP32, ESP32-S2, ESP32-S3, ESP32-C3
* License: MIT
* For License details check the main library implementation file.
**************************************************************/

#pragma once
#pragma GCC optimize("O3")
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"

#include <Arduino.h>
#include <SPI.h>
#include <pgmspace.h>

// Platform detection and capability mapping
#if defined(CONFIG_IDF_TARGET_ESP32)
#define ST7735_PLATFORM_ESP32
#define ST7735_HAS_DUAL_CORE 1
#define ST7735_HAS_SIMD 0
#define ST7735_DEFAULT_CORE_GRAPHICS 0
#define ST7735_DEFAULT_CORE_DISPLAY 1
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define ST7735_PLATFORM_ESP32S2
#define ST7735_HAS_DUAL_CORE 0
#define ST7735_HAS_SIMD 0
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#define ST7735_PLATFORM_ESP32S3
#define ST7735_HAS_DUAL_CORE 1
#define ST7735_HAS_SIMD 1  // LX7 has limited SIMD
#define ST7735_DEFAULT_CORE_GRAPHICS 0
#define ST7735_DEFAULT_CORE_DISPLAY 1
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
#define ST7735_PLATFORM_ESP32C3
#define ST7735_HAS_DUAL_CORE 0
#define ST7735_HAS_SIMD 0
#else
#define ST7735_PLATFORM_GENERIC
#define ST7735_HAS_DUAL_CORE 0
#define ST7735_HAS_SIMD 0
#endif

// Enhanced optimization macros
#define FORCE_INLINE __attribute__((always_inline)) inline
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#define CACHE_ALIGNED __attribute__((aligned(128)))  // Enhanced for LX7
#define FAST_CODE_ATTR IRAM_ATTR
#define PREFETCH(addr) __builtin_prefetch((addr), 0, 3)
#define HOT_FUNCTION __attribute__((hot))
#define COLD_FUNCTION __attribute__((cold))

// SIMD support for ESP32-S3
#if ST7735_HAS_SIMD
#include "xtensa/core-macros.h"
#define VECTORIZED __attribute__((optimize("O3")))
#else
#define VECTORIZED
#endif

// Force Arduino SPI only
#define ST7735_ENABLE_DMA 0
#define ST7735_ENABLE_DUAL_CORE ST7735_HAS_DUAL_CORE

// Include FreeRTOS only for dual-core support
#if ST7735_ENABLE_DUAL_CORE
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#endif

// ========================================
// ENHANCED CONFIGURATION MACROS
// ========================================

#ifndef ST7735_DUAL_CONFIG
#define ST7735_DUAL_CONFIG

// Hardware Configuration - Platform-adaptive defaults
#if defined(ST7735_PLATFORM_ESP32S3)
#ifndef ST7735_SCREEN0_CS
#define ST7735_SCREEN0_CS 15
#endif
#ifndef ST7735_SCREEN0_DC
#define ST7735_SCREEN0_DC 7
#endif
#ifndef ST7735_SCREEN0_RST
#define ST7735_SCREEN0_RST 6
#endif
#ifndef ST7735_SCREEN1_CS
#define ST7735_SCREEN1_CS 10
#endif
#ifndef ST7735_SCREEN1_DC
#define ST7735_SCREEN1_DC 4
#endif
#ifndef ST7735_SCREEN1_RST
#define ST7735_SCREEN1_RST 5
#endif
#ifndef ST7735_SPI_MOSI
#define ST7735_SPI_MOSI 11
#endif
#ifndef ST7735_SPI_SCLK
#define ST7735_SPI_SCLK 12
#endif
#else
// Generic ESP32 family defaults
#ifndef ST7735_SCREEN0_CS
#define ST7735_SCREEN0_CS 15
#endif
#ifndef ST7735_SCREEN0_DC
#define ST7735_SCREEN0_DC 2
#endif
#ifndef ST7735_SCREEN0_RST
#define ST7735_SCREEN0_RST 4
#endif
#ifndef ST7735_SCREEN1_CS
#define ST7735_SCREEN1_CS 5
#endif
#ifndef ST7735_SCREEN1_DC
#define ST7735_SCREEN1_DC 16
#endif
#ifndef ST7735_SCREEN1_RST
#define ST7735_SCREEN1_RST 17
#endif
#ifndef ST7735_SPI_MOSI
#define ST7735_SPI_MOSI 23
#endif
#ifndef ST7735_SPI_SCLK
#define ST7735_SPI_SCLK 18
#endif
#endif

// Enhanced Performance Configuration
#if defined(ST7735_PLATFORM_ESP32S3)
#ifndef ST7735_SPI_FREQUENCY
#define ST7735_SPI_FREQUENCY 80000000UL  // ESP32-S3 maximum
#endif
#ifndef ST7735_BURST_SIZE
#define ST7735_BURST_SIZE 8192  // Larger bursts for S3
#endif
#elif defined(ST7735_PLATFORM_ESP32)
#ifndef ST7735_SPI_FREQUENCY
#define ST7735_SPI_FREQUENCY 40000000UL  // ESP32 stable maximum
#endif
#ifndef ST7735_BURST_SIZE
#define ST7735_BURST_SIZE 4096
#endif
#else
#ifndef ST7735_SPI_FREQUENCY
#define ST7735_SPI_FREQUENCY 26000000UL  // Conservative default
#endif
#ifndef ST7735_BURST_SIZE
#define ST7735_BURST_SIZE 2048
#endif
#endif

// Memory Pool Configuration
#ifndef ST7735_MEMORY_POOL_SIZE
#define ST7735_MEMORY_POOL_SIZE 32768  // 32KB pool for frequent allocations
#endif

// Enhanced reset timing
#ifndef ST7735_RESET_DELAY_MS
#define ST7735_RESET_DELAY_MS 15
#endif
#ifndef ST7735_WAKE_DELAY_MS
#define ST7735_WAKE_DELAY_MS 50
#endif
#ifndef ST7735_INIT_STABILITY_DELAY_MS
#define ST7735_INIT_STABILITY_DELAY_MS 10
#endif

// Memory Configuration - Enhanced for S3
#ifndef ST7735_USE_PSRAM
#if defined(ST7735_PLATFORM_ESP32S3) || defined(ST7735_PLATFORM_ESP32S2)
#define ST7735_USE_PSRAM 1
#else
#define ST7735_USE_PSRAM 0
#endif
#endif

#ifndef ST7735_ENABLE_BOUNDS_CHECK
#define ST7735_ENABLE_BOUNDS_CHECK 1
#endif
#ifndef ST7735_ENABLE_DEBUG
#define ST7735_ENABLE_DEBUG 0
#endif
#ifndef ST7735_ENABLE_FAST_FILL
#define ST7735_ENABLE_FAST_FILL 1
#endif
#ifndef ST7735_ENABLE_BUFFER_PREFETCH
#define ST7735_ENABLE_BUFFER_PREFETCH 1
#endif

// Task Configuration for dual-core
#if ST7735_ENABLE_DUAL_CORE
#ifndef ST7735_GRAPHICS_TASK_STACK
#define ST7735_GRAPHICS_TASK_STACK 4096
#endif
#ifndef ST7735_DISPLAY_TASK_STACK
#define ST7735_DISPLAY_TASK_STACK 2048
#endif
#ifndef ST7735_TASK_PRIORITY
#define ST7735_TASK_PRIORITY 5
#endif
#endif

#endif // ST7735_DUAL_CONFIG

// ========================================
// DISPLAY CONSTANTS & COLOR SPACE
// ========================================

namespace ST7735Colors {
constexpr uint16_t BLACK = 0x0000;
constexpr uint16_t WHITE = 0xFFFF;
constexpr uint16_t RED = 0x001F;       // BGR
constexpr uint16_t GREEN = 0x07E0;     // Same
constexpr uint16_t BLUE = 0xF800;      // BGR
constexpr uint16_t YELLOW = 0x07FF;    // BGR
constexpr uint16_t CYAN = 0xFFE0;      // BGR
constexpr uint16_t MAGENTA = 0xF81F;   // Same
constexpr uint16_t ORANGE = 0x041F;    // BGR
constexpr uint16_t PINK = 0xFE19;
constexpr uint16_t GRAY = 0x8410;
constexpr uint16_t DARK_GRAY = 0x4208;
constexpr uint16_t LIGHT_GRAY = 0xC618;
constexpr uint16_t PURPLE = 0x8010;
constexpr uint16_t BROWN = 0xA145;
}

namespace ST7735Specs {
constexpr uint16_t WIDTH = 128;
constexpr uint16_t HEIGHT = 160;
constexpr uint32_t PIXELS = WIDTH * HEIGHT;
constexpr uint32_t BUFFER_BYTES = PIXELS * sizeof(uint16_t);

constexpr uint8_t FONT_WIDTH = 5;
constexpr uint8_t FONT_HEIGHT = 7;
constexpr uint8_t FONT_SPACING = 6;
constexpr uint8_t FONT_LINE_HEIGHT = 8;
}

// ========================================
// OPTIMIZED FONT DATA - Adafruit Referenced
// ========================================
// Credits: Based on Adafruit GFX library font format
static const uint8_t font5x7_standard[][ST7735Specs::FONT_WIDTH] = {
  // ===== BASIC SYMBOLS (ASCII 32-47) =====
  {0x00, 0x00, 0x00, 0x00, 0x00}, // Space (32) - Index 0
  {0x00, 0x00, 0x5F, 0x00, 0x00}, // ! (33) - Index 1
  {0x00, 0x07, 0x00, 0x07, 0x00}, // " (34) - Index 2
  {0x14, 0x7F, 0x14, 0x7F, 0x14}, // # (35) - Index 3
  {0x24, 0x2A, 0x7F, 0x2A, 0x12}, // $ (36) - Index 4
  {0x23, 0x13, 0x08, 0x64, 0x62}, // % (37) - Index 5
  {0x36, 0x49, 0x55, 0x22, 0x50}, // & (38) - Index 6
  {0x00, 0x05, 0x03, 0x00, 0x00}, // ' (39) - Index 7
  {0x00, 0x1C, 0x22, 0x41, 0x00}, // ( (40) - Index 8
  {0x00, 0x41, 0x22, 0x1C, 0x00}, // ) (41) - Index 9
  {0x14, 0x08, 0x3E, 0x08, 0x14}, // * (42) - Index 10
  {0x08, 0x08, 0x3E, 0x08, 0x08}, // + (43) - Index 11
  {0x00, 0x50, 0x30, 0x00, 0x00}, // , (44) - Index 12
  {0x08, 0x08, 0x08, 0x08, 0x08}, // - (45) - Index 13
  {0x00, 0x60, 0x60, 0x00, 0x00}, // . (46) - Index 14
  {0x20, 0x10, 0x08, 0x04, 0x02}, // / (47) - Index 15
  
  // ===== NUMBERS 0-9 (ASCII 48-57) =====
  {0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0 (48) - Index 16
  {0x00, 0x42, 0x7F, 0x40, 0x00}, // 1 (49) - Index 17
  {0x42, 0x61, 0x51, 0x49, 0x46}, // 2 (50) - Index 18
  {0x21, 0x41, 0x45, 0x4B, 0x31}, // 3 (51) - Index 19
  {0x18, 0x14, 0x12, 0x7F, 0x10}, // 4 (52) - Index 20
  {0x27, 0x45, 0x45, 0x45, 0x39}, // 5 (53) - Index 21
  {0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6 (54) - Index 22
  {0x01, 0x71, 0x09, 0x05, 0x03}, // 7 (55) - Index 23
  {0x36, 0x49, 0x49, 0x49, 0x36}, // 8 (56) - Index 24
  {0x06, 0x49, 0x49, 0x29, 0x1E}, // 9 (57) - Index 25
  
  // ===== PUNCTUATION (ASCII 58-64) =====
  {0x00, 0x36, 0x36, 0x00, 0x00}, // : (58) - Index 26
  {0x00, 0x56, 0x36, 0x00, 0x00}, // ; (59) - Index 27
  {0x08, 0x14, 0x22, 0x41, 0x00}, // < (60) - Index 28
  {0x14, 0x14, 0x14, 0x14, 0x14}, // = (61) - Index 29
  {0x00, 0x41, 0x22, 0x14, 0x08}, // > (62) - Index 30
  {0x02, 0x01, 0x51, 0x09, 0x06}, // ? (63) - Index 31
  {0x32, 0x49, 0x79, 0x41, 0x3E}, // @ (64) - Index 32
  
  // ===== UPPERCASE LETTERS A-Z (ASCII 65-90) =====
  {0x7E, 0x11, 0x11, 0x11, 0x7E}, // A (65) - Index 33
  {0x7F, 0x49, 0x49, 0x49, 0x36}, // B (66) - Index 34
  {0x3E, 0x41, 0x41, 0x41, 0x22}, // C (67) - Index 35
  {0x7F, 0x41, 0x41, 0x22, 0x1C}, // D (68) - Index 36
  {0x7F, 0x49, 0x49, 0x49, 0x41}, // E (69) - Index 37
  {0x7F, 0x09, 0x09, 0x09, 0x01}, // F (70) - Index 38
  {0x3E, 0x41, 0x49, 0x49, 0x7A}, // G (71) - Index 39
  {0x7F, 0x08, 0x08, 0x08, 0x7F}, // H (72) - Index 40
  {0x00, 0x41, 0x7F, 0x41, 0x00}, // I (73) - Index 41
  {0x20, 0x40, 0x41, 0x3F, 0x01}, // J (74) - Index 42
  {0x7F, 0x08, 0x14, 0x22, 0x41}, // K (75) - Index 43
  {0x7F, 0x40, 0x40, 0x40, 0x40}, // L (76) - Index 44
  {0x7F, 0x02, 0x0C, 0x02, 0x7F}, // M (77) - Index 45
  {0x7F, 0x04, 0x08, 0x10, 0x7F}, // N (78) - Index 46
  {0x3E, 0x41, 0x41, 0x41, 0x3E}, // O (79) - Index 47
  {0x7F, 0x09, 0x09, 0x09, 0x06}, // P (80) - Index 48
  {0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q (81) - Index 49
  {0x7F, 0x09, 0x19, 0x29, 0x46}, // R (82) - Index 50
  {0x46, 0x49, 0x49, 0x49, 0x31}, // S (83) - Index 51
  {0x01, 0x01, 0x7F, 0x01, 0x01}, // T (84) - Index 52
  {0x3F, 0x40, 0x40, 0x40, 0x3F}, // U (85) - Index 53
  {0x1F, 0x20, 0x40, 0x20, 0x1F}, // V (86) - Index 54
  {0x3F, 0x40, 0x38, 0x40, 0x3F}, // W (87) - Index 55
  {0x63, 0x14, 0x08, 0x14, 0x63}, // X (88) - Index 56
  {0x07, 0x08, 0x70, 0x08, 0x07}, // Y (89) - Index 57
  {0x61, 0x51, 0x49, 0x45, 0x43}, // Z (90) - Index 58

  // ===== BRACKETS & SYMBOLS (ASCII 91-96) =====
  {0x00, 0x7F, 0x41, 0x41, 0x00}, // [ (91) - Index 59
  {0x02, 0x04, 0x08, 0x10, 0x20}, // \ (92) - Index 60
  {0x00, 0x41, 0x41, 0x7F, 0x00}, // ] (93) - Index 61
  {0x04, 0x02, 0x01, 0x02, 0x04}, // ^ (94) - Index 62
  {0x40, 0x40, 0x40, 0x40, 0x40}, // _ (95) - Index 63
  {0x00, 0x01, 0x02, 0x04, 0x00}, // ` (96) - Index 64

  // ===== LOWERCASE LETTERS a-z (ASCII 97-122) =====
  {0x20, 0x54, 0x54, 0x54, 0x78}, // a (97) - Index 65
  {0x7F, 0x48, 0x44, 0x44, 0x38}, // b (98) - Index 66
  {0x38, 0x44, 0x44, 0x44, 0x20}, // c (99) - Index 67
  {0x38, 0x44, 0x44, 0x48, 0x7F}, // d (100) - Index 68
  {0x38, 0x54, 0x54, 0x54, 0x18}, // e (101) - Index 69
  {0x08, 0x7E, 0x09, 0x01, 0x02}, // f (102) - Index 70
  {0x0C, 0x52, 0x52, 0x52, 0x3E}, // g (103) - Index 71 - Descender Corrected
  {0x7F, 0x08, 0x04, 0x04, 0x78}, // h (104) - Index 72
  {0x00, 0x44, 0x7D, 0x40, 0x00}, // i (105) - Index 73
  {0x20, 0x40, 0x44, 0x3D, 0x00}, // j (106) - Index 74 - Descender Corrected
  {0x7F, 0x10, 0x28, 0x44, 0x00}, // k (107) - Index 75
  {0x00, 0x41, 0x7F, 0x40, 0x00}, // l (108) - Index 76
  {0x7C, 0x04, 0x18, 0x04, 0x78}, // m (109) - Index 77
  {0x7C, 0x08, 0x04, 0x04, 0x78}, // n (110) - Index 78
  {0x38, 0x44, 0x44, 0x44, 0x38}, // o (111) - Index 79
  {0x7C, 0x14, 0x14, 0x14, 0x08}, // p (112) - Index 80 - Descender Corrected
  {0x08, 0x14, 0x14, 0x18, 0x7C}, // q (113) - Index 81 - Descender Corrected
  {0x7C, 0x08, 0x04, 0x04, 0x08}, // r (114) - Index 82
  {0x48, 0x54, 0x54, 0x54, 0x20}, // s (115) - Index 83
  {0x04, 0x3F, 0x44, 0x40, 0x20}, // t (116) - Index 84
  {0x3C, 0x40, 0x40, 0x20, 0x7C}, // u (117) - Index 85
  {0x1C, 0x20, 0x40, 0x20, 0x1C}, // v (118) - Index 86
  {0x3C, 0x40, 0x30, 0x40, 0x3C}, // w (119) - Index 87
  {0x44, 0x28, 0x10, 0x28, 0x44}, // x (120) - Index 88
  {0x0C, 0x50, 0x50, 0x50, 0x3C}, // y (121) - Index 89 - Descender Corrected
  {0x44, 0x64, 0x54, 0x4C, 0x44}, // z (122) - Index 90

  // ===== ADDITIONAL SYMBOLS (ASCII 123-126) =====
  {0x00, 0x08, 0x36, 0x41, 0x00}, // { (123) - Index 91
  {0x00, 0x00, 0x7F, 0x00, 0x00}, // | (124) - Index 92
  {0x00, 0x41, 0x36, 0x08, 0x00}, // } (125) - Index 93
  {0x08, 0x04, 0x08, 0x10, 0x08}  // ~ (126) - Index 94
};

// ========================================
// ENHANCED PERFORMANCE MONITORING
// ========================================

struct CACHE_ALIGNED ST7735PerformanceStats {
    uint32_t frameCount = 0;
    uint32_t totalRenderTime = 0;
    uint32_t totalTransferTime = 0;
    float averageOPS = 0.0f;
    float averageFHOPS = 0.0f;
    uint32_t peakFrameTime = 0;
    uint32_t peakTransferTime = 0;
    uint32_t lastResetTime = 0;
    uint32_t nonZeroRenderCount = 0;
    uint32_t cacheHits = 0;        // New: Cache hit tracking
    uint32_t cacheMisses = 0;      // New: Cache miss tracking
    uint32_t vectorizedOps = 0;    // New: SIMD operation count

    FORCE_INLINE void reset() {
        frameCount = 0;
        totalRenderTime = 0;
        totalTransferTime = 0;
        averageOPS = 0.0f;
        averageFHOPS = 0.0f;
        peakFrameTime = 0;
        peakTransferTime = 0;
        lastResetTime = millis();
        nonZeroRenderCount = 0;
        cacheHits = 0;
        cacheMisses = 0;
        vectorizedOps = 0;
    }

    HOT_FUNCTION FORCE_INLINE void update(uint32_t renderTime, uint32_t transferTime) {
        totalRenderTime += renderTime;
        if (renderTime > 0) nonZeroRenderCount++;
        totalTransferTime += transferTime;
        peakFrameTime = max(peakFrameTime, renderTime);
        peakTransferTime = max(peakTransferTime, transferTime);
        
        if (LIKELY(frameCount < UINT32_MAX - 1)) {
            frameCount++;
        }

        uint32_t elapsed = millis() - lastResetTime;
        if (elapsed >= 5000) {
            if (elapsed > 0 && frameCount > 0) {
                averageOPS = (frameCount * 1000.0f) / elapsed;
            }
            if (elapsed > 0 && nonZeroRenderCount > 0) {
                averageFHOPS = (nonZeroRenderCount * 1000.0f) / elapsed;
            }
            
            frameCount = 0;
            totalRenderTime = 0;
            totalTransferTime = 0;
            nonZeroRenderCount = 0;
            lastResetTime = millis();
        }
    }
};

// ========================================
// MEMORY POOL MANAGEMENT
// ========================================

class CACHE_ALIGNED ST7735MemoryPool {
private:
    uint8_t* poolMemory;
    size_t poolSize;
    size_t poolOffset;
    
public:
    ST7735MemoryPool(size_t size) : poolSize(size), poolOffset(0) {
        #if ST7735_USE_PSRAM
        poolMemory = (uint8_t*)heap_caps_aligned_alloc(128, size, MALLOC_CAP_SPIRAM);
        #else
        poolMemory = (uint8_t*)heap_caps_aligned_alloc(128, size, MALLOC_CAP_INTERNAL);
        #endif
    }
    
    ~ST7735MemoryPool() {
        if (poolMemory) free(poolMemory);
    }
    
    FORCE_INLINE void* allocate(size_t size) {
        if (poolOffset + size > poolSize) return nullptr;
        void* ptr = poolMemory + poolOffset;
        poolOffset = (poolOffset + size + 15) & ~15; // 16-byte align
        return ptr;
    }
    
    FORCE_INLINE void reset() { poolOffset = 0; }

    #if ST7735_HAS_SIMD
    FORCE_INLINE void* allocateAlignedLX7(size_t size) {
        // LX7-optimized allocation with 128-byte alignment
        const size_t alignment = 128;
        size_t alignedSize = (size + alignment - 1) & ~(alignment - 1);
    
        if (poolOffset + alignedSize > poolSize) return nullptr;
    
        void* ptr = poolMemory + poolOffset;
        poolOffset += alignedSize;
    
        // Prefetch the allocated memory
        PREFETCH(ptr);
        return ptr;
    }
    #endif
};

// ========================================
// DUAL-CORE TASK COMMUNICATION
// ========================================

#if ST7735_ENABLE_DUAL_CORE
enum class ST7735TaskCommand : uint8_t {
    UPDATE_DISPLAY_0,
    UPDATE_DISPLAY_1,
    UPDATE_BOTH,
    UPDATE_REGION,
    SHUTDOWN
};

struct CACHE_ALIGNED ST7735TaskMessage {
    ST7735TaskCommand command;
    uint8_t screen;
    int16_t x, y, w, h;
};
#endif

// ========================================
// ENHANCED PLATFORM ABSTRACTION
// ========================================

class ST7735Platform {
public:
    FORCE_INLINE static void* allocateFrameBuffer(size_t size) {
        #if ST7735_USE_PSRAM && defined(ST7735_PLATFORM_ESP32S3)
        void* ptr = heap_caps_aligned_alloc(128, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
        if (!ptr) {
            ptr = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
        }
        return ptr;
        #elif ST7735_USE_PSRAM
        return ps_malloc(size);
        #else
        void* ptr = heap_caps_aligned_alloc(128, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
        return ptr ? ptr : malloc(size);
        #endif
    }

    FORCE_INLINE static void deallocateBuffer(void* ptr) {
        if (ptr) free(ptr);
    }

    FORCE_INLINE static uint32_t getOptimalSPIFrequency() {
        #if defined(ST7735_PLATFORM_ESP32S3)
        return 80000000UL;
        #else
        return ST7735_SPI_FREQUENCY;
        #endif
    }

    FORCE_INLINE static const char* getPlatformName() {
        #if defined(ST7735_PLATFORM_ESP32S3)
        return "ESP32-S3 (LX7)";
        #elif defined(ST7735_PLATFORM_ESP32S2)
        return "ESP32-S2";
        #elif defined(ST7735_PLATFORM_ESP32C3)
        return "ESP32-C3";
        #elif defined(ST7735_PLATFORM_ESP32)
        return "ESP32";
        #else
        return "Generic";
        #endif
    }

    FORCE_INLINE static bool hasDualCore() {
        return ST7735_HAS_DUAL_CORE;
    }

    FORCE_INLINE static bool hasSIMD() {
        return ST7735_HAS_SIMD;
    }
};

// ========================================
// VECTORIZED COLOR OPERATIONS
// ========================================

#if ST7735_HAS_SIMD
namespace ST7735SIMD {
    // Vectorized RGB565 conversion for ESP32-S3 LX7
    VECTORIZED FORCE_INLINE void convertToRGB565_SIMD(const uint16_t* src, uint8_t* dst, size_t count) {
        // Use LX7's limited SIMD for bulk conversion
        for (size_t i = 0; i < count; i += 4) {
            // Process 4 pixels at once using 64-bit operations
            uint64_t pixels = *((uint64_t*)(src + i));
            
            uint8_t* dest = dst + (i * 2);
            dest[0] = (pixels >> 8) & 0xFF;
            dest[1] = pixels & 0xFF;
            dest[2] = (pixels >> 24) & 0xFF;
            dest[3] = (pixels >> 16) & 0xFF;
            dest[4] = (pixels >> 40) & 0xFF;
            dest[5] = (pixels >> 32) & 0xFF;
            dest[6] = (pixels >> 56) & 0xFF;
            dest[7] = (pixels >> 48) & 0xFF;
        }
    }

    VECTORIZED FORCE_INLINE void fastFill_SIMD(uint16_t* buffer, uint16_t color, size_t count) {
        const uint64_t quadColor = ((uint64_t)color << 48) | ((uint64_t)color << 32) |
                                   ((uint64_t)color << 16) | color;
        uint64_t* fastPtr = reinterpret_cast<uint64_t*>(buffer);
        const size_t quadCount = count / 4;
        
        // Unrolled SIMD fill
        for (size_t i = 0; i < quadCount; i += 8) {
            fastPtr[i] = quadColor; fastPtr[i + 1] = quadColor;
            fastPtr[i + 2] = quadColor; fastPtr[i + 3] = quadColor;
            fastPtr[i + 4] = quadColor; fastPtr[i + 5] = quadColor;
            fastPtr[i + 6] = quadColor; fastPtr[i + 7] = quadColor;
        }
    }
}
#endif

// ========================================
// MAIN ENGINE CLASS
// ========================================

class ST7735DualEngine {
private:
    // Hardware configuration
    static constexpr uint8_t SCREEN0_CS = ST7735_SCREEN0_CS;
    static constexpr uint8_t SCREEN0_DC = ST7735_SCREEN0_DC;
    static constexpr uint8_t SCREEN0_RST = ST7735_SCREEN0_RST;
    static constexpr uint8_t SCREEN1_CS = ST7735_SCREEN1_CS;
    static constexpr uint8_t SCREEN1_DC = ST7735_SCREEN1_DC;
    static constexpr uint8_t SCREEN1_RST = ST7735_SCREEN1_RST;
    static constexpr uint8_t MOSI = ST7735_SPI_MOSI;
    static constexpr uint8_t SCLK = ST7735_SPI_SCLK;

    // Display specifications
    static constexpr uint16_t DISPLAY_WIDTH = ST7735Specs::WIDTH;
    static constexpr uint16_t DISPLAY_HEIGHT = ST7735Specs::HEIGHT;
    static constexpr uint32_t BUFFER_SIZE = ST7735Specs::PIXELS;
    static constexpr uint32_t TRANSFER_BYTES = ST7735Specs::BUFFER_BYTES;

    // Enhanced memory buffers with better alignment
    CACHE_ALIGNED uint16_t* buffer0;
    CACHE_ALIGNED uint16_t* buffer1;
    CACHE_ALIGNED uint8_t* transferBuffer;

    // Memory pool for frequent allocations
    ST7735MemoryPool* memoryPool;

    // Performance monitoring 
    ST7735PerformanceStats stats;
    uint32_t lastStatsUpdate = 0;

    // Startup reliability 
    bool startupReliabilityComplete = false;
    uint8_t startupAttempts = 0;
    static constexpr uint8_t MAX_STARTUP_ATTEMPTS = 5;
    static constexpr uint32_t STARTUP_STABILITY_DELAY = 100;
    static constexpr uint32_t INTER_DISPLAY_DELAY = 50;
    static constexpr uint8_t STARTUP_TEST_PATTERN = 0x55;

    #if ST7735_ENABLE_DUAL_CORE
    TaskHandle_t displayTaskHandle = nullptr;
    QueueHandle_t commandQueue = nullptr;
    SemaphoreHandle_t bufferMutex = nullptr;
    bool dualCoreActive = false;
    #endif

    // Enhanced hardware interface functions
    FAST_CODE_ATTR void writeCommand(const uint8_t cmd, const uint8_t cs, const uint8_t dc) const;
    FAST_CODE_ATTR void writeData(const uint8_t data, const uint8_t cs, const uint8_t dc) const;
    FAST_CODE_ATTR void setWindow(const uint16_t x0, const uint16_t y0, const uint16_t x1, const uint16_t y1,
                                  const uint8_t cs, const uint8_t dc) const;
    void initDisplay(const uint8_t cs, const uint8_t dc, const uint8_t rst) const;

    // Enhanced transfer methods
    HOT_FUNCTION FAST_CODE_ATTR void pushColorsFast(const uint16_t* buffer, const uint8_t cs, const uint8_t dc) const;

    // Startup reliability methods
    bool ensureProperReset();
    bool validateDisplayResponse(const uint8_t cs, const uint8_t dc) const;
    bool performStartupReliabilityCheck();
    bool testDisplayCommunication(const uint8_t cs, const uint8_t dc) const;
    void forceDisplayWakeup(const uint8_t cs, const uint8_t dc, const uint8_t rst) const;
    bool validateDisplayInitialization(const uint8_t cs, const uint8_t dc) const;

    #if ST7735_ENABLE_DUAL_CORE
    static void displayTask(void* parameter);
    bool initializeDualCore();
    void shutdownDualCore();
    #endif

    // Enhanced utility functions
    FORCE_INLINE bool isValidCoordinate(const int16_t x, const int16_t y) const {
        #if ST7735_ENABLE_BOUNDS_CHECK
        return LIKELY(x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT);
        #else
        return true;
        #endif
    }

    FORCE_INLINE uint16_t* getBufferPtr(const int screen) const {
        return LIKELY(screen == 0) ? buffer0 : buffer1;
    }

public:
    // Public constants (unchanged)
    static constexpr uint16_t WIDTH = DISPLAY_WIDTH;
    static constexpr uint16_t HEIGHT = DISPLAY_HEIGHT;

    // Constructor and lifecycle
    ST7735DualEngine();
    ~ST7735DualEngine();

    // Core initialization with correct InitResult structure to match cpp initialization
    struct InitResult {
        bool success;
        bool dmaEnabled;
        bool dualCoreEnabled;
        uint32_t spiFrequency;    
        const char* platform;     
    };

    InitResult begin();
    bool ensureStartupReliability();
    void end();

    // ========================================
    // ENHANCED GRAPHICS PRIMITIVES
    // ========================================

    // Buffer management with vectorization
    HOT_FUNCTION void clearBuffer(const int screen, const uint16_t color = ST7735Colors::BLACK);
    void swapBuffers();

    // Pixel operations with prefetching
    HOT_FUNCTION FORCE_INLINE void setPixel(const int screen, const int16_t x, const int16_t y, const uint16_t color) {
        if (UNLIKELY(!isValidCoordinate(x, y))) return;
        uint16_t* buffer = getBufferPtr(screen);
        if (UNLIKELY(!buffer)) return;
        
        #if ST7735_ENABLE_BUFFER_PREFETCH
        PREFETCH(&buffer[(y + 1) * DISPLAY_WIDTH + x]); // Prefetch next line
        #endif

        #if ST7735_ENABLE_DUAL_CORE
        if (dualCoreActive) {
            if (xSemaphoreTake(bufferMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
                buffer[y * DISPLAY_WIDTH + x] = color;
                xSemaphoreGive(bufferMutex);
            }
        } else {
            buffer[y * DISPLAY_WIDTH + x] = color;
        }
        #else
        buffer[y * DISPLAY_WIDTH + x] = color;
        #endif
    }

    FORCE_INLINE uint16_t getPixel(const int screen, const int16_t x, const int16_t y) const {
        if (UNLIKELY(!isValidCoordinate(x, y))) return 0;
        const uint16_t* buffer = getBufferPtr(screen);
        return LIKELY(buffer) ? buffer[y * DISPLAY_WIDTH + x] : 0;
    }

    // Rectangle primitives with vectorization
    HOT_FUNCTION void fillRect(const int screen, const int16_t x, const int16_t y,
                               const int16_t w, const int16_t h, const uint16_t color);
    void drawRect(const int screen, const int16_t x, const int16_t y,
                  const int16_t w, const int16_t h, const uint16_t color);

    // Line primitives
    void drawLine(const int screen, const int16_t x0, const int16_t y0,
                  const int16_t x1, const int16_t y1, const uint16_t color);

    FORCE_INLINE void drawHLine(const int screen, const int16_t x, const int16_t y,
                                const int16_t w, const uint16_t color) {
        fillRect(screen, x, y, w, 1, color);
    }

    FORCE_INLINE void drawVLine(const int screen, const int16_t x, const int16_t y,
                                const int16_t h, const uint16_t color) {
        fillRect(screen, x, y, 1, h, color);
    }

    // Circle primitives 
    void drawCircle(const int screen, const int16_t x, const int16_t y,
                    const int16_t r, const uint16_t color);
    void fillCircle(const int screen, const int16_t x, const int16_t y,
                    const int16_t r, const uint16_t color);

    // Advanced shapes
    void drawTriangle(const int screen, const int16_t x0, const int16_t y0,
                      const int16_t x1, const int16_t y1, const int16_t x2, const int16_t y2,
                      const uint16_t color);
    void fillTriangle(const int screen, const int16_t x0, const int16_t y0,
                      const int16_t x1, const int16_t y1, const int16_t x2, const int16_t y2,
                      const uint16_t color);
    void drawEllipse(const int screen, const int16_t x, const int16_t y,
                     const int16_t rx, const int16_t ry, const uint16_t color);
    void drawRoundRect(const int screen, const int16_t x, const int16_t y,
                       const int16_t w, const int16_t h, const int16_t r, const uint16_t color);
    void fillRoundRect(const int screen, const int16_t x, const int16_t y,
                       const int16_t w, const int16_t h, const int16_t r, const uint16_t color);

    // ========================================
    // TEXT RENDERING
    // ========================================

    void drawChar(const int screen, const int16_t x, const int16_t y, const char c,
                  const uint16_t color, const uint8_t size = 1, const uint16_t bg = ST7735Colors::BLACK);
    void drawText(const int screen, const int16_t x, const int16_t y, const char* text,
                  const uint16_t color, const uint8_t size = 1, const uint16_t bg = ST7735Colors::BLACK);

    int16_t getTextWidth(const char* text, const uint8_t size = 1) const;
    int16_t getTextHeight(const char* text, const uint8_t size = 1) const;

    struct TextCursor { int16_t x, y; };
    TextCursor getTextCursor(const int16_t startX, const int16_t startY,
                            const char* text, const uint8_t size = 1) const;

    void drawTextWithBackground(const int screen, const int16_t x, const int16_t y,
                               const char* text, const uint16_t textColor,
                               const uint16_t bgColor, const uint8_t size = 1);
    void drawTextCentered(const int screen, const int16_t centerX, const int16_t centerY,
                         const char* text, const uint16_t color, const uint8_t size = 1,
                         const uint16_t bg = ST7735Colors::BLACK);
    void drawTextWithSpacing(const int screen, const int16_t x, const int16_t y, const char* text,
                            const uint16_t color, const uint8_t size, const uint8_t spacing,
                            const uint16_t bg);

    // ========================================
    // DISPLAY UPDATES
    // ========================================

    HOT_FUNCTION void updateDisplay(const int screen);
    HOT_FUNCTION void updateBoth();
    void updateRegion(const int screen, const int16_t x, const int16_t y,
                      const int16_t w, const int16_t h);

    #if ST7735_ENABLE_DUAL_CORE
    void updateDisplayAsync(const int screen);
    void updateBothAsync();
    #endif

    // ========================================
    // COLOR UTILITIES
    // ========================================

    static constexpr uint16_t rgb565(const uint8_t r, const uint8_t g, const uint8_t b) {
        return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
    }

    static constexpr uint8_t red565(const uint16_t color) {
        return (color >> 11) << 3;
    }

    static constexpr uint8_t green565(const uint16_t color) {
        return ((color >> 5) & 0x3F) << 2;
    }

    static constexpr uint8_t blue565(const uint16_t color) {
        return (color & 0x1F) << 3;
    }

    static uint16_t blendColors(const uint16_t color1, const uint16_t color2, const uint8_t alpha);
    static uint16_t interpolateColors(const uint16_t color1, const uint16_t color2, const float t);

    // ========================================
    // BUFFER ACCESS
    // ========================================

    FORCE_INLINE uint16_t* getBuffer(const int screen) {
        return getBufferPtr(screen);
    }

    FORCE_INLINE const uint16_t* getBuffer(const int screen) const {
        return getBufferPtr(screen);
    }

    void copyBuffer(const int srcScreen, const int dstScreen);
    void blendBuffers(const int screen1, const int screen2, const uint8_t alpha);

    // ========================================
    // PERFORMANCE & DIAGNOSTICS
    // ========================================

    FORCE_INLINE const ST7735PerformanceStats& getPerformanceStats() const {
        return stats;
    }

    FORCE_INLINE void resetPerformanceStats() {
        stats.reset();
    }

    void printSystemInfo() const;
    void printPerformanceReport() const;

    // Enhanced system info
    FORCE_INLINE uint32_t getFreeHeap() const {
        return ESP.getFreeHeap();
    }

    uint32_t getUsedPSRAM() const;

    FORCE_INLINE uint32_t getBufferMemoryUsage() const {
        return TRANSFER_BYTES * 2;
    }

    FORCE_INLINE const char* getPlatformName() const {
        return ST7735Platform::getPlatformName();
    }

    FORCE_INLINE bool isDMAEnabled() const {
        return false; // Always false in Arduino SPI version
    }

    FORCE_INLINE bool isDualCoreEnabled() const {
        #if ST7735_ENABLE_DUAL_CORE
        return dualCoreActive;
        #else
        return false;
        #endif
    }

    // New: SIMD capability reporting
    FORCE_INLINE bool isSIMDEnabled() const {
        return ST7735_HAS_SIMD;
    }

    // ========================================
    // TEST PATTERNS
    // ========================================

    void drawTestPattern(const int screen);
    void drawColorBars(const int screen);
    void drawGridPattern(const int screen, const uint16_t color = ST7735Colors::WHITE);
};

#pragma GCC diagnostic pop

TFTFriendESPDuoST7735.cpp:

/**************************************************************
* (Core1D) TFTFriend - ESP32S3 ST7735Duo Graphics Engine
* Implementation: Arduino SPI-Only High-Performance Edition
* --------------------------------------------------------
* 
* Designed & Developed by:
* Sir Ronnie @ Core1D Automation Labs
* 
* Version: 2.0 - BLACKTAB Tested Production Ready
* Build Date: August 2025
* Platform Target: ESP32-S3 DevKit C-1 (Primary Test Kit)
* License: MIT
* 
* ========================================================
* PERFORMANCE COMPARISON:
* ========================================================
* 
* Traditional libraries (Adafruit GFX, TFT_eSPI) vs TFTFriend in out of the box features:
* 
* | Feature                | Adafruit GFX | TFT_eSPI  | TFTFriend |
* |------------------------|--------------|-----------|-----------|
* | Dual Display Support   |     None     |   Manual  |   Native  |
* | Memory Operations      | 8-bit loops  |   16-bit  |   64-bit  |
* | FPS Calculation        |    None      |   Basic   |    Pro    |
* | Platform Optimization  |    Generic   |   Manual  |    Auto   |
* | Startup Reliability    |    Basic     |   Issues  |    OTSC   |
* | Buffer Management      |    Single    |   Single  | Dual+Sync |
* | Error Recovery         |    Minimal   |   Basic   |  Managed  |
*
* Comparison with Traditional Libraries:
* β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
* β”‚ Library         β”‚ Raw FPS  β”‚ Frame Variance  β”‚ Perceived Smoothnessβ”‚
* β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
* β”‚ TFTFriend       β”‚  40-49   β”‚    Β±2ms         β”‚  Butter             β”‚
* β”‚ Adafruit GFX    β”‚  64-66   β”‚    Β±15ms        β”‚Xtremely Inconsistent| 
* β”‚ TFT_eSPI        β”‚  55-70   β”‚    Β±12ms        β”‚  To be tested       β”‚
* β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
* 
* Human Vision Science: The eye is more sensitive to timing 
* consistency than absolute speed. Irregular frame intervals 
* create temporal aliasing perceived as jitter and stuttering.
*
* TFTFriend tackles that by addressing and aligning them in Library with ESP32 as priority platform.
* ========================================================
* TECHNICAL ACKNOWLEDGMENTS:
* ========================================================
* 
* Core Technologies & Inspirations:
* 
* β€’ Arduino SPI Library (Cristian Maglie, 2010)
*   Enhanced by Paul Stoffregen with optimizations
*   Foundation: Rock-solid SPI communication layer
* 
* β€’ Adafruit GFX Library (Limor Fried & Team)
*   Contribution: Font formats and graphics primitives
*   Innovation: Pixel-perfect rendering algorithms
* 
* β€’ TFT_eSPI by Bodmer
*   Contribution: ESP32 optimization techniques
*   Innovation: Platform-specific enhancements
* 
* β€’ FreeRTOS (Amazon Web Services / Real-Time Engineers)
*   Contribution: Dual-core task management
*   Innovation: Professional-grade RTOS capabilities
* 
* β€’ Bresenham's Algorithms (Jack Bresenham, 1965-1977)
*   Foundation: Efficient line and circle rasterization
*   Modern optimization: SIMD-style batch processing
* 
* β€’ STMicroelectronics ST7735 Documentation
*   Foundation: Controller specifications and timing
*   Enhancement: Advanced initialization sequences
* 
* ========================================================
* FOR FURTHER STUDY AND REFERENCES
* ========================================================
* 
* Computer Graphics Theory:
* β€’ "Computer Graphics: Principles and Practice" - Foley, van Dam, et al.
* β€’ "Real-Time Rendering" 4th Edition - MΓΆller, Haines, Hoffman
* β€’ "Game Programming Gems" series - Various contributors
* 
* Memory Optimization Sources:
* β€’ Intel optimization manuals (cache alignment principles)
* β€’ ARM Cortex optimization guides (adapted for Xtensa LX7)
* β€’ "What Every Programmer Should Know About Memory" - Ulrich Drepper
* 
* Real-Time Systems:
* β€’ "Real-Time Systems Design and Analysis" - Burns & Wellings
* β€’ FreeRTOS documentation and implementation guides
* β€’ "Hard Real-Time Computing Systems" - Buttazzo
* 
* ESP32 Platform Documentation:
* β€’ Espressif ESP-IDF Programming Guide
* β€’ Xtensa LX7 Processor Architecture Manual
* β€’ ESP32-S3 Technical Reference Manual
* 
* Frame Pacing and Human Vision:
* β€’ "Digital Video and HD Algorithms and Interfaces" - Poynton
* β€’ IEEE papers on temporal perception and frame rate psychology
* β€’ "The Art and Science of Digital Compositing" - Brinkmann
* 
* GCC Compiler Optimization:
* β€’ GCC Manual: Optimization Options and Attributes
* β€’ "Optimizing C++" - Agner Fog
* β€’ Intel Software Developer Manual (vectorization techniques)
*
* ========================================================
* BUILD INFORMATION:
* ========================================================
* 
* Compiler Optimizations:
* β€’ -O3 aggressive optimization enabled
* β€’ Branch prediction hints
* β€’ Function inlining for critical paths
* β€’ Cache-aligned data structure placement
* β€’ Platform-specific instruction utilization
* 
* Memory Model:
* β€’ PSRAM utilization on ESP32-S3/S2
* β€’ Internal RAM fallback with alignment
* β€’ Stack usage minimization
* β€’ Heap fragmentation prevention
* β€’ Memory leak detection in debug builds
* 
* Quality Assurance:
* β€’ Extensive unit testing coverage for Devkit C1
* β€’ Performance regression testing
* β€’ Memory leak detection and prevention
* β€’ Long-duration stability testing
* 
**************************************************************/

#pragma GCC optimize("O3")
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"

#include "ST7735DualEngine.h"

// ========================================
// CONSTRUCTOR AND LIFECYCLE MANAGEMENT
// ========================================

// Engine Constructor
ST7735DualEngine::ST7735DualEngine() :
    buffer0(nullptr), buffer1(nullptr), transferBuffer(nullptr) {
    // Initialize performance stats
    stats.reset();
    lastStatsUpdate = millis();
    stats.lastResetTime = millis(); // Initialize the reset time
    
#if ST7735_ENABLE_DUAL_CORE
    dualCoreActive = false;
    displayTaskHandle = nullptr;
    commandQueue = nullptr;
    bufferMutex = nullptr;
#endif
}

// Engine Destructor
ST7735DualEngine::~ST7735DualEngine() {
    end();
}

ST7735DualEngine::InitResult ST7735DualEngine::begin() {
    InitResult result = {false, false, false, 0, ST7735Platform::getPlatformName()};
    
    Serial.println("=== ST7735DualEngine v1.3 Arduino SPI-Only DIAGNOSTIC ===");
    Serial.printf("Platform: %s\n", result.platform);
    Serial.printf("Free Heap BEFORE: %u KB\n", ESP.getFreeHeap() / 1024);
    
    #if ST7735_USE_PSRAM
    Serial.printf("PSRAM Total: %u KB\n", ESP.getPsramSize() / 1024);
    Serial.printf("PSRAM Free BEFORE: %u KB\n", ESP.getFreePsram() / 1024);
    #endif

    // STEP 1: Test memory allocation with detailed reporting
    Serial.println("STEP 1: Testing memory allocation...");
    
    Serial.printf("  Allocating buffer0 (%u bytes)...", TRANSFER_BYTES);
    buffer0 = static_cast<uint16_t*>(ST7735Platform::allocateFrameBuffer(TRANSFER_BYTES));
    if (buffer0) {
        Serial.println(" SUCCESS");
    } else {
        Serial.println(" FAILED!");
        return result;
    }
    
    Serial.printf("  Allocating buffer1 (%u bytes)...", TRANSFER_BYTES);
    buffer1 = static_cast<uint16_t*>(ST7735Platform::allocateFrameBuffer(TRANSFER_BYTES));
    if (buffer1) {
        Serial.println(" SUCCESS");
    } else {
        Serial.println(" FAILED!");
        end();
        return result;
    }
    
    Serial.printf("  Allocating transferBuffer (%u bytes)...", TRANSFER_BYTES);
    transferBuffer = static_cast<uint8_t*>(malloc(TRANSFER_BYTES));
    if (transferBuffer) {
        Serial.println("SUCCESS");
    } else {
        Serial.println("FAILED!");
        end();
        return result;
    }

    Serial.printf("Free Heap AFTER allocation: %u KB\n", ESP.getFreeHeap() / 1024);
    #if ST7735_USE_PSRAM
    Serial.printf("PSRAM Free AFTER allocation: %u KB\n", ESP.getFreePsram() / 1024);
    #endif

    // STEP 2: Test pin configuration
    Serial.println("STEP 2: Testing pin configuration...");
    Serial.printf("  Screen0: CS=%d, DC=%d, RST=%d\n", SCREEN0_CS, SCREEN0_DC, SCREEN0_RST);
    Serial.printf("  Screen1: CS=%d, DC=%d, RST=%d\n", SCREEN1_CS, SCREEN1_DC, SCREEN1_RST);
    Serial.printf("  SPI: MOSI=%d, SCLK=%d\n", MOSI, SCLK);
    
    // Test pin availability
    pinMode(SCREEN0_CS, OUTPUT);
    pinMode(SCREEN0_DC, OUTPUT);
    pinMode(SCREEN0_RST, OUTPUT);
    pinMode(SCREEN1_CS, OUTPUT);
    pinMode(SCREEN1_DC, OUTPUT);
    pinMode(SCREEN1_RST, OUTPUT);
    Serial.println("  Pin configuration: SUCCESS");

    // STEP 3: Test SPI initialization
    Serial.println("STEP 3: Testing SPI initialization...");
    result.spiFrequency = ST7735Platform::getOptimalSPIFrequency();
    Serial.printf("  SPI Frequency: %u MHz\n", result.spiFrequency / 1000000);
    
    SPI.begin(SCLK, -1, MOSI, -1);
    SPI.setFrequency(result.spiFrequency);
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);
    SPI.setHwCs(false);
    Serial.println("  SPI initialization: SUCCESS");

    // STEP 4: Test display reset sequence
    Serial.println("STEP 4: Testing display reset sequence...");
    
    Serial.print("Resetting Screen 0...");
    initDisplay(SCREEN0_CS, SCREEN0_DC, SCREEN0_RST);
    Serial.println("SUCCESS");
    delay(ST7735_INIT_STABILITY_DELAY_MS);
    
    Serial.print("Resetting Screen 1...");
    initDisplay(SCREEN1_CS, SCREEN1_DC, SCREEN1_RST);
    Serial.println("SUCCESS");
    delay(ST7735_INIT_STABILITY_DELAY_MS);

    // STEP 5: Test display communication
    Serial.println("STEP 5: Testing display communication...");
    
    Serial.print("  Testing Screen 0 communication...");
    if (validateDisplayResponse(SCREEN0_CS, SCREEN0_DC)) {
        Serial.println("SUCCESS");
    } else {
        Serial.println("FAILED!");
        Serial.println("ERROR: Screen 0 not responding");
        end();
        return result;
    }
    
    Serial.print("Testing Screen 1 communication...");
    if (validateDisplayResponse(SCREEN1_CS, SCREEN1_DC)) {
        Serial.println("SUCCESS");
    } else {
        Serial.println("FAILED!");
        Serial.println("ERROR: Screen 1 not responding");
        end();
        return result;
    }

    // STEP 6: Test dual-core initialization (if enabled)
    #if ST7735_ENABLE_DUAL_CORE
    Serial.println("STEP 6: Testing dual-core initialization...");
    if (ST7735Platform::hasDualCore()) {
        if (initializeDualCore()) {
            result.dualCoreEnabled = true;
            Serial.println("  Dual-core: SUCCESS");
        } else {
            Serial.println("  Dual-core: FAILED (continuing without)");
        }
    } else {
        Serial.println("  Dual-core: Not available");
    }
    #endif

    // STEP 7: Test buffer clearing
    Serial.println("STEP 7: Testing buffer operations...");
    clearBuffer(0, ST7735Colors::BLACK);
    clearBuffer(1, ST7735Colors::BLACK);
    Serial.println("  Buffer clearing: SUCCESS");

    result.success = true;
    
    Serial.println("βœ“ ALL TESTS PASSED - Initialization SUCCESS");
    Serial.printf("Final Free Heap: %u KB\n", ESP.getFreeHeap() / 1024);
    return result;
}

void ST7735DualEngine::end() {
    #if ST7735_ENABLE_DUAL_CORE
    shutdownDualCore();
    #endif
    
    // Clean up memory allocations
    ST7735Platform::deallocateBuffer(buffer0);
    ST7735Platform::deallocateBuffer(buffer1);
    free(transferBuffer); // Simple free for Arduino SPI transfer buffer
    
    buffer0 = nullptr;
    buffer1 = nullptr;
    transferBuffer = nullptr;
    
    SPI.end();
}

// ========================================
// ENHANCED RESET RELIABILITY
// ========================================

bool ST7735DualEngine::ensureProperReset() {
    const uint8_t maxRetries = 3;
    for (uint8_t attempt = 0; attempt < maxRetries; ++attempt) {
        #if ST7735_ENABLE_DEBUG
        if (attempt > 0) {
            Serial.printf("Reset attempt %d of %d\n", attempt + 1, maxRetries);
        }
        #endif

        // Initialize both displays with enhanced sequence
        initDisplay(SCREEN0_CS, SCREEN0_DC, SCREEN0_RST);
        delay(ST7735_INIT_STABILITY_DELAY_MS); // Allow display stabilization
        initDisplay(SCREEN1_CS, SCREEN1_DC, SCREEN1_RST);
        delay(ST7735_INIT_STABILITY_DELAY_MS);

        // Validate both displays are responding correctly
        if (validateDisplayResponse(SCREEN0_CS, SCREEN0_DC) &&
            validateDisplayResponse(SCREEN1_CS, SCREEN1_DC)) {
            #if ST7735_ENABLE_DEBUG
            Serial.println("βœ“ Both displays initialized successfully");
            #endif
            return true;
        }

        // Additional delay before retry
        if (attempt < maxRetries - 1) {
            delay(50);
        }
    }

    #if ST7735_ENABLE_DEBUG
    Serial.println("ERROR: Failed to initialize displays after multiple attempts");
    #endif
    return false;
}

bool ST7735DualEngine::validateDisplayResponse(const uint8_t cs, const uint8_t dc) const {
    // Test display responsiveness by attempting to read display ID
    // This helps catch displays stuck in white screen state
    // Send a safe command that all ST7735 displays should respond to
    writeCommand(0x04, cs, dc); // Read Display ID command
    delay(1); // Allow command processing

    // Test basic communication by setting a known state
    writeCommand(0x13, cs, dc); // Normal Display Mode On
    delay(1);

    // The display should now be in a known good state
    return true; // Simple validation - can be enhanced with actual ID reading
}

// ========================================
// DUAL-CORE IMPLEMENTATION
// ========================================

#if ST7735_ENABLE_DUAL_CORE
bool ST7735DualEngine::initializeDualCore() {
    commandQueue = xQueueCreate(16, sizeof(ST7735TaskMessage)); // Increased queue size
    if (!commandQueue) return false;

    bufferMutex = xSemaphoreCreateMutex();
    if (!bufferMutex) {
        vQueueDelete(commandQueue);
        return false;
    }

    BaseType_t result = xTaskCreatePinnedToCore(
        displayTask,
        "ST7735_Display",
        ST7735_DISPLAY_TASK_STACK,
        this,
        ST7735_TASK_PRIORITY,
        &displayTaskHandle,
        ST7735_DEFAULT_CORE_DISPLAY
    );

    if (result != pdPASS) {
        vSemaphoreDelete(bufferMutex);
        vQueueDelete(commandQueue);
        return false;
    }

    dualCoreActive = true;
    return true;
}

void ST7735DualEngine::shutdownDualCore() {
    if (!dualCoreActive) return;

    // Send shutdown command
    ST7735TaskMessage msg = {ST7735TaskCommand::SHUTDOWN, 0, 0, 0, 0, 0};
    xQueueSend(commandQueue, &msg, portMAX_DELAY);

    // Wait for task to complete
    if (displayTaskHandle) {
        vTaskDelete(displayTaskHandle);
        displayTaskHandle = nullptr;
    }

    // Clean up synchronization objects
    if (bufferMutex) {
        vSemaphoreDelete(bufferMutex);
        bufferMutex = nullptr;
    }

    if (commandQueue) {
        vQueueDelete(commandQueue);
        commandQueue = nullptr;
    }

    dualCoreActive = false;
}

void ST7735DualEngine::displayTask(void* parameter) {
    ST7735DualEngine* engine = static_cast<ST7735DualEngine*>(parameter);
    ST7735TaskMessage msg;
    
    while (true) {
        // Wait for commands from main thread
        if (xQueueReceive(engine->commandQueue, &msg, portMAX_DELAY) == pdTRUE) {
            if (msg.command == ST7735TaskCommand::SHUTDOWN) break;
            
            // Acquire buffer mutex for thread safety
            if (xSemaphoreTake(engine->bufferMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
                uint32_t transferStart = millis();
                bool presented = false; // Track if we actually presented frames
                
                switch (msg.command) {
                    case ST7735TaskCommand::UPDATE_DISPLAY_0:
                        engine->pushColorsFast(engine->buffer0, engine->SCREEN0_CS, engine->SCREEN0_DC);
                        break;
                    case ST7735TaskCommand::UPDATE_DISPLAY_1:
                        engine->pushColorsFast(engine->buffer1, engine->SCREEN1_CS, engine->SCREEN1_DC);
                        break;
                    case ST7735TaskCommand::UPDATE_BOTH:
                        engine->pushColorsFast(engine->buffer0, engine->SCREEN0_CS, engine->SCREEN0_DC);
                        engine->pushColorsFast(engine->buffer1, engine->SCREEN1_CS, engine->SCREEN1_DC);
                        presented = true; // Only count updateBoth as a real frame
                        break;
                    case ST7735TaskCommand::UPDATE_REGION:
                        engine->updateRegion(msg.screen, msg.x, msg.y, msg.w, msg.h);
                        break;
                    default:
                        break;
                }
                
                uint32_t transferTime = millis() - transferStart;
                
                // Only update stats for actual frame presentations
                if (presented) {
                    engine->stats.update(0, transferTime);
                } else {
                    // Just update transfer time without incrementing frame count
                    engine->stats.totalTransferTime += transferTime;
                    engine->stats.peakTransferTime = max(engine->stats.peakTransferTime, transferTime);
                }
                
                xSemaphoreGive(engine->bufferMutex);
            }
        }
    }
    vTaskDelete(nullptr);
}
#endif

// ========================================
// OPTIMIZED GRAPHICS PRIMITIVES
// ========================================

void ST7735DualEngine::clearBuffer(const int screen, const uint16_t color) {
    uint16_t* buffer = getBufferPtr(screen);
    if (UNLIKELY(!buffer)) return;

    uint32_t renderStart = millis();

    #if ST7735_ENABLE_DUAL_CORE
    if (dualCoreActive && xSemaphoreTake(bufferMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
    #endif

    // Optimized clearing using different strategies based on color
    if (LIKELY(color == 0)) {
        // Fast path for black - use memset
        memset(buffer, 0, TRANSFER_BYTES);
    } else {
        // Optimized fill for non-zero colors using 64-bit operations where possible
        const uint64_t quadColor = ((uint64_t)color << 48) | ((uint64_t)color << 32) |
                                   ((uint64_t)color << 16) | color;
        uint64_t* fastPtr = reinterpret_cast<uint64_t*>(buffer);
        const uint32_t quadCount = BUFFER_SIZE / 4;

        // Unrolled loop with 64-bit writes
        uint32_t i = 0;
        for (; i < quadCount - 7; i += 8) {
            fastPtr[i] = quadColor; fastPtr[i + 1] = quadColor;
            fastPtr[i + 2] = quadColor; fastPtr[i + 3] = quadColor;
            fastPtr[i + 4] = quadColor; fastPtr[i + 5] = quadColor;
            fastPtr[i + 6] = quadColor; fastPtr[i + 7] = quadColor;
        }

        // Handle remaining quads
        for (; i < quadCount; ++i) {
            fastPtr[i] = quadColor;
        }

        // Handle remaining pixels (if any)
        for (uint32_t remaining = quadCount * 4; remaining < BUFFER_SIZE; ++remaining) {
            buffer[remaining] = color;
        }
    }

    #if ST7735_ENABLE_DUAL_CORE
        xSemaphoreGive(bufferMutex);
    }
    #endif

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::fillRect(const int screen, const int16_t x, const int16_t y,
                                const int16_t w, const int16_t h, const uint16_t color) {
    uint16_t* buffer = getBufferPtr(screen);
    if (UNLIKELY(!buffer)) return;

    uint32_t renderStart = millis();

    // Enhanced clipping algorithm for bounds safety
    int16_t x1 = x, y1 = y, w1 = w, h1 = h;
    if (UNLIKELY(x1 >= DISPLAY_WIDTH || y1 >= DISPLAY_HEIGHT)) return;
    if (x1 + w1 > DISPLAY_WIDTH) w1 = DISPLAY_WIDTH - x1;
    if (y1 + h1 > DISPLAY_HEIGHT) h1 = DISPLAY_HEIGHT - y1;
    if (x1 < 0) { w1 += x1; x1 = 0; }
    if (y1 < 0) { h1 += y1; y1 = 0; }
    if (UNLIKELY(w1 <= 0 || h1 <= 0)) return;

    #if ST7735_ENABLE_DUAL_CORE
    if (dualCoreActive && xSemaphoreTake(bufferMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
    #endif

    // Ultra-optimized fill with multiple strategies
    if (LIKELY(w1 == DISPLAY_WIDTH && x1 == 0)) {
        // Full-width rectangle - fastest path using 64-bit operations
        const uint64_t quadColor = ((uint64_t)color << 48) | ((uint64_t)color << 32) |
                                   ((uint64_t)color << 16) | color;
        uint64_t* fastPtr = reinterpret_cast<uint64_t*>(&buffer[y1 * DISPLAY_WIDTH]);
        const uint32_t totalQuads = (w1 * h1) / 4;

        // Aggressive unrolling
        uint32_t i = 0;
        for (; i < totalQuads - 15; i += 16) {
            fastPtr[i] = quadColor; fastPtr[i + 1] = quadColor;
            fastPtr[i + 2] = quadColor; fastPtr[i + 3] = quadColor;
            fastPtr[i + 4] = quadColor; fastPtr[i + 5] = quadColor;
            fastPtr[i + 6] = quadColor; fastPtr[i + 7] = quadColor;
            fastPtr[i + 8] = quadColor; fastPtr[i + 9] = quadColor;
            fastPtr[i + 10] = quadColor; fastPtr[i + 11] = quadColor;
            fastPtr[i + 12] = quadColor; fastPtr[i + 13] = quadColor;
            fastPtr[i + 14] = quadColor; fastPtr[i + 15] = quadColor;
        }

        for (; i < totalQuads; ++i) {
            fastPtr[i] = quadColor;
        }

        // Handle remaining pixels
        const uint32_t totalPixels = w1 * h1;
        for (uint32_t remaining = totalQuads * 4; remaining < totalPixels; ++remaining) {
            buffer[y1 * DISPLAY_WIDTH + remaining] = color;
        }

    } else {
        // Row-by-row fill with optimized per-row processing
        const uint64_t quadColor = ((uint64_t)color << 48) | ((uint64_t)color << 32) |
                                   ((uint64_t)color << 16) | color;
        for (int16_t row = 0; row < h1; ++row) {
            uint16_t* lineStart = &buffer[(y1 + row) * DISPLAY_WIDTH + x1];

            if (LIKELY(w1 >= 16)) {
                // Use 64-bit operations for wider rectangles
                uint64_t* fastLine = reinterpret_cast<uint64_t*>(lineStart);
                const int16_t fastCols = w1 / 4;

                int16_t col = 0;
                for (; col < fastCols - 7; col += 8) {
                    fastLine[col    ] = quadColor; fastLine[col + 1] = quadColor;
                    fastLine[col + 2] = quadColor; fastLine[col + 3] = quadColor;
                    fastLine[col + 4] = quadColor; fastLine[col + 5] = quadColor;
                    fastLine[col + 6] = quadColor; fastLine[col + 7] = quadColor;
                }

                for (; col < fastCols; ++col) {
                    fastLine[col] = quadColor;
                }

                // Handle remaining pixels in the row
                for (int16_t remaining = fastCols * 4; remaining < w1; ++remaining) {
                    lineStart[remaining] = color;
                }

            } else {
                // Simple loop for narrow rectangles
                for (int16_t col = 0; col < w1; ++col) {
                    lineStart[col] = color;
                }
            }
        }
    }

    #if ST7735_ENABLE_DUAL_CORE
        xSemaphoreGive(bufferMutex);
    }
    #endif

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::drawRect(const int screen, const int16_t x, const int16_t y,
                                const int16_t w, const int16_t h, const uint16_t color) {
    // Optimized outline using horizontal and vertical lines
    drawHLine(screen, x,         y,         w, color); // Top
    drawHLine(screen, x,         y + h - 1, w, color); // Bottom
    drawVLine(screen, x,         y,         h, color); // Left
    drawVLine(screen, x + w - 1, y,         h, color); // Right
}

// ========================================
// ENHANCED HARDWARE INTERFACE
// ========================================

FAST_CODE_ATTR void ST7735DualEngine::writeCommand(const uint8_t cmd, const uint8_t cs, const uint8_t dc) const {
    digitalWrite(dc, LOW); // Command mode
    digitalWrite(cs, LOW);
    SPI.transfer(cmd);
    digitalWrite(cs, HIGH);
}

FAST_CODE_ATTR void ST7735DualEngine::writeData(const uint8_t data, const uint8_t cs, const uint8_t dc) const {
    digitalWrite(dc, HIGH); // Data mode
    digitalWrite(cs, LOW);
    SPI.transfer(data);
    digitalWrite(cs, HIGH);
}

FAST_CODE_ATTR void ST7735DualEngine::setWindow(const uint16_t x0, const uint16_t y0, const uint16_t x1, const uint16_t y1,
                                                const uint8_t cs, const uint8_t dc) const {
    // ST7735 windowing commands for efficient partial updates
    writeCommand(0x2A, cs, dc); // Column Address Set
    writeData(x0 >> 8, cs, dc);
    writeData(x0 & 0xFF, cs, dc);
    writeData(x1 >> 8, cs, dc);
    writeData(x1 & 0xFF, cs, dc);
    
    writeCommand(0x2B, cs, dc); // Page Address Set
    writeData(y0 >> 8, cs, dc);
    writeData(y0 & 0xFF, cs, dc);
    writeData(y1 >> 8, cs, dc);
    writeData(y1 & 0xFF, cs, dc);
    
    writeCommand(0x2C, cs, dc); // Memory Write
}

void ST7735DualEngine::initDisplay(const uint8_t cs, const uint8_t dc, const uint8_t rst) const {
    // Configure GPIO pins
    pinMode(cs, OUTPUT);
    pinMode(dc, OUTPUT);
    pinMode(rst, OUTPUT);
    digitalWrite(cs, HIGH);
    digitalWrite(dc, HIGH);

    // Enhanced hardware reset sequence for reliability
    digitalWrite(rst, HIGH);
    delay(10); // Ensure clean high state
    digitalWrite(rst, LOW);
    delay(ST7735_RESET_DELAY_MS); // Hold reset longer
    digitalWrite(rst, HIGH);
    delay(ST7735_WAKE_DELAY_MS); // Wait longer for stability

    // Optimized ST7735 initialization sequence with enhanced reliability
    writeCommand(0x01, cs, dc); // Software Reset
    delay(120); // Wait for reset completion
    
    writeCommand(0x11, cs, dc); // Sleep Out
    delay(120); // Wait for sleep out

    // Critical: Set pixel format first to establish communication
    writeCommand(0x3A, cs, dc); // Pixel Format Set
    writeData(0x05, cs, dc); // 16-bit RGB565
    delay(10); // Allow format setting

    // Memory data access control (MADCTL)
    writeCommand(0x36, cs, dc); // Memory Access Control
    writeData(0xC8, cs, dc); // Row/column address order / For RGB/BGR mismatch, alternate hex is 0xC0 for RGB
    delay(5);

    // Frame rate control
    writeCommand(0xB1, cs, dc); // Frame Rate Control (Normal Mode)
    writeData(0x01, cs, dc); // RTNA
    writeData(0x2C, cs, dc); // FPA
    writeData(0x2D, cs, dc); // BPA

    writeCommand(0xB2, cs, dc); // Frame Rate Control (Idle Mode)
    writeData(0x01, cs, dc);
    writeData(0x2C, cs, dc);
    writeData(0x2D, cs, dc);

    writeCommand(0xB3, cs, dc); // Frame Rate Control (Partial Mode)
    writeData(0x01, cs, dc);
    writeData(0x2C, cs, dc);
    writeData(0x2D, cs, dc);
    writeData(0x01, cs, dc);
    writeData(0x2C, cs, dc);
    writeData(0x2D, cs, dc);

    writeCommand(0xB4, cs, dc); // Display Inversion Control
    writeData(0x07, cs, dc); // 3-bit

    // Power control
    writeCommand(0xC0, cs, dc); // Power Control 1
    writeData(0xA2, cs, dc);
    writeData(0x02, cs, dc);
    writeData(0x84, cs, dc);

    writeCommand(0xC1, cs, dc); // Power Control 2
    writeData(0xC5, cs, dc);

    writeCommand(0xC2, cs, dc); // Power Control 3 (Normal Mode)
    writeData(0x0A, cs, dc);
    writeData(0x00, cs, dc);

    writeCommand(0xC3, cs, dc); // Power Control 4 (Idle Mode)
    writeData(0x8A, cs, dc);
    writeData(0x2A, cs, dc);

    writeCommand(0xC4, cs, dc); // Power Control 5 (Partial Mode)
    writeData(0x8A, cs, dc);
    writeData(0xEE, cs, dc);

    writeCommand(0xC5, cs, dc); // VCOM Control 1
    writeData(0x0E, cs, dc);

    writeCommand(0x20, cs, dc); // Display Inversion Off
    delay(10); // Allow inversion setting

    // Gamma correction
    writeCommand(0xE0, cs, dc); // Gamma Positive Polarity Correction
    writeData(0x02, cs, dc); writeData(0x1C, cs, dc); writeData(0x07, cs, dc); writeData(0x12, cs, dc);
    writeData(0x37, cs, dc); writeData(0x32, cs, dc); writeData(0x29, cs, dc); writeData(0x2D, cs, dc);
    writeData(0x29, cs, dc); writeData(0x25, cs, dc); writeData(0x2B, cs, dc); writeData(0x39, cs, dc);
    writeData(0x00, cs, dc); writeData(0x01, cs, dc); writeData(0x03, cs, dc); writeData(0x10, cs, dc);

    writeCommand(0xE1, cs, dc); // Gamma Negative Polarity Correction
    writeData(0x03, cs, dc); writeData(0x1D, cs, dc); writeData(0x07, cs, dc); writeData(0x06, cs, dc);
    writeData(0x2E, cs, dc); writeData(0x2C, cs, dc); writeData(0x29, cs, dc); writeData(0x2D, cs, dc);
    writeData(0x2E, cs, dc); writeData(0x2E, cs, dc); writeData(0x37, cs, dc); writeData(0x3F, cs, dc);
    writeData(0x00, cs, dc); writeData(0x00, cs, dc); writeData(0x02, cs, dc); writeData(0x10, cs, dc);

    writeCommand(0x13, cs, dc); // Normal Display Mode On
    delay(10);

    // Clear display to black to prevent white screen issue
    setWindow(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, cs, dc);
    digitalWrite(dc, HIGH); // Data mode
    digitalWrite(cs, LOW);
    for (uint32_t i = 0; i < BUFFER_SIZE; ++i) {
        SPI.transfer(0x00); // Black pixel high byte
        SPI.transfer(0x00); // Black pixel low byte
    }
    digitalWrite(cs, HIGH);

    writeCommand(0x29, cs, dc); // Display On
    delay(100); // Final stabilization delay
}

FAST_CODE_ATTR void ST7735DualEngine::pushColorsFast(const uint16_t* buffer, const uint8_t cs, const uint8_t dc) const {
    // Set full screen window
    setWindow(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, cs, dc);

    // Ultra-fast conversion with unrolled loops - THIS IS THE KEY PERFORMANCE GAIN
    uint8_t* txBuffer = transferBuffer;
    const uint16_t* src = buffer;

    // Ultra-fast conversion with unrolled loops
    for (uint32_t i = 0; i < BUFFER_SIZE; i += 8) {
        const uint16_t c0 = src[i]; const uint16_t c1 = src[i + 1];
        const uint16_t c2 = src[i + 2]; const uint16_t c3 = src[i + 3];
        const uint16_t c4 = src[i + 4]; const uint16_t c5 = src[i + 5];
        const uint16_t c6 = src[i + 6]; const uint16_t c7 = src[i + 7];

        uint8_t* dest = &txBuffer[i * 2];
        dest[0]  = c0 >> 8; dest[1]  = c0 & 0xFF;
        dest[2]  = c1 >> 8; dest[3]  = c1 & 0xFF;
        dest[4]  = c2 >> 8; dest[5]  = c2 & 0xFF;
        dest[6]  = c3 >> 8; dest[7]  = c3 & 0xFF;
        dest[8]  = c4 >> 8; dest[9]  = c4 & 0xFF;
        dest[10] = c5 >> 8; dest[11] = c5 & 0xFF;
        dest[12] = c6 >> 8; dest[13] = c6 & 0xFF;
        dest[14] = c7 >> 8; dest[15] = c7 & 0xFF;
    }

    // High-speed bulk Arduino SPI transfer
    digitalWrite(dc, HIGH); // Data mode
    digitalWrite(cs, LOW);
    SPI.transferBytes(txBuffer, nullptr, TRANSFER_BYTES);
    digitalWrite(cs, HIGH);
}

// ========================================
// ADVANCED GRAPHICS PRIMITIVES
// ========================================

void ST7735DualEngine::drawLine(const int screen, const int16_t x0, const int16_t y0,
                                const int16_t x1, const int16_t y1, const uint16_t color) {
    // Enhanced Bresenham's line algorithm with optimizations
    const int16_t dx = abs(x1 - x0);
    const int16_t dy = abs(y1 - y0);

    // Fast paths for horizontal and vertical lines
    if (UNLIKELY(dx == 0)) {
        drawVLine(screen, x0, min(y0, y1), dy + 1, color);
        return;
    }

    if (UNLIKELY(dy == 0)) {
        drawHLine(screen, min(x0, x1), y0, dx + 1, color);
        return;
    }

    // Optimized Bresenham with reduced calculations
    const int16_t sx = (x0 < x1) ? 1 : -1;
    const int16_t sy = (y0 < y1) ? 1 : -1;
    int16_t err = dx - dy;
    int16_t x = x0, y = y0;

    uint32_t renderStart = millis();

    // Main loop with unrolled inner operations
    while (true) {
        setPixel(screen, x, y, color);
        if (UNLIKELY(x == x1 && y == y1)) break;

        const int16_t e2 = err << 1;
        if (e2 > -dy) {
            err -= dy;
            x += sx;
        }
        if (e2 < dx) {
            err += dx;
            y += sy;
        }
    }

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::drawCircle(const int screen, const int16_t x, const int16_t y,
                                  const int16_t r, const uint16_t color) {
    // Optimized Bresenham circle algorithm
    int16_t f = 1 - r;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * r;
    int16_t px = 0;
    int16_t py = r;

    uint32_t renderStart = millis();

    // Draw cardinal points
    setPixel(screen, x,     y + r, color);
    setPixel(screen, x,     y - r, color);
    setPixel(screen, x + r, y,     color);
    setPixel(screen, x - r, y,     color);

    while (px < py) {
        if (f >= 0) {
            py--;
            ddF_y += 2;
            f += ddF_y;
        }
        px++;
        ddF_x += 2;
        f += ddF_x;

        // Draw 8-way symmetry points with optimal ordering
        setPixel(screen, x + px, y + py, color);
        setPixel(screen, x - px, y + py, color);
        setPixel(screen, x + px, y - py, color);
        setPixel(screen, x - px, y - py, color);
        setPixel(screen, x + py, y + px, color);
        setPixel(screen, x - py, y + px, color);
        setPixel(screen, x + py, y - px, color);
        setPixel(screen, x - py, y - px, color);
    }

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::fillCircle(const int screen, const int16_t x, const int16_t y,
                                  const int16_t r, const uint16_t color) {
    uint32_t renderStart = millis();

    int16_t     f = 1 - r;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * r;
    int16_t    px = 0;
    int16_t    py = r;

    // Draw the initial horizontal line at top and bottom
    drawHLine(screen, x - r, y, 2 * r + 1, color);

    while (px < py) {
        if (f >= 0) {
            py--;
            ddF_y += 2;
            f += ddF_y;
        }
        px++;
        ddF_x += 2;
        f += ddF_x;

        if (px <= py) {
            drawHLine(screen, x - px, y + py, 2 * px + 1, color);
            drawHLine(screen, x - px, y - py, 2 * px + 1, color);
        }
        if (px < py) {
            drawHLine(screen, x - py, y + px, 2 * py + 1, color);
            drawHLine(screen, x - py, y - px, 2 * py + 1, color);
        }
    }

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::drawTriangle(const int screen, const int16_t x0, const int16_t y0,
                                    const int16_t x1, const int16_t y1, const int16_t x2, const int16_t y2,
                                    const uint16_t color) {
    // Draw triangle outline using three lines
    drawLine(screen, x0, y0, x1, y1, color);
    drawLine(screen, x1, y1, x2, y2, color);
    drawLine(screen, x2, y2, x0, y0, color);
}

void ST7735DualEngine::fillTriangle(const int screen, const int16_t x0, const int16_t y0,
                                    const int16_t x1, const int16_t y1, const int16_t x2, const int16_t y2,
                                    const uint16_t color) {
    uint32_t renderStart = millis();

    // Sort vertices by Y coordinate (bubble sort for simplicity with 3 elements)
    int16_t sx0 = x0, sy0 = y0;
    int16_t sx1 = x1, sy1 = y1;
    int16_t sx2 = x2, sy2 = y2;

    // Sort by Y coordinate
    if (sy0 > sy1) {
        int16_t temp;
        temp = sx0; sx0 = sx1; sx1 = temp;
        temp = sy0; sy0 = sy1; sy1 = temp;
    }
    if (sy1 > sy2) {
        int16_t temp;
        temp = sx1; sx1 = sx2; sx2 = temp;
        temp = sy1; sy1 = sy2; sy2 = temp;
    }
    if (sy0 > sy1) {
        int16_t temp;
        temp = sx0; sx0 = sx1; sx1 = temp;
        temp = sy0; sy0 = sy1; sy1 = temp;
    }

    // Handle degenerate cases
    if (UNLIKELY(sy0 == sy2)) {
        // All points on same horizontal line
        int16_t minX = min(sx0, min(sx1, sx2));
        int16_t maxX = max(sx0, max(sx1, sx2));
        drawHLine(screen, minX, sy0, maxX - minX + 1, color);
        return;
    }

    // Calculate slopes for triangle edges
    const int32_t dx01 = sx1 - sx0;
    const int32_t dy01 = sy1 - sy0;
    const int32_t dx02 = sx2 - sx0;
    const int32_t dy02 = sy2 - sy0;
    const int32_t dx12 = sx2 - sx1;
    const int32_t dy12 = sy2 - sy1;

    // Fill upper triangle part
    for (int16_t y = sy0; y <= sy1; ++y) {
        int16_t xa, xb;
        if (dy01 != 0) {
            xa = sx0 + ((int32_t)(y - sy0) * dx01) / dy01;
        } else {
            xa = sx0;
        }
        if (dy02 != 0) {
            xb = sx0 + ((int32_t)(y - sy0) * dx02) / dy02;
        } else {
            xb = sx0;
        }
        if (xa > xb) {
            int16_t temp = xa;
            xa = xb;
            xb = temp;
        }
        if (xb > xa) {
            drawHLine(screen, xa, y, xb - xa + 1, color);
        }
    }

    // Fill lower triangle part
    for (int16_t y = sy1 + 1; y <= sy2; ++y) {
        int16_t xa, xb;
        if (dy12 != 0) {
            xa = sx1 + ((int32_t)(y - sy1) * dx12) / dy12;
        } else {
            xa = sx1;
        }
        if (dy02 != 0) {
            xb = sx0 + ((int32_t)(y - sy0) * dx02) / dy02;
        } else {
            xb = sx0;
        }
        if (xa > xb) {
            int16_t temp = xa;
            xa = xb;
            xb = temp;
        }
        if (xb > xa) {
            drawHLine(screen, xa, y, xb - xa + 1, color);
        }
    }

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::drawEllipse(const int screen, const int16_t x, const int16_t y,
                                   const int16_t rx, const int16_t ry, const uint16_t color) {
    // Bresenham's ellipse algorithm
    int32_t rxSq = (int32_t)rx * rx;
    int32_t rySq = (int32_t)ry * ry;
    int32_t x1 = 0, y1 = ry;
    int32_t px = 0, py = 2 * rxSq * y1;

    uint32_t renderStart = millis();

    // Plot initial points
    setPixel(screen, x + x1, y + y1, color);
    setPixel(screen, x - x1, y + y1, color);
    setPixel(screen, x + x1, y - y1, color);
    setPixel(screen, x - x1, y - y1, color);

    // Region 1
    int32_t p = rySq - rxSq * ry + rxSq / 4;
    while (px < py) {
        x1++;
        px += 2 * rySq;
        if (p < 0) {
            p += rySq + px;
        } else {
            y1--;
            py -= 2 * rxSq;
            p += rySq + px - py;
        }

        setPixel(screen, x + x1, y + y1, color);
        setPixel(screen, x - x1, y + y1, color);
        setPixel(screen, x + x1, y - y1, color);
        setPixel(screen, x - x1, y - y1, color);
    }

    // Region 2
    p = rySq * (x1 + 1/2) * (x1 + 1/2) + rxSq * (y1 - 1) * (y1 - 1) - rxSq * rySq;
    while (y1 > 0) {
        y1--;
        py -= 2 * rxSq;
        if (p > 0) {
            p += rxSq - py;
        } else {
            x1++;
            px += 2 * rySq;
            p += rxSq - py + px;
        }

        setPixel(screen, x + x1, y + y1, color);
        setPixel(screen, x - x1, y + y1, color);
        setPixel(screen, x + x1, y - y1, color);
        setPixel(screen, x - x1, y - y1, color);
    }

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::drawRoundRect(const int screen, const int16_t x, const int16_t y,
                                     const int16_t w, const int16_t h, const int16_t r, const uint16_t color) {
    // Clamp radius to reasonable values
    int16_t radius = min(r, min((int16_t)(w/2), (int16_t)(h/2)));
    if (radius <= 0) {
        drawRect(screen, x, y, w, h, color);
        return;
    }

    uint32_t renderStart = millis();

    // Draw straight edges
    drawHLine(screen, x + radius, y,          w - 2*radius, color); // Top
    drawHLine(screen, x + radius, y + h - 1,  w - 2*radius, color); // Bottom
    drawVLine(screen, x,          y + radius, h - 2*radius, color); // Left
    drawVLine(screen, x + w - 1,  y + radius, h - 2*radius, color); // Right

    // Draw rounded corners using circle algorithm
    int16_t     f = 1 - radius;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * radius;
    int16_t    px = 0;
    int16_t    py = radius;

    while (px < py) {
        if (f >= 0) {
            py--;
            ddF_y += 2;
            f += ddF_y;
        }
        px++;
        ddF_x += 2;
        f += ddF_x;

        // Draw corner pixels
        setPixel(screen, x + radius - px, y + radius - py, color); // Top-left
        setPixel(screen, x + w - radius - 1 + px, y + radius - py, color); // Top-right
        setPixel(screen, x + radius - px, y + h - radius - 1 + py, color); // Bottom-left
        setPixel(screen, x + w - radius - 1 + px, y + h - radius - 1 + py, color); // Bottom-right
        setPixel(screen, x + radius - py, y + radius - px, color); // Top-left
        setPixel(screen, x + w - radius - 1 + py, y + radius - px, color); // Top-right
        setPixel(screen, x + radius - py, y + h - radius - 1 + px, color); // Bottom-left
        setPixel(screen, x + w - radius - 1 + py, y + h - radius - 1 + px, color); // Bottom-right
    }

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::fillRoundRect(const int screen, const int16_t x, const int16_t y,
                                     const int16_t w, const int16_t h, const int16_t r,
                                     const uint16_t color) {
    // Fix for square holes in rounded rectangles
    int16_t radius = min(r, min((int16_t)(w/2), (int16_t)(h/2)));
    if (radius <= 0) {
        fillRect(screen, x, y, w, h, color);
        return;
    }

    uint32_t renderStart = millis();

    // Fill the center rectangle
    fillRect(screen, x + radius, y, w - 2*radius, h, color);
    // Fill the side rectangles to connect with rounded corners
    fillRect(screen, x, y + radius, radius, h - 2*radius, color); // Left side
    fillRect(screen, x + w - radius, y + radius, radius, h - 2*radius, color); // Right side

    // Round Corner Fill
    int16_t f = 1 - radius;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * radius;
    int16_t px = 0;
    int16_t py = radius;

    while (px < py) {
        if (f >= 0) {
            py--;
            ddF_y += 2;
            f += ddF_y;
        }
        px++;
        ddF_x += 2;
        f += ddF_x;

        // Fill corner pixels to prevent holes
        // Top corners
        for (int16_t i = x + radius - px; i <= x + radius + px + w - 2*radius - 1; i++) {
            setPixel(screen, i, y + radius - py, color);
        }
        for (int16_t i = x + radius - py; i <= x + radius + py + w - 2*radius - 1; i++) {
            setPixel(screen, i, y + radius - px, color);
        }

        // Bottom corners
        for (int16_t i = x + radius - px; i <= x + radius + px + w - 2*radius - 1; i++) {
            setPixel(screen, i, y + h - radius - 1 + py, color);
        }
        for (int16_t i = x + radius - py; i <= x + radius + py + w - 2*radius - 1; i++) {
            setPixel(screen, i, y + h - radius - 1 + px, color);
        }
    }

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

// ========================================
// TEXT RENDERING SYSTEM
// ========================================

void ST7735DualEngine::drawChar(const int screen, const int16_t x, const int16_t y, const char c,
                                const uint16_t color, const uint8_t size, const uint16_t bg) {
    // Fixed character mapping for standard ASCII set
    uint8_t fontIndex = 0;
    if (c >= 32 && c <= 126) {
        // Direct mapping for ASCII 32-126 (printable characters)
        fontIndex = c - 32;
    } else {
        // Default to space for unsupported characters
        fontIndex = 0;
    }

    // Bounds check for font array - the font array has 95 characters (32-126)
    if (UNLIKELY(fontIndex >= 95)) {
        fontIndex = 0;
    }

    uint32_t renderStart = millis();

    // Optimized character rendering
    if (LIKELY(size == 1)) {
        // Fast path for size 1 characters
        for (uint8_t col = 0; col < ST7735Specs::FONT_WIDTH; ++col) {
            const uint8_t line = pgm_read_byte(&font5x7_standard[fontIndex][col]);
            for (uint8_t row = 0; row < ST7735Specs::FONT_HEIGHT; ++row) {
                const bool pixelOn = line & (1 << row);
                setPixel(screen, x + col, y + row, pixelOn ? color : bg);
            }
        }
    } else {
        // Scaled character rendering using fillRect for efficiency
        for (uint8_t col = 0; col < ST7735Specs::FONT_WIDTH; ++col) {
            const uint8_t line = pgm_read_byte(&font5x7_standard[fontIndex][col]);
            for (uint8_t row = 0; row < ST7735Specs::FONT_HEIGHT; ++row) {
                const bool pixelOn = line & (1 << row);
                const uint16_t pixelColor = pixelOn ? color : bg;
                fillRect(screen, x + col * size, y + row * size, size, size, pixelColor);
            }
        }
    }

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

void ST7735DualEngine::drawText(const int screen, const int16_t x, const int16_t y, const char* text,
                                const uint16_t color, const uint8_t size, const uint16_t bg) {
    if (UNLIKELY(!text)) return;

    int16_t cursorX = x;
    int16_t cursorY = y;
    const int16_t charWidth = ST7735Specs::FONT_WIDTH * size;
    const int16_t charHeight = ST7735Specs::FONT_HEIGHT * size;
    const int16_t lineHeight = ST7735Specs::FONT_LINE_HEIGHT * size;

    // Add bounds checking for text rendering
    if (UNLIKELY(cursorY >= DISPLAY_HEIGHT || cursorY + charHeight < 0)) return;

    while (*text) {
        if (*text == '\n') {
            // Move to next line
            cursorX = x;
            cursorY += lineHeight;
            // Check if we've moved beyond display bounds
            if (cursorY >= DISPLAY_HEIGHT) break;
        } else if (*text == '\r') {
            // Carriage return - move to beginning of current line
            cursorX = x;
        } else if (*text == '\t') {
            // Tab character - move to next tab stop (every 4 character widths)
            int16_t tabStop = ((cursorX - x) / (charWidth * 4) + 1) * (charWidth * 4);
            cursorX = x + tabStop;
            // Handle tab overflow
            if (cursorX >= DISPLAY_WIDTH) {
                cursorX = x;
                cursorY += lineHeight;
                if (cursorY >= DISPLAY_HEIGHT) break;
            }
        } else if (*text >= 32 && *text <= 126) {
            // Printable character
            // Check horizontal bounds before drawing
            if (cursorX + charWidth > 0 && cursorX < DISPLAY_WIDTH) {
                drawChar(screen, cursorX, cursorY, *text, color, size, bg);
            }
            cursorX += charWidth;
            // Simple word wrap for long text
            if (cursorX + charWidth > DISPLAY_WIDTH) {
                cursorX = x;
                cursorY += lineHeight;
                // Check if we've moved beyond display bounds
                if (cursorY >= DISPLAY_HEIGHT) break;
            }
        }
        // Skip non-printable characters (except newline, return, tab)
        ++text;
    }
}

// ========================================
// TEXT RENDERING HELPERS
// ========================================

int16_t ST7735DualEngine::getTextWidth(const char* text, const uint8_t size) const {
    if (!text) return 0;

    int16_t maxWidth = 0;
    int16_t currentWidth = 0;
    const int16_t charWidth = ST7735Specs::FONT_WIDTH * size;

    while (*text) {
        if (*text == '\n') {
            // Newline - record max width and reset current
            maxWidth = max(maxWidth, currentWidth);
            currentWidth = 0;
        } else if (*text == '\r') {
            // Carriage return - reset current line width
            currentWidth = 0;
        } else if (*text == '\t') {
            // Tab - calculate tab stop
            int16_t tabStop = ((currentWidth) / (charWidth * 4) + 1) * (charWidth * 4);
            currentWidth = tabStop;
        } else if (*text >= 32 && *text <= 126) {
            // Printable character
            currentWidth += charWidth;
        }
        // Skip non-printable characters
        ++text;
    }

    // Return the maximum width encountered
    return max(maxWidth, currentWidth);
}

int16_t ST7735DualEngine::getTextHeight(const char* text, const uint8_t size) const {
    if (!text) return ST7735Specs::FONT_LINE_HEIGHT * size;

    int16_t lines = 1; // Start with 1 line
    const char* ptr = text;

    while (*ptr) {
        if (*ptr == '\n') {
            lines++;
        }
        ++ptr;
    }

    return lines * ST7735Specs::FONT_LINE_HEIGHT * size;
}

ST7735DualEngine::TextCursor ST7735DualEngine::getTextCursor(const int16_t startX, const int16_t startY,
                                                             const char* text, const uint8_t size) const {
    TextCursor cursor = {startX, startY};
    if (!text) return cursor;

    const int16_t charWidth = ST7735Specs::FONT_WIDTH * size;
    const int16_t lineHeight = ST7735Specs::FONT_LINE_HEIGHT * size;

    while (*text) {
        if (*text == '\n') {
            cursor.x = startX;
            cursor.y += lineHeight;
        } else if (*text == '\r') {
            cursor.x = startX;
        } else if (*text == '\t') {
            int16_t tabStop = ((cursor.x - startX) / (charWidth * 4) + 1) * (charWidth * 4);
            cursor.x = startX + tabStop;
            if (cursor.x >= DISPLAY_WIDTH) {
                cursor.x = startX;
                cursor.y += lineHeight;
            }
        } else if (*text >= 32 && *text <= 126) {
            cursor.x += charWidth;
            if (cursor.x + charWidth > DISPLAY_WIDTH) {
                cursor.x = startX;
                cursor.y += lineHeight;
            }
        }
        ++text;
    }

    return cursor;
}

void ST7735DualEngine::drawTextWithBackground(const int screen, const int16_t x, const int16_t y,
                                              const char* text, const uint16_t textColor,
                                              const uint16_t bgColor, const uint8_t size) {
    if (!text) return;

    // Calculate text dimensions
    int16_t textWidth = getTextWidth(text, size);
    int16_t textHeight = getTextHeight(text, size);

    // Clear background first
    fillRect(screen, x, y, textWidth, textHeight, bgColor);

    // Draw text
    drawText(screen, x, y, text, textColor, size, bgColor);
}

void ST7735DualEngine::drawTextCentered(const int screen, const int16_t centerX, const int16_t centerY,
                                        const char* text, const uint16_t color, const uint8_t size,
                                        const uint16_t bg) {
    if (!text) return;

    int16_t textWidth = getTextWidth(text, size);
    int16_t textHeight = getTextHeight(text, size);
    int16_t x = centerX - textWidth / 2;
    int16_t y = centerY - textHeight / 2;

    drawText(screen, x, y, text, color, size, bg);
}

void ST7735DualEngine::drawTextWithSpacing(const int screen, const int16_t x, const int16_t y, const char* text,
                                           const uint16_t color, const uint8_t size, const uint8_t spacing,
                                           const uint16_t bg) {
    if (UNLIKELY(!text)) return;
    
    int16_t cursorX = x;
    int16_t cursorY = y;
    const int16_t charWidth = ST7735Specs::FONT_WIDTH * size;
    const int16_t charHeight = ST7735Specs::FONT_HEIGHT * size;
    const int16_t lineHeight = ST7735Specs::FONT_LINE_HEIGHT * size;
    
    // Add bounds checking for text rendering
    if (UNLIKELY(cursorY >= DISPLAY_HEIGHT || cursorY + charHeight < 0)) return;
    
    while (*text) {
        if (*text == '\n') {
            // Move to next line
            cursorX = x;
            cursorY += lineHeight;
            // Check if we've moved beyond display bounds
            if (cursorY >= DISPLAY_HEIGHT) break;
        } else if (*text == '\r') {
            // Carriage return - move to beginning of current line
            cursorX = x;
        } else if (*text == '\t') {
            // Tab character - move to next tab stop (every 4 character widths)
            int16_t tabStop = ((cursorX - x) / (charWidth * 4) + 1) * (charWidth * 4);
            cursorX = x + tabStop;
            // Handle tab overflow
            if (cursorX >= DISPLAY_WIDTH) {
                cursorX = x;
                cursorY += lineHeight;
                if (cursorY >= DISPLAY_HEIGHT) break;
            }
        } else if (*text >= 32 && *text <= 126) {
            // Printable character
            // Check horizontal bounds before drawing
            if (cursorX + charWidth > 0 && cursorX < DISPLAY_WIDTH) {
                drawChar(screen, cursorX, cursorY, *text, color, size, bg);
            }
            
            cursorX += charWidth + spacing; // Add custom spacing between characters
            
            // Simple word wrap for long text
            if (cursorX + charWidth > DISPLAY_WIDTH) {
                cursorX = x;
                cursorY += lineHeight;
                // Check if we've moved beyond display bounds
                if (cursorY >= DISPLAY_HEIGHT) break;
            }
        }
        // Skip non-printable characters (except newline, return, tab)
        ++text;
    }
}

// ========================================
// DISPLAY UPDATE SYSTEM
// ========================================

void ST7735DualEngine::updateDisplay(const int screen) {
    const uint8_t cs = LIKELY(screen == 0) ? SCREEN0_CS : SCREEN1_CS;
    const uint8_t dc = LIKELY(screen == 0) ? SCREEN0_DC : SCREEN1_DC;
    const uint16_t* buffer = getBufferPtr(screen);
    if (UNLIKELY(!buffer)) return;

    uint32_t transferStart = millis();

    // Always use Arduino SPI - no DMA in this version
    pushColorsFast(buffer, cs, dc);

    uint32_t transferTime = millis() - transferStart;
    stats.update(0, transferTime);
}

void ST7735DualEngine::updateBoth() {
    uint32_t renderStart = millis();
    
    updateDisplay(0);
    updateDisplay(1);
    
    uint32_t renderTime = millis() - renderStart;
    
    // Count this as one presented frame with proper timing
    stats.update(0, renderTime); // renderTime includes both display updates
}

void ST7735DualEngine::updateRegion(const int screen, const int16_t x, const int16_t y,
                                    const int16_t w, const int16_t h) {
    // Validate screen parameter
    if (UNLIKELY(screen < 0 || screen > 1)) return;

    const uint8_t cs = LIKELY(screen == 0) ? SCREEN0_CS : SCREEN1_CS;
    const uint8_t dc = LIKELY(screen == 0) ? SCREEN0_DC : SCREEN1_DC;
    const uint16_t* buffer = getBufferPtr(screen);
    if (UNLIKELY(!buffer)) return;

    // Clamp region to display bounds
    int16_t x1 = max((int16_t)0, min((int16_t)(DISPLAY_WIDTH - 1), x));
    int16_t y1 = max((int16_t)0, min((int16_t)(DISPLAY_HEIGHT - 1), y));
    int16_t x2 = max((int16_t)0, min((int16_t)(DISPLAY_WIDTH - 1), (int16_t)(x + w - 1)));
    int16_t y2 = max((int16_t)0, min((int16_t)(DISPLAY_HEIGHT - 1), (int16_t)(y + h - 1)));

    if (UNLIKELY(x1 >= x2 || y1 >= y2)) return; // Invalid region

    uint32_t transferStart = millis();

    // Set display window for the specific region
    setWindow(x1, y1, x2, y2, cs, dc);

    // Calculate region dimensions
    const int16_t regionWidth = x2 - x1 + 1;
    const int16_t regionHeight = y2 - y1 + 1;
    const uint32_t regionPixels = regionWidth * regionHeight;
    const uint32_t regionBytes = regionPixels * 2; // 2 bytes per RGB565 pixel

    // Prepare transfer buffer for the region
    uint8_t* txBuffer = transferBuffer; // Reuse transfer buffer for region transfers
    uint32_t bufferIndex = 0;

    // Copy region pixels to transfer buffer with byte order conversion
    for (int16_t row = y1; row <= y2; ++row) {
        for (int16_t col = x1; col <= x2; ++col) {
            const uint16_t pixel = buffer[row * DISPLAY_WIDTH + col];
            txBuffer[bufferIndex++] = pixel >> 8; // High byte
            txBuffer[bufferIndex++] = pixel & 0xFF; // Low byte
        }
    }

    // Transfer the region data
    digitalWrite(dc, HIGH); // Data mode
    digitalWrite(cs, LOW);
    SPI.transferBytes(txBuffer, nullptr, regionBytes);
    digitalWrite(cs, HIGH);

    uint32_t transferTime = millis() - transferStart;
    stats.update(0, transferTime);
}

#if ST7735_ENABLE_DUAL_CORE
void ST7735DualEngine::updateDisplayAsync(const int screen) {
    if (UNLIKELY(!dualCoreActive)) {
        updateDisplay(screen);
        return;
    }

    ST7735TaskMessage msg = {
        LIKELY(screen == 0) ? ST7735TaskCommand::UPDATE_DISPLAY_0 : ST7735TaskCommand::UPDATE_DISPLAY_1,
        static_cast<uint8_t>(screen), 0, 0, 0, 0
    };

    xQueueSend(commandQueue, &msg, 0); // Non-blocking send
}

void ST7735DualEngine::updateBothAsync() {
    if (!dualCoreActive || !commandQueue) {
        updateBoth();
        return;
    }

    ST7735TaskMessage msg = {ST7735TaskCommand::UPDATE_BOTH, 0, 0, 0, 0, 0};
    if (xQueueSend(commandQueue, &msg, pdMS_TO_TICKS(5)) != pdTRUE) {
        #if ST7735_ENABLE_DEBUG
        Serial.println("Warning: Command queue full, falling back to synchronous update");
        #endif
                updateBoth();
    }
}
#endif

// ========================================
// OTSC IMPLEMENTATION
// ========================================

bool ST7735DualEngine::ensureStartupReliability() {
    if (startupReliabilityComplete) {
        return true; // Already verified, no CPU overhead
    }

    #if ST7735_ENABLE_DEBUG
    Serial.println("=== Starting Reliability Check ===");
    #endif

    for (startupAttempts = 1; startupAttempts <= MAX_STARTUP_ATTEMPTS; ++startupAttempts) {
        #if ST7735_ENABLE_DEBUG
        Serial.printf("Reliability attempt %d/%d\n", startupAttempts, MAX_STARTUP_ATTEMPTS);
        #endif

        if (performStartupReliabilityCheck()) {
            startupReliabilityComplete = true;
            #if ST7735_ENABLE_DEBUG
            Serial.printf("βœ“ Startup reliability achieved on attempt %d\n", startupAttempts);
            #endif
            return true;
        }

        // Progressive delay increase for stubborn displays
        delay(STARTUP_STABILITY_DELAY * startupAttempts);

        // Force re-initialization on failed attempts
        if (startupAttempts < MAX_STARTUP_ATTEMPTS) {
            #if ST7735_ENABLE_DEBUG
            Serial.println("Forcing display re-initialization...");
            #endif
            forceDisplayWakeup(SCREEN0_CS, SCREEN0_DC, SCREEN0_RST);
            delay(INTER_DISPLAY_DELAY);
            forceDisplayWakeup(SCREEN1_CS, SCREEN1_DC, SCREEN1_RST);
            delay(INTER_DISPLAY_DELAY);
        }
    }

    #if ST7735_ENABLE_DEBUG
    Serial.println("⚠ Warning: Startup reliability check failed after all attempts");
    Serial.println("Displays may work but reliability not guaranteed");
    #endif
    return false;
}

bool ST7735DualEngine::performStartupReliabilityCheck() {
    // Test both displays with minimal overhead
    bool screen0_ok = testDisplayCommunication(SCREEN0_CS, SCREEN0_DC);
    delay(10); // Brief pause between tests
    bool screen1_ok = testDisplayCommunication(SCREEN1_CS, SCREEN1_DC);

    if (!screen0_ok || !screen1_ok) {
        #if ST7735_ENABLE_DEBUG
        Serial.printf("Communication test failed - Screen0: %s, Screen1: %s\n",
                     screen0_ok ? "OK" : "FAIL", screen1_ok ? "OK" : "FAIL");
        #endif
        return false;
    }

    // Perform visual test to catch white screen issue
    return validateDisplayInitialization(SCREEN0_CS, SCREEN0_DC) &&
           validateDisplayInitialization(SCREEN1_CS, SCREEN1_DC);
}

bool ST7735DualEngine::testDisplayCommunication(const uint8_t cs, const uint8_t dc) const {
    // Quick communication test - minimal CPU overhead
    // Test 1: Try to read display ID (if supported)
    writeCommand(0x04, cs, dc); // Read Display ID
    delay(2);

    // Test 2: Set and verify a known state
    writeCommand(0x36, cs, dc); // Memory Access Control
    writeData(0xC8, cs, dc); // Known good value
    delay(1);

    // Test 3: Verify display accepts pixel format command
    writeCommand(0x3A, cs, dc); // Pixel Format Set
    writeData(0x05, cs, dc); // 16-bit RGB565
    delay(1);

    // If we get here without hanging, communication is likely working
    return true;
}

void ST7735DualEngine::forceDisplayWakeup(const uint8_t cs, const uint8_t dc, const uint8_t rst) const {
    // Aggressive wakeup sequence for stubborn displays
    // Force hardware reset with extended timing
    digitalWrite(rst, LOW);
    delay(20); // Longer reset hold
    digitalWrite(rst, HIGH);
    delay(150); // Longer recovery time

    // Send wakeup commands sequence
    writeCommand(0x01, cs, dc); // Software Reset
    delay(150);
    writeCommand(0x11, cs, dc); // Sleep Out
    delay(150);

    // Force pixel format early to establish communication
    writeCommand(0x3A, cs, dc); // Pixel Format Set
    writeData(0x05, cs, dc); // 16-bit RGB565
    delay(20);

    // Force display on
    writeCommand(0x29, cs, dc); // Display On
    delay(50);

    // Clear any potential white screen by filling with black
    setWindow(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, cs, dc);
    digitalWrite(dc, HIGH); // Data mode
    digitalWrite(cs, LOW);

    // Fast black fill - unrolled for speed
    for (uint32_t i = 0; i < BUFFER_SIZE; i += 4) {
        SPI.transfer(0x00); SPI.transfer(0x00); // Pixel 1
        SPI.transfer(0x00); SPI.transfer(0x00); // Pixel 2
        SPI.transfer(0x00); SPI.transfer(0x00); // Pixel 3
        SPI.transfer(0x00); SPI.transfer(0x00); // Pixel 4
    }

    digitalWrite(cs, HIGH);
    delay(10);
}

bool ST7735DualEngine::validateDisplayInitialization(const uint8_t cs, const uint8_t dc) const {
    // Visual validation to catch white screen issue
    // Draw a test pattern to verify display is responding correctly
    setWindow(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, cs, dc);
    digitalWrite(dc, HIGH); // Data mode
    digitalWrite(cs, LOW);

    // Draw alternating pattern - should be visible if display works
    for (uint32_t i = 0; i < BUFFER_SIZE; ++i) {
        uint8_t pattern = (i & 1) ? STARTUP_TEST_PATTERN : ~STARTUP_TEST_PATTERN;
        SPI.transfer(pattern);
        SPI.transfer(pattern);
    }

    digitalWrite(cs, HIGH);
    delay(50); // Allow pattern to display

    // Clear back to black
    setWindow(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, cs, dc);
    digitalWrite(dc, HIGH);
    digitalWrite(cs, LOW);

    for (uint32_t i = 0; i < BUFFER_SIZE; ++i) {
        SPI.transfer(0x00);
        SPI.transfer(0x00);
    }

    digitalWrite(cs, HIGH);
    delay(10);

    return true; // If we reach here, display accepted the commands
}

// ========================================
// ADVANCED BUFFER OPERATIONS
// ========================================

void ST7735DualEngine::swapBuffers() {
    uint16_t* temp = buffer0;
    buffer0 = buffer1;
    buffer1 = temp;
}

void ST7735DualEngine::copyBuffer(const int srcScreen, const int dstScreen) {
    const uint16_t* src = getBufferPtr(srcScreen);
    uint16_t* dst = getBufferPtr(dstScreen);
    if (UNLIKELY(!src || !dst)) return;

    #if ST7735_ENABLE_DUAL_CORE
    if (dualCoreActive && xSemaphoreTake(bufferMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
    #endif

    memcpy(dst, src, TRANSFER_BYTES);

    #if ST7735_ENABLE_DUAL_CORE
        xSemaphoreGive(bufferMutex);
    }
    #endif
}

void ST7735DualEngine::blendBuffers(const int screen1, const int screen2, const uint8_t alpha) {
    const uint16_t* src1 = getBufferPtr(screen1);
    uint16_t* src2 = getBufferPtr(screen2);
    if (UNLIKELY(!src1 || !src2)) return;

    uint32_t renderStart = millis();

    #if ST7735_ENABLE_DUAL_CORE
    if (dualCoreActive && xSemaphoreTake(bufferMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
    #endif

    // Blend screen1 into screen2 with alpha transparency
    const uint8_t invAlpha = 255 - alpha;
    for (uint32_t i = 0; i < BUFFER_SIZE; ++i) {
        const uint16_t c1 = src1[i];
        const uint16_t c2 = src2[i];

        // Extract RGB components for both colors
        const uint32_t r1 = (c1 >> 11) & 0x1F;
        const uint32_t g1 = (c1 >> 5) & 0x3F;
        const uint32_t b1 = c1 & 0x1F;

        const uint32_t r2 = (c2 >> 11) & 0x1F;
        const uint32_t g2 = (c2 >> 5) & 0x3F;
        const uint32_t b2 = c2 & 0x1F;

        // Blend and pack
        const uint32_t r = (r2 * invAlpha + r1 * alpha) >> 8;
        const uint32_t g = (g2 * invAlpha + g1 * alpha) >> 8;
        const uint32_t b = (b2 * invAlpha + b1 * alpha) >> 8;

        src2[i] = (r << 11) | (g << 5) | b;
    }

    #if ST7735_ENABLE_DUAL_CORE
        xSemaphoreGive(bufferMutex);
    }
    #endif

    uint32_t renderTime = millis() - renderStart;
    stats.update(renderTime, 0);
}

// ========================================
// COLOR UTILITIES
// ========================================

uint16_t ST7735DualEngine::blendColors(const uint16_t color1, const uint16_t color2, const uint8_t alpha) {
    // Optimized alpha blend for RGB565
    const uint8_t invAlpha = 255 - alpha;

    // Extract RGB components
    const uint32_t r1 = (color1 >> 11) & 0x1F;
    const uint32_t g1 = (color1 >> 5) & 0x3F;
    const uint32_t b1 = color1 & 0x1F;

    const uint32_t r2 = (color2 >> 11) & 0x1F;
    const uint32_t g2 = (color2 >> 5) & 0x3F;
    const uint32_t b2 = color2 & 0x1F;

    // Blend and pack
    const uint32_t r = (r1 * invAlpha + r2 * alpha) >> 8;
    const uint32_t g = (g1 * invAlpha + g2 * alpha) >> 8;
    const uint32_t b = (b1 * invAlpha + b2 * alpha) >> 8;

    return (r << 11) | (g << 5) | b;
}

uint16_t ST7735DualEngine::interpolateColors(const uint16_t color1, const uint16_t color2, const float t) {
    const float invT = 1.0f - t;

    const uint8_t r1 = red565(color1);
    const uint8_t g1 = green565(color1);
    const uint8_t b1 = blue565(color1);

    const uint8_t r2 = red565(color2);
    const uint8_t g2 = green565(color2);
    const uint8_t b2 = blue565(color2);

    const uint8_t r = static_cast<uint8_t>(r1 * invT + r2 * t);
    const uint8_t g = static_cast<uint8_t>(g1 * invT + g2 * t);
    const uint8_t b = static_cast<uint8_t>(b1 * invT + b2 * t);

    return rgb565(r, g, b);
}

// ========================================
// PERFORMANCE AND DIAGNOSTICS
// ========================================

void ST7735DualEngine::printSystemInfo() const {
    Serial.println("=== ST7735DualEngine v1.3 Arduino SPI-Only ===");
    Serial.printf("Platform: %s\n", ST7735Platform::getPlatformName());
    Serial.printf("Display Resolution: %dx%d\n", DISPLAY_WIDTH, DISPLAY_HEIGHT);
    Serial.printf("Buffer Size: %u KB each\n", TRANSFER_BYTES / 1024);
    Serial.printf("SPI Frequency: %u MHz\n", ST7735Platform::getOptimalSPIFrequency() / 1000000);
    Serial.printf("Arduino SPI: Enabled (Zero Conflicts)\n");
    Serial.printf("DMA Support: Disabled (Arduino SPI-Only)\n");
    Serial.printf("Dual-Core Support: %s\n", isDualCoreEnabled() ? "Enabled" : "Disabled");
    Serial.printf("Free Heap: %u KB\n", ESP.getFreeHeap() / 1024);

    #if ST7735_USE_PSRAM
    Serial.printf("PSRAM Total: %u KB\n", ESP.getPsramSize() / 1024);
    Serial.printf("PSRAM Free: %u KB\n", ESP.getFreePsram() / 1024);
    Serial.printf("Used PSRAM: %u KB\n", getUsedPSRAM() / 1024);
    #endif

    Serial.printf("Buffer Memory Usage: %u KB\n", getBufferMemoryUsage() / 1024);
    Serial.printf("Font: %dx%d bitmap\n", ST7735Specs::FONT_WIDTH, ST7735Specs::FONT_HEIGHT);
    Serial.println("=================================================");
}

void ST7735DualEngine::printPerformanceReport() const {
    Serial.println("=== Performance Report v1.3 Arduino SPI-Only ===");
    
    // Handle overflow protection for display
    if (stats.frameCount == UINT32_MAX - 1) {
        Serial.println("Frames Rendered: overflow");
    } else {
        Serial.printf("Frames Rendered: %u\n", stats.frameCount);
    }
    
    Serial.printf("Average Operations PS: %.2f\n", stats.averageOPS);
    Serial.printf("Average Frame Heavy Operations PS: %.2f\n", stats.averageFHOPS);
    Serial.printf("Peak Frame Time: %u ms\n", stats.peakFrameTime);
    Serial.printf("Peak Transfer Time: %u ms\n", stats.peakTransferTime);
    Serial.printf("Total Render Time: %u ms\n", stats.totalRenderTime);
    Serial.printf("Total Transfer Time: %u ms\n", stats.totalTransferTime);
    
    if (LIKELY(stats.frameCount > 0 && stats.frameCount < UINT32_MAX - 1)) {
        Serial.printf("Avg Render Time: %.2f ms\n", stats.totalRenderTime / (float)stats.frameCount);
        Serial.printf("Avg Transfer Time: %.2f ms\n", stats.totalTransferTime / (float)stats.frameCount);
    }
    Serial.println("===============================");
}

uint32_t ST7735DualEngine::getUsedPSRAM() const {
    #if ST7735_USE_PSRAM
    return ESP.getPsramSize() - ESP.getFreePsram();
    #else
    return 0;
    #endif
}

// ========================================
// TEST PATTERNS AND DIAGNOSTICS
// ========================================

void ST7735DualEngine::drawTestPattern(const int screen) {
    clearBuffer(screen, ST7735Colors::BLACK);

    // Enhanced test pattern with performance indicators
    for (int16_t y = 0; y < HEIGHT; y += 20) {
        uint16_t color = rgb565((y * 255) / HEIGHT, 128, 255 - (y * 255) / HEIGHT);
        fillRect(screen, 0, y, WIDTH, 20, color);
    }

    // Corner markers with version info
    fillRect(screen, 0, 0, 10, 10, ST7735Colors::WHITE);
    fillRect(screen, WIDTH-10, 0, 10, 10, ST7735Colors::RED);
    fillRect(screen, 0, HEIGHT-10, 10, 10, ST7735Colors::GREEN);
    fillRect(screen, WIDTH-10, HEIGHT-10, 10, 10, ST7735Colors::BLUE);

    // Performance indicator
    drawText(screen, 2, HEIGHT-30, "v1.3", ST7735Colors::WHITE, 1);

    // Center cross
    drawHLine(screen, WIDTH/2 - 10, HEIGHT/2, 20, ST7735Colors::WHITE);
    drawVLine(screen, WIDTH/2, HEIGHT/2 - 10, 20, ST7735Colors::WHITE);
}

void ST7735DualEngine::drawColorBars(const int screen) {
    const uint16_t colors[] = {
        ST7735Colors::WHITE, ST7735Colors::YELLOW, ST7735Colors::CYAN,
        ST7735Colors::GREEN, ST7735Colors::MAGENTA, ST7735Colors::RED,
        ST7735Colors::BLUE, ST7735Colors::BLACK
    };

    const int16_t barHeight = HEIGHT / 8;
    for (int i = 0; i < 8; i++) {
        fillRect(screen, 0, i * barHeight, WIDTH, barHeight, colors[i]);
    }

    // Add text labels for verification
    drawText(screen, 5, 5, "WHITE", ST7735Colors::BLACK, 1);
    drawText(screen, 5, HEIGHT - 15, "BLACK", ST7735Colors::WHITE, 1);
}

void ST7735DualEngine::drawGridPattern(const int screen, const uint16_t color) {
    clearBuffer(screen, ST7735Colors::BLACK);

    // Optimized grid with reduced draw calls
    for (int16_t x = 0; x < WIDTH; x += 16) {
        drawVLine(screen, x, 0, HEIGHT, color);
    }

    for (int16_t y = 0; y < HEIGHT; y += 16) {
        drawHLine(screen, 0, y, WIDTH, color);
    }

    // Grid coordinates
    char buffer[8];
    for (int16_t x = 16; x < WIDTH; x += 32) {
        for (int16_t y = 16; y < HEIGHT; y += 32) {
            snprintf(buffer, sizeof(buffer), "%d,%d", x/16, y/16);
            drawText(screen, x + 2, y + 2, buffer, color, 1);
        }
    }
}

#pragma GCC diagnostic pop

Copy these two files into your Arduino IDE project directory, write the entry point ino sketch, refer to the header file for the porotypes (kept them similar to common prototypes as much as possible for ~zero unnecessary cognitive load in integration) and have a blast.

If anyone else wants to try out for ESP32 or even test it out as is for Devkit C1 for confirmation, please feel free to do so, I will definitely document your contributions in the next patches or updates.

EDIT 04/09/2025: Patched Library V 2.1.1 where most type conversions have been removed to create futureproof overhead buffer for runtime, and critical multiplications and divisions have been replaced and accomplished with bit shift operations.

TFTFRIEND_7735Duo.cpp (96.0 KB)

ST7735DualEngine.h (40.0 KB)

EDIT 10/09/2025: Patched Library V 2.1.2 to address proper FPS tracking, and commented out FHOPS (Frame Heavy Operations Per Second) during performance revision (for less CPU overhead).

TFTFRIEND_7735Duo.cpp (88.7 KB)

ST7735DualEngine.h (41.5 KB)

In the subsequent articles I will be providing example ino sketches.
An ESP-IDF managed SPI version is also in work.

TFTFriend - ESP32 ST7735 Duo Example Sketches:

Example 0: Simple Graphics and Physics Suite:

Remember to copy the above header and cpp implementation file to your Arduino IDE project directory (Top menu>Sketch>Show Sketch Folder>Paste).

/*
 * TFTFriend ST7735 Duo ESP32 - Professional Dual Display Graphics Demo v1.2 for Espressif Systems
 * Multi-platform demonstration with advanced performance features
 * 
 * CREDITS AND ACKNOWLEDGMENTS:
 * - Arduino SPI Library: Original by Cristian Maglie (2010), enhanced by Paul Stoffregen
 * - Adafruit GFX Library: Font format and graphics algorithm inspiration
 * - Bresenham Algorithms: Classic computer graphics algorithms (1965-1977)
 * - ESP-IDF Team: ESP32 family hardware abstraction and DMA implementation
 * - FreeRTOS: Real-time operating system for dual-core task management
 * - ST Microelectronics: ST7735 display controller specification and initialization sequences
 * - Adafruit NeoPixel Library: Phil Burgess, Paint Your Dragon (original authors)
 * 
 * LICENSE: MIT License - See library files for full license text
 * 
 * HARDWARE REQUIREMENTS:
 * - ESP32 family microcontroller (ESP32/ESP32-S2/ESP32-S3/ESP32-C3)
 * - Two ST7735 128x160 displays
 * - NeoPixel RGB LED (WS2812B) on GPIO 38 (ESP32-S3 DevKit C-1)
 * - Connections as defined in ST7735DualEngine.h
 */

#include "ST7735DualEngine.h"
#include <Adafruit_NeoPixel.h> // Adafruit Industries - Phil Burgess, Paint Your Dragon

// ========================================
// NEOPIXEL CONFIGURATION (Separate from Engine)
// ========================================

// NeoPixel setup for ESP32-S3 DevKit C-1
#define NEOPIXEL_PIN 38 // Built-in NeoPixel on ESP32-S3 DevKit C-1
#define NEOPIXEL_COUNT 1 // Single RGB LED
#define NEOPIXEL_TYPE NEO_GRB + NEO_KHZ800

// Initialize NeoPixel with proper credits
Adafruit_NeoPixel statusLED(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE);

// NeoPixel control variables (user-adjustable)
uint8_t ledBrightness = 10; // Start at minimal brightness (0-255)
bool ledEnabled = true; // Enable/disable status LED
uint32_t ledUpdateInterval = 100; // LED update rate in milliseconds

// Status colors for different states
const uint32_t LED_INITIALIZING = statusLED.Color(255, 165, 0); // Orange
const uint32_t LED_RUNNING = statusLED.Color(0, 255, 0); // Green
const uint32_t LED_ERROR = statusLED.Color(255, 0, 0); // Red
const uint32_t LED_PERFORMANCE_GOOD = statusLED.Color(0, 0, 255); // Blue
const uint32_t LED_PERFORMANCE_WARN = statusLED.Color(255, 255, 0); // Yellow

// ========================================
// DEMO CONFIGURATION
// ========================================

// Global graphics engine instance
ST7735DualEngine gfx;

// Demo timing and control - SHORTENED FOR TESTING
constexpr uint32_t DEMO_DURATION_MS = 30000; // 30 seconds per demo for easier testing
constexpr uint32_t TARGET_FPS = 60;
constexpr uint32_t FRAME_TIME_MS = 1000 / TARGET_FPS;
constexpr uint32_t STATS_UPDATE_INTERVAL = 5000; // Update stats every 5 seconds

// Demo state management
struct DemoState {
    uint32_t currentDemo = 0;
    uint32_t demoStartTime = 0;
    uint32_t frameCount = 0;
    float animationTime = 0.0f;
    float deltaTime = 0.0f;
    uint32_t lastFrameTime = 0;
    bool showPerformanceOverlay = true;
} demo;

// Performance tracking for user feedback
struct UserMetrics {
    float currentFPS = 0.0f;
    uint32_t frameTime = 0;
    uint32_t minFrameTime = UINT32_MAX;
    uint32_t maxFrameTime = 0;
    bool performanceGood = true;
    uint32_t lastLEDUpdate = 0;
} metrics;

// Demo function declarations
void demoSynchronizedWaves();
void demoParticlePhysics();
void demoGeometricArt();
void demoTextAndUI();
void demoPerformanceBench();
void demoRotatingLines();
void demoParticleGravity();
void demoStatsOverview();
void demoGraphicsPrimitives();
void demoColorSpectrum();

// Utility function declarations
void updateNeoPixelStatus();
void displayPerformanceOverlay(int screen);
void drawTextFullChar(const int screen, int16_t x, const int16_t y, const char* text, const uint16_t color, const uint8_t size = 1, const uint16_t bg = ST7735Colors::BLACK);

// ========================================
// ARDUINO SETUP
// ========================================

void setup() {
    Serial.begin(115200);
    Serial.println("=== TFTFriend Demo Starting ===");
    
    // Initialize NeoPixel status indicator
    statusLED.begin();
    statusLED.setBrightness(ledBrightness);
    statusLED.setPixelColor(0, LED_INITIALIZING);
    statusLED.show();

    // Initialize graphics engine with detailed reporting
    ST7735DualEngine::InitResult initResult = gfx.begin();
    if (!initResult.success) {
        // Set error status
        if (ledEnabled) {
            statusLED.setPixelColor(0, LED_ERROR);
            statusLED.show();
        }
        
        // Halt with helpful error pattern
        while (1) {
            if (ledEnabled) {
                statusLED.setPixelColor(0, LED_ERROR);
                statusLED.show();
                delay(500);
                statusLED.setPixelColor(0, 0);
                statusLED.show();
                delay(500);
            } else {
                delay(1000);
            }
        }
    }

    // Initialize demo timing
    demo.demoStartTime = millis();
    demo.lastFrameTime = demo.demoStartTime;
    metrics.lastLEDUpdate = demo.demoStartTime;
    demo.showPerformanceOverlay = true; // Lock to true for long runs

    // Set success status
    if (ledEnabled) {
        statusLED.setPixelColor(0, LED_RUNNING);
        statusLED.show();
    }

    // Show test pattern briefly for display verification
    gfx.drawTestPattern(0);
    gfx.drawTestPattern(1);
    gfx.updateBoth();
    delay(2000);
    
    Serial.println("=== Demo sequence will run every 8 seconds ===");
    Serial.println("Demo 0: Sync Waves (0-8s)");
    Serial.println("Demo 1: Particle Physics (8-16s)");
    Serial.println("Demo 2: Geometric Art (16-24s)");
    Serial.println("Demo 3: Text & UI (24-32s)");
    Serial.println("Demo 4: Performance Bench (32-40s)");
    Serial.println("Demo 5: Rotating Lines (40-48s)");
    Serial.println("Demo 6: Particle Gravity (48-56s)");
    Serial.println("Demo 7: STATS OVERVIEW (56-64s) <<<--- LOOK HERE!");
    Serial.println("Demo 8: Graphics Primitives (64-72s)");
    Serial.println("Demo 9: Color Spectrum (72-80s)");
}

// ========================================
// ARDUINO MAIN LOOP
// ========================================

void loop() {
    uint32_t frameStart = millis();
    
    // Calculate timing first to ensure accuracy
    demo.deltaTime = (frameStart - demo.lastFrameTime) / 1000.0f;
    demo.animationTime += demo.deltaTime;
    demo.lastFrameTime = frameStart;

    // Determine current demo based on time
    uint32_t totalTime = frameStart - demo.demoStartTime;
    uint32_t newDemo = (totalTime / DEMO_DURATION_MS) % 10; // 10 demos total (0-9)

    if (newDemo != demo.currentDemo) {
        demo.currentDemo = newDemo;
        demo.frameCount = 0; // Reset frame count for new demo
        demo.animationTime = 0.0f; // Reset animation time
        gfx.resetPerformanceStats();
        metrics.minFrameTime = UINT32_MAX;
        metrics.maxFrameTime = 0;
        metrics.currentFPS = 0.0f; // Reset FPS

        // **ADDED: Debug output to track demo switching**
        Serial.printf(">>> SWITCHING TO DEMO %u at time %u seconds <<<\n", 
                     demo.currentDemo, totalTime / 1000);
        
        if (demo.currentDemo == 7) {
            Serial.println("*** STATS OVERVIEW DEMO STARTING NOW! ***");
        }

        #if ST7735_ENABLE_DEBUG
        Serial.printf("Demo %u: Free Heap: %u KB, Used PSRAM: %u KB\n",
                     demo.currentDemo, gfx.getFreeHeap() / 1024, gfx.getUsedPSRAM() / 1024);
        if (gfx.getFreeHeap() < 10000 || (ST7735_USE_PSRAM && gfx.getUsedPSRAM() > ESP.getPsramSize() * 0.9)) {
            Serial.println("WARNING: Low memory or high PSRAM usage detected. Reinitializing buffers...");
            gfx.end();
            gfx.begin();
        }
        #endif
    }

    // Local FPS calculation for accuracy
    static uint32_t fpsStartTime = frameStart;
    static uint32_t fpsFrameCount = 0;
    fpsFrameCount++;
    if (frameStart - fpsStartTime >= 1000) {
        metrics.currentFPS = fpsFrameCount * 1000.0f / (frameStart - fpsStartTime);
        fpsFrameCount = 0;
        fpsStartTime = frameStart;
    }

    // Execute current demo
    switch (demo.currentDemo) {
        case 0: demoSynchronizedWaves(); break;
        case 1: demoParticlePhysics(); break;
        case 2: demoGeometricArt(); break;
        case 3: demoTextAndUI(); break;
        case 4: demoPerformanceBench(); break;
        case 5: demoRotatingLines(); break;
        case 6: demoParticleGravity(); break;
        case 7: demoStatsOverview(); break;
        case 8: demoGraphicsPrimitives(); break;
        case 9: demoColorSpectrum(); break;
        default: demo.currentDemo = 0; break;
    }

    // Add performance overlay after demo rendering (disabled for stats overview)
    if (demo.showPerformanceOverlay && demo.currentDemo != 7) {
        displayPerformanceOverlay(1);
    }

    // Update displays
    #if ST7735_ENABLE_DUAL_CORE
    if (gfx.isDualCoreEnabled()) {
        gfx.updateBothAsync();
    } else {
        gfx.updateBoth();
    }
    #else
    gfx.updateBoth();
    #endif

    // Calculate performance metrics
    uint32_t frameTime = millis() - frameStart;
    demo.frameCount++;
    metrics.frameTime = frameTime;
    metrics.minFrameTime = min(metrics.minFrameTime, frameTime);
    metrics.maxFrameTime = max(metrics.maxFrameTime, frameTime);

    // Update performance statistics
    const ST7735PerformanceStats& stats = gfx.getPerformanceStats();
    metrics.performanceGood = (metrics.currentFPS >= 45.0f && frameTime <= 25);

    // Update NeoPixel status
    updateNeoPixelStatus();

    // Frame rate control
    if (frameTime < FRAME_TIME_MS) {
        delay(FRAME_TIME_MS - frameTime);
    }

    // Log high frame times after frame rendering to avoid delays
    #if ST7735_ENABLE_DEBUG
    if (frameTime > FRAME_TIME_MS) {
        Serial.printf("Warning: High frame time in demo %u: %u ms\n", demo.currentDemo, frameTime);
    }
    #endif

    // Reset performance stats every hour to prevent overflow
    static uint32_t lastStatsReset = 0;
    if (millis() - lastStatsReset > 3600000) { // 1 hour
        gfx.resetPerformanceStats();
        lastStatsReset = millis();
    }
}

// ========================================
// DEMONSTRATION IMPLEMENTATIONS
// ========================================

void demoSynchronizedWaves() {
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    const float frequency = 2.0f;
    const int16_t amplitude = 60;
    const int16_t centerY = gfx.HEIGHT / 2;
    
    for (int16_t x = 0; x < gfx.WIDTH; x++) {
        float phase1 = (x / 20.0f) + (demo.animationTime * frequency);
        int16_t y1 = centerY + (amplitude * sin(phase1));
        gfx.setPixel(0, x, y1, ST7735Colors::CYAN);
        
        float phase2 = (x / 20.0f) - (demo.animationTime * frequency);
        int16_t y2 = centerY + (amplitude * sin(phase2));
        gfx.setPixel(1, x, y2, ST7735Colors::MAGENTA);
    }
    
    int16_t circleX = (gfx.WIDTH / 2) + (30 * sin(demo.animationTime * 3));
    int16_t circleY = (gfx.HEIGHT / 2) + (40 * cos(demo.animationTime * 2));
    gfx.drawCircle(0, circleX, circleY, 15, ST7735Colors::YELLOW);
    gfx.drawCircle(1, gfx.WIDTH - circleX, circleY, 15, ST7735Colors::GREEN);
    
    gfx.drawText(0, 5, 5, "SYNC WAVES", ST7735Colors::WHITE, 1);
}

void demoParticlePhysics() {
    constexpr int MAX_PARTICLES = 50;
    static struct Particle {
        float x, y, vx, vy;
        uint16_t color;
        bool active;
    } particles[MAX_PARTICLES];
    
    static bool initialized = false;
    static uint32_t lastDemo = UINT32_MAX;
    
    if (demo.currentDemo != lastDemo) {
        initialized = false;
        lastDemo = demo.currentDemo;
    }
    
    if (!initialized) {
        for (int i = 0; i < MAX_PARTICLES; i++) {
            particles[i].x = gfx.WIDTH / 2.0f;
            particles[i].y = gfx.HEIGHT / 2.0f;
            particles[i].vx = (random(-100, 100) / 10.0f);
            particles[i].vy = (random(-100, 100) / 10.0f);
            particles[i].color = gfx.rgb565(random(100, 255), random(100, 255), random(100, 255));
            particles[i].active = true;
        }
        initialized = true;
    }
    
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    for (int i = 0; i < MAX_PARTICLES; i++) {
        if (particles[i].active) {
            particles[i].x += particles[i].vx * demo.deltaTime;
            particles[i].y += particles[i].vy * demo.deltaTime;
            
            if (particles[i].x <= 0 || particles[i].x >= gfx.WIDTH) {
                particles[i].vx *= -0.8f;
                particles[i].x = constrain(particles[i].x, 0, gfx.WIDTH - 1);
            }
            
            if (particles[i].y <= 0 || particles[i].y >= gfx.HEIGHT) {
                particles[i].vy *= -0.8f;
                particles[i].y = constrain(particles[i].y, 0, gfx.HEIGHT - 1);
            }
            
            int16_t px = (int16_t)particles[i].x;
            int16_t py = (int16_t)particles[i].y;
            gfx.fillCircle(0, px, py, 2, particles[i].color);
            gfx.fillCircle(1, gfx.WIDTH - px, py, 2, particles[i].color);
            gfx.setPixel(0, px + 1, py, particles[i].color >> 1);
            gfx.setPixel(1, gfx.WIDTH - px - 1, py, particles[i].color >> 1);
        }
    }
    
    gfx.drawText(0, 5, 5, "PARTICLES ORIG", ST7735Colors::WHITE, 1);
}

void demoGeometricArt() {
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    const int16_t centerX = gfx.WIDTH / 2;
    const int16_t centerY = gfx.HEIGHT / 2;
    
    for (int i = 0; i < 50; i++) {
        float angle = (i * 0.3f) + (demo.animationTime * 2);
        float radius = i * 1.5f;
        int16_t x = centerX + (radius * cos(angle));
        int16_t y = centerY + (radius * sin(angle));
        if (x >= 0 && x < gfx.WIDTH && y >= 0 && y < gfx.HEIGHT) {
            uint16_t color = gfx.rgb565(i * 5, 255 - i * 3, 128 + i * 2);
            gfx.setPixel(0, x, y, color);
        }
    }
    
    for (int r = 10; r < 60; r += 10) {
        uint16_t color;
        switch ((r / 10) % 4) {
            case 0: color = ST7735Colors::RED; break;
            case 1: color = ST7735Colors::GREEN; break;
            case 2: color = ST7735Colors::BLUE; break;
            case 3: color = ST7735Colors::YELLOW; break;
        }
        
        float breathe = sin(demo.animationTime * 3 + r * 0.1f) * 5;
        gfx.drawCircle(1, gfx.WIDTH / 2, gfx.HEIGHT / 2, r + (int16_t)breathe, color);
    }
    
    gfx.drawText(0, 5, 5, "GEOMETRIC ART", ST7735Colors::WHITE, 1);
}

void demoTextAndUI() {
    // Clear buffers explicitly to prevent artifacts
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    static int16_t scrollPos = gfx.WIDTH;
    scrollPos -= 2;
    if (scrollPos < -200) scrollPos = gfx.WIDTH;
    
    drawTextFullChar(0, scrollPos, 20, "ESP32 SPI DUO", ST7735Colors::WHITE, 2);
    
    int16_t smallScrollPos = scrollPos;
    // Yellow text uses original drawText for "stack-to-horizontal" effect
    gfx.drawText(0, smallScrollPos, 50, "HIGH PERFORMANCE", ST7735Colors::YELLOW, 1);
    smallScrollPos += gfx.getTextWidth("HIGH PERFORMANCE") + 10; // Stack horizontally when entering
    drawTextFullChar(0, smallScrollPos, 50, "DUAL DISPLAY", ST7735Colors::CYAN, 1);
    smallScrollPos += gfx.getTextWidth("DUAL DISPLAY") + 10;
    drawTextFullChar(0, smallScrollPos, 50, "GRAPHICS ENGINE", ST7735Colors::GREEN, 1);
    
    // Move "Mixed Case: Abc123" to y=90 to avoid potential clipping and change color to white
    #if ST7735_ENABLE_DEBUG
    Serial.println("Rendering text: Mixed Case: Abc123");
    for (const char* c = "Mixed Case: Abc123"; *c != '\0'; ++c) {
        Serial.printf("Char: %c (ASCII: %d)\n", *c, (int)*c);
    }
    #endif
    drawTextFullChar(0, 10, 90, "Mixed Case: Abc123", ST7735Colors::WHITE, 1);
    
    char buffer[32];
    drawTextFullChar(1, 5, 10, "SYSTEM STATUS", ST7735Colors::WHITE, 1);
    
    snprintf(buffer, sizeof(buffer), "FPS: %.1f", metrics.currentFPS);
    drawTextFullChar(1, 5, 25, buffer, ST7735Colors::GREEN, 1);
    
    snprintf(buffer, sizeof(buffer), "HEAP: %uKB", gfx.getFreeHeap() / 1024);
    drawTextFullChar(1, 5, 40, buffer, ST7735Colors::YELLOW, 1);
    
    snprintf(buffer, sizeof(buffer), "PSRAM: %uKB", gfx.getUsedPSRAM() / 1024);
    drawTextFullChar(1, 5, 55, buffer, ST7735Colors::CYAN, 1);
    
    snprintf(buffer, sizeof(buffer), "TIME: %.1fs", demo.animationTime);
    drawTextFullChar(1, 5, 70, buffer, ST7735Colors::MAGENTA, 1);
    
    uint8_t textSize = 1 + (uint8_t)(2 * sin(demo.animationTime * 2));
    drawTextFullChar(0, 10, 120, "SCALE", ST7735Colors::RED, textSize);
    
    gfx.drawText(0, 5, 5, "TEXT & UI", ST7735Colors::WHITE, 1);
}

void demoPerformanceBench() {
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    static uint32_t frameHistory[gfx.WIDTH];
    static int historyIndex = 0;
    
    frameHistory[historyIndex] = metrics.frameTime;
    historyIndex = (historyIndex + 1) % gfx.WIDTH;
    
    for (int x = 0; x < gfx.WIDTH; x++) {
        int index = (historyIndex + x) % gfx.WIDTH;
        int height = map(frameHistory[index], 0, 50, 0, gfx.HEIGHT - 20);
        height = constrain(height, 0, gfx.HEIGHT - 20);
        uint16_t color = (height < 20) ? ST7735Colors::GREEN :
                        (height < 30) ? ST7735Colors::YELLOW : ST7735Colors::RED;
        gfx.drawVLine(0, x, gfx.HEIGHT - height, height, color);
    }
    
    char buffer[32];
    int16_t y = 10;
    gfx.drawText(1, 5, y, "PERFORMANCE", ST7735Colors::WHITE, 1);
    y += 15;
    
    snprintf(buffer, sizeof(buffer), "AVG FPS: %.1f", metrics.currentFPS);
    gfx.drawText(1, 5, y, buffer, ST7735Colors::GREEN, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "MIN FT: %ums", metrics.minFrameTime);
    gfx.drawText(1, 5, y, buffer, ST7735Colors::CYAN, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "MAX FT: %ums", metrics.maxFrameTime);
    gfx.drawText(1, 5, y, buffer, ST7735Colors::YELLOW, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "FRAMES: %u", demo.frameCount);
    gfx.drawText(1, 5, y, buffer, ST7735Colors::MAGENTA, 1);
    y += 15;
    
    float load = (metrics.frameTime / (float)FRAME_TIME_MS) * 100;
    int16_t barWidth = (int16_t)(gfx.WIDTH * 0.8f * (load / 100.0f));
    barWidth = constrain(barWidth, 0, (int16_t)(gfx.WIDTH * 0.8f));
    uint16_t loadColor = (load < 60) ? ST7735Colors::GREEN :
                        (load < 80) ? ST7735Colors::YELLOW : ST7735Colors::RED;
    
    gfx.drawRect(1, 5, y, (int16_t)(gfx.WIDTH * 0.8f), 8, ST7735Colors::WHITE);
    gfx.fillRect(1, 6, y + 1, barWidth, 6, loadColor);
    
    gfx.drawText(0, 5, 5, "PERF BENCH", ST7735Colors::WHITE, 1);
}

void demoRotatingLines() {
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::DARK_GRAY);
    
    const int16_t centerX = gfx.WIDTH / 2;
    const int16_t centerY = gfx.HEIGHT / 2;
    const int segments = 8;
    
    for (int i = 0; i < segments; i++) {
        float angle = (i * 2 * PI / segments) + (demo.animationTime * 0.5f);
        int16_t x1 = centerX + (40 * cos(angle));
        int16_t y1 = centerY + (40 * sin(angle));
        int16_t x2 = centerX + (20 * cos(angle + PI));
        int16_t y2 = centerY + (20 * sin(angle + PI));
        uint16_t color = (i % 2) ? ST7735Colors::RED : ST7735Colors::BLUE;
        gfx.fillTriangle(0, centerX, centerY, x1, y1, x2, y2, color);
    }
    
    static float rectX = 20, rectY = 20;
    static float velX = 150, velY = 180; // Increased velocity for more visible X movement
    
    rectX += velX * demo.deltaTime;
    rectY += velY * demo.deltaTime;
    
    // Clamp and reverse to prevent overshoot/clipping
    if (rectX <= 0) {
        rectX = 0;
        velX *= -1;
    } else if (rectX >= gfx.WIDTH - 30) {
        rectX = gfx.WIDTH - 30;
        velX *= -1;
    }
    
    if (rectY <= 0) {
        rectY = 0;
        velY *= -1;
    } else if (rectY >= gfx.HEIGHT - 20) {
        rectY = gfx.HEIGHT - 20;
        velY *= -1;
    }
    
    gfx.fillRoundRect(1, (int16_t)rectX, (int16_t)rectY, 30, 20, 5, ST7735Colors::ORANGE);
    
    gfx.drawText(0, 5, 5, "ROTATING LINES", ST7735Colors::WHITE, 1);
}

void demoParticleGravity() {
    constexpr int MAX_PARTICLES = 50;
    static struct Particle {
        float x, y, vx, vy;
        uint16_t color;
        bool active;
    } particles[MAX_PARTICLES];
    
    static bool initialized = false;
    static uint32_t lastDemo = UINT32_MAX;
    
    if (demo.currentDemo != lastDemo) {
        initialized = false;
        lastDemo = demo.currentDemo;
    }
    
    if (!initialized) {
        for (int i = 0; i < MAX_PARTICLES; i++) {
            particles[i].x = gfx.WIDTH / 2.0f;
            particles[i].y = gfx.HEIGHT / 2.0f;
            particles[i].vx = (random(-100, 100) / 10.0f);
            particles[i].vy = (random(-50, 0) / 10.0f);
            particles[i].color = gfx.rgb565(random(100, 255), random(100, 255), random(100, 255));
            particles[i].active = true;
        }
        initialized = true;
    }
    
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    for (int i = 0; i < MAX_PARTICLES; i++) {
        if (particles[i].active) {
            particles[i].vy += 50 * demo.deltaTime; // Gravity
            particles[i].x += particles[i].vx * demo.deltaTime;
            particles[i].y += particles[i].vy * demo.deltaTime;
            
            if (particles[i].x <= 0 || particles[i].x >= gfx.WIDTH) {
                particles[i].vx *= -0.8f;
                particles[i].x = constrain(particles[i].x, 0, gfx.WIDTH - 1);
            }
            if (particles[i].y >= gfx.HEIGHT - 2) {
                particles[i].vy *= -0.7f;
                particles[i].y = gfx.HEIGHT - 2;
            }
            if (particles[i].y <= 0) {
                particles[i].vy *= -0.8f;
                particles[i].y = 0;
            }
            
            int16_t px = (int16_t)particles[i].x;
            int16_t py = (int16_t)particles[i].y;
            gfx.fillCircle(0, px, py, 2, particles[i].color);
            gfx.fillCircle(1, gfx.WIDTH - px, py, 2, particles[i].color);
            gfx.setPixel(0, px + 1, py, particles[i].color >> 1);
            gfx.setPixel(1, gfx.WIDTH - px - 1, py, particles[i].color >> 1);
        }
    }
    
    gfx.drawText(0, 5, 5, "PARTICLE GRAVITY", ST7735Colors::WHITE, 1);
}

void demoStatsOverview() {
    // **FIXED: Added serial debug to confirm this function is called**
    static bool debugPrinted = false;
    if (!debugPrinted) {
        Serial.println("*** STATS OVERVIEW DEMO IS RUNNING! ***");
        debugPrinted = true;
    }
    
    // Screen 0: Dynamic stats update
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    char buffer[32];
    int16_t y = 10;
    
    gfx.drawText(0, 5, y, "STATS OVERVIEW", ST7735Colors::WHITE, 1);
    y += 15;
    
    const ST7735PerformanceStats& stats = gfx.getPerformanceStats();
    
    snprintf(buffer, sizeof(buffer), "FPS: %.1f", metrics.currentFPS);
    gfx.drawText(0, 5, y, buffer, ST7735Colors::GREEN, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "AVG FPS: %.1f", stats.averageFHOPS);
    gfx.drawText(0, 5, y, buffer, ST7735Colors::CYAN, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "FRAME TIME: %ums", metrics.frameTime);
    gfx.drawText(0, 5, y, buffer, ST7735Colors::YELLOW, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "MIN FT: %ums", metrics.minFrameTime);
    gfx.drawText(0, 5, y, buffer, ST7735Colors::GREEN, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "MAX FT: %ums", metrics.maxFrameTime);
    gfx.drawText(0, 5, y, buffer, ST7735Colors::RED, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "HEAP FREE: %uKB", gfx.getFreeHeap() / 1024);
    gfx.drawText(0, 5, y, buffer, ST7735Colors::MAGENTA, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "PSRAM USED: %uKB", gfx.getUsedPSRAM() / 1024);
    gfx.drawText(0, 5, y, buffer, ST7735Colors::ORANGE, 1);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "FRAME COUNT: %u", demo.frameCount);
    gfx.drawText(0, 5, y, buffer, ST7735Colors::BLUE, 1);
    
    // **FIXED: Screen 1 mini-demo cycling with proper initialization**
    static uint32_t miniDemoStart = 0;
    static uint32_t miniDemoIndex = 0;
    static bool statsInitialized = false;
    
    // Proper initialization when entering stats overview
    if (demo.currentDemo == 7 && !statsInitialized) {
        miniDemoStart = millis();
        miniDemoIndex = 0;
        statsInitialized = true;
        Serial.println("Stats overview mini-demo cycling started");
    } else if (demo.currentDemo != 7) {
        statsInitialized = false; // Reset when leaving stats demo
    }
    
    // Cycle through mini-demos every 2 seconds for variety
    if (statsInitialized && (millis() - miniDemoStart > 2000)) {
        miniDemoIndex = (miniDemoIndex + 1) % 9; // 9 different mini-demos
        miniDemoStart = millis();
        Serial.printf("Mini-demo switched to: %u\n", miniDemoIndex);
    }
    
    // **FIXED: Always clear screen 1 buffer first**
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    // Show mini-demo on screen 1
    switch (miniDemoIndex) {
        case 0: 
            // Mini sync waves
            for (int16_t x = 0; x < gfx.WIDTH; x += 4) {
                float phase = (x / 10.0f) + (demo.animationTime * 1.0f);
                int16_t y = (gfx.HEIGHT / 2) + (20 * sin(phase));
                if (y >= 0 && y < gfx.HEIGHT) {
                    gfx.setPixel(1, x, y, ST7735Colors::CYAN);
                }
            }
            gfx.drawText(1, 5, 5, "MINI: WAVES", ST7735Colors::WHITE, 1);
            break;
            
        case 1: 
            // Mini geometric art
            for (int i = 0; i < 20; i++) {
                float angle = (i * 0.5f) + (demo.animationTime * 1);
                int16_t x = (gfx.WIDTH / 2) + (i * 2 * cos(angle));
                int16_t y = (gfx.HEIGHT / 2) + (i * 2 * sin(angle));
                if (x >= 0 && x < gfx.WIDTH && y >= 0 && y < gfx.HEIGHT) {
                    gfx.setPixel(1, x, y, ST7735Colors::GREEN);
                }
            }
            gfx.drawText(1, 5, 5, "MINI: GEO", ST7735Colors::WHITE, 1);
            break;
            
        case 2: 
            // Mini performance bars
            for (int x = 0; x < gfx.WIDTH; x += 8) {
                int height = (x * gfx.HEIGHT) / gfx.WIDTH;
                gfx.drawVLine(1, x, gfx.HEIGHT - height, height, ST7735Colors::YELLOW);
            }
            gfx.drawText(1, 5, 5, "MINI: BARS", ST7735Colors::WHITE, 1);
            break;
            
        case 3: 
            // Mini rotating lines
            {
                int16_t centerX = gfx.WIDTH / 2;
                int16_t centerY = gfx.HEIGHT / 2;
                float angle = demo.animationTime;
                int16_t x1 = centerX + (30 * cos(angle));
                int16_t y1 = centerY + (30 * sin(angle));
                gfx.drawLine(1, centerX, centerY, x1, y1, ST7735Colors::RED);
                gfx.drawLine(1, centerX, centerY, gfx.WIDTH - x1, y1, ST7735Colors::BLUE);
            }
            gfx.drawText(1, 5, 5, "MINI: LINES", ST7735Colors::WHITE, 1);
            break;
            
        case 4: 
            // Mini circles
            {
                int16_t centerX = gfx.WIDTH / 2;
                int16_t centerY = gfx.HEIGHT / 2;
                int radius = 20 + (10 * sin(demo.animationTime * 2));
                gfx.drawCircle(1, centerX, centerY, radius, ST7735Colors::MAGENTA);
            }
            gfx.drawText(1, 5, 5, "MINI: CIRCLE", ST7735Colors::WHITE, 1);
            break;
            
        case 5: 
            // Mini rectangles
            {
                int16_t size = 20 + (10 * sin(demo.animationTime));
                int16_t x = (gfx.WIDTH - size) / 2;
                int16_t y = (gfx.HEIGHT - size) / 2;
                gfx.drawRect(1, x, y, size, size, ST7735Colors::ORANGE);
            }
            gfx.drawText(1, 5, 5, "MINI: RECT", ST7735Colors::WHITE, 1);
            break;
            
        case 6: 
            // Mini color bars
            for (int y = 0; y < gfx.HEIGHT; y += 20) {
                uint16_t color = gfx.rgb565((y * 255) / gfx.HEIGHT, 128, 255 - (y * 255) / gfx.HEIGHT);
                gfx.fillRect(1, 0, y, gfx.WIDTH, 20, color);
            }
            gfx.drawText(1, 5, 5, "MINI: COLOR", ST7735Colors::WHITE, 1);
            break;
            
        case 7: 
            // Mini primitives
            gfx.drawLine(1, 0, 0, gfx.WIDTH-1, gfx.HEIGHT-1, ST7735Colors::RED);
            gfx.drawLine(1, 0, gfx.HEIGHT-1, gfx.WIDTH-1, 0, ST7735Colors::GREEN);
            gfx.fillCircle(1, gfx.WIDTH/2, gfx.HEIGHT/2, 15, ST7735Colors::BLUE);
            gfx.drawText(1, 5, 5, "MINI: PRIM", ST7735Colors::WHITE, 1);
            break;
            
        case 8: 
            // Mini spectrum
            for (int x = 0; x < gfx.WIDTH; x += 4) {
                for (int y = 0; y < gfx.HEIGHT; y += 4) {
                    uint8_t hue = (x + y + (int)(demo.animationTime * 50)) % 256;
                    uint16_t color = gfx.rgb565(hue, 255-hue, 128);
                    gfx.setPixel(1, x, y, color);
                }
            }
            gfx.drawText(1, 5, 5, "MINI: SPEC", ST7735Colors::WHITE, 1);
            break;
    }
}

void demoGraphicsPrimitives() {
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    // Animated positions for screen 0
    float offsetX = 10 * sin(demo.animationTime * 2.0f);
    float offsetY = 10 * cos(demo.animationTime * 3.0f);
    int16_t cx = (gfx.WIDTH / 2) + static_cast<int16_t>(offsetX);
    int16_t cy = (gfx.HEIGHT / 2) + static_cast<int16_t>(offsetY);
    
    // Draw on screen 0 with animation
    gfx.drawLine(0, 0, 0, gfx.WIDTH - 1, gfx.HEIGHT - 1, ST7735Colors::RED);
    gfx.drawLine(0, 0, gfx.HEIGHT - 1, gfx.WIDTH - 1, 0, ST7735Colors::GREEN);
    gfx.drawCircle(0, cx, cy, 30, ST7735Colors::BLUE);
    gfx.fillCircle(0, (gfx.WIDTH / 4) + static_cast<int16_t>(offsetX), (gfx.HEIGHT / 4) + static_cast<int16_t>(offsetY), 15, ST7735Colors::YELLOW);
    gfx.drawRect(0, 10 + static_cast<int16_t>(offsetX), 10 + static_cast<int16_t>(offsetY), gfx.WIDTH - 20, gfx.HEIGHT - 20, ST7735Colors::CYAN);
    gfx.fillRect(0, 20 + static_cast<int16_t>(offsetX), 20 + static_cast<int16_t>(offsetY), 40, 30, ST7735Colors::MAGENTA);
    gfx.drawTriangle(0, cx, 20 + static_cast<int16_t>(offsetY), (gfx.WIDTH / 4) + static_cast<int16_t>(offsetX), 60 + static_cast<int16_t>(offsetY), (3 * gfx.WIDTH / 4) + static_cast<int16_t>(offsetX), 60 + static_cast<int16_t>(offsetY), ST7735Colors::WHITE);
    gfx.fillTriangle(0, cx, 80 + static_cast<int16_t>(offsetY), (gfx.WIDTH / 4) + static_cast<int16_t>(offsetX), 120 + static_cast<int16_t>(offsetY), (3 * gfx.WIDTH / 4) + static_cast<int16_t>(offsetX), 120 + static_cast<int16_t>(offsetY), ST7735Colors::ORANGE);
    gfx.drawRoundRect(0, (gfx.WIDTH / 4) + static_cast<int16_t>(offsetX), (gfx.HEIGHT / 2 + 20) + static_cast<int16_t>(offsetY), 60, 30, 10, ST7735Colors::GREEN);
    gfx.fillRoundRect(0, (gfx.WIDTH / 2 + 10) + static_cast<int16_t>(offsetX), (gfx.HEIGHT / 2 + 20) + static_cast<int16_t>(offsetY), 40, 20, 5, ST7735Colors::RED);
    
    // Mirror similar primitives on screen 1 with variation
    gfx.drawLine(1, 1, 0, gfx.WIDTH - 1, gfx.HEIGHT - 1, ST7735Colors::BLUE);
    gfx.drawLine(1, 1, gfx.HEIGHT - 1, gfx.WIDTH - 1, 0, ST7735Colors::YELLOW);
    gfx.drawCircle(1, gfx.WIDTH / 2, gfx.HEIGHT / 2, 25, ST7735Colors::RED);
    gfx.fillCircle(1, 3 * gfx.WIDTH / 4, 3 * gfx.HEIGHT / 4, 10, ST7735Colors::GREEN);
    gfx.drawRect(1, 15, 15, gfx.WIDTH - 30, gfx.HEIGHT - 30, ST7735Colors::MAGENTA);
    gfx.fillRect(1, 25, 25, 50, 40, ST7735Colors::CYAN);
    gfx.drawTriangle(1, gfx.WIDTH / 2, 30, gfx.WIDTH / 4, 70, 3 * gfx.WIDTH / 4, 70, ST7735Colors::ORANGE);
    gfx.fillTriangle(1, gfx.WIDTH / 2, 90, gfx.WIDTH / 4, 130, 3 * gfx.WIDTH / 4, 130, ST7735Colors::WHITE);
    gfx.drawRoundRect(1, gfx.WIDTH / 4 + 10, gfx.HEIGHT / 2 + 30, 50, 25, 8, ST7735Colors::YELLOW);
    gfx.fillRoundRect(1, gfx.WIDTH / 2 + 20, gfx.HEIGHT / 2 + 30, 30, 15, 4, ST7735Colors::BLUE);
    
    // Add text label with credit
    gfx.drawText(0, 5, 5, "GFX PRIMITIVES", ST7735Colors::WHITE, 1);
    gfx.drawText(1, 5, 5, "GFX PRIMITIVES", ST7735Colors::WHITE, 1);
}

void demoColorSpectrum() {
    // Clear buffers to ensure no artifacts
    gfx.clearBuffer(0, ST7735Colors::BLACK);
    gfx.clearBuffer(1, ST7735Colors::BLACK);
    
    // Screen 0: Horizontal rainbow gradient shifting over time
    float shift0 = demo.animationTime * 50.0f; // Shift speed
    for (int16_t y = 0; y < gfx.HEIGHT; y++) {
        for (int16_t x = 0; x < gfx.WIDTH; x++) {
            uint8_t hue = static_cast<uint8_t>(static_cast<int>(x + y + shift0) % 256);
            uint8_t r = (hue < 85) ? hue * 3 : ((hue < 170) ? 255 : (255 - (hue - 170) * 3));
            uint8_t g = (hue < 85) ? 255 - hue * 3 : ((hue < 170) ? (hue - 85) * 3 : 0);
            uint8_t b = (hue < 85) ? 0 : ((hue < 170) ? 255 - (hue - 85) * 3 : (hue - 170) * 3);
            gfx.setPixel(0, x, y, gfx.rgb565(r, g, b));
        }
    }
    
    // Screen 1: Vertical color bars with different shifting
    float shift1 = demo.animationTime * 30.0f; // Different speed
    for (int16_t x = 0; x < gfx.WIDTH; x++) {
        for (int16_t y = 0; y < gfx.HEIGHT; y++) {
            uint8_t hue = static_cast<uint8_t>(static_cast<int>(x + y + shift1) % 256);
            uint8_t r = sin((hue / 255.0f) * 2 * PI) * 127 + 128;
            uint8_t g = sin((hue / 255.0f) * 2 * PI + 2 * PI / 3) * 127 + 128;
            uint8_t b = sin((hue / 255.0f) * 2 * PI + 4 * PI / 3) * 127 + 128;
            gfx.setPixel(1, x, y, gfx.rgb565(r, g, b));
        }
    }
    
    // Draw a background rectangle for text to ensure contrast
    gfx.fillRect(0, 5, 5, gfx.getTextWidth("COLOR SPECTRUM") + 6, 12, ST7735Colors::BLACK);
    // Use drawTextFullChar for consistent rendering
    #if ST7735_ENABLE_DEBUG
    Serial.println("Rendering text in demoColorSpectrum: COLOR SPECTRUM");
    for (const char* c = "COLOR SPECTRUM"; *c != '\0'; ++c) {
        Serial.printf("Char: %c (ASCII: %d)\n", *c, (int)*c);
    }
    #endif
    drawTextFullChar(0, 5, 5, "COLOR SPECTRUM", ST7735Colors::WHITE, 1, ST7735Colors::BLACK);
}

// ========================================
// UTILITY FUNCTIONS
// ========================================

void updateNeoPixelStatus() {
    if (!ledEnabled || millis() - metrics.lastLEDUpdate < ledUpdateInterval) {
        return;
    }
    
    uint32_t statusColor;
    if (metrics.performanceGood) {
        statusColor = LED_PERFORMANCE_GOOD;
    } else if (metrics.currentFPS > 30) {
        statusColor = LED_PERFORMANCE_WARN;
    } else {
        statusColor = LED_ERROR;
    }
    
    float breathe = (sin(millis() * 0.003f) + 1.0f) * 0.5f;
    uint8_t brightness = (uint8_t)(ledBrightness * (0.3f + 0.7f * breathe));
    statusLED.setBrightness(brightness);
    statusLED.setPixelColor(0, statusColor);
    statusLED.show();
    metrics.lastLEDUpdate = millis();
}

void displayPerformanceOverlay(int screen) {
    if (!demo.showPerformanceOverlay) {
        #if ST7735_ENABLE_DEBUG
        Serial.println("Warning: Performance overlay disabled");
        #endif
        return;
    }
    
    char buffer[16];
    snprintf(buffer, sizeof(buffer), "%.1f", metrics.currentFPS);
    uint16_t fpsColor;
    if (metrics.currentFPS >= 50) fpsColor = ST7735Colors::GREEN;
    else if (metrics.currentFPS >= 30) fpsColor = ST7735Colors::YELLOW;
    else fpsColor = ST7735Colors::RED;
    
    // Ensure coordinates are within bounds
    int16_t x = gfx.WIDTH - 35;
    int16_t y = 2;
    if (x < 0 || y < 0 || x + 35 > gfx.WIDTH || y + 12 > gfx.HEIGHT) {
        #if ST7735_ENABLE_DEBUG
        Serial.println("Warning: FPS overlay coordinates out of bounds");
        #endif
        x = max((int16_t)0, min(x, (int16_t)(gfx.WIDTH - 35)));
        y = max((int16_t)0, min(y, (int16_t)(gfx.HEIGHT - 12)));
    }
    
    gfx.fillRect(screen, x, y - 2, 35, 12, ST7735Colors::BLACK);
    gfx.drawText(screen, x + 3, y, buffer, fpsColor, 1);
}

// Custom drawText with full-character clipping
void drawTextFullChar(const int screen, int16_t x, const int16_t y, const char* text, const uint16_t color, const uint8_t size, const uint16_t bg) {
    int16_t cursor_x = x;
    for (const char* c = text; *c != '\0'; ++c) {
        int16_t char_w = ST7735Specs::FONT_SPACING * size;
        if (cursor_x >= 0 && cursor_x + char_w - 1 < gfx.WIDTH) {
            gfx.drawChar(screen, cursor_x, y, *c, color, size, bg);
        }
        cursor_x += char_w;
    }
}

Here is the same ino sketch but implemented in Adafruit GFX without any enhancement:

/*
 * ADAFRUIT GFX ST7735 Performance Comparison Demo
 * Direct equivalent of TFTFriend demo using only standard Adafruit libraries
 * 
 * Hardware: ESP32-S3 DevKit C-1 with ST7735 128x160 displays
 * Purpose: Fair performance comparison without custom optimizations
 */

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <Adafruit_NeoPixel.h>

// ========================================
// HARDWARE CONFIGURATION
// ========================================

// Display pins (using same as TFTFriend for fair comparison)
#define TFT0_CS   15
#define TFT0_RST  6
#define TFT0_DC   7
#define TFT1_CS   10
#define TFT1_RST  5
#define TFT1_DC   4
#define TFT_SCLK  12
#define TFT_MOSI  11

// NeoPixel configuration
#define NEOPIXEL_PIN 38
#define NEOPIXEL_COUNT 1
Adafruit_NeoPixel statusLED(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

// Display instances
Adafruit_ST7735 tft0 = Adafruit_ST7735(TFT0_CS, TFT0_DC, TFT_MOSI, TFT_SCLK, TFT0_RST);
Adafruit_ST7735 tft1 = Adafruit_ST7735(TFT1_CS, TFT1_DC, TFT_MOSI, TFT_SCLK, TFT1_RST);

// ========================================
// DEMO CONFIGURATION
// ========================================

constexpr uint32_t DEMO_DURATION_MS = 60000; // 60 seconds per demo
constexpr uint32_t TARGET_FPS = 60;
constexpr uint32_t FRAME_TIME_MS = 1000 / TARGET_FPS;

// Demo state management
struct DemoState {
    uint32_t currentDemo = 0;
    uint32_t demoStartTime = 0;
    uint32_t frameCount = 0;
    float animationTime = 0.0f;
    float deltaTime = 0.0f;
    uint32_t lastFrameTime = 0;
    bool showPerformanceOverlay = true;
} demo;

// Performance tracking
struct UserMetrics {
    float currentFPS = 0.0f;
    uint32_t frameTime = 0;
    uint32_t minFrameTime = UINT32_MAX;
    uint32_t maxFrameTime = 0;
    bool performanceGood = true;
    uint32_t lastLEDUpdate = 0;
} metrics;

// Status LED colors
const uint32_t LED_INITIALIZING = statusLED.Color(255, 165, 0);  // Orange
const uint32_t LED_RUNNING = statusLED.Color(0, 255, 0);        // Green
const uint32_t LED_ERROR = statusLED.Color(255, 0, 0);          // Red
const uint32_t LED_PERFORMANCE_GOOD = statusLED.Color(0, 0, 255); // Blue
const uint32_t LED_PERFORMANCE_WARN = statusLED.Color(255, 255, 0); // Yellow

// ========================================
// DEMO FUNCTIONS
// ========================================

void demoSynchronizedWaves() {
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    const float frequency = 2.0f;
    const int16_t amplitude = 60;
    const int16_t centerY = 80; // HEIGHT / 2
    
    // Draw wave on both displays
    for (int16_t x = 0; x < 128; x++) {
        float phase1 = (x / 20.0f) + (demo.animationTime * frequency);
        int16_t y1 = centerY + (amplitude * sin(phase1));
        tft0.drawPixel(x, y1, ST77XX_CYAN);
        
        float phase2 = (x / 20.0f) - (demo.animationTime * frequency);
        int16_t y2 = centerY + (amplitude * sin(phase2));
        tft1.drawPixel(x, y2, ST77XX_MAGENTA);
    }
    
    // Moving circles
    int16_t circleX = 64 + (30 * sin(demo.animationTime * 3));
    int16_t circleY = 80 + (40 * cos(demo.animationTime * 2));
    tft0.drawCircle(circleX, circleY, 15, ST77XX_YELLOW);
    tft1.drawCircle(128 - circleX, circleY, 15, ST77XX_GREEN);
    
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("SYNC WAVES");
}

void demoParticlePhysics() {
    constexpr int MAX_PARTICLES = 50;
    static struct Particle {
        float x, y, vx, vy;
        uint16_t color;
        bool active;
    } particles[MAX_PARTICLES];
    
    static bool initialized = false;
    static uint32_t lastDemo = UINT32_MAX;
    
    if (demo.currentDemo != lastDemo) {
        initialized = false;
        lastDemo = demo.currentDemo;
    }
    
    if (!initialized) {
        for (int i = 0; i < MAX_PARTICLES; i++) {
            particles[i].x = 64;
            particles[i].y = 80;
            particles[i].vx = (random(-100, 100) / 10.0f);
            particles[i].vy = (random(-100, 100) / 10.0f);
            particles[i].color = tft0.color565(random(100, 255), random(100, 255), random(100, 255));
            particles[i].active = true;
        }
        initialized = true;
    }
    
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    for (int i = 0; i < MAX_PARTICLES; i++) {
        if (particles[i].active) {
            particles[i].x += particles[i].vx * demo.deltaTime;
            particles[i].y += particles[i].vy * demo.deltaTime;
            
            // Boundary collision
            if (particles[i].x <= 0 || particles[i].x >= 128) {
                particles[i].vx *= -0.8f;
                particles[i].x = constrain(particles[i].x, 0, 127);
            }
            if (particles[i].y <= 0 || particles[i].y >= 160) {
                particles[i].vy *= -0.8f;
                particles[i].y = constrain(particles[i].y, 0, 159);
            }
            
            int16_t px = (int16_t)particles[i].x;
            int16_t py = (int16_t)particles[i].y;
            tft0.fillCircle(px, py, 2, particles[i].color);
            tft1.fillCircle(128 - px, py, 2, particles[i].color);
        }
    }
    
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("PARTICLES ORIG");
}

void demoGeometricArt() {
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    const int16_t centerX = 64;
    const int16_t centerY = 80;
    
    // Spiral pattern
    for (int i = 0; i < 50; i++) {
        float angle = (i * 0.3f) + (demo.animationTime * 2);
        float radius = i * 1.5f;
        int16_t x = centerX + (radius * cos(angle));
        int16_t y = centerY + (radius * sin(angle));
        if (x >= 0 && x < 128 && y >= 0 && y < 160) {
            uint16_t color = tft0.color565(i * 5, 255 - i * 3, 128 + i * 2);
            tft0.drawPixel(x, y, color);
        }
    }
    
    // Breathing circles
    for (int r = 10; r < 60; r += 10) {
        uint16_t color;
        switch ((r / 10) % 4) {
            case 0: color = ST77XX_RED; break;
            case 1: color = ST77XX_GREEN; break;
            case 2: color = ST77XX_BLUE; break;
            case 3: color = ST77XX_YELLOW; break;
        }
        
        float breathe = sin(demo.animationTime * 3 + r * 0.1f) * 5;
        tft1.drawCircle(64, 80, r + (int16_t)breathe, color);
    }
    
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("GEOMETRIC ART");
}

void demoTextAndUI() {
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    // Scrolling text
    static int16_t scrollPos = 128;
    scrollPos -= 2;
    if (scrollPos < -200) scrollPos = 128;
    
    tft0.setCursor(scrollPos, 20);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(2);
    tft0.print("ESP32 SPI DUO");
    
    tft0.setCursor(scrollPos, 50);
    tft0.setTextColor(ST77XX_YELLOW);
    tft0.setTextSize(1);
    tft0.print("HIGH PERFORMANCE");
    
    // System status on second display
    tft1.setCursor(5, 10);
    tft1.setTextColor(ST77XX_WHITE);
    tft1.setTextSize(1);
    tft1.print("SYSTEM STATUS");
    
    char buffer[32];
    tft1.setCursor(5, 25);
    tft1.setTextColor(ST77XX_GREEN);
    snprintf(buffer, sizeof(buffer), "FPS: %.1f", metrics.currentFPS);
    tft1.print(buffer);
    
    tft1.setCursor(5, 40);
    tft1.setTextColor(ST77XX_YELLOW);
    snprintf(buffer, sizeof(buffer), "HEAP: %uKB", ESP.getFreeHeap() / 1024);
    tft1.print(buffer);
    
    tft1.setCursor(5, 55);
    tft1.setTextColor(ST77XX_CYAN);
    snprintf(buffer, sizeof(buffer), "TIME: %.1fs", demo.animationTime);
    tft1.print(buffer);
    
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("TEXT & UI");
}

void demoPerformanceBench() {
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    // Frame time history graph
    static uint32_t frameHistory[128];
    static int historyIndex = 0;
    
    frameHistory[historyIndex] = metrics.frameTime;
    historyIndex = (historyIndex + 1) % 128;
    
    for (int x = 0; x < 128; x++) {
        int index = (historyIndex + x) % 128;
        int height = map(frameHistory[index], 0, 50, 0, 140);
        height = constrain(height, 0, 140);
        
        uint16_t color = (height < 20) ? ST77XX_GREEN :
                        (height < 30) ? ST77XX_YELLOW : ST77XX_RED;
        
        tft0.drawFastVLine(x, 160 - height, height, color);
    }
    
    // Performance stats
    char buffer[32];
    tft1.setCursor(5, 10);
    tft1.setTextColor(ST77XX_WHITE);
    tft1.setTextSize(1);
    tft1.print("PERFORMANCE");
    
    tft1.setCursor(5, 25);
    tft1.setTextColor(ST77XX_GREEN);
    snprintf(buffer, sizeof(buffer), "AVG FPS: %.1f", metrics.currentFPS);
    tft1.print(buffer);
    
    tft1.setCursor(5, 37);
    tft1.setTextColor(ST77XX_CYAN);
    snprintf(buffer, sizeof(buffer), "MIN FT: %ums", metrics.minFrameTime);
    tft1.print(buffer);
    
    tft1.setCursor(5, 49);
    tft1.setTextColor(ST77XX_YELLOW);
    snprintf(buffer, sizeof(buffer), "MAX FT: %ums", metrics.maxFrameTime);
    tft1.print(buffer);
    
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("PERF BENCH");
}

void demoRotatingLines() {
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(0x2104); // Dark gray
    
    const int16_t centerX = 64;
    const int16_t centerY = 80;
    const int segments = 8;
    
    // Rotating triangles
    for (int i = 0; i < segments; i++) {
        float angle = (i * 2 * PI / segments) + (demo.animationTime * 0.5f);
        int16_t x1 = centerX + (40 * cos(angle));
        int16_t y1 = centerY + (40 * sin(angle));
        int16_t x2 = centerX + (20 * cos(angle + PI));
        int16_t y2 = centerY + (20 * sin(angle + PI));
        
        uint16_t color = (i % 2) ? ST77XX_RED : ST77XX_BLUE;
        tft0.fillTriangle(centerX, centerY, x1, y1, x2, y2, color);
    }
    
    // Bouncing rectangle
    static float rectX = 20, rectY = 20;
    static float velX = 150, velY = 180;
    
    rectX += velX * demo.deltaTime;
    rectY += velY * demo.deltaTime;
    
    if (rectX <= 0) {
        rectX = 0;
        velX *= -1;
    } else if (rectX >= 98) { // 128 - 30
        rectX = 98;
        velX *= -1;
    }
    
    if (rectY <= 0) {
        rectY = 0;
        velY *= -1;
    } else if (rectY >= 140) { // 160 - 20
        rectY = 140;
        velY *= -1;
    }
    
    tft1.fillRoundRect((int16_t)rectX, (int16_t)rectY, 30, 20, 5, ST77XX_ORANGE);
    
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("ROTATING LINES");
}

void demoParticleGravity() {
    constexpr int MAX_PARTICLES = 50;
    static struct Particle {
        float x, y, vx, vy;
        uint16_t color;
        bool active;
    } particles[MAX_PARTICLES];
    
    static bool initialized = false;
    static uint32_t lastDemo = UINT32_MAX;
    
    if (demo.currentDemo != lastDemo) {
        initialized = false;
        lastDemo = demo.currentDemo;
    }
    
    if (!initialized) {
        for (int i = 0; i < MAX_PARTICLES; i++) {
            particles[i].x = 64;
            particles[i].y = 80;
            particles[i].vx = (random(-100, 100) / 10.0f);
            particles[i].vy = (random(-50, 0) / 10.0f);
            particles[i].color = tft0.color565(random(100, 255), random(100, 255), random(100, 255));
            particles[i].active = true;
        }
        initialized = true;
    }
    
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    for (int i = 0; i < MAX_PARTICLES; i++) {
        if (particles[i].active) {
            particles[i].vy += 50 * demo.deltaTime; // Gravity
            particles[i].x += particles[i].vx * demo.deltaTime;
            particles[i].y += particles[i].vy * demo.deltaTime;
            
            // Boundary collision with bounce
            if (particles[i].x <= 0 || particles[i].x >= 128) {
                particles[i].vx *= -0.8f;
                particles[i].x = constrain(particles[i].x, 0, 127);
            }
            if (particles[i].y >= 158) {
                particles[i].vy *= -0.7f;
                particles[i].y = 158;
            }
            if (particles[i].y <= 0) {
                particles[i].vy *= -0.8f;
                particles[i].y = 0;
            }
            
            int16_t px = (int16_t)particles[i].x;
            int16_t py = (int16_t)particles[i].y;
            tft0.fillCircle(px, py, 2, particles[i].color);
            tft1.fillCircle(128 - px, py, 2, particles[i].color);
        }
    }
    
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("PARTICLE GRAVITY");
}

void demoGraphicsPrimitives() {
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    // Animated positions
    float offsetX = 10 * sin(demo.animationTime * 2.0f);
    float offsetY = 10 * cos(demo.animationTime * 3.0f);
    int16_t cx = 64 + (int16_t)offsetX;
    int16_t cy = 80 + (int16_t)offsetY;
    
    // Various primitives on screen 0
    tft0.drawLine(0, 0, 127, 159, ST77XX_RED);
    tft0.drawLine(0, 159, 127, 0, ST77XX_GREEN);
    tft0.drawCircle(cx, cy, 30, ST77XX_BLUE);
    tft0.fillCircle(32 + (int16_t)offsetX, 40 + (int16_t)offsetY, 15, ST77XX_YELLOW);
    tft0.drawRect(10 + (int16_t)offsetX, 10 + (int16_t)offsetY, 108, 140, ST77XX_CYAN);
    tft0.fillRect(20 + (int16_t)offsetX, 20 + (int16_t)offsetY, 40, 30, ST77XX_MAGENTA);
    tft0.drawTriangle(cx, 20 + (int16_t)offsetY, 32 + (int16_t)offsetX, 60 + (int16_t)offsetY, 96 + (int16_t)offsetX, 60 + (int16_t)offsetY, ST77XX_WHITE);
    
    // Similar primitives on screen 1
    tft1.drawLine(1, 0, 127, 159, ST77XX_BLUE);
    tft1.drawLine(1, 159, 127, 0, ST77XX_YELLOW);
    tft1.drawCircle(64, 80, 25, ST77XX_RED);
    tft1.fillCircle(96, 120, 10, ST77XX_GREEN);
    tft1.drawRect(15, 15, 98, 130, ST77XX_MAGENTA);
    tft1.fillRect(25, 25, 50, 40, ST77XX_CYAN);
    
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("GFX PRIMITIVES");
    
    tft1.setCursor(5, 5);
    tft1.setTextColor(ST77XX_WHITE);
    tft1.setTextSize(1);
    tft1.print("GFX PRIMITIVES");
}

void demoColorSpectrum() {
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    // Full screen color gradient - this is intensive for Adafruit GFX
    float shift0 = demo.animationTime * 50.0f;
    for (int16_t y = 0; y < 160; y += 2) { // Skip every other line for performance
        for (int16_t x = 0; x < 128; x += 2) { // Skip every other pixel
            uint8_t hue = (uint8_t)((x + y + (int)shift0) % 256);
            uint8_t r = (hue < 85) ? hue * 3 : ((hue < 170) ? 255 : (255 - (hue - 170) * 3));
            uint8_t g = (hue < 85) ? 255 - hue * 3 : ((hue < 170) ? (hue - 85) * 3 : 0);
            uint8_t b = (hue < 85) ? 0 : ((hue < 170) ? 255 - (hue - 85) * 3 : (hue - 170) * 3);
            
            uint16_t color = tft0.color565(r, g, b);
            tft0.drawPixel(x, y, color);
            
            // Different pattern on second display
            uint8_t hue2 = (uint8_t)((x + y + (int)(shift0 * 0.6f)) % 256);
            uint8_t r2 = sin((hue2 / 255.0f) * 2 * PI) * 127 + 128;
            uint8_t g2 = sin((hue2 / 255.0f) * 2 * PI + 2 * PI / 3) * 127 + 128;
            uint8_t b2 = sin((hue2 / 255.0f) * 2 * PI + 4 * PI / 3) * 127 + 128;
            
            uint16_t color2 = tft1.color565(r2, g2, b2);
            tft1.drawPixel(x, y, color2);
        }
    }
    
    // Text with background
    tft0.fillRect(5, 5, 90, 12, ST77XX_BLACK);
    tft0.setCursor(5, 5);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("COLOR SPECTRUM");
}

void demoStatsOverview() {
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    // Performance statistics on screen 0
    char buffer[32];
    tft0.setCursor(5, 10);
    tft0.setTextColor(ST77XX_WHITE);
    tft0.setTextSize(1);
    tft0.print("STATS OVERVIEW");
    
    tft0.setCursor(5, 25);
    tft0.setTextColor(ST77XX_GREEN);
    snprintf(buffer, sizeof(buffer), "FPS: %.1f", metrics.currentFPS);
    tft0.print(buffer);
    
    tft0.setCursor(5, 37);
    tft0.setTextColor(ST77XX_YELLOW);
    snprintf(buffer, sizeof(buffer), "FRAME TIME: %ums", metrics.frameTime);
    tft0.print(buffer);
    
    tft0.setCursor(5, 49);
    tft0.setTextColor(ST77XX_GREEN);
    snprintf(buffer, sizeof(buffer), "MIN FT: %ums", metrics.minFrameTime);
    tft0.print(buffer);
    
    tft0.setCursor(5, 61);
    tft0.setTextColor(ST77XX_RED);
    snprintf(buffer, sizeof(buffer), "MAX FT: %ums", metrics.maxFrameTime);
    tft0.print(buffer);
    
    tft0.setCursor(5, 73);
    tft0.setTextColor(ST77XX_MAGENTA);
    snprintf(buffer, sizeof(buffer), "HEAP FREE: %uKB", ESP.getFreeHeap() / 1024);
    tft0.print(buffer);
    
    // Mini demo cycling on screen 1
    static uint32_t miniDemoStart = 0;
    static uint32_t miniDemoIndex = 0;
    
    if (demo.currentDemo != 7) {
        miniDemoStart = millis();
        miniDemoIndex = 0;
    } else if (millis() - miniDemoStart > 5000) {
        miniDemoIndex = (miniDemoIndex + 1) % 8;
        miniDemoStart = millis();
    }
    
    // Simple mini-demos for screen 1
    switch (miniDemoIndex % 4) {
        case 0:
            for (int i = 0; i < 20; i++) {
                tft1.drawPixel(random(128), random(160), random(0xFFFF));
            }
            break;
        case 1:
            tft1.drawCircle(64, 80, 20 + 10 * sin(demo.animationTime * 4), ST77XX_CYAN);
            break;
        case 2:
            tft1.fillRect(20, 20, 88, 120, random(0xFFFF));
            break;
        case 3:
            tft1.drawLine(0, 0, 127, 159, random(0xFFFF));
            break;
    }
}

// ========================================
// UTILITY FUNCTIONS
// ========================================

void updateNeoPixelStatus() {
    uint8_t ledBrightness = 10;
    uint32_t ledUpdateInterval = 100;
    
    if (!statusLED.getBrightness() || millis() - metrics.lastLEDUpdate < ledUpdateInterval) {
        return;
    }
    
    uint32_t statusColor;
    if (metrics.performanceGood) {
        statusColor = LED_PERFORMANCE_GOOD;
    } else if (metrics.currentFPS > 30) {
        statusColor = LED_PERFORMANCE_WARN;
    } else {
        statusColor = LED_ERROR;
    }
    
    float breathe = (sin(millis() * 0.003f) + 1.0f) * 0.5f;
    uint8_t brightness = (uint8_t)(ledBrightness * (0.3f + 0.7f * breathe));
    statusLED.setBrightness(brightness);
    statusLED.setPixelColor(0, statusColor);
    statusLED.show();
    metrics.lastLEDUpdate = millis();
}

void displayPerformanceOverlay() {
    char buffer[16];
    snprintf(buffer, sizeof(buffer), "%.1f", metrics.currentFPS);
    
    uint16_t fpsColor;
    if (metrics.currentFPS >= 50) fpsColor = ST77XX_GREEN;
    else if (metrics.currentFPS >= 30) fpsColor = ST77XX_YELLOW;
    else fpsColor = ST77XX_RED;
    
    // FPS overlay on screen 1
    tft1.fillRect(90, 2, 35, 10, ST77XX_BLACK);
    tft1.setCursor(93, 2);
    tft1.setTextColor(fpsColor);
    tft1.setTextSize(1);
    tft1.print(buffer);
}

// ========================================
// ARDUINO SETUP AND LOOP
// ========================================

void setup() {
    Serial.begin(115200);
    
    // Initialize NeoPixel
    statusLED.begin();
    statusLED.setBrightness(10);
    statusLED.setPixelColor(0, LED_INITIALIZING);
    statusLED.show();
    
    // Initialize displays
    Serial.println("=== Adafruit GFX ST7735 Performance Demo ===");
    
    tft0.initR(INITR_BLACKTAB);
    tft1.initR(INITR_BLACKTAB);
    
    tft0.setRotation(0);
    tft1.setRotation(0);
    
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    // Test pattern
    tft0.fillScreen(ST77XX_RED);
    tft1.fillScreen(ST77XX_BLUE);
    delay(1000);
    tft0.fillScreen(ST77XX_BLACK);
    tft1.fillScreen(ST77XX_BLACK);
    
    // Initialize timing
    demo.demoStartTime = millis();
    demo.lastFrameTime = demo.demoStartTime;
    metrics.lastLEDUpdate = demo.demoStartTime;
    
    statusLED.setPixelColor(0, LED_RUNNING);
    statusLED.show();
    
    Serial.println("Initialization complete - Starting demo cycle");
}

void loop() {
    uint32_t frameStart = millis();
    
    // Calculate timing
    demo.deltaTime = (frameStart - demo.lastFrameTime) / 1000.0f;
    demo.animationTime += demo.deltaTime;
    demo.lastFrameTime = frameStart;
    
    // Determine current demo
    uint32_t totalTime = frameStart - demo.demoStartTime;
    uint32_t newDemo = (totalTime / DEMO_DURATION_MS) % 9;
    
    if (newDemo != demo.currentDemo) {
        demo.currentDemo = newDemo;
        demo.frameCount = 0;
        demo.animationTime = 0.0f;
        metrics.minFrameTime = UINT32_MAX;
        metrics.maxFrameTime = 0;
        Serial.printf("Starting demo %u\n", demo.currentDemo);
    }
    
    // FPS calculation
    static uint32_t fpsStartTime = frameStart;
    static uint32_t fpsFrameCount = 0;
    fpsFrameCount++;
    
    if (frameStart - fpsStartTime >= 1000) {
        metrics.currentFPS = fpsFrameCount * 1000.0f / (frameStart - fpsStartTime);
        fpsFrameCount = 0;
        fpsStartTime = frameStart;
    }
    
    // Execute current demo
    switch (demo.currentDemo) {
        case 0: demoSynchronizedWaves(); break;
        case 1: demoParticlePhysics(); break;
        case 2: demoGeometricArt(); break;
        case 3: demoTextAndUI(); break;
        case 4: demoPerformanceBench(); break;
        case 5: demoRotatingLines(); break;
        case 6: demoParticleGravity(); break;
        case 7: demoStatsOverview(); break;
        case 8: demoGraphicsPrimitives(); break;
        case 9: demoColorSpectrum(); break;
        default: demo.currentDemo = 0; break;
    }
    
    // Performance overlay
    if (demo.showPerformanceOverlay && demo.currentDemo != 7) {
        displayPerformanceOverlay();
    }
    
    // Calculate frame metrics
    uint32_t frameTime = millis() - frameStart;
    demo.frameCount++;
    metrics.frameTime = frameTime;
    metrics.minFrameTime = min(metrics.minFrameTime, frameTime);
    metrics.maxFrameTime = max(metrics.maxFrameTime, frameTime);
    metrics.performanceGood = (metrics.currentFPS >= 45.0f && frameTime <= 25);
    
    // Update status LED
    updateNeoPixelStatus();
    
    // Frame rate control
    if (frameTime < FRAME_TIME_MS) {
        delay(FRAME_TIME_MS - frameTime);
    }
    
    if (frameTime > FRAME_TIME_MS) {
        Serial.printf("Warning: High frame time in demo %u: %u ms\n", demo.currentDemo, frameTime);
    }
}

And here is the same above sketch but with FreeRTOS enhancements:

/*
 * Adafruit GFX ST7735 Duo ESP32 - Benchmark Comparison Demo v1.0
 * 
 * This sketch replicates the functionality of the TFTFriend GFX demo to provide a
 * fair, scientifically-controlled benchmark using the standard Adafruit GFX library.
 * It uses FreeRTOS to mirror the dual-core architecture of the original demo.
 *
 * ARCHITECTURE:
 * - Core 0 (GraphicsTask): Handles all demo logic, calculations, and rendering to off-screen buffers (GFXcanvas16).
 * - Core 1 (DisplayTask):  Handles pushing the completed frames from the buffers to the physical ST7735 displays.
 * - Synchronization: A FreeRTOS semaphore is used to signal when a frame is ready to be displayed.
 * 
 * LIBRARIES USED:
 * - Adafruit_GFX:      Core graphics library.
 * - Adafruit_ST7735:   Driver for the ST7735 display.
 * - Adafruit_NeoPixel: Driver for the status LED.
 * - SPI:               For communication with the displays.
 * - FreeRTOS:          For multi-core task management.
 *
 * HARDWARE REQUIREMENTS (Identical to original demo):
 * - ESP32-S3 microcontroller (or other dual-core ESP32).
 * - Two ST7735 128x160 displays.
 * - NeoPixel RGB LED (WS2812B) on GPIO 38.
 * - Connections as defined below.
 */

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <Adafruit_NeoPixel.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

// ========================================
// HARDWARE & DISPLAY CONFIGURATION
// ========================================

// Pin configuration for ESP32-S3 DevKit C-1 (matches original sketch)
#define SPI_MOSI    11
#define SPI_SCLK    12
#define TFT1_CS     15
#define TFT1_DC     7
#define TFT1_RST    6
#define TFT2_CS     10
#define TFT2_DC     4
#define TFT2_RST    5

// Common display dimensions
#define TFT_WIDTH   128
#define TFT_HEIGHT  160

// Initialize two Adafruit ST7735 display objects
Adafruit_ST7735 tft1 = Adafruit_ST7735(TFT1_CS, TFT1_DC, TFT1_RST);
Adafruit_ST7735 tft2 = Adafruit_ST7735(TFT2_CS, TFT2_DC, TFT2_RST);

// Create two off-screen graphics buffers (canvases) for drawing
GFXcanvas16 canvas1(TFT_WIDTH, TFT_HEIGHT);
GFXcanvas16 canvas2(TFT_WIDTH, TFT_HEIGHT);

// ========================================
// NEOPIXEL CONFIGURATION
// ========================================
#define NEOPIXEL_PIN     38
#define NEOPIXEL_COUNT   1
#define NEOPIXEL_TYPE    NEO_GRB + NEO_KHZ800
Adafruit_NeoPixel statusLED(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE);

uint8_t ledBrightness = 10;
bool ledEnabled = true;
uint32_t ledUpdateInterval = 100;

// Status colors
const uint32_t LED_INITIALIZING = statusLED.Color(255, 165, 0); // Orange
const uint32_t LED_RUNNING      = statusLED.Color(0, 255, 0);   // Green
const uint32_t LED_ERROR        = statusLED.Color(255, 0, 0);   // Red
const uint32_t LED_PERFORMANCE_GOOD = statusLED.Color(0, 0, 255); // Blue
const uint32_t LED_PERFORMANCE_WARN = statusLED.Color(255, 255, 0); // Yellow

// ========================================
// DEMO & BENCHMARK CONFIGURATION
// ========================================
constexpr uint32_t DEMO_DURATION_MS = 60000;
constexpr uint32_t TARGET_FPS = 60;
constexpr uint32_t FRAME_TIME_MS = 1000 / TARGET_FPS;

struct DemoState {
    uint32_t currentDemo = 0;
    uint32_t demoStartTime = 0;
    uint32_t frameCount = 0;
    float animationTime = 0.0f;
    float deltaTime = 0.0f;
    uint32_t lastFrameTime = 0;
    bool showPerformanceOverlay = true;
} demo;

struct UserMetrics {
    float currentFPS = 0.0f;
    uint32_t frameTime = 0;
    uint32_t minFrameTime = UINT32_MAX;
    uint32_t maxFrameTime = 0;
    bool performanceGood = true;
    uint32_t lastLEDUpdate = 0;
} metrics;

// ========================================
// FREERTOS & DUAL-CORE CONFIGURATION
// ========================================
TaskHandle_t GraphicsTaskHandle;
TaskHandle_t DisplayTaskHandle;
SemaphoreHandle_t frameSemaphore;

// Forward declarations for demo functions
void demoSynchronizedWaves();
void demoParticlePhysics();
void demoGeometricArt();
void demoTextAndUI();
void demoPerformanceBench();
void demoRotatingLines();
void demoParticleGravity();
void demoStatsOverview();
void demoGraphicsPrimitives();
void demoColorSpectrum();

// Forward declarations for utility functions
void updateNeoPixelStatus();
void displayPerformanceOverlay(GFXcanvas16* canvas);

// ========================================
// ARDUINO SETUP
// ========================================
void setup() {
    Serial.begin(115200);
    while (!Serial);
    Serial.println("Adafruit GFX Benchmark Sketch Initializing...");

    // Initialize NeoPixel
    statusLED.begin();
    statusLED.setBrightness(ledBrightness);
    statusLED.setPixelColor(0, LED_INITIALIZING);
    statusLED.show();

    // Initialize SPI and both displays
    SPI.begin(SPI_SCLK, -1, SPI_MOSI, -1);
    tft1.initR(INITR_BLACKTAB);
    tft2.initR(INITR_BLACKTAB);
    
    // Set custom SPI speed for ESP32-S3
    SPI.setFrequency(80000000); 

    tft1.setRotation(0);
    tft2.setRotation(0);
    tft1.fillScreen(ST7735_BLACK);
    tft2.fillScreen(ST7735_BLACK);
    Serial.println("Displays initialized.");

    // Create a semaphore to signal frame completion
    frameSemaphore = xSemaphoreCreateBinary();
    if (frameSemaphore == NULL) {
        Serial.println("Error creating semaphore");
        statusLED.setPixelColor(0, LED_ERROR);
        statusLED.show();
        while(1);
    }
    
    // Create the graphics task on Core 0
    xTaskCreatePinnedToCore(
        GraphicsTask,         // Task function
        "GraphicsTask",       // Name of the task
        10000,                // Stack size in words
        NULL,                 // Task input parameter
        1,                    // Priority of the task
        &GraphicsTaskHandle,  // Task handle to keep track of created task
        0);                   // Pin to core 0

    // Create the display update task on Core 1
    xTaskCreatePinnedToCore(
        DisplayTask,          // Task function
        "DisplayTask",        // Name of the task
        4096,                 // Stack size in words
        NULL,                 // Task input parameter
        1,                    // Priority of the task
        &DisplayTaskHandle,   // Task handle
        1);                   // Pin to core 1

    Serial.println("FreeRTOS tasks created. Setup complete.");
    statusLED.setPixelColor(0, LED_RUNNING);
    statusLED.show();
}

// ========================================
// MAIN ARDUINO LOOP (Not used with FreeRTOS)
// ========================================
void loop() {
    vTaskDelay(1000 / portTICK_PERIOD_MS); // Yield to tasks
}

// ========================================
// CORE 0: GRAPHICS RENDERING TASK
// ========================================
void GraphicsTask(void* pvParameters) {
    Serial.println("Graphics Task started on Core 0");

    demo.demoStartTime = millis();
    demo.lastFrameTime = demo.demoStartTime;
    metrics.lastLEDUpdate = demo.demoStartTime;

    for (;;) {
        uint32_t frameStart = millis();
        demo.deltaTime = (frameStart - demo.lastFrameTime) / 1000.0f;
        demo.animationTime += demo.deltaTime;
        demo.lastFrameTime = frameStart;

        // Determine current demo
        uint32_t totalTime = frameStart - demo.demoStartTime;
        uint32_t newDemo = (totalTime / DEMO_DURATION_MS) % 10;
        if (newDemo != demo.currentDemo) {
            demo.currentDemo = newDemo;
            demo.frameCount = 0;
            demo.animationTime = 0.0f;
            metrics.minFrameTime = UINT32_MAX;
            metrics.maxFrameTime = 0;
            metrics.currentFPS = 0.0f;
            Serial.printf("Switching to Demo %u\n", demo.currentDemo);
        }

        // Local FPS calculation
        static uint32_t fpsStartTime = frameStart;
        static uint32_t fpsFrameCount = 0;
        fpsFrameCount++;
        if (frameStart - fpsStartTime >= 1000) {
            metrics.currentFPS = fpsFrameCount * 1000.0f / (frameStart - fpsStartTime);
            fpsFrameCount = 0;
            fpsStartTime = frameStart;
        }

        // Execute current demo (draws to canvases)
        switch (demo.currentDemo) {
            case 0: demoSynchronizedWaves(); break;
            case 1: demoParticlePhysics(); break;
            case 2: demoGeometricArt(); break;
            case 3: demoTextAndUI(); break;
            case 4: demoPerformanceBench(); break;
            case 5: demoRotatingLines(); break;
            case 6: demoParticleGravity(); break;
            case 7: demoStatsOverview(); break;
            case 8: demoGraphicsPrimitives(); break;
            case 9: demoColorSpectrum(); break;
            default: demo.currentDemo = 0; break;
        }

        if (demo.showPerformanceOverlay && demo.currentDemo != 7) {
            displayPerformanceOverlay(&canvas2);
        }

        // Signal to the display task that a frame is ready
        xSemaphoreGive(frameSemaphore);

        // Calculate performance metrics
        uint32_t frameTime = millis() - frameStart;
        demo.frameCount++;
        metrics.frameTime = frameTime;
        metrics.minFrameTime = min(metrics.minFrameTime, frameTime);
        metrics.maxFrameTime = max(metrics.maxFrameTime, frameTime);
        metrics.performanceGood = (metrics.currentFPS >= 45.0f && frameTime <= 25);
        
        updateNeoPixelStatus();
        
        // Frame rate control
        if (frameTime < FRAME_TIME_MS) {
            vTaskDelay((FRAME_TIME_MS - frameTime) / portTICK_PERIOD_MS);
        }
    }
}

// ========================================
// CORE 1: DISPLAY UPDATE TASK
// ========================================
void DisplayTask(void* pvParameters) {
    Serial.println("Display Task started on Core 1");
    for (;;) {
        // Wait for the graphics task to signal a new frame is ready
        if (xSemaphoreTake(frameSemaphore, portMAX_DELAY) == pdTRUE) {
            // Push the buffer from canvas1 to tft1
            tft1.drawRGBBitmap(0, 0, canvas1.getBuffer(), TFT_WIDTH, TFT_HEIGHT);
            // Push the buffer from canvas2 to tft2
            tft2.drawRGBBitmap(0, 0, canvas2.getBuffer(), TFT_WIDTH, TFT_HEIGHT);
        }
    }
}

// ========================================
// DEMONSTRATION IMPLEMENTATIONS (Ported to Adafruit GFX API)
// ========================================

void demoSynchronizedWaves() {
    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLACK);
    const float frequency = 2.0f;
    const int16_t amplitude = 60;
    const int16_t centerY = TFT_HEIGHT / 2;

    for (int16_t x = 0; x < TFT_WIDTH; x++) {
        float phase1 = (x / 20.0f) + (demo.animationTime * frequency);
        int16_t y1 = centerY + (amplitude * sin(phase1));
        canvas1.drawPixel(x, y1, ST7735_CYAN);

        float phase2 = (x / 20.0f) - (demo.animationTime * frequency);
        int16_t y2 = centerY + (amplitude * sin(phase2));
        canvas2.drawPixel(x, y2, ST7735_MAGENTA);
    }
    
    int16_t circleX = (TFT_WIDTH / 2) + (30 * sin(demo.animationTime * 3));
    int16_t circleY = (TFT_HEIGHT / 2) + (40 * cos(demo.animationTime * 2));
    canvas1.drawCircle(circleX, circleY, 15, ST7735_YELLOW);
    canvas2.drawCircle(TFT_WIDTH - circleX, circleY, 15, ST7735_GREEN);
    
    canvas1.setCursor(5, 5);
    canvas1.setTextColor(ST7735_WHITE);
    canvas1.setTextSize(1);
    canvas1.print("SYNC WAVES");
}

void demoParticlePhysics() {
    constexpr int MAX_PARTICLES = 50;
    static struct Particle { float x, y, vx, vy; uint16_t color; bool active; } particles[MAX_PARTICLES];
    static bool initialized = false;

    if (!initialized) {
        for (int i = 0; i < MAX_PARTICLES; i++) {
            particles[i] = {
                TFT_WIDTH / 2.0f, TFT_HEIGHT / 2.0f,
                (random(-100, 100) / 10.0f), (random(-100, 100) / 10.0f),
                tft1.color565(random(100, 255), random(100, 255), random(100, 255)),
                true
            };
        }
        initialized = true;
    }

    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLACK);

    for (int i = 0; i < MAX_PARTICLES; i++) {
        if (particles[i].active) {
            particles[i].x += particles[i].vx * demo.deltaTime;
            particles[i].y += particles[i].vy * demo.deltaTime;

            if (particles[i].x <= 0 || particles[i].x >= TFT_WIDTH) {
                particles[i].vx *= -0.8f;
                particles[i].x = constrain(particles[i].x, 0, TFT_WIDTH - 1);
            }
            if (particles[i].y <= 0 || particles[i].y >= TFT_HEIGHT) {
                particles[i].vy *= -0.8f;
                particles[i].y = constrain(particles[i].y, 0, TFT_HEIGHT - 1);
            }

            int16_t px = (int16_t)particles[i].x;
            int16_t py = (int16_t)particles[i].y;
            canvas1.fillCircle(px, py, 2, particles[i].color);
            canvas2.fillCircle(TFT_WIDTH - px, py, 2, particles[i].color);
        }
    }
    canvas1.setCursor(5, 5); canvas1.setTextColor(ST7735_WHITE); canvas1.setTextSize(1);
    canvas1.print("PARTICLES");
}

void demoGeometricArt() {
    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLACK);
    const int16_t centerX = TFT_WIDTH / 2;
    const int16_t centerY = TFT_HEIGHT / 2;

    for (int i = 0; i < 50; i++) {
        float angle = (i * 0.3f) + (demo.animationTime * 2);
        float radius = i * 1.5f;
        int16_t x = centerX + (radius * cos(angle));
        int16_t y = centerY + (radius * sin(angle));
        uint16_t color = tft1.color565(i * 5, 255 - i * 3, 128 + i * 2);
        canvas1.drawPixel(x, y, color);
    }
    
    for (int r = 10; r < 60; r += 10) {
        uint16_t color;
        switch ((r / 10) % 4) {
            case 0: color = ST7735_RED; break;
            case 1: color = ST7735_GREEN; break;
            case 2: color = ST7735_BLUE; break;
            case 3: color = ST7735_YELLOW; break;
        }
        float breathe = sin(demo.animationTime * 3 + r * 0.1f) * 5;
        canvas2.drawCircle(centerX, centerY, r + (int16_t)breathe, color);
    }

    canvas1.setCursor(5, 5); canvas1.setTextColor(ST7735_WHITE); canvas1.setTextSize(1);
    canvas1.print("GEOMETRIC ART");
}

void demoTextAndUI() {
    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLACK);

    static int16_t scrollPos = TFT_WIDTH;
    scrollPos -= 2;
    if (scrollPos < -200) scrollPos = TFT_WIDTH;

    // Screen 1: Text Demo
    canvas1.setCursor(scrollPos, 20);
    canvas1.setTextColor(ST7735_WHITE);
    canvas1.setTextSize(2);
    canvas1.print("ESP32 SPI DUO");

    canvas1.setCursor(scrollPos, 50);
    canvas1.setTextColor(ST7735_YELLOW);
    canvas1.setTextSize(1);
    canvas1.print("HIGH PERFORMANCE");

    canvas1.setCursor(10, 90);
    canvas1.setTextColor(ST7735_WHITE);
    canvas1.setTextSize(1);
    canvas1.print("Mixed Case: Abc123");

    uint8_t textSize = 1 + (uint8_t)(2 * sin(demo.animationTime * 2));
    canvas1.setCursor(10, 120);
    canvas1.setTextColor(ST7735_RED);
    canvas1.setTextSize(textSize);
    canvas1.print("SCALE");

    canvas1.setCursor(5, 5); canvas1.setTextColor(ST7735_WHITE); canvas1.setTextSize(1);
    canvas1.print("TEXT & UI");

    // Screen 2: System Status
    char buffer[32];
    canvas2.setCursor(5, 10);
    canvas2.setTextColor(ST7735_WHITE);
    canvas2.setTextSize(1);
    canvas2.print("SYSTEM STATUS");

    snprintf(buffer, sizeof(buffer), "FPS: %.1f", metrics.currentFPS);
    canvas2.setCursor(5, 25); canvas2.setTextColor(ST7735_GREEN); canvas2.print(buffer);

    snprintf(buffer, sizeof(buffer), "HEAP: %uKB", ESP.getFreeHeap() / 1024);
    canvas2.setCursor(5, 40); canvas2.setTextColor(ST7735_YELLOW); canvas2.print(buffer);

    #if defined(CONFIG_IDF_TARGET_ESP32S3) && board_has_psram
    snprintf(buffer, sizeof(buffer), "PSRAM: %uKB", (ESP.getPsramSize() - ESP.getFreePsram()) / 1024);
    #else
    snprintf(buffer, sizeof(buffer), "PSRAM: N/A");
    #endif
    canvas2.setCursor(5, 55); canvas2.setTextColor(ST7735_CYAN); canvas2.print(buffer);

    snprintf(buffer, sizeof(buffer), "TIME: %.1fs", demo.animationTime);
    canvas2.setCursor(5, 70); canvas2.setTextColor(ST7735_MAGENTA); canvas2.print(buffer);
}

void demoPerformanceBench() {
    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLACK);

    // Screen 1: Frame time graph
    static uint32_t frameHistory[TFT_WIDTH];
    static int historyIndex = 0;
    frameHistory[historyIndex] = metrics.frameTime;
    historyIndex = (historyIndex + 1) % TFT_WIDTH;

    for (int x = 0; x < TFT_WIDTH; x++) {
        int index = (historyIndex + x) % TFT_WIDTH;
        int height = map(frameHistory[index], 0, 50, 0, TFT_HEIGHT - 20);
        height = constrain(height, 0, TFT_HEIGHT - 20);
        uint16_t color = (height < 20) ? ST7735_GREEN : (height < 30) ? ST7735_YELLOW : ST7735_RED;
        canvas1.drawFastVLine(x, TFT_HEIGHT - height, height, color);
    }
    
    canvas1.setCursor(5, 5); canvas1.setTextColor(ST7735_WHITE); canvas1.setTextSize(1);
    canvas1.print("PERF BENCH");

    // Screen 2: Performance stats
    char buffer[32];
    int16_t y = 10;
    canvas2.setCursor(5, y); canvas2.setTextColor(ST7735_WHITE); canvas2.setTextSize(1);
    canvas2.print("PERFORMANCE");
    y += 15;

    snprintf(buffer, sizeof(buffer), "AVG FPS: %.1f", metrics.currentFPS);
    canvas2.setCursor(5, y); canvas2.setTextColor(ST7735_GREEN); canvas2.print(buffer);
    y += 12;

    snprintf(buffer, sizeof(buffer), "MIN FT: %ums", metrics.minFrameTime);
    canvas2.setCursor(5, y); canvas2.setTextColor(ST7735_CYAN); canvas2.print(buffer);
    y += 12;
    
    snprintf(buffer, sizeof(buffer), "MAX FT: %ums", metrics.maxFrameTime);
    canvas2.setCursor(5, y); canvas2.setTextColor(ST7735_YELLOW); canvas2.print(buffer);
    y += 12;

    snprintf(buffer, sizeof(buffer), "FRAMES: %u", demo.frameCount);
    canvas2.setCursor(5, y); canvas2.setTextColor(ST7735_MAGENTA); canvas2.print(buffer);
}

void demoRotatingLines() {
    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLUE);
    const int16_t centerX = TFT_WIDTH / 2;
    const int16_t centerY = TFT_HEIGHT / 2;

    // Screen 1: Rotating lines
    const int segments = 8;
    for (int i = 0; i < segments; i++) {
        float angle = (i * 2 * PI / segments) + (demo.animationTime * 0.5f);
        int16_t x1 = centerX + (40 * cos(angle));
        int16_t y1 = centerY + (40 * sin(angle));
        int16_t x2 = centerX + (20 * cos(angle + PI));
        int16_t y2 = centerY + (20 * sin(angle + PI));
        uint16_t color = (i % 2) ? ST7735_RED : ST7735_BLUE;
        canvas1.fillTriangle(centerX, centerY, x1, y1, x2, y2, color);
    }

    // Screen 2: Bouncing rectangle
    static float rectX = 20, rectY = 20;
    static float velX = 150, velY = 180;
    rectX += velX * demo.deltaTime;
    rectY += velY * demo.deltaTime;

    if (rectX <= 0 || rectX >= TFT_WIDTH - 30) { velX *= -1; rectX = constrain(rectX, 0, TFT_WIDTH - 30); }
    if (rectY <= 0 || rectY >= TFT_HEIGHT - 20) { velY *= -1; rectY = constrain(rectY, 0, TFT_HEIGHT - 20); }

    canvas2.fillRoundRect((int16_t)rectX, (int16_t)rectY, 30, 20, 5, ST7735_ORANGE);
    
    canvas1.setCursor(5, 5); canvas1.setTextColor(ST7735_WHITE); canvas1.setTextSize(1);
    canvas1.print("ROTATING LINES");
}

void demoParticleGravity() {
    constexpr int MAX_PARTICLES = 50;
    static struct Particle { float x, y, vx, vy; uint16_t color; bool active; } particles[MAX_PARTICLES];
    static bool initialized = false;

    if (!initialized) {
        for (int i = 0; i < MAX_PARTICLES; i++) {
            particles[i] = {
                TFT_WIDTH / 2.0f, TFT_HEIGHT / 2.0f,
                (random(-100, 100) / 10.0f), (random(-50, 0) / 10.0f),
                tft1.color565(random(100, 255), random(100, 255), random(100, 255)),
                true
            };
        }
        initialized = true;
    }
    
    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLACK);

    for (int i = 0; i < MAX_PARTICLES; i++) {
        if (particles[i].active) {
            particles[i].vy += 50 * demo.deltaTime; // Gravity
            particles[i].x += particles[i].vx * demo.deltaTime;
            particles[i].y += particles[i].vy * demo.deltaTime;

            if (particles[i].x <= 0 || particles[i].x >= TFT_WIDTH) {
                particles[i].vx *= -0.8f;
                particles[i].x = constrain(particles[i].x, 0, TFT_WIDTH - 1);
            }
            if (particles[i].y >= TFT_HEIGHT - 2) {
                particles[i].vy *= -0.7f;
                particles[i].y = TFT_HEIGHT - 2;
            }
            if (particles[i].y <= 0) {
                particles[i].vy *= -0.8f;
                particles[i].y = 0;
            }
            
            int16_t px = (int16_t)particles[i].x;
            int16_t py = (int16_t)particles[i].y;
            canvas1.fillCircle(px, py, 2, particles[i].color);
            canvas2.fillCircle(TFT_WIDTH - px, py, 2, particles[i].color);
        }
    }
    canvas1.setCursor(5, 5); canvas1.setTextColor(ST7735_WHITE); canvas1.setTextSize(1);
    canvas1.print("PARTICLE GRAVITY");
}

void demoStatsOverview() {
    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLACK);
    
    // Screen 1: Full stats
    char buffer[32];
    int16_t y = 10;
    canvas1.setCursor(5, y); canvas1.setTextColor(ST7735_WHITE); canvas1.setTextSize(1);
    canvas1.print("STATS OVERVIEW");
    y += 15;

    snprintf(buffer, sizeof(buffer), "FPS: %.1f", metrics.currentFPS);
    canvas1.setCursor(5, y); canvas1.setTextColor(ST7735_GREEN); canvas1.print(buffer); y += 12;
    
    snprintf(buffer, sizeof(buffer), "FRAME TIME: %ums", metrics.frameTime);
    canvas1.setCursor(5, y); canvas1.setTextColor(ST7735_YELLOW); canvas1.print(buffer); y += 12;

    snprintf(buffer, sizeof(buffer), "MIN FT: %ums", metrics.minFrameTime);
    canvas1.setCursor(5, y); canvas1.setTextColor(ST7735_GREEN); canvas1.print(buffer); y += 12;

    snprintf(buffer, sizeof(buffer), "MAX FT: %ums", metrics.maxFrameTime);
    canvas1.setCursor(5, y); canvas1.setTextColor(ST7735_RED); canvas1.print(buffer); y += 12;
    
    snprintf(buffer, sizeof(buffer), "HEAP FREE: %uKB", ESP.getFreeHeap() / 1024);
    canvas1.setCursor(5, y); canvas1.setTextColor(ST7735_MAGENTA); canvas1.print(buffer); y += 12;

    #if defined(CONFIG_IDF_TARGET_ESP32S3) && board_has_psram
    snprintf(buffer, sizeof(buffer), "PSRAM USED: %uKB", (ESP.getPsramSize() - ESP.getFreePsram())/1024);
    #else
    snprintf(buffer, sizeof(buffer), "PSRAM USED: N/A");
    #endif
    canvas1.setCursor(5, y); canvas1.setTextColor(ST7735_ORANGE); canvas1.print(buffer); y += 12;

    snprintf(buffer, sizeof(buffer), "FRAME COUNT: %u", demo.frameCount);
    canvas1.setCursor(5, y); canvas1.setTextColor(ST7735_BLUE); canvas1.print(buffer);

    // Screen 2: Mini-demo cycle (simplified for benchmark clarity)
    canvas2.setCursor(5, 5); canvas2.setTextColor(ST7735_WHITE); canvas2.print("Mini-Demo View");
    float breathe = (sin(demo.animationTime * 2.0f) + 1.0f) / 2.0f;
    int16_t radius = 10 + (30 * breathe);
    canvas2.drawCircle(TFT_WIDTH/2, TFT_HEIGHT/2, radius, ST7735_CYAN);
}

void demoGraphicsPrimitives() {
    canvas1.fillScreen(ST7735_BLACK);
    canvas2.fillScreen(ST7735_BLACK);
    
    float offsetX = 10 * sin(demo.animationTime * 2.0f);
    float offsetY = 10 * cos(demo.animationTime * 3.0f);

    // Screen 1: Animated primitives
    canvas1.drawLine(0, 0, TFT_WIDTH - 1, TFT_HEIGHT - 1, ST7735_RED);
    canvas1.drawCircle(TFT_WIDTH / 2 + offsetX, TFT_HEIGHT / 2 + offsetY, 30, ST7735_BLUE);
    canvas1.fillRect(20 + offsetX, 20 + offsetY, 40, 30, ST7735_MAGENTA);
    canvas1.fillTriangle(TFT_WIDTH / 2, 80 + offsetY, TFT_WIDTH/4, 120 + offsetY, 3*TFT_WIDTH/4, 120 + offsetY, ST7735_ORANGE);
    canvas1.drawRoundRect(TFT_WIDTH / 4 + offsetX, TFT_HEIGHT / 2 + 20 + offsetY, 60, 30, 10, ST7735_GREEN);

    // Screen 2: Mirrored static primitives
    canvas2.drawLine(0, 0, TFT_WIDTH - 1, TFT_HEIGHT - 1, ST7735_BLUE);
    canvas2.drawCircle(TFT_WIDTH / 2, TFT_HEIGHT / 2, 25, ST7735_RED);
    canvas2.fillRect(25, 25, 50, 40, ST7735_CYAN);
    canvas2.fillTriangle(TFT_WIDTH / 2, 90, TFT_WIDTH / 4, 130, 3 * TFT_WIDTH / 4, 130, ST7735_WHITE);
    canvas2.drawRoundRect(TFT_WIDTH/4 + 10, TFT_HEIGHT/2 + 30, 50, 25, 8, ST7735_YELLOW);

    canvas1.setCursor(5, 5); canvas1.setTextColor(ST7735_WHITE); canvas1.print("GFX PRIMITIVES");
    canvas2.setCursor(5, 5); canvas2.setTextColor(ST7735_WHITE); canvas2.print("GFX PRIMITIVES");
}

void demoColorSpectrum() {
    // Screen 1: Shifting gradient
    float shift0 = demo.animationTime * 50.0f;
    for (int16_t y = 0; y < TFT_HEIGHT; y++) {
        for (int16_t x = 0; x < TFT_WIDTH; x++) {
            uint8_t hue = (uint8_t)(x + y + shift0) % 255;
            canvas1.drawPixel(x, y, statusLED.gamma32(statusLED.ColorHSV(hue << 8)));
        }
    }

    // Screen 2: Different shifting gradient
    float shift1 = demo.animationTime * 30.0f;
    for (int16_t x = 0; x < TFT_WIDTH; x++) {
        uint8_t hue = (uint8_t)(x + shift1) % 255;
        canvas2.drawFastVLine(x, 0, TFT_HEIGHT, statusLED.gamma32(statusLED.ColorHSV(hue << 8)));
    }
    
    canvas1.setCursor(5, 5); canvas1.setTextColor(ST7735_WHITE); canvas1.print("COLOR SPECTRUM");
}

// ========================================
// UTILITY FUNCTIONS
// ========================================

void updateNeoPixelStatus() {
    if (!ledEnabled || millis() - metrics.lastLEDUpdate < ledUpdateInterval) return;

    uint32_t statusColor = metrics.performanceGood ? LED_PERFORMANCE_GOOD : 
                          (metrics.currentFPS > 30 ? LED_PERFORMANCE_WARN : LED_ERROR);

    float breathe = (sin(millis() * 0.003f) + 1.0f) * 0.5f;
    uint8_t brightness = (uint8_t)(ledBrightness * (0.3f + 0.7f * breathe));
    statusLED.setBrightness(brightness);
    statusLED.setPixelColor(0, statusColor);
    statusLED.show();
    metrics.lastLEDUpdate = millis();
}

void displayPerformanceOverlay(GFXcanvas16* canvas) {
    if (!demo.showPerformanceOverlay) return;

    char buffer[16];
    snprintf(buffer, sizeof(buffer), "%.1f", metrics.currentFPS);
    uint16_t fpsColor = (metrics.currentFPS >= 50) ? ST7735_GREEN : 
                       ((metrics.currentFPS >= 30) ? ST7735_YELLOW : ST7735_RED);

    int16_t x = TFT_WIDTH - 35;
    int16_t y = 2;

    canvas->fillRect(x, y - 2, 35, 12, ST7735_BLACK);
    canvas->setCursor(x + 3, y);
    canvas->setTextColor(fpsColor);
    canvas->setTextSize(1);
    canvas->print(buffer);
}

While the out of the box Adafruit GFX library test is almost impractical in the given hardware setup, providing <1 FPS (Except for color spectrum test, ~27 FPS), the FreeRTOS enhanced Adafruit GFX sketch does provide a consistent 65>FPS in almost all tests, the movements and animations are however choppy. While the black flickering effect for the FreeRTOS Adafruit sketch can be removed with double buffering, this introduces a new glitch for the animations: micro pausing periodically, and retracing its origin point instead of continuing from its paused position.
While better performance can be achieved out of the Adafruit libraries with further tweaking, TFTFriend GFX provides these ease of life out of the box, by ensuring aggressive optimized guarantees of a consistent frame rate delivery, with the con of slightly underperforming to Adafruit library in terms of RAW FPS (i.e. Average 40 FPS compared to Average 60FPS). Even with below 30 FPS render at stress tests, the movement smoothness are consistent to the eye and do not break up).
As I don’t own any other boards of the ESP32 Series other than the Devkit C1, the future updates and patches will be fully dependent on when I get to have a hold on the hardware.
If you have good knowledge and what to look for in the datasheet, should not be a problem for you, but it will require some configuration changes, which I intend to include in preprocessing macros eventually with testing. Meanwhile I will just keep on adding other display supports.

Cheers and have a nice week ahead.

TFTFriend - ESP32 ST7735 Duo Example Sketches (Part 2):

Example 1: Pixel Wash GFX Procedural Demo:

The following is the refactored and modified Pixel Wash sketch that has been previously used with MEGA and ESP8266 with heavy dirty based optimizations. Now it has been optimized for ESP32 S3 specifically. (The library headers already handle LX7 alignment, here I have removed some aggressive dirty rendering procedures to provide more performant visuals.

// ESP32-S3 PIXEL WASH v3.8
// Core1D Automation Labs - Optimized for ESP32-S3 DevKit C-1 N8R8
// Removed all ESP8266 dirty rendering optimizations
// Added ESP32-S3 performance utilization with minor tweaks and updates to the animation procedures themselves/

#include "ST7735DualEngine.h"
#include <Adafruit_NeoPixel.h>

// ========================================
// HARDWARE CONFIGURATION ESP32-S3 DevKit C-1
// ========================================
constexpr uint8_t NEOPIXEL_PIN   = 38;
constexpr uint8_t NEOPIXEL_COUNT = 1;

// ========================================
// DISPLAY CONFIGURATION - Full 128x160 Coverage
// ========================================
constexpr uint16_t DISPLAY_WIDTH  = 128;
constexpr uint16_t DISPLAY_HEIGHT = 160;

// Grid configuration for full 128x160 coverage
constexpr uint8_t GRID_WIDTH  = 16; // 32 per display for 128px width
constexpr uint8_t GRID_HEIGHT = 10; // 40 rows for 160px height
constexpr uint8_t CELL_SIZE   = 16; // 8x8 pixel cells
/*
// Grid configuration for full 128x160 coverage
constexpr uint8_t GRID_WIDTH  = 32; // 16 per display for 128px width
constexpr uint8_t GRID_HEIGHT = 20; // 20 rows for 160px height
constexpr uint8_t CELL_SIZE   = 8; // 8x8 pixel cells

// Grid configuration for full 128x160 coverage
constexpr uint8_t GRID_WIDTH  = 64; // 32 per display for 128px width
constexpr uint8_t GRID_HEIGHT = 40; // 40 rows for 160px height
constexpr uint8_t CELL_SIZE   = 4; // 4x4 pixel cells

// Grid configuration for full 128x160 coverage
constexpr uint8_t GRID_WIDTH  = 128; // 64 per display for 128px width
constexpr uint8_t GRID_HEIGHT = 80; // 80 rows for 160px height
constexpr uint8_t CELL_SIZE   = 2; // 2x2 pixel cells
*/

// Display mapping
constexpr uint8_t GRID_SPLIT = GRID_WIDTH / 2; // 16 cells per display
constexpr uint8_t X_OFFSET   = 0;
constexpr uint8_t Y_OFFSET   = 0;

// Performance configuration - ESP32-S3 optimized
constexpr uint32_t MODE_CHANGE_INTERVAL = 10000;

// ========================================
// ENGINE AND NEOPIXEL INITIALIZATION
// ========================================
ST7735DualEngine engine;
Adafruit_NeoPixel neopixel(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

// ========================================
// SIMPLIFIED MEMORY MANAGEMENT - NO DIRTY TRACKING
// ========================================
constexpr uint16_t GRID_BYTES = (GRID_WIDTH * GRID_HEIGHT + 7) / 8;
uint8_t currentGrid[GRID_BYTES];
uint8_t    nextGrid[GRID_BYTES];
uint8_t intensityGrid[GRID_WIDTH * GRID_HEIGHT];

// Conway's Game of Life cell tracking
uint8_t        cellAgeGrid[GRID_WIDTH * GRID_HEIGHT];
uint8_t cellGenerationGrid[GRID_WIDTH * GRID_HEIGHT];
uint16_t globalGeneration = 0;

// Shared buffer for neighbor calculations
uint8_t neighborCount[GRID_WIDTH * GRID_HEIGHT];

// ========================================
// NEOPIXEL CONTROL VARIABLES
// ========================================
uint8_t      neoPixelBrightness = 64;
bool            neoPixelEnabled = true;
uint32_t neoPixelUpdateInterval = 50;

enum NeoPixelMode {
  NEOPIXEL_MODE_OFF,
  NEOPIXEL_MODE_AUTO_CYCLE,
  NEOPIXEL_MODE_PERFORMANCE_INDICATOR
};

NeoPixelMode neoPixelMode = NEOPIXEL_MODE_AUTO_CYCLE;

// ========================================
// VISUAL MODES AND STATE
// ========================================
enum VisualMode {
  MODE_CONWAY = 0,
  MODE_BRIGHT,
  MODE_FIRE,
  MODE_WATER,
  MODE_WIND,
  MODE_CYCLONE,
  MODE_PORTAL,
  MODE_WORMHOLE,
  MODE_COUNT
};

VisualMode currentMode      = MODE_CONWAY;
uint32_t lastModeChange     = 0;
uint32_t lastNeoPixelUpdate = 0;

const char* const modeNames[] = {
  "Conway GoL", "Dynamic Color", "Fire", "Water", "Wind", "Cyclone", "Portal", "Wormhole"
};

// ========================================
// OPTIMIZED MATH FUNCTIONS
// ========================================
static uint32_t rngState = 1;

constexpr uint8_t SIN_TABLE_SIZE = 64;
const uint8_t sinTable[SIN_TABLE_SIZE] = {
  0, 6, 12, 18, 25, 31, 37, 43, 49, 54, 60, 65, 71, 76, 81, 85,
  90, 94, 98, 102, 106, 109, 112, 115, 117, 120, 122, 124, 126, 127, 128, 129,
  129, 129, 128, 127, 126, 124, 122, 120, 117, 115, 112, 109, 106, 102, 98, 94,
  90, 85, 81, 76, 71, 65, 60, 54, 49, 43, 37, 31, 25, 18, 12, 6
};

inline int16_t fastSin8(uint8_t angle) {
  return (int16_t)pgm_read_byte(&sinTable[angle & (SIN_TABLE_SIZE - 1)]) - 128;
}

inline int16_t fastCos8(uint8_t angle) {
  return fastSin8(angle + (SIN_TABLE_SIZE >> 2));
}

inline uint8_t fastSqrt8(uint16_t value) {
  if (value < 4) return value >> 1;
  uint8_t result = 0;
  uint8_t bit = 64;
  while (bit > value) bit >>= 2;
  while (bit != 0) {
    if (value >= result + bit) {
      value -= result + bit;
      result = (result >> 1) + bit;
    } else {
      result >>= 1;
    }
    bit >>= 2;
  }
  return result;
}

inline uint32_t fastRandom() {
  rngState ^= rngState << 13;
  rngState ^= rngState >> 17;
  rngState ^= rngState << 5;
  return rngState;
}

void initRandomSeed() {
  uint32_t seed = 0;
  for (uint8_t i = 0; i < 32; i++) {
    seed = (seed << 1) | (analogRead(A0) & 1);
    delay(1);
  }
  rngState = seed ? seed : 12345;
}

// ========================================
// CORRECTED BGR COLOR UTILITIES
// ========================================
constexpr uint16_t createBGR565(uint8_t r, uint8_t g, uint8_t b) {
  return ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3);
}

// ========================================
// BIT MANIPULATION FUNCTIONS
// ========================================
inline void setBit(uint8_t* array, const uint16_t bit) {
  const uint16_t byteIndex = bit >> 3;
  const uint8_t bitIndex   = bit & 7;
  array[byteIndex] |= (1 << bitIndex);
}

inline void clrBit(uint8_t* array, const uint16_t bit) {
  const uint16_t byteIndex = bit >> 3;
  const uint8_t bitIndex   = bit & 7;
  array[byteIndex] &= ~(1 << bitIndex);
}

inline bool getBit(const uint8_t* array, const uint16_t bit) {
  const uint16_t byteIndex = bit >> 3;
  const uint8_t bitIndex   = bit & 7;
  return (array[byteIndex] >> bitIndex) & 1;
}

// ========================================
// SIMPLIFIED GRID MANAGEMENT - NO DIRTY TRACKING
// ========================================
inline uint16_t coordToBit(const uint16_t x, const uint16_t y) {
  return y * GRID_WIDTH + x;
}

inline uint16_t coordToIndex(const uint8_t x, const uint8_t y) {
  return y * GRID_WIDTH + x;
}

inline bool getCellState(const uint8_t* grid, const uint8_t x, const uint8_t y) {
  if (x >= GRID_WIDTH || y >= GRID_HEIGHT) return false;
  return getBit(grid, coordToBit(x, y));
}

inline void setCellState(uint8_t* grid, const uint8_t x, const uint8_t y, const bool alive) {
  if (x >= GRID_WIDTH || y >= GRID_HEIGHT) return;
  const uint16_t bit = coordToBit(x, y);
  if (alive) setBit(grid, bit);
  else clrBit(grid, bit);
}

inline uint8_t getIntensity(const uint8_t x, const uint8_t y) {
  if (x >= GRID_WIDTH || y >= GRID_HEIGHT) return 0;
  return intensityGrid[coordToIndex(x, y)];
}

inline void setIntensity(const uint8_t x, const uint8_t y, const uint8_t intensity) {
  if (x >= GRID_WIDTH || y >= GRID_HEIGHT) return;
  intensityGrid[coordToIndex(x, y)] = intensity;
}

// Conway's Game of Life cell tracking functions
inline uint8_t getCellAge(const uint8_t x, const uint8_t y) {
  if (x >= GRID_WIDTH || y >= GRID_HEIGHT) return 0;
  return cellAgeGrid[coordToIndex(x, y)];
}

inline void setCellAge(const uint8_t x, const uint8_t y, const uint8_t age) {
  if (x >= GRID_WIDTH || y >= GRID_HEIGHT) return;
  cellAgeGrid[coordToIndex(x, y)] = age;
}

inline uint8_t getCellGeneration(const uint8_t x, const uint8_t y) {
  if (x >= GRID_WIDTH || y >= GRID_HEIGHT) return 0;
  return cellGenerationGrid[coordToIndex(x, y)];
}

inline void setCellGeneration(const uint8_t x, const uint8_t y, const uint8_t generation) {
  if (x >= GRID_WIDTH || y >= GRID_HEIGHT) return;
  cellGenerationGrid[coordToIndex(x, y)] = generation;
}

// ========================================
// ENHANCED FIRE EFFECT
// ========================================
void updateFire() {
  static uint32_t lastUpdate = 0;
  const uint32_t currentTime = millis();

  if (currentTime - lastUpdate < 40) return;
  lastUpdate = currentTime;

  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      
      if (y == GRID_HEIGHT - 1) {
        // **BOTTOM ROW** - MEGA wick behavior (simplified)
        if (fastRandom() % 3 == 0) {
          setIntensity(x, y, 255);
          setCellState(currentGrid, x, y, true);
        } else {
          // Add some base intensity even when not max
          uint8_t baseIntensity = 200 + (fastRandom() % 55); // 200-255 range
          setIntensity(x, y, baseIntensity);
          setCellState(currentGrid, x, y, true);
        }
        
      } else {
        // **FLAME AREA** - Exact MEGA algorithm
        uint16_t newIntensity = 0;
        
        // **MEGA HEAT TRANSFER**
        if (y < GRID_HEIGHT - 1) newIntensity += (getIntensity(x, y + 1) * 205) >> 8; // 0.8
        if (x > 0              ) newIntensity += (getIntensity(x - 1, y) *  77) >> 8; // 0.3
        if (x < GRID_WIDTH - 1 ) newIntensity += (getIntensity(x + 1, y) *  77) >> 8; // 0.3
        
        // **MEGA DIVISION**
        newIntensity = (newIntensity * 182) >> 8; // divide by 1.4
        
        // **MEGA COOLING** - Simple and gentle (this is the key!)
        int16_t cooling = fastRandom() % 15; // Just random 0-14, no height factor!
        
        newIntensity = (newIntensity > cooling) ? newIntensity - cooling : 0;
        if (newIntensity > 255) newIntensity = 255;
        
        setIntensity(x, y, newIntensity);
        setCellState(currentGrid, x, y, newIntensity > 30);
      }
    }
  }
}

// ========================================
// NEOPIXEL MANAGEMENT
// ========================================
void updateNeoPixel() {
  if (!neoPixelEnabled) {
    neopixel.setPixelColor(0, 0);
    neopixel.show();
    return;
  }
  
  const uint32_t currentTime = millis();
  if (currentTime - lastNeoPixelUpdate < neoPixelUpdateInterval) return;
  lastNeoPixelUpdate = currentTime;
  
  uint32_t color = 0;
  
  switch (neoPixelMode) {
    case NEOPIXEL_MODE_OFF:
      color = 0;
      break;
      
    case NEOPIXEL_MODE_AUTO_CYCLE:
      switch (currentMode) {
        case MODE_FIRE:
          color = neopixel.Color(neoPixelBrightness, neoPixelBrightness >> 2, 0);
          break;
        case MODE_WATER:
          color = neopixel.Color(0, neoPixelBrightness >> 1, neoPixelBrightness);
          break;
        case MODE_WIND:
          color = neopixel.Color(neoPixelBrightness >> 1, neoPixelBrightness, neoPixelBrightness >> 1);
          break;
        case MODE_CONWAY:
          color = neopixel.Color(neoPixelBrightness >> 1, 0, neoPixelBrightness >> 1);
          break;
        default: {
          static uint8_t cyclePhase = 0;
          cyclePhase++;
          const uint8_t hue = (cyclePhase + (currentMode * 32)) & 0xFF;
                      color = neopixel.ColorHSV(hue * 256, 255, neoPixelBrightness);
          break;
        }
      }
      break;
      
    case NEOPIXEL_MODE_PERFORMANCE_INDICATOR:
      const uint32_t freeHeap = ESP.getFreeHeap();
      if (freeHeap > 100000) {
        color = neopixel.Color(0, neoPixelBrightness, 0);
      } else if (freeHeap > 50000) {
        color = neopixel.Color(neoPixelBrightness, neoPixelBrightness >> 1, 0);
      } else {
        color = neopixel.Color(neoPixelBrightness, 0, 0);
      }
      break;
  }
  
  neopixel.setPixelColor(0, color);
  neopixel.show();
}

// ========================================
// CONWAY'S GAME OF LIFE - ENHANCED IMPLEMENTATION
// ========================================
void addConwayPatterns() {
  const uint8_t patternType = fastRandom() % 5;
  const uint8_t startX = fastRandom() % (GRID_WIDTH - 6);
  const uint8_t startY = fastRandom() % (GRID_HEIGHT - 6);
  
  switch (patternType) {
    case 0: // Glider
      setCellState(currentGrid, startX + 1, startY,     true);
      setCellState(currentGrid, startX + 2, startY + 1, true);
      setCellState(currentGrid, startX,     startY + 2, true);
      setCellState(currentGrid, startX + 1, startY + 2, true);
      setCellState(currentGrid, startX + 2, startY + 2, true);
      break;
      
    case 1: // Blinker
      setCellState(currentGrid, startX,     startY + 1, true);
      setCellState(currentGrid, startX + 1, startY + 1, true);
      setCellState(currentGrid, startX + 2, startY + 1, true);
      break;
      
    case 2: // Block
      setCellState(currentGrid, startX,     startY,     true);
      setCellState(currentGrid, startX + 1, startY,     true);
      setCellState(currentGrid, startX,     startY + 1, true);
      setCellState(currentGrid, startX + 1, startY + 1, true);
      break;
      
    case 3: // Toad
      setCellState(currentGrid, startX + 1, startY,     true);
      setCellState(currentGrid, startX + 2, startY,     true);
      setCellState(currentGrid, startX + 3, startY,     true);
      setCellState(currentGrid, startX,     startY + 1, true);
      setCellState(currentGrid, startX + 1, startY + 1, true);
      setCellState(currentGrid, startX + 2, startY + 1, true);
      break;
      
    case 4: // Random cluster
      for (uint8_t i = 0; i < 6; i++) {
        const uint8_t x = startX + (fastRandom() % 4);
        const uint8_t y = startY + (fastRandom() % 4);
        setCellState(currentGrid, x, y, true);
      }
      break;
  }
}

void addConwaySeedPatterns() {
  addConwayPatterns();
  addConwayPatterns();
}

void updateConway() {
  static uint16_t stagnationCounter = 0;
  static uint16_t lastLiveCells = 0;
  static uint32_t lastPatternInject = 0;
  
  globalGeneration++;
  
  // Calculate neighbor counts with proper wraparound
  memset(neighborCount, 0, sizeof(neighborCount));
  
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      if (getCellState(currentGrid, x, y)) {
        for (int8_t dy = -1; dy <= 1; dy++) {
          for (int8_t dx = -1; dx <= 1; dx++) {
            if (dx == 0 && dy == 0) continue;
            uint8_t nx = (x + dx + GRID_WIDTH) % GRID_WIDTH;
            uint8_t ny = (y + dy + GRID_HEIGHT) % GRID_HEIGHT;
            neighborCount[ny * GRID_WIDTH + nx]++;
          }
        }
      }
    }
  }
  
  // Apply Conway's rules AND track cell ages
  memset(nextGrid, 0, GRID_BYTES);
  uint16_t liveCells = 0;
  
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      uint8_t neighbors = neighborCount[y * GRID_WIDTH + x];
      bool alive = getCellState(currentGrid, x, y);
      uint8_t currentAge = getCellAge(x, y);
      
      // Pure Conway rules
      bool newState = false;
      if (alive && (neighbors == 2 || neighbors == 3)) {
        newState = true; // Survival
      } else if (!alive && neighbors == 3) {
        newState = true; // Birth
      }
      
      setCellState(nextGrid, x, y, newState);
      
      // Handle age and colors
      if (newState) {
        if (alive) {
          // Cell survived - age it
          uint8_t newAge = (currentAge < 255) ? currentAge + 1 : 255;
          setCellAge(x, y, newAge);
        } else {
          // New birth
          setCellAge(x, y, 1);
          setCellGeneration(x, y, globalGeneration & 0xFF);
        }
        
        // Enhanced intensity based on age - wide range for visibility
        uint8_t age = getCellAge(x, y);
        uint8_t birthGen = getCellGeneration(x, y);
        
        uint16_t baseIntensity = 80 + (age * 3); // 80-845 range (clamped to 255)
        uint8_t genWave = (fastSin8((birthGen + globalGeneration) * 6) + 128) >> 2; // 0-63 range
        uint8_t posVariation = ((x ^ y ^ (globalGeneration & 0xFF)) & 0x1F); // 0-31 range
        
        uint16_t finalIntensity = baseIntensity + genWave + posVariation;
        finalIntensity = constrain(finalIntensity, 80, 255);
        setIntensity(x, y, finalIntensity);
        liveCells++;
      } else {
        // Dead cell - faster fade
        setCellAge(x, y, 0);
        uint8_t currentIntensity = getIntensity(x, y);
        if (currentIntensity > 20) {
          setIntensity(x, y, currentIntensity - 20);
        } else {
          setIntensity(x, y, 0);
        }
      }
    }
  }
  
  // Copy next generation to current
  memcpy(currentGrid, nextGrid, GRID_BYTES);
  
  // Check for stagnation and inject new patterns
  if (liveCells == lastLiveCells) {
    stagnationCounter++;
  } else {
    stagnationCounter = 0;
  }
  
  uint32_t currentTime = millis();
  if (stagnationCounter > 8 || liveCells < 3 || 
      (currentTime - lastPatternInject > 12000)) {
    addConwaySeedPatterns();
    stagnationCounter = 0;
    lastPatternInject = currentTime;
  }
  
  lastLiveCells = liveCells;
}

// ========================================
// OTHER EFFECTS - MATCHING ESP8266 REFERENCE
// ========================================
void updateBright() {
  static uint8_t   pulsePhase = 0;
  static uint8_t   colorPhase = 0;
  static uint8_t  patternMode = 0;
  static uint16_t modeCounter = 0;
  
  pulsePhase += 2;
  colorPhase += 1;
  modeCounter++;
  
  // Change pattern every 5 seconds
  if (modeCounter % 250 == 0) {
    patternMode = (patternMode + 1) % 4;
  }
  
  const int16_t globalPulse = fastSin8(pulsePhase) + 128;
  
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      uint16_t intensity = 0;
      
      switch (patternMode) {
        case 0: { // Radial pulse
          const int8_t dx = x - (GRID_WIDTH >> 1);
          const int8_t dy = y - (GRID_HEIGHT >> 1);
          const uint8_t   distance = fastSqrt8(dx*dx + dy*dy);
          const int16_t radialWave = fastSin8(distance * 8 - pulsePhase) + 128;
          intensity = (globalPulse + radialWave) >> 1;
          break;
        }
        
        case 1: { // Wave interference
          const int16_t wave1 = fastSin8((x * 8) + pulsePhase) + 128;
          const int16_t wave2 = fastCos8((y * 8) + (pulsePhase >> 1)) + 128;
          intensity = (wave1 + wave2) >> 1;
          break;
        }
        
        case 2: { // Spiral
          const int8_t dx = x - (GRID_WIDTH >> 1);
          const int8_t dy = y - (GRID_HEIGHT >> 1);
          const uint8_t distance = fastSqrt8(dx*dx + dy*dy);
          const int16_t   spiral = fastSin8((distance * 4) + pulsePhase) + 128;
          intensity = (globalPulse + spiral) >> 1;
          break;
        }
        
        case 3: { // Plasma
          const int16_t plasma = 
            fastSin8((x * 4) + pulsePhase) +
            fastSin8((y * 4) + (pulsePhase >> 1)) +
            fastSin8(((x + y) * 2) + (pulsePhase >> 2));
          intensity = (plasma >> 2) + 128;
          break;
        }
      }
      
      // Color cycling
      intensity += (fastSin8((x + y + colorPhase) * 2) >> 4);
      
      // Ensure valid range
      if (intensity < 50) intensity = 0;
      if (intensity > 255) intensity = 255;
      
      setIntensity(x, y, intensity);
      setCellState(currentGrid, x, y, intensity > 80);
    }
  }
}

void updateWater() {
  static uint16_t      wavePhase = 0;  // **CHANGED: uint16_t instead of uint8_t**
  static uint32_t     lastUpdate = 0;
  static uint16_t lastValidPhase = 0;  // **ADDED: Backup phase tracking**
  
  const uint32_t currentTime = millis();
  if (currentTime - lastUpdate < 50) return; // **ADDED: Consistent timing**
  lastUpdate = currentTime;
  
  // **PREVENT BACKWARD JUMPS** - Ensure monotonic increase
  if (wavePhase < lastValidPhase && (lastValidPhase - wavePhase) > 32768) {
    // Handle wraparound case (normal)
    lastValidPhase = wavePhase;
  } else if (wavePhase < lastValidPhase) {
    // **DETECT BACKWARD JUMP** - Restore from backup
    wavePhase = lastValidPhase + 2; // Continue from last valid + increment
  } else {
    // Normal forward progression
    lastValidPhase = wavePhase;
  }
  
  wavePhase += 2; // **CONSISTENT INCREMENT**
  
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      // **EXACT GREENTAB REFERENCE FORMULA** - Multi-wave water
      int16_t wave1 = fastSin8((x * 4 + wavePhase) >> 2);
      int16_t wave2 = fastCos8((y * 3 + wavePhase) >> 2);
      int16_t wave3 = fastSin8(((x + y) * 2 + wavePhase) >> 3);
      
      // **SAME INTENSITY CALCULATION AS REFERENCE**
      int16_t intensity = 140 + (wave1 >> 2) + (wave2 >> 2) + (wave3 >> 3);
      
      // **SAME BOUNDS AS REFERENCE**
      if (intensity > 255) intensity = 255;
      if (intensity < 50) intensity = 50;
      
      setIntensity(x, y, intensity);
      setCellState(currentGrid, x, y, intensity > 100);
    }
  }
}

void updateWind() {
  static uint8_t windOffset = 0;
  windOffset += 2;
  
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      uint8_t pattern = ((x + windOffset) % 8) + ((y + windOffset/3) % 4) * 2;
      uint8_t intensity = (pattern * 28) + (fastRandom() % 40) + 50;
      
      if (intensity > 255) intensity = 255;
      
      setIntensity(x, y, intensity);
      setCellState(currentGrid, x, y, intensity > 90);
    }
  }
}

void updateCyclone() {
  static uint8_t angle = 0;
  angle += 3; // Converted from 0.1 float increment
  
  uint8_t centerX = GRID_WIDTH / 2;
  uint8_t centerY = GRID_HEIGHT / 2;
  
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      int8_t        dx = x - centerX;
      int8_t        dy = y - centerY;
      uint8_t distance = fastSqrt8(dx * dx + dy * dy);
      
      // Create spiral effect using fast math
      uint8_t     cellAngle = angle + (distance << 2); // distance * 4 for spiral
      int16_t spiralPattern = fastSin8(cellAngle);
      
      // Distance-based intensity falloff
      uint16_t intensity = (spiralPattern + 128); // Convert -128..127 to 0..255
      
      // Apply distance-based falloff (maximum distance ~25)
      if (distance > 0) {
        intensity = (intensity * (25 - min(distance, (uint8_t)25))) / 25;
      }
      
      if (intensity > 255) intensity = 255;
      
      setIntensity(x, y, intensity);
      setCellState(currentGrid, x, y, intensity > 50);
    }
  }
}

void updatePortal() {
  static bool         expanding = true;
  static uint8_t     portalSize = 5;
  static uint8_t lastPortalSize = 5;
  static uint8_t  ringThickness = 2;
  static uint32_t    lastUpdate = 0;
  
  const uint32_t currentTime = millis();
  if (currentTime - lastUpdate < 80) return;
  lastUpdate = currentTime;
  
  // Update portal size
  if (expanding) {
    portalSize++;
    if (portalSize > 15) {
      expanding = false;
    }
  } else {
    portalSize--;
    if (portalSize < 3) {
      expanding = true;
    }
  }
  
  uint8_t centerX = GRID_WIDTH / 2;
  uint8_t centerY = GRID_HEIGHT / 2;
  
  // **ALWAYS DRAW BACKGROUND PATTERN** - never clear it
  static uint8_t patternPhase = 0;
  patternPhase += 2;
  
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      int8_t dx = x - centerX;
      int8_t dy = y - centerY;
      uint8_t distance = fastSqrt8(dx * dx + dy * dy);
      
      // **ALWAYS DRAW PATTERN INSIDE PORTAL** - never disappears
      if (distance <= (portalSize - ringThickness)) {
        // **ANIMATED PATTERN** - checkerboard with motion
        if (((x + y + (patternPhase >> 2)) % 3) == 0) {
          uint8_t patternIntensity = 60 + ((fastSin8((x * 4 + y * 4 + patternPhase)) + 128) >> 3);
          setIntensity(x, y, patternIntensity);
          setCellState(currentGrid, x, y, true);
        } else {
          setIntensity(x, y, 20); // Dim background
          setCellState(currentGrid, x, y, false);
        }
      }
      
      // **DRAW PORTAL RING** - bright with shimmer
      if (distance >= (portalSize - ringThickness) && distance <= (portalSize + ringThickness)) {
        uint8_t distanceFromRing = (distance > portalSize) ? 
          (distance - portalSize) : (portalSize - distance);
        
        if (distanceFromRing <= ringThickness) {
          uint16_t intensity = 255 - (distanceFromRing * 100 / ringThickness);
          
          // Shimmer effect
          uint8_t shimmer = (fastSin8((x * 4 + y * 4 + patternPhase) * 6) + 128) >> 1;
          intensity = (intensity * (shimmer + 128)) >> 8;
          
          if (intensity > 255) intensity = 255;
          if (intensity < 120) intensity = 120;
          
          setIntensity(x, y, intensity);
          setCellState(currentGrid, x, y, true);
        }
      }
      
      // **OUTSIDE PORTAL** - black/dark
      if (distance > (portalSize + ringThickness)) {
        setIntensity(x, y, 0);
        setCellState(currentGrid, x, y, false);
      }
    }
  }
  
  lastPortalSize = portalSize;
}

void updateWormhole() {
  static float tunnelDepth = 0;
  static float rotationAngle = 0;
  
  // **EXACT MEGA INCREMENTS**
  tunnelDepth += 0.15f;
  rotationAngle += 0.04f;
  
  uint8_t centerX = GRID_WIDTH / 2;
  uint8_t centerY = GRID_HEIGHT / 2;
  
  // **CALCULATE MAXIMUM DISTANCE** - exact MEGA formula
  float maxDistance = fastSqrt((GRID_WIDTH/2) * (GRID_WIDTH/2) + (GRID_HEIGHT/2) * (GRID_HEIGHT/2));
  
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      float dx = x - centerX;
      float dy = y - centerY;
      float distance = fastSqrt(dx * dx + dy * dy);
      
      uint8_t intensity = 0;
      
      if (distance > 0.1f) { // **AVOID CENTER SINGULARITY** - exact MEGA condition
        
        // **CREATE TUNNEL COORDINATE SYSTEM** - exact MEGA
        float angle = fastAtan2(dy, dx);
        float radius = distance;
        
        // **TUNNEL DEPTH CALCULATION** - exact MEGA formula
        float depth = tunnelDepth + (20.0f / (radius + 1.0f));
        
        // **CREATE SPIRAL ARMS** - 6 arms rotating, exact MEGA
        float spiralAngle = angle + rotationAngle + (depth * 0.3f);
        float spiralPattern = fastSin(spiralAngle * 6.0f);
        
        // **CREATE DEPTH RINGS** - exact MEGA formula
        float ringPattern = fastSin(depth * 0.8f);
        
        // **COMBINE PATTERNS** - exact MEGA mixing
        float combinedPattern = (spiralPattern * 0.7f + ringPattern * 0.3f);
        
        // **ONLY SHOW POSITIVE VALUES** - exact MEGA condition
        if (combinedPattern > 0.1f) {
          
          // **CONVERT TO INTENSITY** - exact MEGA scaling
          intensity = combinedPattern * 255.0f;
          
          // **EXTENDED DISTANCE-BASED FADING** - exact MEGA falloff
          float falloff = 1.0f;
          if (radius > 5.0f) {
            // **SCALE FALLOFF TO REACH CORNERS** - exact MEGA formula
            falloff = 1.0f - ((radius - 5.0f) / (maxDistance - 5.0f));
            if (falloff < 0) falloff = 0;
          }
          
          intensity = intensity * falloff;
          
          // **ENSURE MINIMUM VISIBILITY** - exact MEGA threshold
          if (intensity > 0 && intensity < 40) {
            intensity = 40;
          }
          
          // **CAP MAXIMUM INTENSITY** - exact MEGA limit
          if (intensity > 255) intensity = 255;
        }
      } else {
        // **HANDLE CENTER AREA** - exact MEGA behavior
        intensity = 255;
      }
      
      setIntensity(x, y, intensity);
      setCellState(currentGrid, x, y, intensity > 0);
    }
  }
}

// ========================================
// COLOR MANAGEMENT
// ========================================
uint16_t getModeColor(const uint8_t x, const uint8_t y, const uint16_t generation, const bool alive) {
  // **REALISTIC FIRE PALETTE** - White hottest to black coolest
  static const uint16_t firePalette[] = {
    0x0000,  // 0: Black (coolest areas)
    0x0008,  // 1: Very Dark Red
    0x0010,  // 2: Dark Red  
    0x0018,  // 3: Dark Red-Brown
    0x001F,  // 4: Red (ST7735Colors::RED)
    0x011F,  // 5: Red-Orange transition
    0x021F,  // 6: Orange-Red
    0x031F,  // 7: Orange
    0x041F,  // 8: Bright Orange (ST7735Colors::ORANGE)
    0x051F,  // 9: Yellow-Orange
    0x061F,  // 10: Light Orange-Yellow
    0x07FF,  // 11: Yellow (ST7735Colors::YELLOW)
    0x87FF,  // 12: Light Yellow
    0xCFFF,  // 13: Very Light Yellow
    0xFFFF   // 14: White (hottest - natural embers)
  };
  
  // **WATER PALETTE** - NO BLACK, contrasting blues only
  static const uint16_t waterPalette[] = {
    0x4000,  // Dark Blue (NO BLACK)
    0x6000,  // Darker Blue
    0x8000,  // Medium Dark Blue
    0xA000,  // Blue
    0xF800,  // Bright Blue (ST7735Colors::BLUE)
    0xF810,  // Blue with slight green
    0xF820,  // Blue-Cyan
    0xF840,  // Light Blue-Cyan
    0xF860,  // Cyan-ish
    0xF8A0,  // Brighter Cyan-ish
    0xF8C0,  // Very Light Cyan-ish
    0xF8E0,  // Near Cyan
    0xFFE0   // Cyan (ST7735Colors::CYAN)
  };
  
  // **WIND PALETTE** - NO BLACK, greens and teals
  static const uint16_t windPalette[] = {
    0x2000,  // Dark Blue-Green (NO BLACK)
    0x0200,  // Dark Green
    0x0400,  // Darker Green
    0x0600,  // Green
    0x07E0,  // Bright Green (ST7735Colors::GREEN)
    0x27E0,  // Green-Teal
    0x47E0,  // Teal-ish
    0x67E0,  // Light Teal
    0x87E0,  // Bright Teal
    0xA7E0,  // Very Light Teal
    0xC7E0,  // Near Cyan
    0xFFE0   // Cyan (ST7735Colors::CYAN)
  };
  
  // **BRIGHT PALETTE** - NO BLACK, full spectrum
  static const uint16_t brightPalette[] = {
    0x2000,  // Dark Blue (NO BLACK)
    0xF800,  // Blue (ST7735Colors::BLUE) 
    0x07E0,  // Green (ST7735Colors::GREEN)
    0xFFE0,  // Cyan (ST7735Colors::CYAN)
    0x001F,  // Red (ST7735Colors::RED)
    0xF81F,  // Magenta (ST7735Colors::MAGENTA)
    0x07FF,  // Yellow (ST7735Colors::YELLOW)
    0xFFFF,  // White (ST7735Colors::WHITE)
    0x041F,  // Orange (ST7735Colors::ORANGE)
    0xFE19,  // Pink (ST7735Colors::PINK)
    0x8010,  // Purple (ST7735Colors::PURPLE)
    0x8410   // Gray (ST7735Colors::GRAY)
  };
  
  // **CYCLONE PALETTE** - NO BLACK, purple-red gradients
  static const uint16_t cyclonePalette[] = {
    0x4000,  // Dark Purple-Blue (NO BLACK)
    0x8000,  // Dark Purple-Red
    0xC000,  // Purple-Red
    0x001F,  // Red (ST7735Colors::RED)
    0x021F,  // Red-Orange
    0x041F,  // Orange (ST7735Colors::ORANGE)
    0x061F,  // Bright Orange
    0x07FF,  // Yellow (ST7735Colors::YELLOW)
    0x8FFF,  // Light Yellow
    0xF81F,  // Magenta (ST7735Colors::MAGENTA)
    0xFE19,  // Pink (ST7735Colors::PINK)
    0xFFFF   // White
  };
  
  // **PORTAL PALETTE** - NO BLACK, magical purple-blue
  static const uint16_t portalPalette[] = {
    0x4000,  // Dark Blue (NO BLACK)
    0x8010,  // Purple-Blue
    0x8020,  // Blue-Purple
    0x8030,  // Light Blue-Purple
    0xF800,  // Blue (ST7735Colors::BLUE)
    0xF810,  // Blue-Purple mix
    0xF820,  // Purple-Blue mix
    0xF830,  // Light Purple-Blue
    0xF81F,  // Magenta (ST7735Colors::MAGENTA)
    0xFE19,  // Pink (ST7735Colors::PINK)
    0x8FFF,  // Light Purple-White
    0xFFFF   // White
  };
  
  // **WORMHOLE PALETTE** - NO BLACK, deep space colors
  static const uint16_t wormholePalette[] = {
    0x4000,  // Dark Blue (NO BLACK)
    0x8010,  // Dark Purple-Blue
    0x8020,  // Purple-Blue
    0x8030,  // Light Purple-Blue
    0xF800,  // Blue (ST7735Colors::BLUE)
    0xF81F,  // Magenta (ST7735Colors::MAGENTA)
    0x8FFF,  // Light Purple
    0xFE19,  // Pink (ST7735Colors::PINK)
    0x07FF,  // Yellow (ST7735Colors::YELLOW)
    0x8FFF,  // Light Yellow
    0xFFE0,  // Cyan (ST7735Colors::CYAN)
    0xFFFF   // White
  };
  
  const uint16_t* palette;
  uint8_t paletteSize;
  
  switch (currentMode) {
    case MODE_CONWAY:
      palette = brightPalette;
      paletteSize = 12;
      break;
    case MODE_FIRE:
      palette = firePalette;
      paletteSize = 15; // **INCREASED to 15 for smooth gradients**
      break;
    case MODE_WATER:
      palette = waterPalette;
      paletteSize = 13;
      break;
    case MODE_WIND:
      palette = windPalette;
      paletteSize = 12;
      break;
    case MODE_CYCLONE:
      palette = cyclonePalette;
      paletteSize = 12;
      break;
    case MODE_PORTAL:
      palette = portalPalette;
      paletteSize = 12;
      break;
    case MODE_WORMHOLE:
      palette = wormholePalette;
      paletteSize = 12;
      break;
    default: // MODE_BRIGHT
      palette = brightPalette;
      paletteSize = 12;
      break;
  }
  
  uint8_t colorIndex;
  
  if (currentMode == MODE_CONWAY) {
    if (!alive) {
      // For dead cells, check if they're fading
      uint8_t intensity = getIntensity(x, y);
      if (intensity == 0) return 0x2000; // Dark blue (NO BLACK for Conway)
      
      // Fading cells get dimmed version based on age and generation
      uint8_t      age = getCellAge(x, y);
      uint8_t birthGen = getCellGeneration(x, y);
            colorIndex = ((age >> 4) + (x + y + generation + birthGen) % 6) % (paletteSize - 1) + 1;
      
      // Dim the color based on remaining intensity
      uint16_t fullColor = palette[colorIndex];
      uint8_t      scale = intensity;
      uint8_t          r = ((fullColor >> 11) * scale) >> 8;
      uint8_t          g = (((fullColor >> 5) & 0x3F) * scale) >> 8;
      uint8_t          b = ((fullColor & 0x1F) * scale) >> 8;
      return (r << 11) | (g << 5) | b;
    }
    
    // Live cells get vibrant colors based on age and generation
    uint8_t      age = getCellAge(x, y);
    uint8_t birthGen = getCellGeneration(x, y);
    colorIndex = ((age >> 4) + (x + y + generation + birthGen) % 6) % (paletteSize - 1) + 1;
    
  } else if (currentMode == MODE_FIRE) {
    const uint8_t intensity = getIntensity(x, y);
    if (intensity == 0) return 0x0000; // Black for cool areas (FIRE ONLY)
    
    // **ENHANCED COLOR MAPPING** - More dynamic range
    uint8_t heightFactor = (GRID_HEIGHT - y); // Higher at bottom
    
    // **WICK AREA ENHANCEMENT** - Bottom half gets intensity boost
    uint16_t enhancedIntensity = intensity;
    if (y >= (GRID_HEIGHT / 2)) {
      // **In wick area** - boost colors toward white-hot
      enhancedIntensity = (intensity * (120 + heightFactor * 3)) >> 8;
    } else {
      // **In flame area** - normal mapping with slight bottom bias
      enhancedIntensity = (intensity * (100 + heightFactor)) >> 8;
    }
    
    if (enhancedIntensity > 255) enhancedIntensity = 255;
    
    // **NATURAL EMBER DETECTION** - Only pixels that naturally reach high intensity
    if (enhancedIntensity >= 250) {
      return 0xFFFF; // White for natural hot spots/embers
    }
    
    // **SMOOTH COLOR MAPPING** - 15 color palette for smooth gradients
    colorIndex = (enhancedIntensity * 14) / 255; // 0-14 range
    if (colorIndex >= paletteSize) colorIndex = paletteSize - 1;
    
  } else {
    // **OTHER MODES** - Standard intensity mapping (NO BLACK)
    const uint8_t intensity = getIntensity(x, y);
    if (intensity == 0) {
      // Return appropriate dark color for each mode (NO BLACK except fire)
      switch (currentMode) {
        case MODE_WATER:    return 0x4000;  // Dark blue
        case MODE_WIND:     return 0x2000;  // Dark blue-green
        case MODE_CYCLONE:  return 0x4000;  // Dark purple-blue
        case MODE_PORTAL:   return 0x4000;  // Dark blue
        case MODE_WORMHOLE: return 0x4000;  // Dark blue
        default:            return 0x2000;  // Dark blue
      }
    }
    
    colorIndex = (intensity * (paletteSize - 1)) / 255;
    if (colorIndex >= paletteSize) colorIndex = paletteSize - 1;
  }
  
  return palette[colorIndex];
}

// ========================================
// SIMPLIFIED HIGH-PERFORMANCE RENDERING - NO DIRTY TRACKING
// ========================================
void renderFullGrid(const uint16_t generation) {
  // Direct rendering - let ESP32-S3 handle the performance
  for (uint8_t y = 0; y < GRID_HEIGHT; y++) {
    for (uint8_t x = 0; x < GRID_WIDTH; x++) {
      const bool currentState = getCellState(currentGrid, x, y);
      const uint16_t color = getModeColor(x, y, generation, currentState);
      
      // Calculate screen coordinates for full 128x160 coverage
      const uint16_t  localX = (x % GRID_SPLIT) * CELL_SIZE;
      const uint16_t screenY = Y_OFFSET + y * CELL_SIZE;
      
      if (x < GRID_SPLIT) {
        // Left display (Display 0)
        engine.fillRect(0, localX, screenY, CELL_SIZE, CELL_SIZE, color);
      } else {
        // Right display (Display 1)
        engine.fillRect(1, localX, screenY, CELL_SIZE, CELL_SIZE, color);
      }
    }
  }
  
  // Update both displays - let the engine handle optimization
  engine.updateBoth();
}

// ========================================
// MODE MANAGEMENT
// ========================================
void updateModeRotation() {
  const uint32_t currentTime = millis();
  if (currentTime - lastModeChange >= MODE_CHANGE_INTERVAL) {
       currentMode = (VisualMode)((currentMode + 1) % MODE_COUNT);
    lastModeChange = currentTime;
    
    // Clear state completely
    memset(currentGrid,        0, GRID_BYTES                );
    memset(intensityGrid,      0, sizeof(intensityGrid)     );
    memset(cellAgeGrid,        0, sizeof(cellAgeGrid)       );
    memset(cellGenerationGrid, 0, sizeof(cellGenerationGrid));
    globalGeneration = 0;
    
    // Enhanced mode transition display
    engine.clearBuffer(0, ST7735Colors::BLACK);
    engine.clearBuffer(1, ST7735Colors::BLACK);
    
    char buffer[20];
    strcpy_P(buffer, (char*)pgm_read_ptr(&(modeNames[currentMode])));
    
    engine.drawTextCentered(0, 64, 70, "MODE",   ST7735Colors::CYAN, 2);
    engine.drawTextCentered(0, 64, 90, "CHANGE", ST7735Colors::CYAN, 1);
    
    engine.drawTextCentered(1, 64, 70, buffer,   ST7735Colors::YELLOW, 1);
    engine.drawTextCentered(1, 64, 90, "ACTIVE", ST7735Colors::YELLOW, 1);
    
    engine.updateBoth();
    delay(2000);
    
    engine.clearBuffer(0, ST7735Colors::BLACK);
    engine.clearBuffer(1, ST7735Colors::BLACK);
    engine.updateBoth();
    
    // Initialize Conway patterns when entering Conway mode
    if (currentMode == MODE_CONWAY) {
      addConwayPatterns();
      addConwayPatterns();
    }
  }
}

// ========================================
// MATH UTILITIES
// ========================================
inline uint8_t fastAtan2(int8_t y, int8_t x) {
  if (x == 0 && y == 0) return 0;
  
  // Use lookup table approximation for speed
  if (x >= 0) {
    if (y >= 0) {
      if (x >= y) return (32 * y) / x;           // 0 to 45 degrees
      else return 64 - (32 * x) / y;            // 45 to 90 degrees
    } else {
      if (x >= -y) return 256 - (32 * (-y)) / x; // 315 to 360 degrees
      else return 192 + (32 * x) / (-y);        // 270 to 315 degrees
    }
  } else {
    if (y >= 0) {
      if (-x >= y) return 128 - (32 * y) / (-x); // 135 to 180 degrees
      else return 64 + (32 * (-x)) / y;         // 90 to 135 degrees
    } else {
      if (-x >= -y) return 128 + (32 * (-y)) / (-x); // 180 to 225 degrees
      else return 192 - (32 * (-x)) / (-y);     // 225 to 270 degrees
    }
  }
}

inline float fastSqrt(float x) {
  if (x <= 0) return 0;
  // Use Newton-Raphson approximation for speed
  float guess = x * 0.5f;
  for (uint8_t i = 0; i < 3; i++) {
    guess = 0.5f * (guess + x / guess);
  }
  return guess;
}

inline float fastSin(float x) {
  // Normalize to 0-2Ο€ range
  const float pi2 = 6.28318530f;
  while (x > pi2) x -= pi2;
  while (x < 0) x += pi2;
  
  // Taylor series approximation
  const float pi = 3.14159265f;
  if (x > pi) {
    x -= pi;
    return -fastSin(x);
  }
  
  float x2 = x * x;
  return x * (1.0f - x2 * (0.16666667f - x2 * 0.00833333f));
}

// ========================================
// MAIN SETUP AND LOOP
// ========================================
void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println("ESP32-S3 PIXEL WASH v3.8 Starting - HIGH PERFORMANCE VERSION!");
  Serial.println("REMOVED: All ESP8266 dirty rendering optimizations");
  Serial.println("ADDED: Full ESP32-S3 performance utilization");
  
  // Initialize random seed
  initRandomSeed();
  
  // Initialize the custom dual engine
  ST7735DualEngine::InitResult result = engine.begin();
  if (!result.success) {
    Serial.println("ERROR: Failed to initialize ST7735DualEngine!");
    while(1) delay(1000);
  }
  
  // Print initialization results
  Serial.printf("ST7735DualEngine initialized successfully!\n");
  Serial.printf("Platform: %s\n", result.platform);
  Serial.printf("SPI Frequency: %u MHz\n", result.spiFrequency / 1000000);
  Serial.printf("DMA Enabled: %s\n", result.dmaEnabled ? "Yes" : "No");
  Serial.printf("Dual-Core Enabled: %s\n", result.dualCoreEnabled ? "Yes" : "No");
  
  // Initialize NeoPixel
  neopixel.begin();
  neopixel.setBrightness(neoPixelBrightness);
  neopixel.clear();
  neopixel.show();
  
  // Enhanced title screen
  engine.clearBuffer(0, ST7735Colors::BLACK);
  engine.clearBuffer(1, ST7735Colors::BLACK);
  
  // Left display
  engine.drawTextCentered(0, 64,  40, "PIXEL",     ST7735Colors::CYAN,   2);
  engine.drawTextCentered(0, 64,  60, "WASH",      ST7735Colors::CYAN,   2);
  engine.drawTextCentered(0, 64,  85, "v3.8",      ST7735Colors::GREEN,  1);
  engine.drawTextCentered(0, 64, 100, "HIGH PERF", ST7735Colors::ORANGE, 1);
  engine.drawTextCentered(0, 64, 115, "NO DIRTY",  ST7735Colors::RED,    1);
  
  // Right display
  engine.drawTextCentered(1, 64,  40, "ESP32-S3",   ST7735Colors::YELLOW,  2);
  engine.drawTextCentered(1, 64,  60, "DUAL ENG",   ST7735Colors::MAGENTA, 1);
  engine.drawTextCentered(1, 64,  80, "128x160",    ST7735Colors::BLUE,    1);
  engine.drawTextCentered(1, 64,  95, "FULL SPEED", ST7735Colors::GREEN,   1);
  engine.drawTextCentered(1, 64, 110, "BENCHMARK",  ST7735Colors::CYAN,    1);
  
  engine.updateBoth();
  delay(3000);
  
  // Clear displays and initialize
  engine.clearBuffer(0, ST7735Colors::BLACK);
  engine.clearBuffer(1, ST7735Colors::BLACK);
  engine.updateBoth();
  
  // Initialize Conway patterns
  addConwayPatterns();
  addConwayPatterns();
  
  lastModeChange = millis();
  
  // Print system information
  engine.printSystemInfo();
  Serial.println("Initialization complete!");
  Serial.printf("Grid: %dx%d, Cell Size: %d, Free Heap: %u bytes\n",
    GRID_WIDTH, GRID_HEIGHT, CELL_SIZE, ESP.getFreeHeap());
}

void loop() {
  static uint32_t            lastUpdate = 0;
  static uint16_t            generation = 0;
  static uint32_t lastPerformanceReport = 0;
  
  const uint32_t currentTime = millis();
  
  // Update mode rotation
  updateModeRotation();
  
  // Update NeoPixel
  updateNeoPixel();
  
  // Run visual effects with high-performance intervals
  const uint8_t updateInterval = (currentMode == MODE_CONWAY) ? 120 : 33; // 8.3fps vs 30fps
  if (currentTime - lastUpdate >= updateInterval) {
    lastUpdate = currentTime;
    
    switch (currentMode) {
      case MODE_CONWAY:   updateConway();   break;
      case MODE_FIRE:     updateFire();     break;
      case MODE_WATER:    updateWater();    break;
      case MODE_BRIGHT:   updateBright();   break;
      case MODE_WIND:     updateWind();     break;
      case MODE_CYCLONE:  updateCyclone();  break;
      case MODE_PORTAL:   updatePortal();   break;
      case MODE_WORMHOLE: updateWormhole(); break;
    }
    
    // Direct full-grid rendering - no dirty optimization
    renderFullGrid(generation);
    generation++;
  }
  
  // Performance monitoring
  if (currentTime - lastPerformanceReport > 15000) { // Every 15 seconds
    lastPerformanceReport = currentTime;
    Serial.println("=== HIGH-PERFORMANCE REPORT ===");
    engine.printPerformanceReport();
    Serial.printf("Mode: %s, Generation: %u, Free Heap: %u bytes\n",
      modeNames[currentMode], generation, ESP.getFreeHeap());
    Serial.println("NO DIRTY TRACKING - FULL ESP32-S3 SPEED!");
  }
  
  yield();
}

The next example is of a basic side scrolling Space Shooter Engine template. Currently for demo the ship is controlled by AI. (Edit: 10/09/2025)

FPS Obtained: 25 FPS Avg. Tested with V2.1.2

/*
 * TFTFriend SpaceDefender - AI-Controlled Spaceship Game Demo for ST7735DualEngine
 * Uses Screen 0: Game display, Screen 1: Status/debug display/Detailed HUD Demo
 * v1.2 - Enhanced with NeoPixel integration on GPIO 38 for Devkit C1
 */

#include "ST7735DualEngine.h"
#include <Adafruit_NeoPixel.h>
#include <cmath>
#include <vector>

// NeoPixel Configuration
#define NEOPIXEL_PIN        38
#define NEOPIXEL_COUNT       1  // Assuming single NeoPixel, adjust if you have more
#define NEOPIXEL_BRIGHTNESS 50  // Global brightness (0-255), adjust as needed
Adafruit_NeoPixel strip(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

// Game Constants
const int GAME_SCREEN   = 0;
const int STATUS_SCREEN = 1;
const int MAX_STARS     = 50;
const int MAX_OBSTACLES = 8;
const int MAX_BULLETS   = 6;
const int MAX_PARTICLES = 30;

// Ship Configuration
const int SHIP_WIDTH     = 12;
const int SHIP_HEIGHT    = 8;
const int SHIP_X         = 20;  // Fixed X position
const float SHIP_SPEED   = 2.5f;
const float BULLET_SPEED = 4.0f;

// Enhanced AI Parameters
const float AI_REACTION_DISTANCE = 60.0f;
const float AI_DODGE_STRENGTH    = 3.0f;
const float AI_CENTER_PULL       = 1.2f;  // Auto-centering strength
const float AI_CENTER_DEADZONE   = 15.0f;  // How close to center before centering activates
const int AI_SHOOT_COOLDOWN      = 15;

// Game Physics
const float SCROLL_SPEED        = 2.0f;
const float OBSTACLE_SPAWN_RATE = 0.02f;

// Path checking parameters
const float PATH_CHECK_WIDTH    = 16.0f;  // Width of path ahead to check
const float PATH_CHECK_DISTANCE = 80.0f;  // How far ahead to look

// NeoPixel effect parameters
const int NEOPIXEL_UPDATE_RATE = 50;  // Update every 50ms

struct Vector2 {
    float x, y;
    Vector2(float x = 0, float y = 0) : x(x), y(y) {}
    
    Vector2 operator+(const Vector2& other) const { return Vector2(x + other.x, y + other.y); }
    Vector2 operator-(const Vector2& other) const { return Vector2(x - other.x, y - other.y); }
    Vector2 operator*(float scalar) const { return Vector2(x * scalar, y * scalar); }
    
    float length() const { return sqrt(x * x + y * y); }
    Vector2 normalize() const { 
        float len = length();
        return len > 0 ? Vector2(x / len, y / len) : Vector2(0, 0);
    }
};

struct Star {
    Vector2  pos;
    float    speed;
    uint16_t brightness;
    bool     active;
    
    Star() : pos(0, 0), speed(0), brightness(0), active(false) {}
};

struct Obstacle {
    Vector2  pos;
    Vector2  vel;
    int      size;
    uint16_t color;
    uint16_t shadeColor;
    uint16_t highlightColor;
    bool     active;
    int      rotation;
    int      colorType;
    
    Obstacle() : pos(0, 0), vel(0, 0), size(0), color(0), shadeColor(0), 
                highlightColor(0), active(false), rotation(0), colorType(0) {}
};

struct Bullet {
    Vector2 pos;
    Vector2 vel;
    bool active;
    int life;
    
    Bullet() : pos(0, 0), vel(0, 0), active(false), life(0) {}
};

struct Particle {
    Vector2  pos;
    Vector2  vel;
    uint16_t color;
    int      life;
    int      maxLife;
    bool     active;
    
    Particle() : pos(0, 0), vel(0, 0), color(0), life(0), maxLife(0), active(false) {}
};

struct Ship {
    Vector2 pos;
    Vector2 targetPos;
    Vector2 vel;
    int     health;
    bool    alive;
    int     invulnerabilityTimer;
    
    Ship() : pos(SHIP_X, ST7735DualEngine::HEIGHT / 2), targetPos(pos), vel(0, 0), 
             health(3), alive(true), invulnerabilityTimer(0) {}
};

class SpaceDefender {
private:
    ST7735DualEngine* engine;
    
    // Game Objects
    Ship     ship;
    Star     stars[MAX_STARS];
    Obstacle obstacles[MAX_OBSTACLES];
    Bullet   bullets[MAX_BULLETS];
    Particle particles[MAX_PARTICLES];
    
    // Game State
    unsigned long lastUpdate;
    unsigned long gameStartTime;
    int           score;
    int           level;
    int           aiShootCooldown;
    bool          gameOver;
    
    // AI State
    int   nearestObstacleIndex;
    float threatLevel;
    bool  pathClear;  // Is the path ahead clear?
    
    // Visual effects
    int gameOverFlashTimer;
    
    // NeoPixel state
    unsigned long lastNeoPixelUpdate;
    int           neoPixelEffectFrame;
    
public:
    SpaceDefender(ST7735DualEngine* eng) : engine(eng) {
        reset();
    }
    
    void reset() {
        lastUpdate           = millis();
        gameStartTime        = lastUpdate;
        score                = 0;
        level                = 1;
        aiShootCooldown      = 0;
        gameOver             = false;
        nearestObstacleIndex = -1;
        threatLevel          = 0;
        pathClear            = true;
        gameOverFlashTimer   = 0;
        lastNeoPixelUpdate   = 0;
        neoPixelEffectFrame  = 0;
        
        ship = Ship();
        
        // Initialize stars
        for (int i = 0; i < MAX_STARS; i++) {
            respawnStar(i);
        }
        
        // Clear other objects
        for (int i = 0; i < MAX_OBSTACLES; i++) obstacles[i].active = false;
        for (int i = 0; i < MAX_BULLETS;   i++) bullets[i].active   = false;
        for (int i = 0; i < MAX_PARTICLES; i++) particles[i].active = false;
        
        // Initialize NeoPixel
        updateNeoPixel();
    }
    
    void update() {
        unsigned long currentTime = millis();
        float deltaTime = (currentTime - lastUpdate) / 1000.0f;
        if (deltaTime > 0.033f) deltaTime = 0.033f; // Cap at 30 FPS minimum
        
        // Always update background elements
        updateStarfield(deltaTime);
        updateObstacles(deltaTime);
        updateBullets(deltaTime);
        updateParticles(deltaTime);
        
        if (!gameOver) {
            updateAI(deltaTime);
            updateShip(deltaTime);
            checkCollisions();
            spawnObstacles();
            updateGameLogic(deltaTime);
        } else {
            // Update game over visual effects
            gameOverFlashTimer++;
        }
        
        // Update NeoPixel effects
        updateNeoPixel();
        
        render();
        lastUpdate = currentTime;
    }
    
private:
    void updateStarfield(float deltaTime) {
        for (int i = 0; i < MAX_STARS; i++) {
            if (stars[i].active) {
                stars[i].pos.x -= stars[i].speed * SCROLL_SPEED * deltaTime * 60;
                
                if (stars[i].pos.x < -2) {
                    respawnStar(i);
                }
            }
        }
    }
    
    void respawnStar(int index) {
        stars[index].pos.x = ST7735DualEngine::WIDTH + random(50);
        stars[index].pos.y = random(ST7735DualEngine::HEIGHT);
        stars[index].speed = 0.3f + (random(70) / 100.0f);
        
        // Different star brightnesses
        int brightness = random(4);
        switch (brightness) {
            case 0: stars[index].brightness = engine->rgb565(100, 100, 100); break;
            case 1: stars[index].brightness = engine->rgb565(150, 150, 150); break;
            case 2: stars[index].brightness = engine->rgb565(200, 200, 200); break;
           default: stars[index].brightness = ST7735Colors::WHITE;           break;
        }
        
        stars[index].active = true;
    }
    
    void updateObstacles(float deltaTime) {
    for (int i = 0; i < MAX_OBSTACLES; i++) {
        if (obstacles[i].active) {
            obstacles[i].pos = obstacles[i].pos + obstacles[i].vel * deltaTime * 60;
            obstacles[i].rotation += 2;
            
            // More aggressive removal - remove when any part is off left edge
            // This prevents drawing artifacts during the transition frame
            if (obstacles[i].pos.x + obstacles[i].size < 0) {
                obstacles[i].active = false;
                }
            }
        }
    }
    
    void spawnObstacles() {
        if (gameOver) return; // Don't spawn new obstacles when game is over, terminate the function here itself
        
        if (random(1000) / 1000.0f < OBSTACLE_SPAWN_RATE * (1 + level * 0.1f)) {
            for (int i = 0; i < MAX_OBSTACLES; i++) {
                if (!obstacles[i].active) {
                    obstacles[i].pos.x    = ST7735DualEngine::WIDTH + 20;
                    obstacles[i].pos.y    = 10 + random(ST7735DualEngine::HEIGHT - 20);
                    obstacles[i].vel.x    = -SCROLL_SPEED * (0.8f + random(60) / 100.0f);
                    obstacles[i].vel.y    = (random(100) - 50) / 100.0f;
                    obstacles[i].size     = 8 + random(8);
                    obstacles[i].rotation = 0;
                    obstacles[i].active   = true;
                    
                    // Enhanced planet color variations
                    obstacles[i].colorType = random(8);
                    switch (obstacles[i].colorType) {
                        case 0: // Mars-Red
                            obstacles[i].color          = engine->rgb565(180,  80, 40);
                            obstacles[i].shadeColor     = engine->rgb565(120,  50, 20);
                            obstacles[i].highlightColor = engine->rgb565(220, 120, 80);
                            break;
                        case 1: // Earth-blue-green
                            obstacles[i].color          = engine->rgb565( 60, 120, 180);
                            obstacles[i].shadeColor     = engine->rgb565( 30,  80, 120);
                            obstacles[i].highlightColor = engine->rgb565(100, 160, 220);
                            break;
                        case 2: // Venus-yellow
                            obstacles[i].color          = engine->rgb565(200, 180,  60);
                            obstacles[i].shadeColor     = engine->rgb565(140, 120,  30);
                            obstacles[i].highlightColor = engine->rgb565(240, 220, 100);
                            break;
                        case 3: // Purple gas giant
                            obstacles[i].color          = engine->rgb565(120,  60, 180);
                            obstacles[i].shadeColor     = engine->rgb565( 80,  30, 120);
                            obstacles[i].highlightColor = engine->rgb565(160, 100, 220);
                            break;
                        case 4: // Orange desert
                            obstacles[i].color          = engine->rgb565(200, 120, 40);
                            obstacles[i].shadeColor     = engine->rgb565(140,  80, 20);
                            obstacles[i].highlightColor = engine->rgb565(240, 160, 80);
                            break;
                        case 5: // Ice world blue-white
                            obstacles[i].color          = engine->rgb565(140, 160, 200);
                            obstacles[i].shadeColor     = engine->rgb565(100, 120, 160);
                            obstacles[i].highlightColor = engine->rgb565(180, 200, 240);
                            break;
                        case 6: // Volcanic world red-orange
                            obstacles[i].color          = engine->rgb565(200, 100,  60);
                            obstacles[i].shadeColor     = engine->rgb565(140,  60,  30);
                            obstacles[i].highlightColor = engine->rgb565(240, 140, 100);
                            break;
                        default: // Rocky asteroid gray
                            obstacles[i].color          = engine->rgb565(120, 120, 100);
                            obstacles[i].shadeColor     = engine->rgb565( 80,  80,  60);
                            obstacles[i].highlightColor = engine->rgb565(160, 160, 140);
                            break;
                    }
                    break;
                }
            }
        }
    }
    
    void updateBullets(float deltaTime) {
        for (int i = 0; i < MAX_BULLETS; i++) {
            if (bullets[i].active) {
                bullets[i].pos = bullets[i].pos + bullets[i].vel * deltaTime * 60;
                bullets[i].life--;
                
                if (bullets[i].life <= 0 || bullets[i].pos.x > ST7735DualEngine::WIDTH + 5) {
                    bullets[i].active = false;
                }
            }
        }
    }
    
    void updateParticles(float deltaTime) {
        for (int i = 0; i < MAX_PARTICLES; i++) {
            if (particles[i].active) {
                particles[i].pos = particles[i].pos + particles[i].vel * deltaTime * 60;
                particles[i].life--;
                
                if (particles[i].life <= 0) {
                    particles[i].active = false;
                }
            }
        }
    }
    
    bool checkPathClear() {
        // Check if the path directly ahead of the ship is clear
        float shipCenterY = ship.pos.y;
        float shipMinY = shipCenterY - PATH_CHECK_WIDTH / 2;
        float shipMaxY = shipCenterY + PATH_CHECK_WIDTH / 2;
        
        for (int i = 0; i < MAX_OBSTACLES; i++) {
            if (!obstacles[i].active) continue;
            
            // Check if obstacle is in front of ship and within checking distance
            if (obstacles[i].pos.x > ship.pos.x && 
                obstacles[i].pos.x < ship.pos.x + PATH_CHECK_DISTANCE) {
                
                float obsMinY = obstacles[i].pos.y - obstacles[i].size;
                float obsMaxY = obstacles[i].pos.y + obstacles[i].size;
                
                // Check for overlap in Y axis with the ship's path
                if (obsMaxY >= shipMinY && obsMinY <= shipMaxY) {
                    return false; // Path is blocked
                }
            }
        }
        
        return true; // Path is clear
    }
    
    void updateAI(float deltaTime) {
        if (!ship.alive) return;
        
        // Check if path ahead is clear
        pathClear = checkPathClear();
        
        // Find nearest obstacle
        float nearestDistance = 999999;
        nearestObstacleIndex = -1;
        
        for (int i = 0; i < MAX_OBSTACLES; i++) {
            if (obstacles[i].active) {
                float distance = (obstacles[i].pos - ship.pos).length();
                if (distance < nearestDistance && obstacles[i].pos.x > ship.pos.x) {
                    nearestDistance = distance;
                    nearestObstacleIndex = i;
                }
            }
        }
        
        // Calculate threat level
        threatLevel = 0;
        if (nearestObstacleIndex >= 0 && nearestDistance < AI_REACTION_DISTANCE) {
            threatLevel = 1.0f - (nearestDistance / AI_REACTION_DISTANCE);
        }
        
        // AI Decisions
        Vector2 avoidanceVector(0, 0);
        bool shouldShoot = false;
        
        if (nearestObstacleIndex >= 0) {
            Obstacle& nearest = obstacles[nearestObstacleIndex];
            Vector2 toObstacle = nearest.pos - ship.pos;
            
            // Check if obstacle is in path
            if (abs(toObstacle.y) < 20 && toObstacle.x > 0 && toObstacle.x < 80) {
                // Dodge decision
                if (toObstacle.length() < AI_REACTION_DISTANCE) {
                    avoidanceVector.y = toObstacle.y > 0 ? -AI_DODGE_STRENGTH : AI_DODGE_STRENGTH;
                    
                    // Prefer dodging toward center of screen
                    float centerY = ST7735DualEngine::HEIGHT / 2;
                    if (ship.pos.y > centerY) {
                        avoidanceVector.y -= 1.0f;
                    } else {
                        avoidanceVector.y += 1.0f;
                    }
                }
                
                // Shoot decision
                if (abs(toObstacle.y) < 10 && aiShootCooldown <= 0) {
                    shouldShoot = true;
                }
            }
        }
        
        // Auto-centering behavior when no immediate threats
        float centerY = ST7735DualEngine::HEIGHT / 2;
        float distanceFromCenter = abs(ship.pos.y - centerY);
        
        if (threatLevel < 0.3f && distanceFromCenter > AI_CENTER_DEADZONE) {
            // Apply gentle pull toward center
            float centerPull = (ship.pos.y > centerY) ? -AI_CENTER_PULL : AI_CENTER_PULL;
            // Scale centering force by distance from center
            float centerForce = centerPull * (distanceFromCenter / (ST7735DualEngine::HEIGHT / 2));
            avoidanceVector.y += centerForce;
        }
        
        // Update target position
        ship.targetPos.y = ship.pos.y + avoidanceVector.y;
        
        // Keep ship on screen with soft boundaries
        if (ship.targetPos.y < 10) ship.targetPos.y = 10;
        if (ship.targetPos.y > ST7735DualEngine::HEIGHT - 10) {
            ship.targetPos.y = ST7735DualEngine::HEIGHT - 10;
        }
        
        // Shoot if AI decides to
        if (shouldShoot) {
            shootBullet();
            aiShootCooldown = AI_SHOOT_COOLDOWN;
        }
        
        if (aiShootCooldown > 0) aiShootCooldown--;
    }
    
    void updateShip(float deltaTime) {
        if (!ship.alive) return;
        
        // Smooth movement toward target
        Vector2 toTarget = ship.targetPos - ship.pos;
        ship.vel = ship.vel * 0.85f + toTarget.normalize() * SHIP_SPEED * 0.15f;
        ship.pos = ship.pos + ship.vel * deltaTime * 60;
        
        // Keep ship bounds
        if (ship.pos.y < 5) ship.pos.y = 5;
        if (ship.pos.y > ST7735DualEngine::HEIGHT - 5) ship.pos.y = ST7735DualEngine::HEIGHT - 5;
        
        if (ship.invulnerabilityTimer > 0) {
            ship.invulnerabilityTimer--;
        }
    }
    
    void shootBullet() {
        for (int i = 0; i < MAX_BULLETS; i++) {
            if (!bullets[i].active) {
                bullets[i].pos = Vector2(ship.pos.x + SHIP_WIDTH, ship.pos.y);
                bullets[i].vel = Vector2(BULLET_SPEED, 0);
                bullets[i].active = true;
                bullets[i].life = 120; // 2 seconds at 60 FPS
                break;
            }
        }
    }
    
    void checkCollisions() {
        if (!ship.alive || ship.invulnerabilityTimer > 0) return;
        
        // Bullet vs Obstacle collisions
        for (int b = 0; b < MAX_BULLETS; b++) {
            if (!bullets[b].active) continue;
            
            for (int o = 0; o < MAX_OBSTACLES; o++) {
                if (!obstacles[o].active) continue;
                
                Vector2 diff = bullets[b].pos - obstacles[o].pos;
                if (diff.length() < obstacles[o].size) {
                    // Hit!
                    createExplosion(obstacles[o].pos, obstacles[o].size);
                    obstacles[o].active = false;
                    bullets[b].active = false;
                    score += 10 * level;
                    break;
                }
            }
        }
        
        // Ship vs Obstacle collisions (only if game not over)
        if (!gameOver) {
            for (int o = 0; o < MAX_OBSTACLES; o++) {
                if (!obstacles[o].active) continue;
                
                Vector2 diff = ship.pos - obstacles[o].pos;
                if (diff.length() < obstacles[o].size + 4) {
                    // Ship hit!
                    createExplosion(ship.pos, 15);
                    ship.health--;
                    ship.invulnerabilityTimer = 120; // 2 seconds
                    
                    if (ship.health <= 0) {
                        ship.alive = false;
                        gameOver = true;
                    }
                    break;
                }
            }
        }
    }
    
    void createExplosion(Vector2 center, int size) {
        // Find the obstacle that was destroyed to get its color
        Obstacle* destroyedObstacle = nullptr;
        for (int i = 0; i < MAX_OBSTACLES; i++) {
            if (obstacles[i].active) {
                Vector2 diff = obstacles[i].pos - center;
                if (diff.length() < size + 5) {
                    destroyedObstacle = &obstacles[i];
                    break;
                }
            }
        }
        
        int numParticles = size / 2 + 5;
        
        for (int i = 0; i < numParticles && i < MAX_PARTICLES; i++) {
            for (int p = 0; p < MAX_PARTICLES; p++) {
                if (!particles[p].active) {
                    particles[p].pos = center + Vector2(random(10) - 5, random(10) - 5);
                    
                    float angle = (i / (float)numParticles) * 2 * PI + random(100) / 100.0f;
                    float speed = 1 + random(4);
                    particles[p].vel = Vector2(cos(angle) * speed, sin(angle) * speed);
                    
                    particles[p].life = 25 + random(25);
                    particles[p].maxLife = particles[p].life;
                    particles[p].active = true;
                    
                    // Create particles based on destroyed object color
                    if (destroyedObstacle) {
                        // Extract RGB components from planet color
                        uint8_t r = (destroyedObstacle->color >> 11) & 0x1F;
                        uint8_t g = (destroyedObstacle->color >> 5) & 0x3F;
                        uint8_t b = destroyedObstacle->color & 0x1F;
                        
                        // Create color variations for debris
                        int variation = random(4);
                        switch (variation) {
                            case 0: // Brighter version
                                particles[p].color = engine->rgb565(
                                    min(255, r * 10), min(255, g * 5), min(255, b * 10));
                                break;
                            case 1: // Darker version  
                                particles[p].color = engine->rgb565(
                                    max(0, r * 6), max(0, g * 3), max(0, b * 6));
                                break;
                            case 2: // Add orange fire effect
                                particles[p].color = engine->rgb565(
                                    min(255, r * 8 + 100), min(255, g * 4 + 50), max(0, b * 4));
                                break;
                            default: // Original planet color
                                particles[p].color = destroyedObstacle->color;
                                break;
                        }
                    } else {
                        // Default explosion colors for ship destruction
                        int colorType = random(3);
                        switch (colorType) {
                            case 0: particles[p].color = engine->rgb565(255, 100, 0); break;  // Orange
                            case 1: particles[p].color = engine->rgb565(255, 200, 0); break; // Yellow
                            default: particles[p].color = engine->rgb565(255, 50, 50); break; // Red
                        }
                    }
                    break;
                }
            }
        }
    }
    
    void updateGameLogic(float deltaTime) {
        // Level progression
        int newLevel = (score / 200) + 1;
        if (newLevel > level) {
            level = newLevel;
        }
    }
    
    void updateNeoPixel() {
        unsigned long currentTime = millis();
        
        // Update NeoPixel at a controlled rate
        if (currentTime - lastNeoPixelUpdate >= NEOPIXEL_UPDATE_RATE) {
            lastNeoPixelUpdate = currentTime;
            neoPixelEffectFrame++;
            
            if (gameOver) {
                // Game Over: Fast rainbow flash
                int hue = (neoPixelEffectFrame * 20) % 360;
                strip.setPixelColor(0, strip.ColorHSV(hue * 182, 255, NEOPIXEL_BRIGHTNESS));
                
            } else if (!ship.alive) {
                // Ship destroyed but game not over: Red pulse
                int brightness = (NEOPIXEL_BRIGHTNESS/2) + (int)((NEOPIXEL_BRIGHTNESS/2) * sin(neoPixelEffectFrame * 0.3));
                strip.setPixelColor(0, strip.Color(brightness, 0, 0));
                
            } else if (ship.invulnerabilityTimer > 0) {
                // Invulnerable: White flash
                if ((neoPixelEffectFrame % 4) < 2) {
                    strip.setPixelColor(0, strip.Color(NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS));
                } else {
                    strip.setPixelColor(0, strip.Color(0, 0, 0));
                }
                
            } else if (threatLevel > 0.7f) {
                // High threat: Red alert pulse
                int brightness = (NEOPIXEL_BRIGHTNESS*2/5) + (int)((NEOPIXEL_BRIGHTNESS*3/5) * threatLevel * (0.5 + 0.5 * sin(neoPixelEffectFrame * 0.5)));
                strip.setPixelColor(0, strip.Color(brightness, 0, 0));
                
            } else if (threatLevel > 0.3f) {
                // Medium threat: Orange warning
                int brightness = (NEOPIXEL_BRIGHTNESS/3) + (int)((NEOPIXEL_BRIGHTNESS*2/5) * threatLevel);
                strip.setPixelColor(0, strip.Color(brightness, brightness/2, 0));
                
            } else if (!pathClear) {
                // Path blocked but low threat: Yellow caution
                int brightness = (NEOPIXEL_BRIGHTNESS/2) + (int)((NEOPIXEL_BRIGHTNESS/4) * sin(neoPixelEffectFrame * 0.2));
                strip.setPixelColor(0, strip.Color(brightness, brightness, 0));
                
            } else {
                // Safe: Green with subtle blue engine glow effect
                float centerDistance = abs(ship.pos.y - (ST7735DualEngine::HEIGHT / 2));
                float maxDistance = ST7735DualEngine::HEIGHT / 2;
                float centerRatio = 1.0f - (centerDistance / maxDistance);
                
                // Base green with blue accent based on centering
                int green = (NEOPIXEL_BRIGHTNESS*2/5) + (int)((NEOPIXEL_BRIGHTNESS*2/5) * centerRatio);
                int blue = (int)((NEOPIXEL_BRIGHTNESS/5) * (1.0f - centerRatio)) + (int)((NEOPIXEL_BRIGHTNESS/8) * sin(neoPixelEffectFrame * 0.1));
                strip.setPixelColor(0, strip.Color(0, green, blue));
            }
            
            // Special effects for specific events
            
            // Level up effect (brief white flash)
            static int lastLevel = 1;
            if (level > lastLevel) {
                strip.setPixelColor(0, strip.Color(NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS));
                lastLevel = level;
            }
            
            // Shooting effect (brief cyan flash)
            static int lastShootCooldown = 0;
            if (aiShootCooldown > lastShootCooldown) {
                strip.setPixelColor(0, strip.Color(0, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS));
            }
            lastShootCooldown = aiShootCooldown;
            
            strip.show();
        }
    }
    
    void render() {
    // Start frame timing
    engine->getPerformanceStats().startFrame();
    
    // Clear both screens efficiently
    engine->clearBuffer(GAME_SCREEN, ST7735Colors::BLACK);
    engine->clearBuffer(STATUS_SCREEN, ST7735Colors::BLACK);
    
    // Render game content
    renderGame();
    renderStatus();
    
    // Present complete frame (this counts as 1 FPS)
    engine->updateBoth();
    
    // Frame timing is handled in updateBoth() via stats.endFrame()
    }

    
    void renderGame() {
        // Draw stars
        for (int i = 0; i < MAX_STARS; i++) {
            if (stars[i].active) {
                engine->setPixel(GAME_SCREEN, (int)stars[i].pos.x, (int)stars[i].pos.y, 
                                stars[i].brightness);
            }
        }
        
        // Draw obstacles
        for (int i = 0; i < MAX_OBSTACLES; i++) {
            if (obstacles[i].active) {
                drawAsteroid(obstacles[i]);
            }
        }
        
        // Draw bullets
        for (int i = 0; i < MAX_BULLETS; i++) {
            if (bullets[i].active) {
                engine->fillRect(GAME_SCREEN, (int)bullets[i].pos.x - 1, (int)bullets[i].pos.y - 1, 
                                3, 2, engine->rgb565(0, 255, 100));
            }
        }
        
        // Draw particles
        for (int i = 0; i < MAX_PARTICLES; i++) {
            if (particles[i].active) {
                float alpha = particles[i].life / (float)particles[i].maxLife;
                if (alpha > 0.1f) {
                    engine->setPixel(GAME_SCREEN, (int)particles[i].pos.x, (int)particles[i].pos.y,
                                    particles[i].color);
                }
            }
        }
        
        // Draw ship (even when game over, for visual continuity)
        if (ship.alive || gameOver) {
            drawShip();
        }
        
        // Draw HUD elements
        drawHUD();
    }
    
    void drawAsteroid(const Obstacle& obs) {
    const int x = (int)obs.pos.x;  // Use const to avoid recalculation
    const int y = (int)obs.pos.y;
    const int size = obs.size;
    
    // Early exit if planet is completely off-screen (prevent any artifacts)
    if (x + size < 0 || x - size >= ST7735DualEngine::WIDTH || 
        y + size < 0 || y - size >= ST7735DualEngine::HEIGHT) {
        return;
    }
    
    // Efficient 3D shading using concentric circles
    // Draw shadow/dark side first (larger circle)
    engine->fillCircle(GAME_SCREEN, x + 1, y + 1, size - 1, obs.shadeColor);
    
    // Draw main planet body
    engine->fillCircle(GAME_SCREEN, x, y, size - 2, obs.color);
    
    // Add highlight (smaller circle, offset toward light source)
    const int highlightSize = size / 3;
    if (highlightSize > 1) {
        engine->fillCircle(GAME_SCREEN, x - size/3, y - size/4, highlightSize, obs.highlightColor);
    }
    
    // Add surface details based on planet type for extra realism
    // Only if planet is reasonably on-screen and large enough
    if (size > 10 && x + size > 0 && x - size < ST7735DualEngine::WIDTH) {
        switch (obs.colorType) {
            case 1: // Earth-like - add continents
                if (random(100) < 30) {
                    // Bounds check before drawing continent
                    if (x >= 2 && x <= ST7735DualEngine::WIDTH - 3) {
                        engine->fillRect(GAME_SCREEN, x - 2, y, 4, 2, obs.shadeColor);
                    }
                }
                break;
                
            case 2: // Venus-like - add atmospheric bands
                {
                    // Calculate safe line bounds to prevent artifacts
                    const int lineStart = max(0, x - size/2);
                    const int lineEnd = min(ST7735DualEngine::WIDTH - 1, x + size/2);
                    const int lineWidth = lineEnd - lineStart;
                    
                    // Only draw if there's a meaningful line to draw
                    if (lineWidth > 0 && lineStart < ST7735DualEngine::WIDTH) {
                        // Upper band
                        if (y - 1 >= 0 && y - 1 < ST7735DualEngine::HEIGHT) {
                            engine->drawHLine(GAME_SCREEN, lineStart, y - 1, lineWidth, obs.highlightColor);
                        }
                        // Lower band  
                        if (y + 2 >= 0 && y + 2 < ST7735DualEngine::HEIGHT) {
                            engine->drawHLine(GAME_SCREEN, lineStart, y + 2, lineWidth, obs.shadeColor);
                        }
                    }
                }
                break;
                
            case 3: // Gas giant - add bands
                {
                    // Pre-calculate bounds to avoid repeated calculations
                    const int planetLeft = x - size/2;
                    const int planetRight = x + size/2;
                    const int clippedLeft = max(0, planetLeft);
                    const int clippedRight = min(ST7735DualEngine::WIDTH - 1, planetRight);
                    const int bandWidth = clippedRight - clippedLeft;
                    
                    // Only draw bands if planet is meaningfully on-screen
                    if (bandWidth > 0 && clippedLeft < ST7735DualEngine::WIDTH) {
                        for (int band = -size/2; band < size/2; band += 3) {
                            const int bandY = y + band;
                            // Bounds check for Y coordinate
                            if (bandY >= 0 && bandY < ST7735DualEngine::HEIGHT) {
                                engine->drawHLine(GAME_SCREEN, clippedLeft, bandY, bandWidth,
                                    engine->blendColors(obs.color, obs.shadeColor, 128));
                            }
                        }
                    }
                }
                break;
                
            case 6: // Volcanic world - add lava spots
                if (random(100) < 40) {
                    const int lavaX = x + random(6) - 3;
                    const int lavaY = y + random(6) - 3;
                    // Bounds check for lava spots
                    if (lavaX >= 0 && lavaX < ST7735DualEngine::WIDTH && 
                        lavaY >= 0 && lavaY < ST7735DualEngine::HEIGHT) {
                        engine->setPixel(GAME_SCREEN, lavaX, lavaY, engine->rgb565(255, 100, 0));
                    }
                }
                break;
        }
    }
    
    // Draw rough edge outline for rocky asteroids
    if (obs.colorType == 7) { // Rocky asteroid
        for (int angle = 0; angle < 360; angle += 60) {
            const float rad = (angle + obs.rotation) * PI / 180.0f;
            const float variation = 0.8f + (random(40) / 100.0f);
            const int px = x + cos(rad) * size * variation;
            const int py = y + sin(rad) * size * variation;
            // Bounds check for edge outline
            if (px >= 0 && px < ST7735DualEngine::WIDTH && py >= 0 && py < ST7735DualEngine::HEIGHT) {
                engine->setPixel(GAME_SCREEN, px, py, obs.shadeColor);
                }
            }
        }
    }


    
    void drawShip() {
        int x = (int)ship.pos.x;
        int y = (int)ship.pos.y;
        
        // Ship flashing when invulnerable (but still show when game over)
        if (!gameOver && ship.invulnerabilityTimer > 0 && (ship.invulnerabilityTimer % 8) < 4) {
            return; // Skip drawing for flashing effect
        }
        
        // Enhanced ship design with multiple colors and details
        uint16_t shipHull = engine->rgb565(120, 140, 255);      // Light blue hull
        uint16_t shipAccent = engine->rgb565(80, 100, 200);     // Darker blue accents
        uint16_t shipGlow = engine->rgb565(200, 220, 255);      // White-blue glow
        uint16_t thrusterColor = engine->rgb565(255, 120, 0);   // Orange thruster
        uint16_t thrusterCore = engine->rgb565(255, 200, 100);  // Yellow core
        uint16_t engineGlow = engine->rgb565(0, 150, 255);      // Blue engine glow
        
        // Main ship body (elongated hexagon for more futuristic look)
        int shipPoints[][2] = {
            {x + SHIP_WIDTH, y},           // Nose
            {x + SHIP_WIDTH - 3, y - 2},   // Upper nose
            {x + 2, y - 3},                // Upper hull  
            {x - 2, y - 2},                // Upper rear
            {x - 2, y + 2},                // Lower rear
            {x + 2, y + 3},                // Lower hull
            {x + SHIP_WIDTH - 3, y + 2}    // Lower nose
        };
        
        // Fill ship hull
        engine->fillTriangle(GAME_SCREEN, x + SHIP_WIDTH, y, x + 2, y - 3, x + 2, y + 3, shipHull);
        engine->fillTriangle(GAME_SCREEN, x + 2, y - 3, x - 2, y - 2, x - 2, y + 2, shipAccent);
        engine->fillTriangle(GAME_SCREEN, x + 2, y + 3, x - 2, y + 2, x + 2, y - 3, shipAccent);
        
        // Ship outline for definition
        engine->drawLine(GAME_SCREEN, x + SHIP_WIDTH, y, x + 2, y - 3, shipGlow);
        engine->drawLine(GAME_SCREEN, x + SHIP_WIDTH, y, x + 2, y + 3, shipGlow);
        engine->drawLine(GAME_SCREEN, x + 2, y - 3, x - 2, y - 2, shipAccent);
        engine->drawLine(GAME_SCREEN, x + 2, y + 3, x - 2, y + 2, shipAccent);
        
        // Cockpit/bridge area
        engine->fillRect(GAME_SCREEN, x + 6, y - 1, 4, 2, shipGlow);
        engine->setPixel(GAME_SCREEN, x + 8, y, engine->rgb565(100, 255, 100)); // Green cockpit light
        
        // Wing details
        engine->setPixel(GAME_SCREEN, x + 3, y - 2, shipGlow);
        engine->setPixel(GAME_SCREEN, x + 3, y + 2, shipGlow);
        
        // Engine details
        engine->fillRect(GAME_SCREEN, x - 1, y - 1, 2, 2, engineGlow);
        
        // Animated thruster effect (continue even when game over for visual continuity)
        int thrusterFrame = (millis() / 50) % 4;
        switch (thrusterFrame) {
        case 0:
            // Minimal thruster (start of cycle)
            engine->setPixel(GAME_SCREEN, x - 3, y, thrusterCore);
            engine->setPixel(GAME_SCREEN, x - 4, y, thrusterColor);
            break;
        case 1:
            // Short thruster (expanding)
            engine->drawLine(GAME_SCREEN, x - 3, y, x - 5, y, thrusterCore);
            engine->setPixel(GAME_SCREEN, x - 6, y, thrusterColor);
            break;
        case 2:
            // Medium thruster (more expansion)
            engine->drawLine(GAME_SCREEN, x - 3, y, x - 6, y, thrusterColor);
            engine->setPixel(GAME_SCREEN, x - 7, y + 1, thrusterColor);
            engine->setPixel(GAME_SCREEN, x - 7, y - 1, thrusterColor);
            break;
        case 3:
            // Full thruster (maximum expansion)
            engine->drawLine(GAME_SCREEN, x - 3, y, x - 8, y, thrusterColor);
            engine->setPixel(GAME_SCREEN, x - 6, y, thrusterCore);
            engine->setPixel(GAME_SCREEN, x - 9, y + 1, thrusterColor);
            engine->setPixel(GAME_SCREEN, x - 9, y - 1, thrusterColor);
            break;
    }
        
        // Add subtle engine trails (only when alive)
        if (ship.alive && abs(ship.vel.y) > 0.5f) {
            // Maneuvering thruster effects
            uint16_t maneuverColor = engine->rgb565(100, 150, 255);
            if (ship.vel.y < 0) { // Moving up, thruster below
                engine->setPixel(GAME_SCREEN, x - 1, y + 2, maneuverColor);
            } else { // Moving down, thruster above
                engine->setPixel(GAME_SCREEN, x - 1, y - 2, maneuverColor);
            }
        }
        
        // Health-based visual effects (only when alive)
        if (ship.alive && ship.health == 1) {
            // Damage effects - sparks
            if (random(100) < 30) {
                engine->setPixel(GAME_SCREEN, x + random(8) - 4, y + random(6) - 3, 
                                engine->rgb565(255, 200, 0));
            }
        }
        
        // AI activity indicator (subtle pulsing) - only when alive
        if (ship.alive && threatLevel > 0.3f) {
            int pulse = (millis() / 100) % 20;
            if (pulse < 10) {
                engine->setPixel(GAME_SCREEN, x + 4, y - 2, engine->rgb565(255, 0, 0)); // Red alert
                engine->setPixel(GAME_SCREEN, x + 4, y + 2, engine->rgb565(255, 0, 0));
            }
        }
    }
    
    void drawHUD() {
        // Health bar (only show when alive)
        if (ship.alive) {
            for (int i = 0; i < ship.health; i++) {
                engine->fillRect(GAME_SCREEN, 5 + i * 8, 5, 6, 4, engine->rgb565(0, 255, 0));
            }
        }
        
        // Score
        char scoreText[20];
        sprintf(scoreText, "Score: %d", score);
        engine->drawText(GAME_SCREEN, 5, ST7735DualEngine::HEIGHT - 15, scoreText, 
                        ST7735Colors::WHITE, 1);
        
        // Enhanced flashing game over text
        if (gameOver) {
            // Create flashing effect with multiple colors
            uint16_t gameOverColor;
            int flashPhase = (gameOverFlashTimer / 15) % 6; // Change every 15 frames, 6 colors
            
            switch (flashPhase) {
                case 0: gameOverColor = engine->rgb565(255, 0, 0); break;     // Red
                case 1: gameOverColor = engine->rgb565(255, 100, 0); break;   // Orange
                case 2: gameOverColor = engine->rgb565(255, 255, 0); break;   // Yellow
                case 3: gameOverColor = engine->rgb565(255, 0, 255); break;   // Magenta
                case 4: gameOverColor = engine->rgb565(100, 100, 255); break; // Light Blue
                case 5: gameOverColor = ST7735Colors::WHITE; break;           // White
                default: gameOverColor = engine->rgb565(255, 0, 0); break;
            }
            
            engine->drawTextCentered(GAME_SCREEN, ST7735DualEngine::WIDTH / 2, 
                                   ST7735DualEngine::HEIGHT / 2, "GAME OVER", 
                                   gameOverColor, 2);
            
            // Add a subtitle that also flashes
            if ((gameOverFlashTimer / 8) % 2 == 0) { // Faster flash for subtitle
                char finalScoreText[30];
                sprintf(finalScoreText, "Final Score: %d", score);
                engine->drawTextCentered(GAME_SCREEN, ST7735DualEngine::WIDTH / 2, 
                                       ST7735DualEngine::HEIGHT / 2 + 20, finalScoreText, 
                                       engine->rgb565(200, 200, 200), 1);
            }
        }
    }
    
    void renderStatus() {
    // AI Status Display
    char buffer[30];
    
    engine->drawText(STATUS_SCREEN, 5, 10, "AI STATUS", ST7735Colors::CYAN, 1);
    
    sprintf(buffer, "Level: %d", level);
    engine->drawText(STATUS_SCREEN, 5, 25, buffer, ST7735Colors::WHITE, 1);
    
    sprintf(buffer, "Threat: %.1f", threatLevel);
    engine->drawText(STATUS_SCREEN, 5, 35, buffer, 
                    threatLevel > 0.5f ? engine->rgb565(255, 100, 0) : ST7735Colors::GREEN, 1);
    
    sprintf(buffer, "Health: %d", ship.health);
    engine->drawText(STATUS_SCREEN, 5, 45, buffer, 
                    ship.health > 1 ? ST7735Colors::GREEN : engine->rgb565(255, 0, 0), 1);
    
    sprintf(buffer, "Ship Y: %.1f", ship.pos.y);
    engine->drawText(STATUS_SCREEN, 5, 55, buffer, ST7735Colors::WHITE, 1);
    
    sprintf(buffer, "Target Y: %.1f", ship.targetPos.y);
    engine->drawText(STATUS_SCREEN, 5, 65, buffer, ST7735Colors::YELLOW, 1);
    
    // Path status
    engine->drawText(STATUS_SCREEN, 90, 25, pathClear ? "PATH: C" : "PATH: B", 
                pathClear ? ST7735Colors::GREEN : ST7735Colors::RED, 1);
    
    // Centering status
    float centerY = ST7735DualEngine::HEIGHT / 2;
    float distanceFromCenter = abs(ship.pos.y - centerY);
    bool centeringActive = (threatLevel < 0.3f && distanceFromCenter > AI_CENTER_DEADZONE);
    
    engine->drawText(STATUS_SCREEN, 90, 35, centeringActive ? "CENTERING" : "STABLE", 
                    centeringActive ? ST7735Colors::YELLOW : ST7735Colors::GREEN, 1);
    
    // Debug info
    sprintf(buffer, "pathClear: %s", pathClear ? "true" : "false");
    engine->drawText(STATUS_SCREEN, 5, 140, buffer, ST7735Colors::WHITE, 1);
    
    // Enhanced Minimap-style threat display with sized markers
    engine->drawText(STATUS_SCREEN, 5, 80, "RADAR:", ST7735Colors::CYAN, 1);
    engine->drawRect(STATUS_SCREEN, 5, 90, 80, 40,   ST7735Colors::GREEN);
    
    // Show ship position on radar - ship color changes based on collision course
    int shipRadarY = 90 + (ship.pos.y / ST7735DualEngine::HEIGHT) * 40;
    
    // Ensure ship marker stays within radar bounds
    if (shipRadarY >= 90 && shipRadarY <= 129) {
        // Ship color changes based on collision course/path status
        uint16_t shipRadarColor;
        if (pathClear) {
            shipRadarColor = 0x07E0;  // Green - safe path
        } else {
            shipRadarColor = 0xF800;  // Red - collision course
        }
        
        engine->fillRect(STATUS_SCREEN, 8, shipRadarY - 1, 3, 3, shipRadarColor);
    }
    
    // Show obstacles on radar - ALWAYS use their original colors regardless of threat
    for (int i = 0; i < MAX_OBSTACLES; i++) {
        if (obstacles[i].active && obstacles[i].pos.x > 0) {
            int obsRadarX =  5 + (obstacles[i].pos.x / ST7735DualEngine::WIDTH ) * 80;
            int obsRadarY = 90 + (obstacles[i].pos.y / ST7735DualEngine::HEIGHT) * 40;
            
            // Scale obstacle size for radar (minimum 1 pixel, maximum 4 pixels)
            int radarSize = max(1, min(4, obstacles[i].size / 4));
            
            // Use the obstacle's original color (scaled down for radar visibility)
            uint16_t radarColor = obstacles[i].color;
            
            // Calculate bounds for the radar marker
            int markerLeft   = obsRadarX  - radarSize / 2;
            int markerTop    = obsRadarY  - radarSize / 2;
            int markerRight  = markerLeft + radarSize - 1;
            int markerBottom = markerTop  + radarSize - 1;
            
            // Only draw if marker is within radar bounds
            if (markerLeft >= 5 && markerTop >= 90 && 
                markerRight <= 84 && markerBottom <= 129) { // Radar bounds: x(5-84), y(90-129)
                
                // Draw sized marker with original planet color
                if (radarSize == 1) {
                    engine->setPixel(STATUS_SCREEN, obsRadarX, obsRadarY, radarColor);
                } else {
                    engine->fillRect(STATUS_SCREEN, markerLeft, markerTop, 
                                   radarSize, radarSize, radarColor);
                }
            }
        }
    }
    
    // Steering Yoke Feedback Display (right side of radar)
    int yokeX       = 95;  // X position for the yoke display
    int yokeTopY    = 90;  // Top of the yoke bar
    int yokeBottomY = 129;  // Bottom of the yoke bar
    int yokeCenterY = (yokeTopY + yokeBottomY) / 2;  // Center line
    int yokeHeight  = yokeBottomY - yokeTopY;
    
    // Draw the yoke background bar
    engine->drawRect(STATUS_SCREEN, yokeX, yokeTopY, 4, yokeHeight, ST7735Colors::WHITE);
    
    // Draw center reference line (red)
    engine->drawHLine(STATUS_SCREEN, yokeX - 2, yokeCenterY, 8, engine->rgb565(255, 0, 0));
    
    // Calculate yoke position based on ship's Y-axis acceleration
    float yokeOffset = 0;
    
    // Use ship's Y velocity as the primary input for yoke position
    // Positive velocity (moving down) = yoke deflects down (positive offset)
    // Negative velocity (moving up)   = yoke deflects up (negative offset)
    // Zero     velocity (not moving)  = yoke stays centered
    yokeOffset = ship.vel.y * 8.0f;  // Scale factor to make deflection visible
    
    // Clamp yoke offset to reasonable range
    yokeOffset = max(-yokeHeight/2.0f + 2.0f, min(yokeHeight/2.0f - 2.0f, yokeOffset));
    
    // Calculate yoke indicator position
    int yokeIndicatorY = yokeCenterY + (int)yokeOffset;
    
    // Ensure yoke indicator stays within bounds
    yokeIndicatorY = max(yokeTopY + 1, min(yokeBottomY - 1, yokeIndicatorY));
    
    // Draw yoke position indicator based on acceleration/movement
    uint16_t yokeColor;
    float absVelocity = abs(ship.vel.y);
    
    if (absVelocity < 0.2f) {
        yokeColor = engine->rgb565(0, 255, 0);        // Green for minimal movement (centered)
        } else if (absVelocity < 1.0f) {
            yokeColor = engine->rgb565(255, 255, 0);      // Yellow for moderate acceleration
        } else {
            yokeColor = engine->rgb565(255, 100, 0);      // Orange for high acceleration
        }
    
    // Draw the yoke indicator as a small horizontal bar
    engine->fillRect(STATUS_SCREEN, yokeX, yokeIndicatorY - 1, 4, 3, yokeColor);
    
    // Add yoke labels
    engine->drawText(STATUS_SCREEN, yokeX - 8, yokeTopY - 8, "YOKE", ST7735Colors::WHITE, 1);
    
    // Show ship velocity value instead of yoke offset
    sprintf(buffer, "%.1f", ship.vel.y);
    engine->drawText(STATUS_SCREEN, yokeX + 6, yokeCenterY - 3, buffer, ST7735Colors::WHITE, 1);
    
    // Performance info
    sprintf(buffer, "FPS: %.1f", engine->getPerformanceStats().averageFPS);
    engine->drawText(STATUS_SCREEN, 90, 10, buffer, ST7735Colors::WHITE, 1);
    
    // Game status
    if (gameOver) {
        engine->drawText(STATUS_SCREEN, 90, 45, "GAME OVER", 0xF800, 1);  // Explicit red
        } else {
            engine->drawText(STATUS_SCREEN, 90, 45, "PLAYING", 0x07E0, 1);    // Explicit green
        }
    }
};

// Global game instance
ST7735DualEngine engine;
SpaceDefender* game = nullptr;

void setup() {
    Serial.begin(115200);
    Serial.println("=== SpaceDefender v1.2 Enhanced with NeoPixel ===");
    
    // Initialize NeoPixel
    strip.begin();
    strip.setBrightness(NEOPIXEL_BRIGHTNESS);  // Set global brightness
    strip.clear();
    strip.show();
    Serial.printf("NeoPixel initialized on GPIO %d (Brightness: %d/255)\n", NEOPIXEL_PIN, NEOPIXEL_BRIGHTNESS);
    
    // Initialize the dual display engine
    auto result = engine.begin();
    if (!result.success) {
        Serial.println("Failed to initialize displays!");
        // Error indication on NeoPixel
        strip.setPixelColor(0, strip.Color(NEOPIXEL_BRIGHTNESS, 0, 0));
        strip.show();
        while (1) delay(100);
    }
    
    Serial.printf("Displays initialized - SPI: %u MHz\n", result.spiFrequency / 1000000);
    Serial.printf("DMA: %s, Dual-Core: %s\n", 
                  result.dmaEnabled ? "Yes" : "No",
                  result.dualCoreEnabled ? "Yes" : "No");
    
    // Startup sequence on NeoPixel
    for (int i = 0; i < 3; i++) {
        strip.setPixelColor(0, strip.Color(0, NEOPIXEL_BRIGHTNESS, 0));
        strip.show();
        delay(200);
        strip.setPixelColor(0, strip.Color(0, 0, 0));
        strip.show();
        delay(200);
    }
    
    // Create and start game
    game = new SpaceDefender(&engine);
    
    Serial.println("Game started! Enhanced AI with NeoPixel feedback...");
    Serial.println("NeoPixel Effects:");
    Serial.println("- Green: Safe flight");
    Serial.println("- Yellow: Path blocked (low threat)");
    Serial.println("- Orange: Medium threat");
    Serial.println("- Red: High threat/damage");
    Serial.println("- White flash: Invulnerable/level up");
    Serial.println("- Cyan flash: Shooting");
    Serial.println("- Rainbow: Game Over");
}

void loop() {
    if (game) {
        game->update();
    }
    
    // Small delay to prevent watchdog issues
    delay(1);
}

More examples are on their way.
I will also be working on a full tutorial on how to use the header file in thorough detail in a separate topic.

Have a nice week ahead.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.