diff --git a/components/doomgeneric/CMakeLists.txt b/components/doomgeneric/CMakeLists.txt
index 2745547f3325255604e87036e42311249cc88c03..2e31d9dd6b1384ebdeed35fe03c358f51a485ece 100644
--- a/components/doomgeneric/CMakeLists.txt
+++ b/components/doomgeneric/CMakeLists.txt
@@ -1,5 +1,6 @@
 idf_component_register(
     SRCS
+        doomgeneric/doomgeneric_badge23.c
         doomgeneric/am_map.c
         doomgeneric/d_event.c
         doomgeneric/d_items.c
@@ -83,4 +84,9 @@ idf_component_register(
         doomgeneric/w_main.c
         doomgeneric/w_wad.c
         doomgeneric/z_zone.c
+    INCLUDE_DIRS
+        .
+    REQUIRES
+        gc9a01
+        spi_flash
 )
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 08d677031a1ef302b26334ae10829fa26ecc4d15..17515b8bb0002b05729a9a1abca59bcfcc840414 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,193 +1,46 @@
-# This copies micropython/ports/esp32/CMakeLists.txt but modifies it to remove
-# the concept of 'boards' as it's not conducive to a sensible
-# directory/project structure for our badge project:
-#
-# 1. We don't want to have stuff deeply inside micropython/ports/esp/boards.
-#    Ideally, the micropython directory will be a submodule that's tracking
-#    upstream without any modifications.
-# 2. We don't want to have to call cmake with -DMICROPY_BOARD. Calling plain
-#    `cmake` or `idf.py` should just work and do the thing we want.
-# 3. We don't target multiple boards anyways, so all that board functionality
-#    is just dead weight.
-
-set(MICROPY_DIR "${PROJECT_DIR}/micropython")
-set(MICROPY_PORT_DIR "${MICROPY_DIR}/ports/esp32")
-set(MICROPY_QSTRDEFS_PORT "${MICROPY_PORT_DIR}/qstrdefsport.h")
-
-include("${MICROPY_DIR}/py/py.cmake")
-
-set(USER_C_MODULES "${PROJECT_DIR}/usermodule/micropython.cmake")
-
-if(NOT CMAKE_BUILD_EARLY_EXPANSION)
-    # Enable extmod components that will be configured by extmod.cmake.
-    # A board may also have enabled additional components.
-    set(MICROPY_PY_BTREE ON)
-
-    include(${MICROPY_DIR}/py/usermod.cmake)
-    include(${MICROPY_DIR}/extmod/extmod.cmake)
-endif()
-
-set(MICROPY_SOURCE_SHARED
-    ${MICROPY_DIR}/shared/readline/readline.c
-    ${MICROPY_DIR}/shared/netutils/netutils.c
-    ${MICROPY_DIR}/shared/timeutils/timeutils.c
-    ${MICROPY_DIR}/shared/runtime/interrupt_char.c
-    ${MICROPY_DIR}/shared/runtime/stdout_helpers.c
-    ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c
-    ${MICROPY_DIR}/shared/runtime/pyexec.c
-)
-
-set(MICROPY_SOURCE_LIB
-    ${MICROPY_DIR}/lib/littlefs/lfs1.c
-    ${MICROPY_DIR}/lib/littlefs/lfs1_util.c
-    ${MICROPY_DIR}/lib/littlefs/lfs2.c
-    ${MICROPY_DIR}/lib/littlefs/lfs2_util.c
-    ${MICROPY_DIR}/lib/mbedtls_errors/mp_mbedtls_errors.c
-    ${MICROPY_DIR}/lib/oofatfs/ff.c
-    ${MICROPY_DIR}/lib/oofatfs/ffunicode.c
-)
-
-set(MICROPY_SOURCE_DRIVERS
-    ${MICROPY_DIR}/drivers/bus/softspi.c
-    ${MICROPY_DIR}/drivers/dht/dht.c
-)
-
-set(MICROPY_SOURCE_PORT
-    ${MICROPY_PORT_DIR}/main.c
-    ${MICROPY_PORT_DIR}/uart.c
-    ${MICROPY_PORT_DIR}/usb.c
-    ${MICROPY_PORT_DIR}/usb_serial_jtag.c
-    ${MICROPY_PORT_DIR}/gccollect.c
-    ${MICROPY_PORT_DIR}/mphalport.c
-    ${MICROPY_PORT_DIR}/fatfs_port.c
-    ${MICROPY_PORT_DIR}/help.c
-    ${MICROPY_PORT_DIR}/modutime.c
-    ${MICROPY_PORT_DIR}/machine_bitstream.c
-    ${MICROPY_PORT_DIR}/machine_timer.c
-    ${MICROPY_PORT_DIR}/machine_pin.c
-    ${MICROPY_PORT_DIR}/machine_touchpad.c
-    ${MICROPY_PORT_DIR}/machine_adc.c
-    ${MICROPY_PORT_DIR}/machine_adcblock.c
-    ${MICROPY_PORT_DIR}/machine_dac.c
-    ${MICROPY_PORT_DIR}/machine_i2c.c
-    ${MICROPY_PORT_DIR}/machine_i2s.c
-    ${MICROPY_PORT_DIR}/machine_uart.c
-    ${MICROPY_PORT_DIR}/modmachine.c
-    ${MICROPY_PORT_DIR}/network_common.c
-    ${MICROPY_PORT_DIR}/network_lan.c
-    ${MICROPY_PORT_DIR}/network_ppp.c
-    ${MICROPY_PORT_DIR}/network_wlan.c
-    ${MICROPY_PORT_DIR}/mpnimbleport.c
-    ${MICROPY_PORT_DIR}/modsocket.c
-    ${MICROPY_PORT_DIR}/modesp.c
-    ${MICROPY_PORT_DIR}/esp32_nvs.c
-    ${MICROPY_PORT_DIR}/esp32_partition.c
-    ${MICROPY_PORT_DIR}/esp32_rmt.c
-    ${MICROPY_PORT_DIR}/esp32_ulp.c
-    ${MICROPY_PORT_DIR}/modesp32.c
-    ${MICROPY_PORT_DIR}/machine_hw_spi.c
-    ${MICROPY_PORT_DIR}/machine_wdt.c
-    ${MICROPY_PORT_DIR}/mpthreadport.c
-    ${MICROPY_PORT_DIR}/machine_rtc.c
-    ${MICROPY_PORT_DIR}/machine_sdcard.c
-)
-
-set(MICROPY_SOURCE_QSTR
-    "${MICROPY_SOURCE_PY}"
-    "${MICROPY_SOURCE_EXTMOD}"
-    "${MICROPY_SOURCE_USERMOD}"
-    "${MICROPY_SOURCE_SHARED}"
-    "${MICROPY_SOURCE_LIB}"
-    "${MICROPY_SOURCE_PORT}"
-)
-
 set(IDF_COMPONENTS
-    app_update
-    badge23
-    badge23_hwconfig
+    badge23_bsp
     bootloader_support
-    bt
+    doomgeneric
     driver
-    esp_adc_cal
     esp_common
-    esp_eth
     esp_event
     esp_hw_support
     esp_ringbuf
     esp_rom
     esp_pm
-    esp_wifi
     esp_system
     esp_timer
-    esp_netif
     esp32s3
     freertos
     hal
     heap
     log
-    lwip
-    mbedtls
-    mdns
     newlib
     nvs_flash
     sdmmc
+    spiffs
     soc
     spi_flash
-    tcpip_adapter
     ulp
     vfs
     xtensa
 )
 
-set(MICROPY_FROZEN_MANIFEST ${PROJECT_DIR}/manifest.py)
-set(MICROPY_CROSS_FLAGS -march=xtensawin)
-
 idf_component_register(
     SRCS
-        ${MICROPY_SOURCE_PY}
-        ${MICROPY_SOURCE_EXTMOD}
-        ${MICROPY_SOURCE_USERMOD}
-        ${MICROPY_SOURCE_SHARED}
-        ${MICROPY_SOURCE_LIB}
-        ${MICROPY_SOURCE_DRIVERS}
-        ${MICROPY_SOURCE_PORT}
+        main.c
     INCLUDE_DIRS
-        # Actual micropython include paths.
-        ${MICROPY_INC_CORE}
-        ${MICROPY_INC_USERMOD}
-        ${MICROPY_PORT_DIR}
-        # Needed for genhdr/* which for some reason is placed directly into
-        # the output dir. Gross.
-        ${CMAKE_BINARY_DIR}
-        # Needed for include/mpconfigboard.h.
         "include"
     REQUIRES
         ${IDF_COMPONENTS}
 )
 
-# micropython machinery uses this to inspect include directories and compile
-# definitions. We're effectively looping back MICROPY_{SOURCE/INC} through
-# COMPONENT_TARGET (as generated by idf_component_register) back into
-# micropython.
-set(MICROPY_TARGET ${COMPONENT_TARGET})
-
-# Feed information from IDF component targets into micropy qstr machinery.
-foreach(comp ${IDF_COMPONENTS})
-    micropy_gather_target_properties(__idf_${comp})
-endforeach()
-
-# micropython/qstr wants to intern strings from nimble private headers. Tell
-# them how to find them.
-target_include_directories(${COMPONENT_TARGET} PUBLIC
-    "${IDF_PATH}/components/bt/host/nimble/nimble"
-)
-
-target_compile_definitions(${COMPONENT_TARGET} PUBLIC
-    # micropython includes FatFS which is configurable via its own
-    # configuration file, which is expected to be dfined as FFCONF_H.
-    # micropython also ships such a configuration file. I don't know why
-    # micropython doesn't just hardcode this...
-    FFCONF_H=\"${MICROPY_OOFATFS_DIR}/ffconf.h\"
-)
+#spiffs_create_partition_image(spiffs ../spiffs FLASH_IN_PROJECT)
+get_filename_component(image_path "${PROJECT_DIR}/doom1.wad" ABSOLUTE)
+idf_component_get_property(main_args esptool_py FLASH_ARGS)
+idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS)
+esptool_py_flash_target(wad-flash "${main_args}" "${sub_args}" ALWAYS_PLAINTEXT)
+esptool_py_flash_to_partition(wad-flash "wad" "${image_path}")
+#esptool_py_flash_to_partition(flash "wad" "${image_path}")
 
-include("${MICROPY_DIR}/py/mkrules.cmake")
diff --git a/main/main.c b/main/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..6a329111a2c3e24e8ebfd216846f48be25517003
--- /dev/null
+++ b/main/main.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "esp_log.h"
+#include "esp_err.h"
+#include "driver/i2c.h"
+
+#include "badge23_portexpander.h"
+
+#include "doomgeneric/doomgeneric.h"
+
+
+static const char *TAG = "main";
+
+#define I2C_MASTER_NUM              0                          /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
+#define I2C_MASTER_FREQ_HZ          400000                     /*!< I2C master clock frequency */
+#define I2C_MASTER_TX_BUF_DISABLE   0                          /*!< I2C master doesn't need buffer */
+#define I2C_MASTER_RX_BUF_DISABLE   0                          /*!< I2C master doesn't need buffer */
+#define CONFIG_I2C_MASTER_SDA 2
+#define CONFIG_I2C_MASTER_SCL 1
+
+static esp_err_t i2c_master_init(void)
+{
+    int i2c_master_port = I2C_MASTER_NUM;
+
+    i2c_config_t conf = {
+        .mode = I2C_MODE_MASTER,
+        .sda_io_num = CONFIG_I2C_MASTER_SDA,
+        .scl_io_num = CONFIG_I2C_MASTER_SCL,
+        .sda_pullup_en = GPIO_PULLUP_ENABLE,
+        .scl_pullup_en = GPIO_PULLUP_ENABLE,
+        .master.clk_speed = I2C_MASTER_FREQ_HZ,
+    };
+
+    i2c_param_config(i2c_master_port, &conf);
+
+    return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
+}
+
+void app_main(void) {
+    i2c_master_init();
+    portexpander_init();
+    char *args[] = {
+        "doomgeneric",
+        "-iwad", "doom1.wad",
+        NULL,
+    };
+    doomgeneric_Create(3, args);
+    for (;;) {
+        doomgeneric_Tick();
+    }
+}
diff --git a/sdkconfig.p3 b/sdkconfig.p3
index 1680de7e8e6690eb6531b519983cf998ca72beb2..1e0f323c5d0a7d0e6246fb143eac38b89ccf099d 100644
--- a/sdkconfig.p3
+++ b/sdkconfig.p3
@@ -2,28 +2,42 @@ CONFIG_APP_EXCLUDE_PROJECT_VER_VAR=y
 CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y
 CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
 CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
-CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
 CONFIG_ESPTOOLPY_AFTER_NORESET=y
 CONFIG_PARTITION_TABLE_CUSTOM=y
-CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="micropython/ports/esp32/partitions-8MiB.csv"
 CONFIG_COMPILER_OPTIMIZATION_PERF=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
-CONFIG_BT_ENABLED=y
-CONFIG_BT_NIMBLE_ENABLED=y
-CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4
-CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y
+CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
+CONFIG_ESP32S3_SPIRAM_SUPPORT=y
+CONFIG_SPIRAM_MODE_OCT=y
+CONFIG_SPIRAM_TYPE_ESPPSRAM64=y
+CONFIG_SPIRAM_SPEED_80M=y
+CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
+CONFIG_ESP32S3_NO_BLOBS=y
+# CONFIG_ETH_USE_SPI_ETHERNET is not set
+# CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS is not set
+# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set
 CONFIG_PM_ENABLE=y
 CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
 # CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 is not set
 # CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 is not set
+CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10
+CONFIG_ESP32_WIFI_RX_BA_WIN=6
 # CONFIG_ESP32_WIFI_IRAM_OPT is not set
 # CONFIG_ESP32_WIFI_RX_IRAM_OPT is not set
+# CONFIG_ESP_WIFI_SOFTAP_SUPPORT is not set
 CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
-CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y
 CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y
 CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
-CONFIG_LOG_DEFAULT_LEVEL_ERROR=y
-CONFIG_LWIP_PPP_SUPPORT=y
-CONFIG_LWIP_PPP_PAP_SUPPORT=y
-CONFIG_LWIP_PPP_CHAP_SUPPORT=y
+# CONFIG_LWIP_IPV6 is not set
+CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
+CONFIG_MBEDTLS_ECP_RESTARTABLE=y
+CONFIG_MBEDTLS_CMAC_C=y
+# CONFIG_MQTT_PROTOCOL_311 is not set
+# CONFIG_MQTT_TRANSPORT_SSL is not set
+# CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE is not set
+# CONFIG_SPIFFS_CACHE_WR is not set
+CONFIG_SPIFFS_PAGE_SIZE=1024
+# CONFIG_WS_TRANSPORT is not set
+# CONFIG_WPA_MBEDTLS_CRYPTO is not set
 CONFIG_BADGE23_HW_GEN_P3=y
diff --git a/sdkconfig.p4 b/sdkconfig.p4
index 3244f8307c78e2e3e2ed23ce8d138ec81a41df61..1e0f323c5d0a7d0e6246fb143eac38b89ccf099d 100644
--- a/sdkconfig.p4
+++ b/sdkconfig.p4
@@ -2,28 +2,42 @@ CONFIG_APP_EXCLUDE_PROJECT_VER_VAR=y
 CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y
 CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
 CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
-CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
 CONFIG_ESPTOOLPY_AFTER_NORESET=y
 CONFIG_PARTITION_TABLE_CUSTOM=y
-CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="micropython/ports/esp32/partitions-8MiB.csv"
 CONFIG_COMPILER_OPTIMIZATION_PERF=y
 CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
-CONFIG_BT_ENABLED=y
-CONFIG_BT_NIMBLE_ENABLED=y
-CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4
-CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y
+CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
+CONFIG_ESP32S3_SPIRAM_SUPPORT=y
+CONFIG_SPIRAM_MODE_OCT=y
+CONFIG_SPIRAM_TYPE_ESPPSRAM64=y
+CONFIG_SPIRAM_SPEED_80M=y
+CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
+CONFIG_ESP32S3_NO_BLOBS=y
+# CONFIG_ETH_USE_SPI_ETHERNET is not set
+# CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS is not set
+# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set
 CONFIG_PM_ENABLE=y
 CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
 # CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 is not set
 # CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 is not set
+CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10
+CONFIG_ESP32_WIFI_RX_BA_WIN=6
 # CONFIG_ESP32_WIFI_IRAM_OPT is not set
 # CONFIG_ESP32_WIFI_RX_IRAM_OPT is not set
+# CONFIG_ESP_WIFI_SOFTAP_SUPPORT is not set
 CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
-CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y
 CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y
 CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
-CONFIG_LOG_DEFAULT_LEVEL_ERROR=y
-CONFIG_LWIP_PPP_SUPPORT=y
-CONFIG_LWIP_PPP_PAP_SUPPORT=y
-CONFIG_LWIP_PPP_CHAP_SUPPORT=y
-CONFIG_BADGE23_HW_GEN_P4=y
\ No newline at end of file
+# CONFIG_LWIP_IPV6 is not set
+CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
+CONFIG_MBEDTLS_ECP_RESTARTABLE=y
+CONFIG_MBEDTLS_CMAC_C=y
+# CONFIG_MQTT_PROTOCOL_311 is not set
+# CONFIG_MQTT_TRANSPORT_SSL is not set
+# CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE is not set
+# CONFIG_SPIFFS_CACHE_WR is not set
+CONFIG_SPIFFS_PAGE_SIZE=1024
+# CONFIG_WS_TRANSPORT is not set
+# CONFIG_WPA_MBEDTLS_CRYPTO is not set
+CONFIG_BADGE23_HW_GEN_P3=y