Porting Another World to ESP32: 50 FPS Vector Graphics, Capacitive Touch, and Bluetooth Audio on a $5 Microcontroller

In 1991, French visionary Éric Chahi released Another World (Out of This World in North America)—a cinematic tour de force that revolutionized video game storytelling. Built entirely on an avant-garde vector polygon engine and a custom 64-thread virtual machine, the game squeezed smooth cinematic cutscenes onto 7.14 MHz Amiga 500 computers with just 512 KB of RAM.

For a long time, I wondered: Could we run the complete Another World engine natively on a bare-metal ESP32 microcontroller with fluid 50 FPS rendering, capacitive touch controls on a custom PCB, and Bluetooth audio streaming?

Here is the story of how anotheresp32 was engineered, the memory and hardware obstacles overcome, and how you can run it on your own board.

Another World ESP32 Running with Custom Capacitive Touch Gamepad


The Hardware: LilyGO TTGO T-Display & “TTECH Game Pad”

The project targets the popular LilyGO TTGO T-Display:

  • MCU: Espressif ESP32-D0WDQ6 (Dual-Core Xtensa LX6 @ 240 MHz, ~320 KB SRAM, 4 MB Flash)
  • Display: 1.14” ST7789 IPS TFT (240x135 pixels, 16-bit RGB565 over high-speed SPI)
  • Cost: ~$5–$8

To make it a true standalone portable console, I etched and milled a custom PCB gamepad: the TTECH ESP32 Game Pad.

                        +-------------------+
                        |    [USB-C PORT]   |
                        |                   |
                        |   TTGO T-Display  |
                        |   [240x135 IPS]   |
                        +-------------------+
                              |   |   |
                  +-----------+---+---+-----------+
                  |  TTECH ESP32 Capacitive Pad   |
                  |                               |
                  |       (UP: GPIO 15)           |
                  |             |                 |
                  | (LEFT: 2) --+-- (RIGHT: 13)   |  (ACTION: 27)
                  |             |                 |
                  |      (DOWN: GPIO 32)          |  (JUMP: 33)
                  |                               |
                  |       [START: GPIO 12]        |
                  +-------------------------------+

Instead of using clicky tactile buttons, the board features 7 bare-copper capacitive touch pads connected directly to the ESP32’s internal touch sensor peripherals (Touch 2 through Touch 9).


Technical Obstacle 1: The Boot-Strapping Pin Trap

During PCB design, GPIO 12 was originally routed to DOWN. However, GPIO 12 is MTDI—a critical ESP32 strapping pin that sets the flash voltage regulator to 1.8V if pulled high at power-on! If a player touches the pad or trace resistance pulls GPIO 12 high during boot, the flash voltage drops and the chip enters an unrecoverable boot loop (flash read err, 1000).

The Solution: We remapped the default movement controls strictly to safe, non-strapping pins:

  • LEFT: GPIO 2 (Touch 2)
  • UP: GPIO 15 (Touch 3)
  • RIGHT: GPIO 13 (Touch 4)
  • DOWN: GPIO 32 (Touch 9) — Safe!
  • ACTION: GPIO 27 (Touch 7)
  • TOUCH JUMP: GPIO 33 (Touch 8)
  • START / PAUSE (Optional): GPIO 12 (Touch 5)

At boot, the driver executes a 100ms baseline calibration cycle on all 7 channels, computing a resting threshold for instantaneous, jitter-free touch response.


Technical Obstacle 2: The 160 KB SRAM Puzzle & 65 KB Polygon Tables

The native Another World engine renders at 320x200 (4-bit indexed color, 16 colors). Each frame page occupies 32,000 bytes.

The original VM engine relies on 4 visual pages:

  • Page 0: Display page (front buffer)
  • Page 1: Back buffer (drawing canvas)
  • Page 2: Background persistence buffer
  • Page 3: Composition / sprite layer

The Problem:

4 x 32,000 bytes = 128,000 bytes (128 KB). On an ESP32 with ~160 KB of free application heap, reserving 128 KB upfront left only ~32 KB of remaining heap. When Part 16001 loaded its 65,156-byte cinematic polygon table (res 25), malloc() failed, leaving Lester standing on a pitch-black screen.

The Engineering Fix:

By analyzing the VM opcode stream, we found that Another World rarely needs 4 distinct physical buffers simultaneously; pages 2 and 3 are largely composition scratchpads. We re-architected the video subsystem to allocate 2 high-speed physical framebuffers (64 KB total) with page aliasing.

This dropped baseline video SRAM consumption from 128 KB to 64 KB, freeing up ~160 KB of contiguous heap. Now, massive 65 KB polygon tables, sound banks, and Amiga audio modules allocate instantly without needing external PSRAM!


Technical Obstacle 3: 50 FPS Vector Polygon Rasterization

Another World does not draw bitmap sprites—it renders dynamic 2D vector polygons grouped in hierarchical transformation trees.

       [Raw Shape Bytecode]
                 │
                 ▼
    [Vertex Transform & Zoom]
                 │
                 ▼
     [Left & Right Edge Walkers]
                 │
                 ▼
    [4-Bit Nibble Packed Scanlines]
                 │
                 ▼ (SPI DMA via LovyanGFX)
    [ST7789 240x135 IPS TFT @ 50 Hz]

To achieve 50 FPS on the ST7789:

  1. Integer Fixed-Point Edge Walking: All polygon bounding box calculations and slope interpolations use fixed-point arithmetic.
  2. Scanline Nibble Packing: The 4-bit indexed page buffers pack two pixels per byte, allowing fast horizontal word fills.
  3. SPI DMA Push via LovyanGFX: Framebuffer updates are converted to 16-bit RGB565 and pushed across hardware SPI at 80 MHz using LovyanGFX’s DMA bus.

The engine supports two scaling modes in hw_config.h:

  • Letterbox Mode (Default): Authentic 16:10 aspect ratio (216x135) centered with clean black pillarboxes.
  • Stretched Mode: Full 240x135 non-uniform screen stretch.

Technical Obstacle 4: Audio Subsystem (8-Bit DAC & Bluetooth A2DP)

Another World’s iconic audio was composed on Amiga 4-channel MODs and digitized 8-bit PCM samples (thunder cracks, elevator whirs, alien beast growls).

We implemented a dual audio subsystem:

  1. Zero-Overhead 8-bit DAC Output (Default):
    • Channels mixed in real-time at 22,050 Hz and streamed via DMA to GPIO 25 (DAC1) and GPIO 26 (DAC2).
  2. Wireless Bluetooth A2DP Audio:
    • Mixed audio is upsampled to 44.1 kHz stereo and streamed to Bluetooth speakers or headphones.
    • Includes direct hardware MAC auto-pairing in include/bt_mac.h for instant connection without scan delays.

Technical Obstacle 5: Commercial Asset Policy & 1-Command Demo Loader

A major priority was ensuring the repository remains 100% legal, open-source, and free of DMCA risks. Commercial game banks (bank01–bank0d, memlist.bin) must never be checked into Git.

To give anyone an instant, out-of-the-box playable experience without hunting for floppy disks, we built the --demo auto-fetcher into tools/pack_assets.py:

# 1. Clone the clean C++ engine repo
git clone https://github.com/tonym128/anotheresp32.git
cd anotheresp32

# 2. Automatically download the 1991 public shareware demo archive
python3 tools/pack_assets.py --demo

# 3. Flash SPIFFS filesystem and firmware to ESP32!
pio run -t uploadfs && pio run -t upload

The engine automatically detects whether you’re running the 1991 Shareware Demo (smoothly looping after the wilderness chase) or the Full 13-Level Game (playing all the way to the fortress palace ending).


Source Code & Hardware Details

The entire codebase, circuit diagrams, test suite, and CI workflows are open-source under the MIT License on GitHub:

👉 github.com/tonym128/anotheresp32

Quick Requirements:

  • Board: LilyGO TTGO T-Display ESP32
  • Toolchain: PlatformIO (VS Code or CLI)
  • Assets: Run python3 tools/pack_assets.py --demo or pack your own DOS/Amiga files!

Acknowledgments & Attributions

This project was made possible thanks to the work and reverse-engineering of several pioneers in retro computing:


Another World is © Éric Chahi and Delphine Software / Dotemu. This project is an independent open-source virtual machine engine created for retro computing research and preservation.

Updated: