Breadboard
docs
Example project · Builds & projects

OLED dungeon (ESP32-S3 + joystick)

An original first-party monochrome raycasting game running as real ESP-IDF firmware on the emulated ESP32-S3. Choose Select, focus the canvas, and use WASD to move/turn plus Space to fire; or drag the physical joystick cap and click it without moving to fire. The fixed 20 Hz game loop samples real ADC1/GPIO input on G1, G2, and G10, renders a complete 128×128 page-major frame, and sends it over hardware I²C on G8/G9 to the real 1.5″ SH1107 model. Split Keep opens with a visible horned warden, then branches through auto-opening doors and north/south routes; clear five wardens to unlock the exit while hunting health and ammunition. Muzzle flash, hit and damage feedback, an objective HUD, win/death screens, and deterministic restart all run on-device. It needs no account, backend, internet, colour display, or PSRAM; the Code tab shows the complete MIT source and portable host harness read-only.

The OLED dungeon (ESP32-S3 + joystick) circuit as rendered by the simulator

What's on the bench

  • Battery
  • ESP32-S3
  • Joystick
  • OLED 1.5″ 128×128
  • Resistor

How it's wired

  • Battery · posESP32-S3 · vin
  • Battery · negESP32-S3 · gnd
  • ESP32-S3 · 3v3OLED 1.5″ 128×128 · vcc
  • ESP32-S3 · gnd2OLED 1.5″ 128×128 · gnd
  • ESP32-S3 · g8OLED 1.5″ 128×128 · sda
  • ESP32-S3 · g9OLED 1.5″ 128×128 · scl
  • ESP32-S3 · 3v3Joystick · vcc
  • ESP32-S3 · gnd2Joystick · gnd
  • ESP32-S3 · g1Joystick · vrx
  • ESP32-S3 · g2Joystick · vry
  • ESP32-S3 · g10Joystick · sw
  • ESP32-S3 · 3v3Resistor · a
  • Resistor · bJoystick · sw

The code

The exact ESP-IDF v5.3.5 project the bundled firmware was compiled from, shown read-only in the simulator’s Code tab.

README.md

# OLED dungeon — original ESP32-S3 raycasting game

This is the complete first-party source for Breadboard's
**OLED dungeon (ESP32-S3 + joystick)** example (`esp32s3-oled-fps`). It is a
small monochrome first-person game written from scratch for BRE-209. It is not
a port, compatibility layer, or adaptation of Doom or another game.

## Original content specification

The single 16×16 level is called **Split Keep**. The player begins at cell
`(2, 7)`, facing an immediately visible courtyard warden, and the exit is at
`(13, 7)`. A hollow central keep separates them. Its west and east doors open
automatically at close range; a north breach and a south breach provide two
longer routes. Five original horned wardens, two ammunition bundles, and two
health markers are placed only on flood-fill-reachable floor cells. The exit
stays locked until all five wardens are defeated. Wall ids 14 use four ordered
dither densities rather than image textures. All title, HUD, enemy, pickup,
weapon, and end-screen graphics are hand-authored geometric primitives or the
project's own 3×5 block glyphs. There is no audio.

The portable simulation uses signed Q16.16 positions, an unsigned 256-step
turn angle, a generated Q15 sine table, a fixed 20 Hz tick, and a fixed-seed
unsigned LCG used only to choose enemy axis ordering. Rendering casts one
integer DDA ray per 128-pixel OLED column directly into the monochrome
SH1107's page-major 2,048-byte framebuffer. Each frame is sent over 1 MHz
hardware I²C as one valid Co-framed transaction per page. It has no color
framebuffer and uses no floating point at runtime.

Game states are title/instructions, play, win, and death. The joystick's Y
axis moves, X turns, and its cap starts, fires, or restarts; with the Select
tool active, the app also maps WASD/arrows and Space to the same physical input
path while the canvas is focused. Gameplay includes collision, auto-opening
doors, a hitscan repeater, projectile-free contact wardens, health/ammunition
pickups, a two-line health/ammo/kill/gate HUD, muzzle flash, hit and damage
feedback, and deterministic restart.

## Licensing boundary and inventory

Every file in this project, including the level, renderer, glyphs, generated
mathematical tables, tests, and documentation, is original Breadboard code and
content under the adjacent MIT `LICENSE`. No third-party game code, maps, art,
audio, names, or story content was consulted, copied, translated, traced, or
adapted. In particular, this project contains no Doom-compatible code or data.

The ESP-IDF platform build links Espressif's Apache-2.0 components. The exact
redistributed binary's licenses, NOTICE, SPDX SBOM, component warnings, input
hashes, and reproducibility evidence live beside `game.bin` under
`packages/app/public/firmware/esp32s3-oled-fps/`; they are release artifacts,
not source dependencies of the portable core.

## Portable host verification

After loading the repository toolchain, configure with any CMake 3.16+ and a
C11 compiler:

```sh
cmake -S firmware-src/esp32s3-oled-fps/host -B /tmp/oled-fps-host
cmake --build /tmp/oled-fps-host
ctest --test-dir /tmp/oled-fps-host --output-on-failure
node firmware-src/esp32s3-oled-fps/scripts/generate_tables.mjs
```

The harness compiles with strict warnings and enables AddressSanitizer plus
UndefinedBehaviorSanitizer on Linux/GNU or non-Apple Clang by default.
AppleClang 17's AddressSanitizer runtime can hang before `main` while reserving
its dynamic shadow on macOS, so the canonical CMake path selects
UndefinedBehaviorSanitizer alone on AppleClang. A network-disabled Debian
13.5/GCC 14.2.0 container run remains the release
AddressSanitizer-plus-UndefinedBehaviorSanitizer gate and is retained in the
release manifest.
The harness proves level
reachability and all four wall materials; collision; doors; pickups; hits,
enemy removal, and depleted ammunition; win/death/restart; extreme map-edge
rays; and two identical runs of a 360-tick trace. The reviewed golden result
is:

```text
state=6f7c3444 frame=2b999c0d trace=00187486
```

`scripts/generate_tables.mjs` checks synchronization by default and writes
only with `--write`.

## Firmware build

The release target is ESP-IDF v5.3.5 at commit
`0c45a102a96e82000f145b786a20de229b9f8557`, target `esp32s3`, 4 MB flash,
and no PSRAM. With that exact checkout exported as `IDF_PATH`, each release
candidate was built from a distinct clean, real (non-symlinked) directory:

```sh
source "$IDF_PATH/export.sh" >/dev/null
idf.py -C firmware-src/esp32s3-oled-fps -B "$BUILD_ROOT/build" \
  -D SDKCONFIG="$BUILD_ROOT/sdkconfig" \
  -D SDKCONFIG_DEFAULTS="$REPO_ROOT/firmware-src/esp32s3-oled-fps/sdkconfig.defaults" \
  -D IDF_TARGET=esp32s3 build
```

The release-time verification used clean roots `.bre209-review-a`
and `.bre209-review-b`. Their application, ELF, generated sdkconfig,
link map, and canonical SPDX outputs were byte-identical; path-bearing raw SPDX
and project-description files are recorded separately. The committed manifest
is an attestation of those release builds, while ordinary app CI re-hashes the
committed evidence rather than rebuilding ESP-IDF. The shipped application
is 212,336 bytes with SHA-256
`1c654047cb03ce4e0cef41b7a396bdbe59dfb2de48d21d4a2da9fc7894651728`.
The release manifest beside it records every output hash and tool version.

## Display-controller reference

The SH1107 initialization follows Sino Wealth's SH1107/SH1107G v2.1 controller
datasheet: command `0x20` selects page addressing, while `0xAD, 0x8B` enables
the internal DC-DC converter when the display is turned on. Reference:
<https://www.laskakit.cz/user/related_files/sh1107_v2-1.pdf>, command sections
3 and 10 (pages 24 and 32 in the document pagination).

## Browser-emulator performance evidence

These are emulator measurements, not real-hardware claims. They were collected
from a production `pnpm build` of the local BRE-209 release candidate, using
the exact shipped application above and production worker bundle SHA-256
`5bdd6eecaa5772510053305c41c3db1ec264e076d875f9d671411a230710a08f`.
The reference host was a MacBook Air (Mac17,4), Apple M5 with 10 cores and
32 GB RAM, running arm64 macOS 27.0 build 26A5421a and Chrome
151.0.7922.174. The viewport was 1920×1080 at the example's normal 110% canvas
zoom. The exact worktree's production Vite preview was isolated on port 4174;
unrelated local servers remained idle and were not opened by the benchmark.

Three independent 60-second wall-time active-play runs drove production
WASD/Space input and observed production serial markers without DEV hooks.
Separate exact-artifact trials measured a short physical cap click from its
decisive pointer-up event to the first changed OLED-region screenshot:

| Run | Wall time | Frames | Achieved pacing | Rendered rate | Cap-to-OLED |
| --- | ---: | ---: | ---: | ---: | ---: |
| 1 | 60.054 s | 1,220 | 1.0157× | 20.315 fps | 88.5 ms |
| 2 | 60.020 s | 1,220 | 1.0163× | 20.327 fps | 102.3 ms |
| 3 | 60.227 s | 1,220 | 1.0128× | 20.257 fps | 76.6 ms |

Median pacing was 1.0157×, median rendered rate was 20.315 fps, and median
input-to-visible-frame latency was 88.5 ms. Latency is the midpoint of the
interval between the last completed unchanged OLED screenshot and the first
completed changed screenshot; conservative completion-time upper bounds were
114.1, 135.6, and 101.5 ms. This includes browser screenshot transfer overhead.
An earlier 400 kHz I²C candidate missed the normal-response gate with a
165.2 ms median; the accepted 1 MHz page-framed transport is the measured fix.
The three accepted throughput runs produced 27, 21, and 58 distinct frame CRCs,
reached play/death/restart states, and had no reset, watchdog, long stall, or
browser console error.

The accepted five-minute active-play soak ran for 300.100 wall seconds,
advanced 6,020 frames, achieved 1.0030× pacing and 20.060 fps, and observed 28
distinct OLED CRCs through the deliberately bounded visible serial window.
It reached play/death/restart states under continuing movement and fire input.
There was no repeated boot marker, watchdog, stuck input, OLED freeze, or
browser console error. After its three-minute settling interval, forced-GC
measurements over the final two minutes changed the main renderer heap from
15,294,824 to 14,513,272 bytes (-781,552 bytes), while documents stayed at 1,
DOM nodes changed from 2,152 to 2,146, and event listeners changed from 633 to
349. This shows no continuing heap, DOM, or listener accumulation.

Finally, six interleaved 15-second headless traces in the order JIT off/on/on/
off/off/on used `Simulation.interact` for the same scripted cap and axis input.
All six produced the same 15 serial samples, three-event stream, final visual
state, frame CRC sequence, state CRC sequence, and final 2,048-byte framebuffer
SHA-256 `555282e04487bfaa8c458f90403f535bfe7aada59b0725c8f9df164ac809b00b`.

LICENSE

MIT License

Copyright (c) 2026 Murray Stott

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.

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)
# Restrict the graph to the game and its declared transitive dependencies.
# ESP-IDF's internal Wi-Fi/PHY/Ethernet master switches default on whenever
# those components enter the graph and cannot be overridden from sdkconfig, so
# component exclusion is the fail-closed control rather than an unused `n`.
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(oled-fps-s3)

idf_build_get_property(oled_fps_build_components BUILD_COMPONENTS)
set(oled_fps_forbidden_components
    bt
    esp_coex
    esp_eth
    esp_netif
    esp_netif_stack
    esp_phy
    esp_psram
    esp_wifi
    ieee802154
    lwip
    openthread
    wpa_supplicant
)
foreach(component IN LISTS oled_fps_forbidden_components)
    if(component IN_LIST oled_fps_build_components)
        message(FATAL_ERROR "radio/network component entered OLED FPS build: ${component}")
    endif()
endforeach()

sdkconfig.defaults

CONFIG_IDF_TARGET="esp32s3"

# Exact release geometry: 4 MB flash and one 1.5 MB factory app. The project
# CMake guard excludes the esp_psram component entirely.
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"

CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_COMPILER_OPTIMIZATION_PERF=y
CONFIG_ESP32S3_DATA_CACHE_32KB=y
CONFIG_FREERTOS_UNICORE=y

# This is a self-contained display/input game. The project CMake allowlist
# excludes and then rejects every radio/network component. Their Kconfig
# symbols are consequently absent rather than silently retaining IDF defaults.

# UART0 only. The default USB-Serial-JTAG secondary duplicates every byte in
# the emulator's merged serial stream.
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192

# Preserve normal watchdog protections. The 20 Hz loop yields every tick.
CONFIG_ESP_TASK_WDT_INIT=y

# Pin every app-descriptor input and remove path/time macros from the build.
CONFIG_APP_PROJECT_VER_FROM_CONFIG=y
CONFIG_APP_PROJECT_VER="bre209-1"
CONFIG_APP_REPRODUCIBLE_BUILD=y

sdkconfig.diagnostic

CONFIG_OLED_FPS_DIAGNOSTIC=y

partitions.csv

# Name,   Type, SubType, Offset,  Size
nvs,      data, nvs,     0x9000,  0x6000
factory,  app,  factory, 0x10000, 0x180000

main/CMakeLists.txt

idf_component_register(
    SRCS
        "app_main.c"
        "content.c"
        "display_sh1107.c"
        "game.c"
        "input.c"
        "render.c"
    INCLUDE_DIRS "."
    PRIV_REQUIRES driver esp_adc esp_driver_gpio esp_driver_uart vfs
)

main/Kconfig.projbuild

menu "OLED dungeon"

config OLED_FPS_DIAGNOSTIC
    bool "Run the ADC/GPIO/SH1107 diagnostic loop instead of the game"
    default n
    help
        Builds the Phase-2 platform probe. It samples the real ADC1/GPIO
        inputs, sends full 2048-byte check frames over I2C0, and prints one
        structured diagnostic line per second. Leave disabled for game.bin.

endmenu

main/app_main.c

/* ESP-IDF platform loop for the original BRE-209 OLED dungeon. MIT. */
#include <inttypes.h>
#include <stdio.h>
#include <string.h>

#include "driver/uart.h"
#include "driver/uart_vfs.h"
#include "esp_check.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "display_sh1107.h"
#include "game.h"
#include "input.h"
#include "render.h"
#include "sdkconfig.h"

#if !CONFIG_OLED_FPS_DIAGNOSTIC
static const char *mode_name(FpsMode mode)
{
    switch (mode) {
    case FPS_MODE_TITLE: return "title";
    case FPS_MODE_PLAY: return "play";
    case FPS_MODE_WIN: return "win";
    case FPS_MODE_DEATH: return "death";
    default: return "unknown";
    }
}

static void print_events(FpsTickEvents events, const FpsGameState *state)
{
    if (events.shot) {
        printf("[oled-fps] event=shot hit=%u killed=%u\n",
               events.hit ? 1u : 0u, events.killed ? 1u : 0u);
    }
    if (events.won) {
        printf("[oled-fps] event=win tick=%" PRIu32 "\n", state->tick);
    }
    if (events.died) {
        printf("[oled-fps] event=death tick=%" PRIu32 "\n", state->tick);
    }
    if (events.restarted) {
        printf("[oled-fps] event=restart tick=%" PRIu32 "\n", state->tick);
    }
}
#endif

#if CONFIG_OLED_FPS_DIAGNOSTIC
static void render_diagnostic(uint8_t framebuffer[OLED_FPS_FB_SIZE], uint32_t frame)
{
    for (size_t i = 0u; i < OLED_FPS_FB_SIZE; i++) {
        const uint8_t page = (uint8_t)(i / OLED_FPS_FB_WIDTH);
        const uint8_t column = (uint8_t)(i % OLED_FPS_FB_WIDTH);
        const uint8_t bit = (uint8_t)(page & 7u);
        framebuffer[i] = (uint8_t)(((column + frame) & 8u) != 0u
            ? (uint8_t)(UINT8_C(1) << bit)
            : (uint8_t)(UINT8_C(0x80) >> bit));
    }
}
#endif

void app_main(void)
{
    static FpsDisplay display;
    static FpsInputDevice input_device;
    static FpsGameState game;
    static uint8_t framebuffer[OLED_FPS_FB_SIZE];

    ESP_ERROR_CHECK(uart_driver_install(UART_NUM_0, 256, 0, 0, NULL, 0));
    uart_vfs_dev_use_driver(UART_NUM_0);
    (void)setvbuf(stdout, NULL, _IONBF, 0);
    printf("[oled-fps] boot version=2 display=sh1107 128x128\n");
    ESP_ERROR_CHECK(fps_display_init(&display));
    ESP_ERROR_CHECK(fps_input_init(&input_device));
    ESP_ERROR_CHECK(fps_input_calibrate(&input_device, 16u));
    fps_game_init(&game, 1u, OLED_FPS_FIXED_SEED);
    fps_render_frame(&game, framebuffer);
    ESP_ERROR_CHECK(fps_display_frame(&display, framebuffer));
    printf("[oled-fps] ready level=1 tick_hz=20 render_hz=20\n");

    TickType_t last_wake = xTaskGetTickCount();
    uint32_t frame = 0u;
    for (;;) {
        FpsInput input = { 0 };
        ESP_ERROR_CHECK(fps_input_sample(&input_device, &input));
#if CONFIG_OLED_FPS_DIAGNOSTIC
        render_diagnostic(framebuffer, frame);
#else
        const FpsTickEvents events = fps_game_tick(&game, input);
        fps_render_frame(&game, framebuffer);
        print_events(events, &game);
#endif
        ESP_ERROR_CHECK(fps_display_frame(&display, framebuffer));
        frame++;
        if ((frame % OLED_FPS_TICK_HZ) == 0u) {
#if CONFIG_OLED_FPS_DIAGNOSTIC
            printf("[oled-fps] diag frame=%" PRIu32 " raw_x=%" PRId32
                   " raw_y=%" PRId32 " turn=%d move=%d switch=%u frame_crc=%08" PRIx32 "\n",
                   frame, input_device.raw_x, input_device.raw_y, input.turn, input.move,
                   input.fire ? 1u : 0u, fps_frame_crc32(framebuffer));
#else
            printf("[oled-fps] sample tick=%" PRIu32 " frame=%" PRIu32
                   " state=%s hp=%u ammo=%u kills=%u enemies=%u crc32=%08" PRIx32
                   " state_crc=%08" PRIx32 "\n",
                   game.tick, frame, mode_name(game.mode), game.health, game.ammo, game.kills,
                   fps_game_alive_enemies(&game), fps_frame_crc32(framebuffer),
                   fps_game_state_crc32(&game));
#endif
        }
        xTaskDelayUntil(&last_wake, pdMS_TO_TICKS(1000u / OLED_FPS_TICK_HZ));
    }
}

main/content.c

/*
 * The level, placements, names, and geometric content in this file were
 * designed from scratch for BRE-209. No third-party game maps or art were
 * consulted or adapted. License: MIT.
 */
#include "content.h"

#define F(c) ((fps_fixed_t)((c) * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF))

/*
 * 16x16 "Split Keep". A west courtyard gives the player an immediate, clear
 * encounter before the hollow central keep. The keep can be crossed through
 * two auto-opening doors or bypassed through either the north or south breach.
 * Values 1..4 are distinct wall materials and 5 is a door. Empty cells are
 * zero. Every placement and route was designed specifically for this game.
 */
static const uint8_t LEVEL_1_CELLS[OLED_FPS_MAP_WIDTH * OLED_FPS_MAP_HEIGHT] = {
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
    1,0,0,4,4,0,0,0,4,4,4,0,0,0,0,1,
    1,0,0,0,0,0,0,2,2,2,2,2,0,0,0,1,
    1,0,0,0,0,0,0,2,0,0,0,2,0,0,0,1,
    1,0,0,3,3,0,0,2,0,3,0,2,0,0,0,1,
    1,0,0,0,0,0,0,2,0,3,0,2,0,0,0,1,
    1,0,0,0,0,0,0,5,0,0,0,5,0,0,0,1,
    1,0,0,0,0,0,0,2,0,3,0,2,0,0,0,1,
    1,0,0,3,3,0,0,2,0,0,0,2,0,0,0,1,
    1,0,0,0,0,0,0,2,0,0,0,2,0,0,0,1,
    1,0,0,0,0,0,0,2,2,2,2,2,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
    1,0,0,4,4,0,0,0,0,0,4,4,0,0,0,1,
    1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
};

static const FpsEnemySpawn LEVEL_1_ENEMIES[] = {
    { 5u, 7u, 1u },
    { 9u, 7u, 2u },
    { 13u, 5u, 2u },
    { 13u, 10u, 2u },
    { 5u, 12u, 2u },
};

static const FpsPickupSpawn LEVEL_1_PICKUPS[] = {
    { 5u, 5u, FPS_PICKUP_AMMO },
    { 9u, 9u, FPS_PICKUP_HEALTH },
    { 13u, 12u, FPS_PICKUP_AMMO },
    { 3u, 3u, FPS_PICKUP_HEALTH },
};

static const FpsDoorSpawn LEVEL_1_DOORS[] = {
    { 7u, 7u },
    { 11u, 7u },
};

_Static_assert(sizeof(LEVEL_1_ENEMIES) / sizeof(LEVEL_1_ENEMIES[0])
                   <= OLED_FPS_MAX_ENEMIES,
               "level enemy inventory exceeds the fixed game-state capacity");
_Static_assert(sizeof(LEVEL_1_PICKUPS) / sizeof(LEVEL_1_PICKUPS[0])
                   <= OLED_FPS_MAX_PICKUPS,
               "level pickup inventory exceeds the fixed game-state capacity");
_Static_assert(sizeof(LEVEL_1_DOORS) / sizeof(LEVEL_1_DOORS[0])
                   <= OLED_FPS_MAX_DOORS,
               "level door inventory exceeds the fixed game-state capacity");

static const FpsLevel LEVEL_1 = {
    .cells = LEVEL_1_CELLS,
    .width = OLED_FPS_MAP_WIDTH,
    .height = OLED_FPS_MAP_HEIGHT,
    .start_x = F(2),
    .start_y = F(7),
    .start_angle = 0u,
    .exit_x = F(13),
    .exit_y = F(7),
    .enemies = LEVEL_1_ENEMIES,
    .enemy_count = (uint8_t)(sizeof(LEVEL_1_ENEMIES) / sizeof(LEVEL_1_ENEMIES[0])),
    .pickups = LEVEL_1_PICKUPS,
    .pickup_count = (uint8_t)(sizeof(LEVEL_1_PICKUPS) / sizeof(LEVEL_1_PICKUPS[0])),
    .doors = LEVEL_1_DOORS,
    .door_count = (uint8_t)(sizeof(LEVEL_1_DOORS) / sizeof(LEVEL_1_DOORS[0])),
};

const FpsLevel *fps_content_level(uint8_t level_id)
{
    (void)level_id;
    return &LEVEL_1;
}

#undef F

main/content.h

/* First-party MIT-licensed game content for BRE-209. */
#ifndef OLED_FPS_CONTENT_H
#define OLED_FPS_CONTENT_H

#include <stdint.h>

#include "game.h"

typedef struct {
    uint8_t cell_x;
    uint8_t cell_y;
    uint8_t health;
} FpsEnemySpawn;

typedef struct {
    uint8_t cell_x;
    uint8_t cell_y;
    uint8_t kind;
} FpsPickupSpawn;

typedef struct {
    uint8_t cell_x;
    uint8_t cell_y;
} FpsDoorSpawn;

typedef struct {
    const uint8_t *cells;
    uint8_t width;
    uint8_t height;
    fps_fixed_t start_x;
    fps_fixed_t start_y;
    uint8_t start_angle;
    fps_fixed_t exit_x;
    fps_fixed_t exit_y;
    const FpsEnemySpawn *enemies;
    uint8_t enemy_count;
    const FpsPickupSpawn *pickups;
    uint8_t pickup_count;
    const FpsDoorSpawn *doors;
    uint8_t door_count;
} FpsLevel;

const FpsLevel *fps_content_level(uint8_t level_id);

#endif

main/display_sh1107.c

/* Hardware-I2C SH1107 transport for BRE-209. First-party MIT code. */
#include "display_sh1107.h"

#include <string.h>

#include "freertos/FreeRTOS.h"

enum {
    OLED_ADDRESS = 0x3d,
    /* The initial 400 kHz correctness run was clean but its 128x128 full-frame
     * title transition measured a 165.2 ms median. The modeled S3/SH1107 seam
     * accepts fast-mode-plus timing, so the release performance candidate uses
     * the plan's explicitly permitted measured 1 MHz path. */
    I2C_CLOCK_HZ = 1000000,
    I2C_TIMEOUT_MS = 100,
};

static esp_err_t send_commands(FpsDisplay *display, const uint8_t *commands, size_t count)
{
    uint8_t packet[40];
    if (count + 1u > sizeof(packet)) {
        return ESP_ERR_INVALID_SIZE;
    }
    packet[0] = 0x00u;
    memcpy(&packet[1], commands, count);
    return i2c_master_write_to_device(I2C_NUM_0, OLED_ADDRESS, packet, count + 1u,
                                      pdMS_TO_TICKS(I2C_TIMEOUT_MS));
}

esp_err_t fps_display_init(FpsDisplay *display)
{
    memset(display, 0, sizeof(*display));
    const i2c_config_t bus_config = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = GPIO_NUM_8,
        .scl_io_num = GPIO_NUM_9,
        .sda_pullup_en = GPIO_PULLUP_DISABLE,
        .scl_pullup_en = GPIO_PULLUP_DISABLE,
        .master.clk_speed = I2C_CLOCK_HZ,
        .clk_flags = 0,
    };
    esp_err_t err = i2c_param_config(I2C_NUM_0, &bus_config);
    if (err != ESP_OK) {
        return err;
    }
    err = i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0u, 0u, 0);
    if (err != ESP_OK) {
        return err;
    }

    static const uint8_t init_commands[] = {
        0xaeu,       /* display off */
        0x20u,       /* page addressing mode (power-on default) */
        0xa8u, 0x7fu,/* multiplex 1/128 */
        0xd3u, 0x00u,/* display offset */
        0xdcu, 0x00u,/* display start line */
        0x81u, 0x4fu,/* contrast */
        0xadu, 0x8bu,/* internal DC-DC enabled when the display turns on */
        0xa4u,       /* display follows RAM */
        0xa6u,       /* normal display */
        0xafu,       /* display on */
    };
    return send_commands(display, init_commands, sizeof(init_commands));
}

esp_err_t fps_display_frame(FpsDisplay *display,
                            const uint8_t framebuffer[OLED_FPS_FB_SIZE])
{
    for (uint8_t page = 0u; page < OLED_FPS_FB_HEIGHT / 8u; page++) {
        /* Co=1 makes each command byte return to control-byte parsing. Co=0,
         * D/C=1 then streams the complete page. One write per page preserves
         * the real SH1107 protocol while halving ESP-IDF transaction setup. */
        display->transfer[0] = 0x80u;
        display->transfer[1] = (uint8_t)(0xb0u | page);
        display->transfer[2] = 0x80u;
        display->transfer[3] = 0x00u;
        display->transfer[4] = 0x80u;
        display->transfer[5] = 0x10u;
        display->transfer[6] = 0x40u;
        memcpy(&display->transfer[7], &framebuffer[(size_t)page * OLED_FPS_FB_WIDTH],
               OLED_FPS_FB_WIDTH);
        const esp_err_t err = i2c_master_write_to_device(I2C_NUM_0, OLED_ADDRESS,
                                                         display->transfer,
                                                         sizeof(display->transfer),
                                                         pdMS_TO_TICKS(I2C_TIMEOUT_MS));
        if (err != ESP_OK) {
            return err;
        }
    }
    return ESP_OK;
}

main/display_sh1107.h

/* ESP-IDF SH1107 transport for the original BRE-209 game. License: MIT. */
#ifndef OLED_FPS_DISPLAY_SH1107_H
#define OLED_FPS_DISPLAY_SH1107_H

#include <stdint.h>

#include "driver/i2c.h"
#include "esp_err.h"

#include "game.h"

typedef struct {
    /* Co-framed page select (6 bytes), data control byte, then 128 pixels. */
    uint8_t transfer[OLED_FPS_FB_WIDTH + 7u];
} FpsDisplay;

esp_err_t fps_display_init(FpsDisplay *display);
esp_err_t fps_display_frame(FpsDisplay *display,
                            const uint8_t framebuffer[OLED_FPS_FB_SIZE]);

#endif

main/game.c

/* Original, platform-free game simulation for BRE-209. License: MIT. */
#include "game.h"

#include <limits.h>
#include <string.h>

#include "content.h"
#include "generated_tables.h"

_Static_assert(sizeof(int16_t) == 2u, "the trig table requires 16-bit int16_t");
_Static_assert(sizeof(int32_t) == 4u, "Q16.16 requires 32-bit int32_t");
_Static_assert(sizeof(uint32_t) == 4u, "the deterministic RNG requires uint32_t");
_Static_assert(OLED_FPS_MAP_WIDTH <= 24u && OLED_FPS_MAP_HEIGHT <= 24u,
               "DDA and compact coordinates are sized for maps up to 24x24");
_Static_assert(OLED_FPS_FB_SIZE == 2048u, "SH1107 page framebuffer must be 2048 bytes");
_Static_assert((int64_t)OLED_FPS_FIXED_ONE * OLED_FPS_MAP_WIDTH < INT32_MAX,
               "map coordinates must fit signed Q16.16");

enum {
    PLAYER_RADIUS = 11796,       /* 0.18 cells in Q16.16. */
    PLAYER_SPEED = 4259,         /* 0.065 cells per 20 Hz tick at full input. */
    ENEMY_SPEED = 1311,          /* 0.020 cells per AI step. */
    DOOR_RANGE = 98304,          /* 1.5 cells. */
    PICKUP_RANGE = 24576,        /* 0.375 cells. */
    EXIT_RANGE = 28672,          /* 0.4375 cells. */
    ENEMY_ATTACK_RANGE = 36045,  /* 0.55 cells. */
    ENEMY_ATTACK_DAMAGE = 12,
    ENEMY_ATTACK_COOLDOWN = 16,
};

static int32_t clamp_axis(int16_t value)
{
    if (value < -127) {
        return -127;
    }
    if (value > 127) {
        return 127;
    }
    return value;
}

static int32_t abs_fixed(fps_fixed_t value)
{
    /* Coordinates and deltas are bounded to 24 cells, so INT32_MIN is not a
     * reachable input and signed negation remains defined. */
    return value < 0 ? -value : value;
}

static int64_t distance_sq(fps_fixed_t ax, fps_fixed_t ay,
                           fps_fixed_t bx, fps_fixed_t by)
{
    const int64_t dx = (int64_t)ax - bx;
    const int64_t dy = (int64_t)ay - by;
    return dx * dx + dy * dy;
}

static int32_t fixed_floor_cell(fps_fixed_t value)
{
    if (value >= 0) {
        return value / OLED_FPS_FIXED_ONE;
    }
    return -1 - (int32_t)((-(int64_t)value - 1) / OLED_FPS_FIXED_ONE);
}

static int32_t q15_floor(int64_t value)
{
    /* Preserve the arithmetic-right-shift result intentionally used by the
     * fixed-point design without relying on implementation-defined shifting
     * of negative signed integers. All callers are bounded far from INT64_MIN. */
    if (value >= 0) {
        return (int32_t)(value / INT64_C(32768));
    }
    return -1 - (int32_t)((-value - 1) / INT64_C(32768));
}

static uint32_t next_random(FpsGameState *state)
{
    /* Numerical Recipes LCG, with fixed unsigned overflow defined by C. */
    state->rng = state->rng * UINT32_C(1664525) + UINT32_C(1013904223);
    return state->rng;
}

static void reset_level(FpsGameState *state, uint8_t level_id, uint32_t seed,
                        FpsMode mode)
{
    const FpsLevel *level = fps_content_level(level_id);
    memset(state, 0, sizeof(*state));
    state->rng = seed;
    state->initial_seed = seed;
    state->level_id = level_id;
    state->player_x = level->start_x;
    state->player_y = level->start_y;
    state->player_angle = level->start_angle;
    state->health = 100u;
    state->ammo = 12u;
    state->mode = mode;

    state->enemy_count = level->enemy_count;
    for (uint8_t i = 0u; i < level->enemy_count; i++) {
        state->enemies[i].x = (fps_fixed_t)(level->enemies[i].cell_x * OLED_FPS_FIXED_ONE
                                            + OLED_FPS_FIXED_HALF);
        state->enemies[i].y = (fps_fixed_t)(level->enemies[i].cell_y * OLED_FPS_FIXED_ONE
                                            + OLED_FPS_FIXED_HALF);
        state->enemies[i].health = level->enemies[i].health;
        state->enemies[i].alive = true;
    }

    state->pickup_count = level->pickup_count;
    for (uint8_t i = 0u; i < level->pickup_count; i++) {
        state->pickups[i].x = (fps_fixed_t)(level->pickups[i].cell_x * OLED_FPS_FIXED_ONE
                                            + OLED_FPS_FIXED_HALF);
        state->pickups[i].y = (fps_fixed_t)(level->pickups[i].cell_y * OLED_FPS_FIXED_ONE
                                            + OLED_FPS_FIXED_HALF);
        state->pickups[i].kind = level->pickups[i].kind;
        state->pickups[i].active = true;
    }

    state->door_count = level->door_count;
    for (uint8_t i = 0u; i < level->door_count; i++) {
        state->doors[i].cell_x = level->doors[i].cell_x;
        state->doors[i].cell_y = level->doors[i].cell_y;
    }
}

void fps_game_init(FpsGameState *state, uint8_t level_id, uint32_t seed)
{
    reset_level(state, level_id, seed, FPS_MODE_TITLE);
}

uint8_t fps_game_cell(const FpsGameState *state, int32_t cell_x, int32_t cell_y)
{
    const FpsLevel *level = fps_content_level(state->level_id);
    if (cell_x < 0 || cell_y < 0 || cell_x >= level->width || cell_y >= level->height) {
        return 1u;
    }
    const uint8_t material = level->cells[(size_t)cell_y * level->width + (size_t)cell_x];
    if (material != 5u) {
        return material;
    }
    for (uint8_t i = 0u; i < state->door_count; i++) {
        const FpsDoor *door = &state->doors[i];
        if (door->cell_x == (uint8_t)cell_x && door->cell_y == (uint8_t)cell_y) {
            return door->open ? 0u : 5u;
        }
    }
    return 5u;
}

static bool position_is_open(const FpsGameState *state, fps_fixed_t x, fps_fixed_t y,
                             int32_t radius)
{
    const fps_fixed_t left = x - radius;
    const fps_fixed_t right = x + radius;
    const fps_fixed_t top = y - radius;
    const fps_fixed_t bottom = y + radius;
    return fps_game_cell(state, fixed_floor_cell(left), fixed_floor_cell(top)) == 0u
        && fps_game_cell(state, fixed_floor_cell(right), fixed_floor_cell(top)) == 0u
        && fps_game_cell(state, fixed_floor_cell(left), fixed_floor_cell(bottom)) == 0u
        && fps_game_cell(state, fixed_floor_cell(right), fixed_floor_cell(bottom)) == 0u;
}

static void open_nearby_doors(FpsGameState *state)
{
    const int64_t range_sq = (int64_t)DOOR_RANGE * DOOR_RANGE;
    for (uint8_t i = 0u; i < state->door_count; i++) {
        FpsDoor *door = &state->doors[i];
        const fps_fixed_t x = (fps_fixed_t)(door->cell_x * OLED_FPS_FIXED_ONE
                                             + OLED_FPS_FIXED_HALF);
        const fps_fixed_t y = (fps_fixed_t)(door->cell_y * OLED_FPS_FIXED_ONE
                                             + OLED_FPS_FIXED_HALF);
        if (!door->open && distance_sq(state->player_x, state->player_y, x, y) <= range_sq) {
            door->open = true;
        }
    }
}

static void move_player(FpsGameState *state, int32_t move_axis)
{
    const int16_t dir_x = FPS_SIN_Q15[(uint8_t)(state->player_angle + 64u)];
    const int16_t dir_y = FPS_SIN_Q15[state->player_angle];
    const fps_fixed_t step = (fps_fixed_t)(((int64_t)PLAYER_SPEED * move_axis) / 127);
    const fps_fixed_t dx = (fps_fixed_t)q15_floor((int64_t)dir_x * step);
    const fps_fixed_t dy = (fps_fixed_t)q15_floor((int64_t)dir_y * step);
    const fps_fixed_t next_x = state->player_x + dx;
    if (position_is_open(state, next_x, state->player_y, PLAYER_RADIUS)) {
        state->player_x = next_x;
    }
    const fps_fixed_t next_y = state->player_y + dy;
    if (position_is_open(state, state->player_x, next_y, PLAYER_RADIUS)) {
        state->player_y = next_y;
    }
}

static bool line_is_clear(const FpsGameState *state, fps_fixed_t to_x, fps_fixed_t to_y)
{
    const int64_t dx = (int64_t)to_x - state->player_x;
    const int64_t dy = (int64_t)to_y - state->player_y;
    for (int32_t step = 1; step < 32; step++) {
        const fps_fixed_t x = state->player_x + (fps_fixed_t)((dx * step) / 32);
        const fps_fixed_t y = state->player_y + (fps_fixed_t)((dy * step) / 32);
        if (fps_game_cell(state, fixed_floor_cell(x), fixed_floor_cell(y)) != 0u) {
            return false;
        }
    }
    return true;
}

static bool fire_weapon(FpsGameState *state, bool *killed)
{
    *killed = false;
    if (state->ammo == 0u) {
        return false;
    }
    state->ammo--;
    const int16_t dir_x = FPS_SIN_Q15[(uint8_t)(state->player_angle + 64u)];
    const int16_t dir_y = FPS_SIN_Q15[state->player_angle];
    int32_t best = INT32_MAX;
    FpsEnemy *target = NULL;
    for (uint8_t i = 0u; i < state->enemy_count; i++) {
        FpsEnemy *enemy = &state->enemies[i];
        if (!enemy->alive) {
            continue;
        }
        const int64_t dx = (int64_t)enemy->x - state->player_x;
        const int64_t dy = (int64_t)enemy->y - state->player_y;
        const int32_t forward = q15_floor(dx * dir_x + dy * dir_y);
        const int32_t lateral = q15_floor(dx * -dir_y + dy * dir_x);
        if (forward <= 0 || (int64_t)abs_fixed(lateral) * 5 > forward
            || forward >= best || !line_is_clear(state, enemy->x, enemy->y)) {
            continue;
        }
        best = forward;
        target = enemy;
    }
    if (target == NULL) {
        return false;
    }
    state->hit_ticks = 4u;
    target->hit_ticks = 4u;
    if (target->health > 1u) {
        target->health--;
    } else {
        target->health = 0u;
        target->alive = false;
        state->kills++;
        *killed = true;
    }
    return true;
}

static void collect_pickups(FpsGameState *state)
{
    const int64_t range_sq = (int64_t)PICKUP_RANGE * PICKUP_RANGE;
    for (uint8_t i = 0u; i < state->pickup_count; i++) {
        FpsPickup *pickup = &state->pickups[i];
        if (!pickup->active
            || distance_sq(state->player_x, state->player_y, pickup->x, pickup->y) > range_sq) {
            continue;
        }
        if (pickup->kind == FPS_PICKUP_HEALTH) {
            const uint16_t healed = (uint16_t)state->health + 30u;
            state->health = (uint8_t)(healed > 100u ? 100u : healed);
        } else if (pickup->kind == FPS_PICKUP_AMMO) {
            const uint16_t loaded = (uint16_t)state->ammo + 6u;
            state->ammo = (uint8_t)(loaded > 20u ? 20u : loaded);
        }
        pickup->active = false;
    }
}

static bool try_enemy_move(FpsGameState *state, FpsEnemy *enemy,
                           fps_fixed_t dx, fps_fixed_t dy, bool x_first)
{
    const fps_fixed_t next_x = enemy->x + dx;
    const fps_fixed_t next_y = enemy->y + dy;
    bool moved = false;
    if (x_first) {
        if (position_is_open(state, next_x, enemy->y, PLAYER_RADIUS)) {
            enemy->x = next_x;
            moved = true;
        }
        if (position_is_open(state, enemy->x, next_y, PLAYER_RADIUS)) {
            enemy->y = next_y;
            moved = true;
        }
    } else {
        if (position_is_open(state, enemy->x, next_y, PLAYER_RADIUS)) {
            enemy->y = next_y;
            moved = true;
        }
        if (position_is_open(state, next_x, enemy->y, PLAYER_RADIUS)) {
            enemy->x = next_x;
            moved = true;
        }
    }
    return moved;
}

static bool update_enemies(FpsGameState *state)
{
    const int64_t attack_sq = (int64_t)ENEMY_ATTACK_RANGE * ENEMY_ATTACK_RANGE;
    bool died = false;
    for (uint8_t i = 0u; i < state->enemy_count; i++) {
        FpsEnemy *enemy = &state->enemies[i];
        if (!enemy->alive) {
            continue;
        }
        if (enemy->attack_cooldown > 0u) {
            enemy->attack_cooldown--;
        }
        if (enemy->hit_ticks > 0u) {
            enemy->hit_ticks--;
        }
        const int64_t d2 = distance_sq(state->player_x, state->player_y, enemy->x, enemy->y);
        if (d2 <= attack_sq) {
            if (enemy->attack_cooldown == 0u) {
                enemy->attack_cooldown = ENEMY_ATTACK_COOLDOWN;
                state->hurt_ticks = 6u;
                if (state->health <= ENEMY_ATTACK_DAMAGE) {
                    state->health = 0u;
                    state->mode = FPS_MODE_DEATH;
                    died = true;
                } else {
                    state->health = (uint8_t)(state->health - ENEMY_ATTACK_DAMAGE);
                }
            }
            continue;
        }
        if ((state->tick & 1u) != 0u) {
            continue;
        }
        const fps_fixed_t raw_x = state->player_x - enemy->x;
        const fps_fixed_t raw_y = state->player_y - enemy->y;
        const int32_t ax = abs_fixed(raw_x);
        const int32_t ay = abs_fixed(raw_y);
        const int32_t scale = (ax > ay ? ax : ay) + (ax > ay ? ay : ax) / 2;
        if (scale == 0) {
            continue;
        }
        const fps_fixed_t dx = (fps_fixed_t)(((int64_t)raw_x * ENEMY_SPEED) / scale);
        const fps_fixed_t dy = (fps_fixed_t)(((int64_t)raw_y * ENEMY_SPEED) / scale);
        const bool x_first = (next_random(state) & 1u) != 0u;
        (void)try_enemy_move(state, enemy, dx, dy, x_first);
    }
    return died;
}

static bool player_reached_exit(const FpsGameState *state)
{
    const FpsLevel *level = fps_content_level(state->level_id);
    const int64_t range_sq = (int64_t)EXIT_RANGE * EXIT_RANGE;
    return distance_sq(state->player_x, state->player_y, level->exit_x, level->exit_y)
        <= range_sq;
}

static void update_exit_progress(FpsGameState *state)
{
    const FpsLevel *level = fps_content_level(state->level_id);
    const int64_t distance = distance_sq(state->player_x, state->player_y,
                                         level->exit_x, level->exit_y);
    const int64_t one = OLED_FPS_FIXED_ONE;
    uint8_t milestone = 0u;
    if (distance <= (int64_t)2 * one * 2 * one) {
        milestone = 3u;
    } else if (distance <= (int64_t)6 * one * 6 * one) {
        milestone = 2u;
    } else if (distance <= (int64_t)10 * one * 10 * one) {
        milestone = 1u;
    }
    /* Progress is a reached milestone, not a compass: exploring backward must
     * not erase an accomplishment already shown on the one-digit OLED HUD. */
    if (milestone > state->exit_progress) {
        state->exit_progress = milestone;
    }
}

FpsTickEvents fps_game_tick(FpsGameState *state, FpsInput input)
{
    FpsTickEvents events = { 0 };
    const bool rising_fire = input.fire && !state->fire_was_down;
    const uint8_t level_id = state->level_id;
    const uint32_t initial_seed = state->initial_seed;

    if (state->mode == FPS_MODE_TITLE) {
        state->tick++;
        state->fire_was_down = input.fire;
        if (rising_fire) {
            reset_level(state, level_id, initial_seed, FPS_MODE_PLAY);
            state->fire_was_down = true;
            events.restarted = true;
        }
        return events;
    }

    if (state->mode == FPS_MODE_WIN || state->mode == FPS_MODE_DEATH) {
        state->tick++;
        state->fire_was_down = input.fire;
        if (rising_fire) {
            reset_level(state, level_id, initial_seed, FPS_MODE_PLAY);
            state->fire_was_down = true;
            events.restarted = true;
        }
        return events;
    }

    state->tick++;
    if (state->muzzle_ticks > 0u) {
        state->muzzle_ticks--;
    }
    if (state->hit_ticks > 0u) {
        state->hit_ticks--;
    }
    if (state->hurt_ticks > 0u) {
        state->hurt_ticks--;
    }
    const int32_t turn_axis = clamp_axis(input.turn);
    const int32_t move_axis = clamp_axis(input.move);
    const int32_t turn_step = turn_axis / 32;
    state->player_angle = (uint8_t)(state->player_angle + turn_step);

    open_nearby_doors(state);
    if (move_axis != 0) {
        move_player(state, move_axis);
        open_nearby_doors(state);
    }

    if (rising_fire) {
        events.shot = state->ammo > 0u;
        if (events.shot) {
            state->muzzle_ticks = 3u;
        }
        events.hit = fire_weapon(state, &events.killed);
    }
    collect_pickups(state);
    events.died = update_enemies(state);
    update_exit_progress(state);

    if (!events.died && fps_game_alive_enemies(state) == 0u && player_reached_exit(state)) {
        state->exit_progress = 4u;
        state->mode = FPS_MODE_WIN;
        events.won = true;
    }
    state->fire_was_down = input.fire;
    return events;
}

uint8_t fps_game_alive_enemies(const FpsGameState *state)
{
    uint8_t count = 0u;
    for (uint8_t i = 0u; i < state->enemy_count; i++) {
        if (state->enemies[i].alive) {
            count++;
        }
    }
    return count;
}

static uint32_t crc_byte(uint32_t crc, uint8_t value)
{
    crc ^= value;
    for (uint8_t bit = 0u; bit < 8u; bit++) {
        const uint32_t mask = UINT32_C(0) - (crc & 1u);
        crc = (crc >> 1u) ^ (UINT32_C(0xedb88320) & mask);
    }
    return crc;
}

static uint32_t crc_u32(uint32_t crc, uint32_t value)
{
    for (uint8_t shift = 0u; shift < 32u; shift += 8u) {
        crc = crc_byte(crc, (uint8_t)(value >> shift));
    }
    return crc;
}

uint32_t fps_game_state_crc32(const FpsGameState *state)
{
    /* Serialize fields explicitly: struct padding is intentionally excluded. */
    uint32_t crc = UINT32_C(0xffffffff);
    crc = crc_u32(crc, state->tick);
    crc = crc_u32(crc, state->rng);
    crc = crc_u32(crc, state->initial_seed);
    crc = crc_u32(crc, (uint32_t)state->player_x);
    crc = crc_u32(crc, (uint32_t)state->player_y);
    crc = crc_byte(crc, state->player_angle);
    crc = crc_byte(crc, state->health);
    crc = crc_byte(crc, state->ammo);
    crc = crc_byte(crc, state->kills);
    crc = crc_byte(crc, state->exit_progress);
    crc = crc_byte(crc, state->muzzle_ticks);
    crc = crc_byte(crc, state->hit_ticks);
    crc = crc_byte(crc, state->hurt_ticks);
    crc = crc_byte(crc, state->level_id);
    crc = crc_byte(crc, state->enemy_count);
    crc = crc_byte(crc, state->pickup_count);
    crc = crc_byte(crc, state->door_count);
    crc = crc_byte(crc, (uint8_t)state->mode);
    crc = crc_byte(crc, state->fire_was_down ? 1u : 0u);
    for (uint8_t i = 0u; i < state->enemy_count; i++) {
        const FpsEnemy *enemy = &state->enemies[i];
        crc = crc_u32(crc, (uint32_t)enemy->x);
        crc = crc_u32(crc, (uint32_t)enemy->y);
        crc = crc_byte(crc, enemy->health);
        crc = crc_byte(crc, enemy->attack_cooldown);
        crc = crc_byte(crc, enemy->hit_ticks);
        crc = crc_byte(crc, enemy->alive ? 1u : 0u);
    }
    for (uint8_t i = 0u; i < state->pickup_count; i++) {
        const FpsPickup *pickup = &state->pickups[i];
        crc = crc_u32(crc, (uint32_t)pickup->x);
        crc = crc_u32(crc, (uint32_t)pickup->y);
        crc = crc_byte(crc, pickup->kind);
        crc = crc_byte(crc, pickup->active ? 1u : 0u);
    }
    for (uint8_t i = 0u; i < state->door_count; i++) {
        const FpsDoor *door = &state->doors[i];
        crc = crc_byte(crc, door->cell_x);
        crc = crc_byte(crc, door->cell_y);
        crc = crc_byte(crc, door->open ? 1u : 0u);
    }
    return ~crc;
}

main/game.h

/*
 * Original OLED dungeon game core for Breadboard (BRE-209).
 *
 * This file is first-party MIT-licensed code. The portable core depends only
 * on C11 fixed-width integer types and is shared unchanged by the ESP-IDF
 * firmware and the host verification harness.
 */
#ifndef OLED_FPS_GAME_H
#define OLED_FPS_GAME_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#define OLED_FPS_FB_WIDTH 128u
#define OLED_FPS_FB_HEIGHT 128u
#define OLED_FPS_FB_SIZE ((OLED_FPS_FB_WIDTH * OLED_FPS_FB_HEIGHT) / 8u)
#define OLED_FPS_MAP_WIDTH 16u
#define OLED_FPS_MAP_HEIGHT 16u
#define OLED_FPS_MAX_ENEMIES 6u
#define OLED_FPS_MAX_PICKUPS 4u
#define OLED_FPS_MAX_DOORS 4u
#define OLED_FPS_TICK_HZ 20u
#define OLED_FPS_FIXED_ONE INT32_C(65536)
#define OLED_FPS_FIXED_HALF INT32_C(32768)
#define OLED_FPS_FIXED_SEED UINT32_C(0x4f4c4544)

typedef int32_t fps_fixed_t;

typedef enum {
    FPS_MODE_TITLE = 0,
    FPS_MODE_PLAY = 1,
    FPS_MODE_WIN = 2,
    FPS_MODE_DEATH = 3,
} FpsMode;

typedef enum {
    FPS_PICKUP_HEALTH = 1,
    FPS_PICKUP_AMMO = 2,
} FpsPickupKind;

typedef struct {
    /* Both axes are normalized to [-127, 127]. Positive move is forward;
     * positive turn rotates clockwise in the game's top-down coordinate
     * system. */
    int16_t turn;
    int16_t move;
    bool fire;
} FpsInput;

typedef struct {
    fps_fixed_t x;
    fps_fixed_t y;
    uint8_t health;
    uint8_t attack_cooldown;
    uint8_t hit_ticks;
    bool alive;
} FpsEnemy;

typedef struct {
    fps_fixed_t x;
    fps_fixed_t y;
    uint8_t kind;
    bool active;
} FpsPickup;

typedef struct {
    uint8_t cell_x;
    uint8_t cell_y;
    bool open;
} FpsDoor;

typedef struct {
    uint32_t tick;
    uint32_t rng;
    uint32_t initial_seed;
    fps_fixed_t player_x;
    fps_fixed_t player_y;
    uint8_t player_angle;
    uint8_t health;
    uint8_t ammo;
    uint8_t kills;
    uint8_t exit_progress;
    uint8_t muzzle_ticks;
    uint8_t hit_ticks;
    uint8_t hurt_ticks;
    uint8_t level_id;
    uint8_t enemy_count;
    uint8_t pickup_count;
    uint8_t door_count;
    FpsMode mode;
    bool fire_was_down;
    FpsEnemy enemies[OLED_FPS_MAX_ENEMIES];
    FpsPickup pickups[OLED_FPS_MAX_PICKUPS];
    FpsDoor doors[OLED_FPS_MAX_DOORS];
} FpsGameState;

typedef struct {
    bool shot;
    bool hit;
    bool killed;
    bool won;
    bool died;
    bool restarted;
} FpsTickEvents;

void fps_game_init(FpsGameState *state, uint8_t level_id, uint32_t seed);
FpsTickEvents fps_game_tick(FpsGameState *state, FpsInput input);
uint8_t fps_game_cell(const FpsGameState *state, int32_t cell_x, int32_t cell_y);
uint8_t fps_game_alive_enemies(const FpsGameState *state);
uint32_t fps_game_state_crc32(const FpsGameState *state);

#ifdef __cplusplus
}
#endif

#endif

main/input.c

/* Real ADC1/GPIO joystick path for BRE-209. First-party MIT code. */
#include "input.h"

#include <string.h>

#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

enum {
    JOYSTICK_X_CHANNEL = ADC_CHANNEL_0, /* GPIO1 on ESP32-S3. */
    JOYSTICK_Y_CHANNEL = ADC_CHANNEL_1, /* GPIO2 on ESP32-S3. */
    JOYSTICK_SWITCH_GPIO = GPIO_NUM_10,
    DEAD_ZONE_ENTER = 240,
    DEAD_ZONE_EXIT = 160,
    AXIS_FULL_SCALE = 1700,
    BUTTON_DEBOUNCE_TICKS = 2,
};

static int16_t normalize_axis(int32_t raw, int32_t center, bool *active)
{
    const int32_t delta = raw - center;
    const int32_t magnitude = delta < 0 ? -delta : delta;
    if (*active) {
        if (magnitude <= DEAD_ZONE_EXIT) {
            *active = false;
            return 0;
        }
    } else if (magnitude < DEAD_ZONE_ENTER) {
        return 0;
    } else {
        *active = true;
    }
    int32_t normalized = (delta * 127) / AXIS_FULL_SCALE;
    if (normalized < -127) {
        normalized = -127;
    } else if (normalized > 127) {
        normalized = 127;
    }
    return (int16_t)normalized;
}

esp_err_t fps_input_init(FpsInputDevice *device)
{
    memset(device, 0, sizeof(*device));
    const adc_oneshot_unit_init_cfg_t unit_config = {
        .unit_id = ADC_UNIT_1,
        .ulp_mode = ADC_ULP_MODE_DISABLE,
    };
    esp_err_t err = adc_oneshot_new_unit(&unit_config, &device->adc);
    if (err != ESP_OK) {
        return err;
    }
    const adc_oneshot_chan_cfg_t channel_config = {
        .atten = ADC_ATTEN_DB_12,
        .bitwidth = ADC_BITWIDTH_DEFAULT,
    };
    err = adc_oneshot_config_channel(device->adc, JOYSTICK_X_CHANNEL, &channel_config);
    if (err != ESP_OK) {
        return err;
    }
    err = adc_oneshot_config_channel(device->adc, JOYSTICK_Y_CHANNEL, &channel_config);
    if (err != ESP_OK) {
        return err;
    }
    const gpio_config_t switch_config = {
        .pin_bit_mask = UINT64_C(1) << JOYSTICK_SWITCH_GPIO,
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };
    err = gpio_config(&switch_config);
    if (err != ESP_OK) {
        return err;
    }
    device->center_x = 2048;
    device->center_y = 2048;
    return ESP_OK;
}

esp_err_t fps_input_calibrate(FpsInputDevice *device, uint8_t samples)
{
    if (samples == 0u) {
        return ESP_ERR_INVALID_ARG;
    }
    int32_t sum_x = 0;
    int32_t sum_y = 0;
    for (uint8_t i = 0u; i < samples; i++) {
        int raw_x = 0;
        int raw_y = 0;
        esp_err_t err = adc_oneshot_read(device->adc, JOYSTICK_X_CHANNEL, &raw_x);
        if (err != ESP_OK) {
            return err;
        }
        err = adc_oneshot_read(device->adc, JOYSTICK_Y_CHANNEL, &raw_y);
        if (err != ESP_OK) {
            return err;
        }
        sum_x += raw_x;
        sum_y += raw_y;
        vTaskDelay(pdMS_TO_TICKS(2));
    }
    device->center_x = sum_x / samples;
    device->center_y = sum_y / samples;
    return ESP_OK;
}

esp_err_t fps_input_sample(FpsInputDevice *device, FpsInput *input)
{
    int raw_x = 0;
    int raw_y = 0;
    esp_err_t err = adc_oneshot_read(device->adc, JOYSTICK_X_CHANNEL, &raw_x);
    if (err != ESP_OK) {
        return err;
    }
    err = adc_oneshot_read(device->adc, JOYSTICK_Y_CHANNEL, &raw_y);
    if (err != ESP_OK) {
        return err;
    }
    device->raw_x = raw_x;
    device->raw_y = raw_y;
    const bool candidate = gpio_get_level(JOYSTICK_SWITCH_GPIO) == 0;
    if (candidate == device->button_candidate) {
        if (device->button_stable_ticks < BUTTON_DEBOUNCE_TICKS) {
            device->button_stable_ticks++;
        }
    } else {
        device->button_candidate = candidate;
        device->button_stable_ticks = 1u;
    }
    if (device->button_stable_ticks >= BUTTON_DEBOUNCE_TICKS) {
        device->button_down = candidate;
    }
    input->turn = normalize_axis(raw_x, device->center_x, &device->x_active);
    input->move = normalize_axis(raw_y, device->center_y, &device->y_active);
    input->fire = device->button_down;
    return ESP_OK;
}

main/input.h

/* ESP-IDF ADC/GPIO input adapter for the original BRE-209 game. MIT. */
#ifndef OLED_FPS_INPUT_H
#define OLED_FPS_INPUT_H

#include <stdbool.h>
#include <stdint.h>

#include "esp_adc/adc_oneshot.h"
#include "esp_err.h"

#include "game.h"

typedef struct {
    adc_oneshot_unit_handle_t adc;
    int32_t center_x;
    int32_t center_y;
    int32_t raw_x;
    int32_t raw_y;
    bool x_active;
    bool y_active;
    bool button_candidate;
    bool button_down;
    uint8_t button_stable_ticks;
} FpsInputDevice;

esp_err_t fps_input_init(FpsInputDevice *device);
esp_err_t fps_input_calibrate(FpsInputDevice *device, uint8_t samples);
esp_err_t fps_input_sample(FpsInputDevice *device, FpsInput *input);

#endif

main/render.c

/* Original integer DDA renderer and geometric OLED art for BRE-209. MIT. */
#include "render.h"

#include <limits.h>
#include <string.h>

#include "content.h"
#include "generated_tables.h"

enum {
    VIEW_HEIGHT = 104,
    HORIZON_Y = 50,
    FOV_TURN_UNITS = 44, /* Approximately 62 degrees in the 8-bit turn space. */
    MIN_RAY_DISTANCE = 8192,
};

static void set_pixel(uint8_t *framebuffer, int32_t x, int32_t y, bool on)
{
    if (x < 0 || x >= (int32_t)OLED_FPS_FB_WIDTH
        || y < 0 || y >= (int32_t)OLED_FPS_FB_HEIGHT) {
        return;
    }
    const size_t index = (size_t)(y >> 3) * OLED_FPS_FB_WIDTH + (size_t)x;
    const uint8_t mask = (uint8_t)(1u << (uint32_t)(y & 7));
    if (on) {
        framebuffer[index] |= mask;
    } else {
        framebuffer[index] &= (uint8_t)~mask;
    }
}

static void fill_rect(uint8_t *framebuffer, int32_t x, int32_t y,
                      int32_t width, int32_t height, bool on)
{
    for (int32_t py = y; py < y + height; py++) {
        for (int32_t px = x; px < x + width; px++) {
            set_pixel(framebuffer, px, py, on);
        }
    }
}

#define G(a, b, c, d, e) ((uint16_t)(((a) << 12) | ((b) << 9) | ((c) << 6) | ((d) << 3) | (e)))

/* Hand-authored 3x5 block glyphs for this game's deliberately small copy. */
static uint16_t glyph(char c)
{
    switch (c) {
    case 'A': return G(2, 5, 7, 5, 5);
    case 'B': return G(6, 5, 6, 5, 6);
    case 'C': return G(3, 4, 4, 4, 3);
    case 'D': return G(6, 5, 5, 5, 6);
    case 'E': return G(7, 4, 6, 4, 7);
    case 'F': return G(7, 4, 6, 4, 4);
    case 'G': return G(3, 4, 5, 5, 3);
    case 'H': return G(5, 5, 7, 5, 5);
    case 'I': return G(7, 2, 2, 2, 7);
    case 'J': return G(1, 1, 1, 5, 2);
    case 'K': return G(5, 5, 6, 5, 5);
    case 'L': return G(4, 4, 4, 4, 7);
    case 'M': return G(5, 7, 7, 5, 5);
    case 'N': return G(5, 7, 7, 7, 5);
    case 'O': return G(2, 5, 5, 5, 2);
    case 'P': return G(6, 5, 6, 4, 4);
    case 'Q': return G(2, 5, 5, 3, 1);
    case 'R': return G(6, 5, 6, 5, 5);
    case 'S': return G(3, 4, 2, 1, 6);
    case 'T': return G(7, 2, 2, 2, 2);
    case 'U': return G(5, 5, 5, 5, 7);
    case 'V': return G(5, 5, 5, 5, 2);
    case 'W': return G(5, 5, 7, 7, 5);
    case 'X': return G(5, 5, 2, 5, 5);
    case 'Y': return G(5, 5, 2, 2, 2);
    case 'Z': return G(7, 1, 2, 4, 7);
    case '0': return G(7, 5, 5, 5, 7);
    case '1': return G(2, 6, 2, 2, 7);
    case '2': return G(6, 1, 7, 4, 7);
    case '3': return G(6, 1, 3, 1, 6);
    case '4': return G(5, 5, 7, 1, 1);
    case '5': return G(7, 4, 6, 1, 6);
    case '6': return G(3, 4, 6, 5, 2);
    case '7': return G(7, 1, 2, 2, 2);
    case '8': return G(2, 5, 2, 5, 2);
    case '9': return G(2, 5, 3, 1, 6);
    case ':': return G(0, 2, 0, 2, 0);
    case '-': return G(0, 0, 7, 0, 0);
    case '/': return G(1, 1, 2, 4, 4);
    case '.': return G(0, 0, 0, 0, 2);
    default: return 0u;
    }
}

#undef G

static void draw_char(uint8_t *framebuffer, int32_t x, int32_t y, char c)
{
    const uint16_t bits = glyph(c);
    for (int32_t row = 0; row < 5; row++) {
        const uint32_t shift = (uint32_t)((4 - row) * 3);
        const uint8_t row_bits = (uint8_t)((bits >> shift) & 7u);
        for (int32_t col = 0; col < 3; col++) {
            if ((row_bits & (uint8_t)(4u >> (uint32_t)col)) != 0u) {
                set_pixel(framebuffer, x + col, y + row, true);
            }
        }
    }
}

static void draw_text(uint8_t *framebuffer, int32_t x, int32_t y, const char *text)
{
    while (*text != '\0') {
        draw_char(framebuffer, x, y, *text++);
        x += 4;
    }
}

static void draw_char_scaled(uint8_t *framebuffer, int32_t x, int32_t y,
                             char c, int32_t scale)
{
    const uint16_t bits = glyph(c);
    for (int32_t row = 0; row < 5; row++) {
        const uint32_t shift = (uint32_t)((4 - row) * 3);
        const uint8_t row_bits = (uint8_t)((bits >> shift) & 7u);
        for (int32_t col = 0; col < 3; col++) {
            if ((row_bits & (uint8_t)(4u >> (uint32_t)col)) != 0u) {
                fill_rect(framebuffer, x + col * scale, y + row * scale,
                          scale, scale, true);
            }
        }
    }
}

static void draw_text_scaled(uint8_t *framebuffer, int32_t x, int32_t y,
                             const char *text, int32_t scale)
{
    while (*text != '\0') {
        draw_char_scaled(framebuffer, x, y, *text++, scale);
        x += 4 * scale;
    }
}

static int32_t centered_text_x(const char *text, int32_t scale)
{
    const int32_t width = (int32_t)strlen(text) * 4 * scale - scale;
    return ((int32_t)OLED_FPS_FB_WIDTH - width) / 2;
}

static void draw_u8_3(uint8_t *framebuffer, int32_t x, int32_t y, uint8_t value)
{
    draw_char(framebuffer, x, y, (char)('0' + value / 100u));
    draw_char(framebuffer, x + 4, y, (char)('0' + (value / 10u) % 10u));
    draw_char(framebuffer, x + 8, y, (char)('0' + value % 10u));
}

static int32_t abs_i32(int32_t value)
{
    return value < 0 ? -value : value;
}

static int32_t q15_floor(int64_t value)
{
    /* Match an arithmetic 15-bit right shift while keeping negative values
     * fully defined by C11. Ray/map bounds keep value far from INT64_MIN. */
    if (value >= 0) {
        return (int32_t)(value / INT64_C(32768));
    }
    return -1 - (int32_t)((-value - 1) / INT64_C(32768));
}

static int32_t fixed_floor_cell(fps_fixed_t value)
{
    if (value >= 0) {
        return value / OLED_FPS_FIXED_ONE;
    }
    return -1 - (int32_t)((-(int64_t)value - 1) / OLED_FPS_FIXED_ONE);
}

static uint8_t wall_density(uint8_t material, bool y_side, int32_t distance)
{
    static const uint8_t density_by_material[6] = { 0u, 16u, 12u, 9u, 6u, 14u };
    uint8_t density = material < 6u ? density_by_material[material] : 8u;
    const int32_t distance_band = distance >> 17; /* One band per two cells. */
    if (distance_band > 0) {
        const uint8_t fade = (uint8_t)(distance_band > 4 ? 4 : distance_band);
        density = density > fade ? (uint8_t)(density - fade) : 1u;
    }
    if (y_side && density > 2u) {
        density = (uint8_t)(density - 2u);
    }
    return density;
}

static void draw_world_backdrop(const FpsGameState *state, uint8_t *framebuffer)
{
    /* Sparse ceiling motes and a perspective floor make the view readable
     * without pretending the one-bit panel can carry colour or gray pixels. */
    for (int32_t y = 4; y < HORIZON_Y - 5; y += 7) {
        const int32_t phase = (int32_t)((state->player_angle >> 3u) + (uint8_t)y) & 15;
        for (int32_t x = phase; x < (int32_t)OLED_FPS_FB_WIDTH; x += 23) {
            set_pixel(framebuffer, x, y, true);
        }
    }
    for (int32_t x = 0; x < (int32_t)OLED_FPS_FB_WIDTH; x += 2) {
        set_pixel(framebuffer, x, HORIZON_Y, true);
    }
    for (int32_t y = HORIZON_Y + 8; y < VIEW_HEIGHT; y++) {
        const int32_t row = y - HORIZON_Y;
        const int32_t stride = row < 18 ? 17 : (row < 32 ? 11 : 7);
        const int32_t phase = ((state->player_x >> 13) + row * 3) % stride;
        if ((row & 3) == 0 || row == 12 || row == 22 || row == 34 || row == 44) {
            for (int32_t x = phase; x < (int32_t)OLED_FPS_FB_WIDTH; x += stride) {
                set_pixel(framebuffer, x, y, true);
            }
        }
    }
}

static void cast_walls(const FpsGameState *state, uint8_t *framebuffer,
                       int32_t depth[OLED_FPS_FB_WIDTH])
{
    for (int32_t column = 0; column < (int32_t)OLED_FPS_FB_WIDTH; column++) {
        const int32_t offset = (column * FOV_TURN_UNITS)
            / ((int32_t)OLED_FPS_FB_WIDTH - 1) - FOV_TURN_UNITS / 2;
        const uint8_t angle = (uint8_t)(state->player_angle + offset);
        const int32_t ray_x = FPS_SIN_Q15[(uint8_t)(angle + 64u)];
        const int32_t ray_y = FPS_SIN_Q15[angle];
        int32_t map_x = fixed_floor_cell(state->player_x);
        int32_t map_y = fixed_floor_cell(state->player_y);
        int32_t step_x;
        int32_t step_y;
        int32_t side_x;
        int32_t side_y;
        const int32_t delta_x = ray_x == 0
            ? INT32_MAX / 4
            : (int32_t)((INT64_C(1) << 31) / abs_i32(ray_x));
        const int32_t delta_y = ray_y == 0
            ? INT32_MAX / 4
            : (int32_t)((INT64_C(1) << 31) / abs_i32(ray_y));

        if (ray_x < 0) {
            step_x = -1;
            side_x = (int32_t)(((int64_t)(state->player_x
                - (map_x * OLED_FPS_FIXED_ONE)) << 15) / -ray_x);
        } else if (ray_x > 0) {
            step_x = 1;
            side_x = (int32_t)(((int64_t)(((map_x + 1) * OLED_FPS_FIXED_ONE)
                - state->player_x) << 15) / ray_x);
        } else {
            step_x = 0;
            side_x = INT32_MAX / 4;
        }
        if (ray_y < 0) {
            step_y = -1;
            side_y = (int32_t)(((int64_t)(state->player_y
                - (map_y * OLED_FPS_FIXED_ONE)) << 15) / -ray_y);
        } else if (ray_y > 0) {
            step_y = 1;
            side_y = (int32_t)(((int64_t)(((map_y + 1) * OLED_FPS_FIXED_ONE)
                - state->player_y) << 15) / ray_y);
        } else {
            step_y = 0;
            side_y = INT32_MAX / 4;
        }

        uint8_t material = 1u;
        bool y_side = false;
        int32_t distance = MIN_RAY_DISTANCE;
        for (uint8_t step = 0u; step < 48u; step++) {
            if (side_x < side_y) {
                distance = side_x;
                side_x = side_x > INT32_MAX - delta_x ? INT32_MAX : side_x + delta_x;
                map_x += step_x;
                y_side = false;
            } else {
                distance = side_y;
                side_y = side_y > INT32_MAX - delta_y ? INT32_MAX : side_y + delta_y;
                map_y += step_y;
                y_side = true;
            }
            material = fps_game_cell(state, map_x, map_y);
            if (material != 0u) {
                break;
            }
        }
        if (distance < MIN_RAY_DISTANCE) {
            distance = MIN_RAY_DISTANCE;
        }
        depth[column] = distance;
        int32_t height = (int32_t)(((int64_t)VIEW_HEIGHT * 3 * OLED_FPS_FIXED_ONE)
                                   / (2 * (int64_t)distance));
        if (height > VIEW_HEIGHT) {
            height = VIEW_HEIGHT;
        }
        if (height < 1) {
            height = 1;
        }
        const int32_t top = HORIZON_Y - height / 2;
        const uint8_t density = wall_density(material, y_side, distance);
        fill_rect(framebuffer, column, top, 1, height, false);
        for (int32_t y = top; y < top + height; y++) {
            const uint8_t threshold = FPS_BAYER4[((uint32_t)y & 3u) * 4u
                                                  + ((uint32_t)column & 3u)];
            if (threshold < density) {
                set_pixel(framebuffer, column, y, true);
            }
        }
    }
}

enum {
    BILLBOARD_ENEMY = 0,
    BILLBOARD_EXIT = 3,
};

static void draw_billboard(const FpsGameState *state, uint8_t *framebuffer,
                           const int32_t depth[OLED_FPS_FB_WIDTH], fps_fixed_t x,
                           fps_fixed_t y, uint8_t kind, bool hit)
{
    const int32_t dir_x = FPS_SIN_Q15[(uint8_t)(state->player_angle + 64u)];
    const int32_t dir_y = FPS_SIN_Q15[state->player_angle];
    const int64_t dx = (int64_t)x - state->player_x;
    const int64_t dy = (int64_t)y - state->player_y;
    const int32_t forward = q15_floor(dx * dir_x + dy * dir_y);
    const int32_t lateral = q15_floor(dx * -dir_y + dy * dir_x);
    if (forward <= MIN_RAY_DISTANCE || (int64_t)abs_i32(lateral) * 2 > forward) {
        return;
    }
    const int32_t center_x = 64 + (int32_t)(((int64_t)lateral * 92) / forward);
    int32_t size = (int32_t)(((int64_t)58 * OLED_FPS_FIXED_ONE) / forward);
    if (size < 5) {
        size = 5;
    }
    if (size > 74) {
        size = 74;
    }
    const int32_t center_y = HORIZON_Y + size / 5;
    const int32_t half = size / 2;
    for (int32_t px = center_x - half; px <= center_x + half; px++) {
        if (px < 0 || px >= (int32_t)OLED_FPS_FB_WIDTH || forward >= depth[px]) {
            continue;
        }
        for (int32_t py = center_y - half; py <= center_y + half; py++) {
            const int32_t sx = px - center_x;
            const int32_t sy = py - center_y;
            const int32_t third = half > 5 ? half / 3 : 1;
            bool on = false;
            if (kind == BILLBOARD_ENEMY) {
                /* Original horned keep-warden. Bright eyes and a broad body
                 * remain legible even when perspective shrinks it. */
                const int32_t eye = half > 9 ? 1 : 0;
                const bool horns = sy <= -third
                    && sy >= -half && abs_i32(sx) >= third
                    && abs_i32(sx) <= half - (sy + half) / 3;
                const bool head = sy >= -third * 2 && sy <= 0
                    && abs_i32(sx) <= third;
                const bool eyes = sy >= -third && sy <= -third + eye
                    && (abs_i32(sx - third / 2) <= eye
                        || abs_i32(sx + third / 2) <= eye);
                const bool torso = sy > -third / 2 && sy <= third
                    && abs_i32(sx) <= half - abs_i32(sy) / 2;
                const bool arms = sy >= 0 && sy <= third
                    && abs_i32(sx) >= third && abs_i32(sx) <= half;
                const bool legs = sy > third && sy <= half
                    && abs_i32(sx) >= third / 3 && abs_i32(sx) <= third;
                on = horns || eyes || arms || legs
                    || (head && (hit || ((sx + sy) & 1) == 0))
                    || (torso && (hit || ((sx - sy) & 2) == 0));
            } else if (kind == FPS_PICKUP_HEALTH) {
                const int32_t stroke = size > 14 ? 2 : 1;
                on = (abs_i32(sx) <= stroke && abs_i32(sy) <= half * 2 / 3)
                    || (abs_i32(sy) <= stroke && abs_i32(sx) <= half * 2 / 3)
                    || abs_i32(sx) == half || abs_i32(sy) == half;
            } else if (kind == FPS_PICKUP_AMMO) {
                const int32_t cartridge = half > 4 ? half / 3 : 1;
                on = (abs_i32(sx) >= cartridge - 1 && abs_i32(sx) <= cartridge + 1
                      && sy >= -half / 2 && sy <= half)
                    || (abs_i32(sx) <= half * 2 / 3 && abs_i32(sy - half) <= 1);
            } else if (kind == BILLBOARD_EXIT) {
                const bool locked = fps_game_alive_enemies(state) != 0u;
                const int32_t post = half > 8 ? 2 : 1;
                const bool frame = (abs_i32(abs_i32(sx) - half) <= post && sy >= -half)
                    || (abs_i32(sy + half) <= post && abs_i32(sx) <= half);
                const bool rune = locked
                    ? abs_i32(abs_i32(sx) - abs_i32(sy)) <= post
                    : (abs_i32(sx) <= post && sy >= -third && sy <= third);
                on = frame || rune;
            }
            if (on) {
                set_pixel(framebuffer, px, py, true);
            }
        }
    }
}

typedef struct {
    fps_fixed_t x;
    fps_fixed_t y;
    int64_t distance;
    uint8_t kind;
    bool hit;
} RenderEntity;

static void draw_entities(const FpsGameState *state, uint8_t *framebuffer,
                          const int32_t depth[OLED_FPS_FB_WIDTH])
{
    RenderEntity entities[OLED_FPS_MAX_ENEMIES + OLED_FPS_MAX_PICKUPS + 1u];
    uint8_t count = 0u;
    for (uint8_t i = 0u; i < state->pickup_count; i++) {
        const FpsPickup *pickup = &state->pickups[i];
        if (pickup->active) {
            entities[count++] = (RenderEntity){
                pickup->x, pickup->y,
                (int64_t)(pickup->x - state->player_x) * (pickup->x - state->player_x)
                    + (int64_t)(pickup->y - state->player_y) * (pickup->y - state->player_y),
                pickup->kind, false,
            };
        }
    }
    for (uint8_t i = 0u; i < state->enemy_count; i++) {
        const FpsEnemy *enemy = &state->enemies[i];
        if (enemy->alive) {
            entities[count++] = (RenderEntity){
                enemy->x, enemy->y,
                (int64_t)(enemy->x - state->player_x) * (enemy->x - state->player_x)
                    + (int64_t)(enemy->y - state->player_y) * (enemy->y - state->player_y),
                BILLBOARD_ENEMY, enemy->hit_ticks > 0u,
            };
        }
    }
    const FpsLevel *level = fps_content_level(state->level_id);
    entities[count++] = (RenderEntity){
        level->exit_x, level->exit_y,
        (int64_t)(level->exit_x - state->player_x) * (level->exit_x - state->player_x)
            + (int64_t)(level->exit_y - state->player_y) * (level->exit_y - state->player_y),
        BILLBOARD_EXIT, false,
    };
    /* Painter's order for overlapping sprites; the wall depth buffer remains
     * authoritative per column. The fixed inventory keeps this tiny. */
    for (uint8_t first = 0u; first < count; first++) {
        uint8_t farthest = first;
        for (uint8_t next = (uint8_t)(first + 1u); next < count; next++) {
            if (entities[next].distance > entities[farthest].distance) {
                farthest = next;
            }
        }
        const RenderEntity swap = entities[first];
        entities[first] = entities[farthest];
        entities[farthest] = swap;
        draw_billboard(state, framebuffer, depth, entities[first].x, entities[first].y,
                       entities[first].kind, entities[first].hit);
    }
}

static void draw_hud(const FpsGameState *state, uint8_t *framebuffer)
{
    fill_rect(framebuffer, 0, VIEW_HEIGHT, OLED_FPS_FB_WIDTH,
              OLED_FPS_FB_HEIGHT - VIEW_HEIGHT, false);
    for (int32_t x = 0; x < (int32_t)OLED_FPS_FB_WIDTH; x += 2) {
        set_pixel(framebuffer, x, VIEW_HEIGHT, true);
    }
    draw_char(framebuffer, 2, 107, 'H');
    draw_u8_3(framebuffer, 7, 107, state->health);
    draw_char(framebuffer, 39, 107, 'A');
    draw_u8_3(framebuffer, 44, 107, state->ammo);
    draw_char(framebuffer, 78, 107, 'K');
    draw_char(framebuffer, 83, 107, (char)('0' + state->kills));
    draw_char(framebuffer, 87, 107, '/');
    draw_char(framebuffer, 91, 107, (char)('0' + state->enemy_count));
    draw_text(framebuffer, 2, 119,
              fps_game_alive_enemies(state) == 0u ? "GATE OPEN" : "GATE LOCKED");
    draw_text(framebuffer, 92, 119, "WASD");
}

static void draw_title(uint8_t *framebuffer)
{
    for (int32_t x = 5; x < 123; x++) {
        set_pixel(framebuffer, x, 5, true);
        set_pixel(framebuffer, x, 75, true);
    }
    for (int32_t y = 5; y <= 75; y++) {
        set_pixel(framebuffer, 5, y, true);
        set_pixel(framebuffer, 122, y, true);
    }
    draw_text_scaled(framebuffer, centered_text_x("SPLIT", 2), 13, "SPLIT", 2);
    draw_text_scaled(framebuffer, centered_text_x("KEEP", 2), 27, "KEEP", 2);
    /* The same original warden silhouette as the game, enlarged into a title
     * crest so the objective is obvious before the first input. */
    for (int32_t y = 43; y <= 68; y++) {
        const int32_t dy = y - 55;
        for (int32_t x = 49; x <= 79; x++) {
            const int32_t dx = x - 64;
            const bool horns = y < 50 && abs_i32(dx) >= 7 && abs_i32(dx) <= 14;
            const bool eyes = y >= 51 && y <= 53 && (abs_i32(dx - 5) <= 1 || abs_i32(dx + 5) <= 1);
            const bool body = y >= 49 && y <= 64 && abs_i32(dx) <= 11 - abs_i32(dy) / 3;
            const bool legs = y > 63 && (abs_i32(dx - 5) <= 2 || abs_i32(dx + 5) <= 2);
            if (horns || eyes || legs || (body && ((x + y) & 1) == 0)) {
                set_pixel(framebuffer, x, y, true);
            }
        }
    }
    draw_text(framebuffer, centered_text_x("KILL 5 WARDENS", 1), 82, "KILL 5 WARDENS");
    draw_text(framebuffer, centered_text_x("WASD MOVE TURN", 1), 94, "WASD MOVE TURN");
    draw_text(framebuffer, centered_text_x("SPACE OR CAP FIRE", 1), 105, "SPACE OR CAP FIRE");
    draw_text(framebuffer, centered_text_x("PRESS FIRE", 1), 119, "PRESS FIRE");
}

static void draw_end(uint8_t *framebuffer, bool won)
{
    draw_text_scaled(framebuffer, centered_text_x(won ? "KEEP CLEAR" : "YOU FELL", 2),
                     18, won ? "KEEP CLEAR" : "YOU FELL", 2);
    if (won) {
        for (int32_t y = 48; y < 82; y++) {
            const int32_t half = (y - 48) / 2;
            set_pixel(framebuffer, 64 - half, y, true);
            set_pixel(framebuffer, 64 + half, y, true);
        }
    } else {
        for (int32_t i = 0; i < 25; i++) {
            set_pixel(framebuffer, 52 + i, 48 + i, true);
            set_pixel(framebuffer, 76 - i, 48 + i, true);
        }
    }
    draw_text(framebuffer, centered_text_x(won ? "WARDENS 5/5" : "TRY THE SOUTH PATH", 1),
              94, won ? "WARDENS 5/5" : "TRY THE SOUTH PATH");
    draw_text(framebuffer, centered_text_x("SPACE OR CAP", 1), 116, "SPACE OR CAP");
}

static void draw_weapon_and_feedback(const FpsGameState *state, uint8_t *framebuffer)
{
    /* Chunky first-person repeater: outline, twin grip rails and sight. */
    for (int32_t y = 82; y < VIEW_HEIGHT; y++) {
        const int32_t half = 3 + (y - 82) / 2;
        set_pixel(framebuffer, 64 - half, y, true);
        set_pixel(framebuffer, 64 + half, y, true);
        if (y >= 91 && ((y + state->tick) & 2u) == 0u) {
            set_pixel(framebuffer, 61, y, true);
            set_pixel(framebuffer, 67, y, true);
        }
    }
    fill_rect(framebuffer, 62, 78, 5, 8, true);
    fill_rect(framebuffer, 63, 76, 3, 4, false);
    set_pixel(framebuffer, 64, 75, true);

    if (state->muzzle_ticks > 0u) {
        for (int32_t ray = -5; ray <= 5; ray++) {
            set_pixel(framebuffer, 64 + ray, 73 - abs_i32(ray), true);
            if ((ray & 1) == 0) {
                set_pixel(framebuffer, 64 + ray / 2, 68 + abs_i32(ray) / 2, true);
            }
        }
    }
    if (state->hit_ticks > 0u) {
        for (int32_t i = 0; i < 6; i++) {
            set_pixel(framebuffer, 58 + i, 45 + i, true);
            set_pixel(framebuffer, 70 - i, 45 + i, true);
            set_pixel(framebuffer, 58 + i, 56 - i, true);
            set_pixel(framebuffer, 70 - i, 56 - i, true);
        }
    } else {
        for (int32_t i = -3; i <= 3; i++) {
            if (i < -1 || i > 1) {
                set_pixel(framebuffer, 64 + i, HORIZON_Y, true);
                set_pixel(framebuffer, 64, HORIZON_Y + i, true);
            }
        }
    }
    if (state->hurt_ticks > 0u) {
        for (int32_t i = 0; i < 18; i++) {
            set_pixel(framebuffer, i, i / 2, true);
            set_pixel(framebuffer, 127 - i, i / 2, true);
            set_pixel(framebuffer, i, VIEW_HEIGHT - 1 - i / 2, true);
            set_pixel(framebuffer, 127 - i, VIEW_HEIGHT - 1 - i / 2, true);
        }
    }
}

void fps_render_frame(const FpsGameState *state, uint8_t framebuffer[OLED_FPS_FB_SIZE])
{
    memset(framebuffer, 0, OLED_FPS_FB_SIZE);
    if (state->mode == FPS_MODE_TITLE) {
        draw_title(framebuffer);
        return;
    }
    if (state->mode == FPS_MODE_WIN) {
        draw_end(framebuffer, true);
        return;
    }
    if (state->mode == FPS_MODE_DEATH) {
        draw_end(framebuffer, false);
        return;
    }

    int32_t depth[OLED_FPS_FB_WIDTH];
    draw_world_backdrop(state, framebuffer);
    cast_walls(state, framebuffer, depth);
    draw_entities(state, framebuffer, depth);
    draw_weapon_and_feedback(state, framebuffer);
    draw_hud(state, framebuffer);
}

uint32_t fps_frame_crc32(const uint8_t framebuffer[OLED_FPS_FB_SIZE])
{
    uint32_t crc = UINT32_C(0xffffffff);
    for (size_t i = 0u; i < OLED_FPS_FB_SIZE; i++) {
        crc ^= framebuffer[i];
        for (uint8_t bit = 0u; bit < 8u; bit++) {
            const uint32_t mask = UINT32_C(0) - (crc & 1u);
            crc = (crc >> 1u) ^ (UINT32_C(0xedb88320) & mask);
        }
    }
    return ~crc;
}

main/render.h

/* First-party MIT-licensed integer OLED renderer for BRE-209. */
#ifndef OLED_FPS_RENDER_H
#define OLED_FPS_RENDER_H

#include <stdint.h>

#include "game.h"

void fps_render_frame(const FpsGameState *state, uint8_t framebuffer[OLED_FPS_FB_SIZE]);
uint32_t fps_frame_crc32(const uint8_t framebuffer[OLED_FPS_FB_SIZE]);

#endif

main/generated_tables.h

/* GENERATED by scripts/generate_tables.mjs. DO NOT EDIT.
 * Mathematical lookup data only; first-party generator licensed MIT. */
#ifndef OLED_FPS_GENERATED_TABLES_H
#define OLED_FPS_GENERATED_TABLES_H

#include <stdint.h>

static const int16_t FPS_SIN_Q15[256] = {
    0, 804, 1608, 2410, 3212, 4011, 4808, 5602, 6393, 7179, 7962, 8739, 9512, 10278, 11039, 11793,
    12539, 13279, 14010, 14732, 15446, 16151, 16846, 17530, 18204, 18868, 19519, 20159, 20787, 21403, 22005, 22594,
    23170, 23731, 24279, 24811, 25329, 25832, 26319, 26790, 27245, 27683, 28105, 28510, 28898, 29268, 29621, 29956,
    30273, 30571, 30852, 31113, 31356, 31580, 31785, 31971, 32137, 32285, 32412, 32521, 32609, 32678, 32728, 32757,
    32767, 32757, 32728, 32678, 32609, 32521, 32412, 32285, 32137, 31971, 31785, 31580, 31356, 31113, 30852, 30571,
    30273, 29956, 29621, 29268, 28898, 28510, 28105, 27683, 27245, 26790, 26319, 25832, 25329, 24811, 24279, 23731,
    23170, 22594, 22005, 21403, 20787, 20159, 19519, 18868, 18204, 17530, 16846, 16151, 15446, 14732, 14010, 13279,
    12539, 11793, 11039, 10278, 9512, 8739, 7962, 7179, 6393, 5602, 4808, 4011, 3212, 2410, 1608, 804,
    0, -804, -1608, -2410, -3212, -4011, -4808, -5602, -6393, -7179, -7962, -8739, -9512, -10278, -11039, -11793,
    -12539, -13279, -14010, -14732, -15446, -16151, -16846, -17530, -18204, -18868, -19519, -20159, -20787, -21403, -22005, -22594,
    -23170, -23731, -24279, -24811, -25329, -25832, -26319, -26790, -27245, -27683, -28105, -28510, -28898, -29268, -29621, -29956,
    -30273, -30571, -30852, -31113, -31356, -31580, -31785, -31971, -32137, -32285, -32412, -32521, -32609, -32678, -32728, -32757,
    -32767, -32757, -32728, -32678, -32609, -32521, -32412, -32285, -32137, -31971, -31785, -31580, -31356, -31113, -30852, -30571,
    -30273, -29956, -29621, -29268, -28898, -28510, -28105, -27683, -27245, -26790, -26319, -25832, -25329, -24811, -24279, -23731,
    -23170, -22594, -22005, -21403, -20787, -20159, -19519, -18868, -18204, -17530, -16846, -16151, -15446, -14732, -14010, -13279,
    -12539, -11793, -11039, -10278, -9512, -8739, -7962, -7179, -6393, -5602, -4808, -4011, -3212, -2410, -1608, -804,
};

static const uint8_t FPS_BAYER4[16] = {
    0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5,
};

#endif

scripts/generate_tables.mjs

#!/usr/bin/env node
/*
 * Generates the only lookup data used by the portable game core. The values
 * are derived mathematical data, not third-party game assets. License: MIT.
 */
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

const target = fileURLToPath(new URL('../main/generated_tables.h', import.meta.url));
const sine = Array.from({ length: 256 }, (_, index) =>
  Math.round(Math.sin((index * Math.PI) / 128) * 32767),
);
const bayer4 = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];

const rows = (values, width) => {
  const out = [];
  for (let offset = 0; offset < values.length; offset += width) {
    out.push(`    ${values.slice(offset, offset + width).join(', ')},`);
  }
  return out.join('\n');
};

const generated = `/* GENERATED by scripts/generate_tables.mjs. DO NOT EDIT.\n * Mathematical lookup data only; first-party generator licensed MIT. */\n#ifndef OLED_FPS_GENERATED_TABLES_H\n#define OLED_FPS_GENERATED_TABLES_H\n\n#include <stdint.h>\n\nstatic const int16_t FPS_SIN_Q15[256] = {\n${rows(sine, 16)}\n};\n\nstatic const uint8_t FPS_BAYER4[16] = {\n${rows(bayer4, 16)}\n};\n\n#endif\n`;

if (process.argv.includes('--write')) {
  writeFileSync(target, generated);
  process.stdout.write(`wrote ${target}\n`);
} else {
  let actual = '';
  try {
    actual = readFileSync(target, 'utf8');
  } catch {
    // The diagnostic below covers a missing file without hiding the cause.
  }
  if (actual !== generated) {
    process.stderr.write('generated_tables.h is stale; run generate_tables.mjs --write\n');
    process.exitCode = 1;
  } else {
    process.stdout.write('generated_tables.h is synchronized\n');
  }
}

scripts/finalize_spdx.py

#!/usr/bin/env python3
"""Canonicalize and fail-closed scan the OLED FPS linked-result SPDX file.

The upstream esp-idf-sbom tool supplies the ESP-IDF component graph. This
small wrapper makes volatile document metadata reproducible, grounds the two
NOASSERTION package records that are known locally (the pinned IDF framework
and this first-party component), grounds the bootloader-only micro-ecc record
in its exact pinned BSD-2-Clause license, adds checksummed first-party file
records, and preserves the raw NOASSERTION finding as an explicit resolved
warning. It does not remove packages or relationships from the tool output.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import re
from pathlib import Path


IDF_COMMIT = "0c45a102a96e82000f145b786a20de229b9f8557"
IDF_LICENSE_SHA256 = "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"
IDF_LICENSE_BYTES = 11358
MICRO_ECC_COMMIT = "24c60e243580c7868f4334a1ba3123481fe1aa48"
MICRO_ECC_LICENSE_SHA256 = "ffd8b033d2df7568c25a98866bd92b1656f7da82d7f813c0b2eb85ec36611193"
MICRO_ECC_LICENSE_BYTES = 1300
PROJECT_ID = "SPDXRef-PROJECT-oled-fps-s3"
FRAMEWORK_ID = "SPDXRef-FRAMEWORK-esp-idf"
MAIN_ID = "SPDXRef-COMPONENT-main"
BOOTLOADER_ID = "SPDXRef-COMPONENT-bootloader"
MICRO_ECC_ID = "SPDXRef-SUBMODULE-bootloader-subproject-components-micro-ecc-micro-ecc"
GPL = re.compile(r"(?:^|[^A-Z])(A?GPL|LGPL)-", re.IGNORECASE)

# These are the exact expressions emitted for this linked result. CC0-1.0
# and Unlicense are established public-domain-dedication precedents in
# docs/internal/dependency-audit-2026-08-09.md; the exact ESP-IDF toolchain
# expressions are already retained in packages/compile/toolchain-idf/sbom/.
# Exact matching makes a dependency-license change fail closed.
APPROVED_LICENSE_EXPRESSIONS = {
    "Apache-2.0",
    "Apache-2.0 AND (CC0-1.0 OR Unlicense)",
    "Apache-2.0 AND BSD-2-Clause",
    "Apache-2.0 AND BSD-3-Clause",
    "Apache-2.0 AND BSD-3-Clause AND CC0-1.0",
    "Apache-2.0 AND BSD-3-Clause AND CC0-1.0 AND MIT",
    "Apache-2.0 AND CC0-1.0",
    "Apache-2.0 AND ISC",
    "Apache-2.0 AND MIT",
    "Apache-2.0 WITH LLVM-exception AND BSD-2-Clause-Views AND BSD-3-Clause",
    "BSD-2-Clause",
    "BSD-3-Clause",
    "MIT",
}

MICRO_ECC_WARNING = (
    "unlinked SPDX inventory warning: "
    f"{MICRO_ECC_ID} (micro-ecc) has PackageLicenseConcluded NOASSERTION; "
    f"{BOOTLOADER_ID} DEPENDS_ON it, but neither package is reachable from "
    f"{PROJECT_ID} via DEPENDS_ON; it is bootloader-only inventory and is not "
    "linked into game.bin. The complete SBOM inventory is not unknown-license clean."
)

# Exact first-party inputs that determine the application image or its
# generated lookup table. Host tests, diagnostics, docs, and this release tool
# are deliberately not firmware inputs.
SOURCE_INPUTS = (
    "CMakeLists.txt",
    "LICENSE",
    "main/CMakeLists.txt",
    "main/Kconfig.projbuild",
    "main/app_main.c",
    "main/content.c",
    "main/content.h",
    "main/display_sh1107.c",
    "main/display_sh1107.h",
    "main/game.c",
    "main/game.h",
    "main/generated_tables.h",
    "main/input.c",
    "main/input.h",
    "main/render.c",
    "main/render.h",
    "partitions.csv",
    "scripts/generate_tables.mjs",
    "sdkconfig.defaults",
)


def digest(path: Path, algorithm: str = "sha256") -> str:
    value = hashlib.new(algorithm)
    with path.open("rb") as source:
        while chunk := source.read(1024 * 1024):
            value.update(chunk)
    return value.hexdigest()


def replace_tag(block: str, tag: str, value: str) -> str:
    pattern = re.compile(rf"^{re.escape(tag)}: .*?$", re.MULTILINE)
    if not pattern.search(block):
        raise SystemExit(f"SPDX block lacks required {tag} tag")
    return pattern.sub(f"{tag}: {value}", block, count=1)


def insert_before_relationships(block: str, line: str) -> str:
    marker = "\nRelationship:"
    index = block.find(marker)
    if index < 0:
        return block.rstrip() + "\n" + line
    return block[:index] + "\n" + line + block[index:]


def package_id(block: str) -> str | None:
    if "PackageName:" not in block:
        return None
    match = re.search(r"^SPDXID: (SPDXRef-\S+)$", block, re.MULTILINE)
    return match.group(1) if match else None


def file_id(path: str) -> str:
    clean = re.sub(r"[^A-Za-z0-9.-]+", "-", path).strip("-")
    return f"SPDXRef-FILE-oled-fps-{clean}"


def source_file_blocks(root: Path) -> tuple[list[str], list[str]]:
    blocks: list[str] = []
    sha1_values: list[str] = []
    for relative in SOURCE_INPUTS:
        path = root / relative
        if not path.is_file() or path.is_symlink():
            raise SystemExit(f"firmware input missing or unsafe: {relative}")
        if GPL.search(path.read_text(encoding="utf-8")):
            raise SystemExit(f"GPL-family identifier in first-party firmware input: {relative}")
        sha1 = digest(path, "sha1")
        sha256 = digest(path)
        sha1_values.append(sha1)
        blocks.append(
            "\n".join(
                (
                    f"FileName: ./{relative}",
                    f"SPDXID: {file_id(relative)}",
                    f"FileChecksum: SHA1: {sha1}",
                    f"FileChecksum: SHA256: {sha256}",
                    "LicenseConcluded: MIT",
                    "LicenseInfoInFile: MIT",
                    "FileCopyrightText: <text>Copyright (c) 2026 Murray Stott</text>",
                )
            )
        )
    return blocks, sha1_values


def normalize_relationships(block: str) -> str:
    lines = block.rstrip().splitlines()
    relationships = sorted(line for line in lines if line.startswith("Relationship: "))
    lines = [line for line in lines if not line.startswith("Relationship: ")]
    return "\n".join(lines + relationships)


def parse_packages(blocks: list[str]) -> dict[str, dict[str, str]]:
    packages: dict[str, dict[str, str]] = {}
    for block in blocks:
        identifier = package_id(block)
        if identifier is None:
            continue
        name = re.search(r"^PackageName: (.+)$", block, re.MULTILINE)
        license_value = re.search(
            r"^PackageLicenseConcluded: (.+)$", block, re.MULTILINE
        )
        if not name or not license_value:
            raise SystemExit(f"incomplete SPDX package block: {identifier}")
        if identifier in packages:
            raise SystemExit(f"duplicate SPDX package id: {identifier}")
        packages[identifier] = {
            "spdxId": identifier,
            "name": name.group(1),
            "licenseConcluded": license_value.group(1),
        }
    return packages


def license_summary(blocks: list[str]) -> dict[str, object]:
    packages = parse_packages(blocks)
    if PROJECT_ID not in packages:
        raise SystemExit("SPDX project package is missing")
    edges: dict[str, set[str]] = {}
    for block in blocks:
        for source, relation, target in re.findall(
            r"^Relationship: (SPDXRef-\S+) (DEPENDS_ON|CONTAINS) (SPDXRef-\S+)$",
            block,
            re.MULTILINE,
        ):
            if relation != "DEPENDS_ON":
                continue
            if source not in packages:
                raise SystemExit(f"dangling DEPENDS_ON source: {source}")
            if target not in packages:
                raise SystemExit(f"dangling DEPENDS_ON target: {target}")
            edges.setdefault(source, set()).add(target)

    reachable = {PROJECT_ID}
    pending = [PROJECT_ID]
    while pending:
        source = pending.pop()
        for target in edges.get(source, set()):
            if target not in reachable:
                reachable.add(target)
                pending.append(target)

    summary = [packages[identifier] for identifier in sorted(reachable)]
    for package in summary:
        license_value = package["licenseConcluded"]
        if license_value in {"", "NONE", "NOASSERTION"}:
            raise SystemExit(
                "unknown license in linked closure: "
                f"{package['spdxId']} ({package['name']})"
            )
        if GPL.search(license_value):
            raise SystemExit(
                "GPL-family license in linked closure: "
                f"{package['spdxId']} ({license_value})"
            )
        if license_value not in APPROVED_LICENSE_EXPRESSIONS:
            raise SystemExit(
                "unreviewed license expression in linked closure: "
                f"{package['spdxId']} ({license_value})"
            )

    unknown_inventory: list[dict[str, str]] = []
    for identifier, package in sorted(packages.items()):
        license_value = package["licenseConcluded"]
        if GPL.search(license_value):
            raise SystemExit(
                "GPL-family license in complete SPDX inventory: "
                f"{identifier} ({license_value})"
            )
        if license_value in {"", "NONE", "NOASSERTION"}:
            unknown_inventory.append(package)
        elif license_value not in APPROVED_LICENSE_EXPRESSIONS:
            raise SystemExit(
                "unreviewed license expression in complete SPDX inventory: "
                f"{identifier} ({license_value})"
            )

    if unknown_inventory:
        raise SystemExit(
            "unexpected unknown-license inventory: "
            + ", ".join(package["spdxId"] for package in unknown_inventory)
        )
    if packages.get(MICRO_ECC_ID, {}).get("licenseConcluded") != "BSD-2-Clause":
        raise SystemExit("micro-ecc canonical license conclusion is not BSD-2-Clause")
    if MICRO_ECC_ID in reachable or BOOTLOADER_ID in reachable:
        raise SystemExit("bootloader-only micro-ecc unexpectedly reached game.bin closure")
    if MICRO_ECC_ID not in edges.get(BOOTLOADER_ID, set()):
        raise SystemExit("micro-ecc bootloader-only reachability evidence is missing")

    return {
        "schemaVersion": 1,
        "inventoryPackageCount": len(packages),
        "linkedClosure": summary,
        "warnings": [
            {
                "code": "RAW_UNLINKED_UNKNOWN_LICENSE_RESOLVED",
                "message": MICRO_ECC_WARNING,
                "spdxId": MICRO_ECC_ID,
                "name": "micro-ecc",
                "rawLicenseConcluded": "NOASSERTION",
                "canonicalLicenseConcluded": "BSD-2-Clause",
                "resolution": (
                    f"Pinned micro-ecc {MICRO_ECC_COMMIT} LICENSE.txt; "
                    f"{MICRO_ECC_LICENSE_BYTES} bytes; SHA-256 "
                    f"{MICRO_ECC_LICENSE_SHA256}."
                ),
                "reachableFromProject": False,
                "incomingDependency": f"{BOOTLOADER_ID} DEPENDS_ON {MICRO_ECC_ID}",
            }
        ],
    }


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", required=True, type=Path)
    parser.add_argument("--output", required=True, type=Path)
    parser.add_argument("--summary-output", type=Path)
    parser.add_argument("--project-root", required=True, type=Path)
    parser.add_argument("--idf-license", required=True, type=Path)
    parser.add_argument("--micro-ecc-license", required=True, type=Path)
    args = parser.parse_args()

    if args.idf_license.stat().st_size != IDF_LICENSE_BYTES:
        raise SystemExit("pinned ESP-IDF LICENSE byte size changed")
    if digest(args.idf_license) != IDF_LICENSE_SHA256:
        raise SystemExit("pinned ESP-IDF LICENSE SHA-256 changed")
    if args.micro_ecc_license.stat().st_size != MICRO_ECC_LICENSE_BYTES:
        raise SystemExit("pinned micro-ecc LICENSE.txt byte size changed")
    if digest(args.micro_ecc_license) != MICRO_ECC_LICENSE_SHA256:
        raise SystemExit("pinned micro-ecc LICENSE.txt SHA-256 changed")

    source = args.input.read_text(encoding="utf-8")
    if "SPDXVersion: SPDX-2.2" not in source or "DocumentName: oled-fps-s3" not in source:
        raise SystemExit("unexpected SPDX document identity")
    if f"git+https://github.com/espressif/esp-idf.git@{IDF_COMMIT}" not in source:
        raise SystemExit("SPDX document does not pin the required ESP-IDF commit")

    raw_blocks = source.rstrip().split("\n\n")
    header_blocks: list[str] = []
    entity_blocks: list[str] = []
    for block in raw_blocks:
        if "PackageName:" in block or "FileName:" in block:
            entity_blocks.append(block)
        else:
            header_blocks.append(block)

    # The tool command, UUID, and wall clock are not dependency evidence.
    if not header_blocks or not header_blocks[0].startswith("# Generated by esp-idf-sbom 1.2.0"):
        raise SystemExit("unexpected esp-idf-sbom generator header")
    header_blocks[0] = (
        "# Generated by esp-idf-sbom 1.2.0 from the pinned linked-result metadata; "
        "canonicalized by scripts/finalize_spdx.py"
    )
    document_index = next(
        (i for i, block in enumerate(header_blocks) if "SPDXID: SPDXRef-DOCUMENT" in block),
        None,
    )
    if document_index is None:
        raise SystemExit("SPDX document header is missing")
    document = replace_tag(
        header_blocks[document_index],
        "DocumentNamespace",
        "https://breadboard.build/spdx/esp32s3-oled-fps/idf-v5.3.5",
    )
    document = replace_tag(document, "Created", "2026-08-30T00:00:00Z")
    header_blocks[document_index] = document

    source_blocks, source_sha1 = source_file_blocks(args.project_root)
    verification = hashlib.sha1("".join(sorted(source_sha1)).encode("ascii")).hexdigest()

    found: set[str] = set()
    normalized_entities: list[str] = []
    for original in entity_blocks:
        block = original
        identifier = package_id(block)
        if identifier == FRAMEWORK_ID:
            found.add(identifier)
            block = replace_tag(block, "PackageLicenseConcluded", "Apache-2.0")
            block = replace_tag(block, "PackageLicenseDeclared", "Apache-2.0")
            block = insert_before_relationships(
                block,
                "PackageComment: <text>esp-idf-sbom 1.2.0 reported NOASSERTION for "
                f"the framework container. The pinned {IDF_COMMIT} checkout's root "
                f"LICENSE is Apache-2.0, {IDF_LICENSE_BYTES} bytes, SHA-256 "
                f"{IDF_LICENSE_SHA256}.</text>",
            )
        elif identifier == MAIN_ID:
            found.add(identifier)
            block = replace_tag(block, "FilesAnalyzed", "true")
            block = insert_before_relationships(
                block, f"PackageVerificationCode: {verification}"
            )
            block = replace_tag(block, "PackageLicenseConcluded", "MIT")
            block = replace_tag(block, "PackageLicenseDeclared", "MIT")
            block = replace_tag(
                block,
                "PackageCopyrightText",
                "<text>Copyright (c) 2026 Murray Stott</text>",
            )
            block = insert_before_relationships(
                block,
                "PackageComment: <text>Original first-party OLED dungeon source and "
                "geometric content. No third-party game code or assets.</text>",
            )
            for relative in SOURCE_INPUTS:
                block += f"\nRelationship: {MAIN_ID} CONTAINS {file_id(relative)}"
            block += (
                f"\nRelationship: {file_id('main/generated_tables.h')} GENERATED_FROM "
                f"{file_id('scripts/generate_tables.mjs')}"
            )
        elif identifier == MICRO_ECC_ID:
            found.add(identifier)
            if "PackageVersion: 1.1" not in block or MICRO_ECC_COMMIT not in block:
                raise SystemExit("unexpected pinned micro-ecc package identity")
            if "PackageLicenseConcluded: NOASSERTION" not in block:
                raise SystemExit("raw micro-ecc NOASSERTION finding is missing")
            block = replace_tag(block, "PackageLicenseConcluded", "BSD-2-Clause")
            block = replace_tag(block, "PackageLicenseDeclared", "BSD-2-Clause")
            block = replace_tag(
                block,
                "PackageCopyrightText",
                "<text>Copyright (c) 2014, Kenneth MacKay</text>",
            )
            block = insert_before_relationships(
                block,
                f"PackageComment: <text>{MICRO_ECC_WARNING} Resolution: Pinned "
                f"micro-ecc {MICRO_ECC_COMMIT} LICENSE.txt; "
                f"{MICRO_ECC_LICENSE_BYTES} bytes; SHA-256 "
                f"{MICRO_ECC_LICENSE_SHA256}.</text>",
            )
        normalized_entities.append(normalize_relationships(block))

    if found != {FRAMEWORK_ID, MAIN_ID, MICRO_ECC_ID}:
        raise SystemExit(f"expected license augmentation packages missing: {sorted(found)}")
    normalized_entities.extend(source_blocks)
    normalized_entities.sort(
        key=lambda block: re.search(r"^SPDXID: (\S+)$", block, re.MULTILINE).group(1)
    )

    complete_blocks = header_blocks + normalized_entities
    output = "\n\n".join(complete_blocks).rstrip() + "\n"
    if re.search(r"/(?:Users|private/tmp|tmp)/", output):
        raise SystemExit("canonical SPDX still contains an absolute local path")

    summary = license_summary(complete_blocks)
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(output, encoding="utf-8")
    if args.summary_output:
        args.summary_output.write_text(
            json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8"
        )
    linked_count = len(summary["linkedClosure"])
    print(
        f"linked SPDX closure: {linked_count} packages; complete canonical inventory "
        "reviewed-expression/GPL/unknown scan clean"
    )
    print(f"WARNING (raw finding resolved in canonical SPDX): {MICRO_ECC_WARNING}")


if __name__ == "__main__":
    main()

scripts/scan_source_licenses.py

#!/usr/bin/env python3
"""Inventory and fail-closed license-scan the complete OLED FPS source tree.

The project is a deliberately small first-party MIT tree.  This scanner walks
every source-tree entry (including host tests, diagnostics, docs, generators,
and release tools), rejects links/non-regular files, records content hashes,
and fails on third-party/dependency directory markers or copyleft-family
license declarations.  The exact rejection-gate implementation lines in the
SPDX finalizer are pinned by line-content hash and count; any additional match,
including pasted licensed material, fails the scan.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import os
import re
from collections import Counter
from pathlib import Path


MIT_LICENSE_SHA256 = "a1bb8ae1dc6cf542f0624f3ec62fa7e7747c18bbd33dfff1127432f1fb61e487"
MIT_LICENSE_BYTES = 1069
FORBIDDEN_DIRECTORY_NAMES = {
    ".git",
    "build",
    "components",
    "managed_components",
    "node_modules",
    "third_party",
    "third-party",
    "vendor",
}
ALLOWED_SPECIAL_NAMES = {
    "CMakeLists.txt",
    "Kconfig.projbuild",
    "LICENSE",
    "README.md",
    "partitions.csv",
    "sdkconfig.defaults",
    "sdkconfig.diagnostic",
}
ALLOWED_SUFFIXES = {".c", ".h", ".md", ".mjs", ".py", ".txt"}
COPYLEFT_FAMILY = re.compile(
    r"(?<![A-Za-z])(?:A?G" r"PL|LG" r"PL)(?:v?[0-9.]+)?(?![A-Za-z])",
    re.IGNORECASE,
)
DECLARATION = re.compile(
    r"(?:SPDX-License-Identifier|License)\s*:\s*([^\r\n*]+)", re.IGNORECASE
)
EXPECTED_COPYLEFT_LINE_HASHES = {
    ("scripts/finalize_spdx.py", "b3aea5624238b21d40daf259758d3266d1df4b14c9591e2503f7effd8c4de5f4"): 1,
    ("scripts/finalize_spdx.py", "553c343b94c151e1da9535f6c0f1225fc97003b56dc1b275cba7884e87ab53a8"): 1,
    ("scripts/finalize_spdx.py", "3c8608c505ad8f9de479e0e24149e78effe791067e427245be8dd90691adc14a"): 1,
    ("scripts/finalize_spdx.py", "79b634005c4cec50c9e8598d8d9c089b02894cbad17d1b1e82cc56f3dcf41ae6"): 2,
    ("scripts/finalize_spdx.py", "6881028cbb3ff91477536de9814f56cae6606b13bf89a51251ad80875e8232ec"): 1,
    ("scripts/finalize_spdx.py", "d149d4a3f22d87caf2f8a27b0b05acba0769874493ab03713c4df56429b1c35f"): 1,
    ("scripts/finalize_spdx.py", "d27d220323b98f5fea85ae5f6ed3b6b33df741c1e5cc24f8b934c4fd625fd497"): 1,
}


def digest(path: Path) -> str:
    value = hashlib.sha256()
    with path.open("rb") as source:
        while chunk := source.read(1024 * 1024):
            value.update(chunk)
    return value.hexdigest()


def tree_hash(files: list[dict[str, object]]) -> str:
    value = hashlib.sha256()
    for record in files:
        value.update(str(record["path"]).encode("utf-8"))
        value.update(b"\0")
        value.update(str(record["sha256"]).encode("ascii"))
        value.update(b"\0")
        value.update(str(record["bytes"]).encode("ascii"))
        value.update(b"\n")
    return value.hexdigest()


def scan(root: Path) -> dict[str, object]:
    root = root.resolve(strict=True)
    license_path = root / "LICENSE"
    if license_path.stat().st_size != MIT_LICENSE_BYTES or digest(license_path) != MIT_LICENSE_SHA256:
        raise SystemExit("adjacent MIT LICENSE bytes/hash changed")

    records: list[dict[str, object]] = []
    copyleft_mentions: list[dict[str, object]] = []
    copyleft_line_counts: Counter[tuple[str, str]] = Counter()
    declarations: list[dict[str, str]] = []
    for directory, dirnames, filenames in os.walk(root, followlinks=False):
        directory_path = Path(directory)
        for name in sorted(dirnames):
            child = directory_path / name
            relative = child.relative_to(root).as_posix()
            if child.is_symlink():
                raise SystemExit(f"source tree contains directory symlink: {relative}")
            if name in FORBIDDEN_DIRECTORY_NAMES:
                raise SystemExit(f"source tree contains forbidden dependency/build directory: {relative}")
        for name in sorted(filenames):
            path = directory_path / name
            relative = path.relative_to(root).as_posix()
            if path.is_symlink() or not path.is_file():
                raise SystemExit(f"source tree contains unsafe entry: {relative}")
            if name not in ALLOWED_SPECIAL_NAMES and path.suffix not in ALLOWED_SUFFIXES:
                raise SystemExit(f"source tree contains unreviewed file type: {relative}")
            raw = path.read_bytes()
            if b"\0" in raw:
                raise SystemExit(f"source tree contains unexpected binary data: {relative}")
            try:
                text = raw.decode("utf-8")
            except UnicodeDecodeError as error:
                raise SystemExit(f"source tree contains non-UTF-8 text: {relative}") from error

            found_declarations = [
                match.group(1).strip().rstrip(".;") for match in DECLARATION.finditer(text)
            ]
            for expression in found_declarations:
                if expression != "MIT":
                    raise SystemExit(f"unreviewed source license declaration in {relative}: {expression}")
                declarations.append({"path": relative, "expression": expression})

            for line_number, line in enumerate(text.splitlines(), start=1):
                if not COPYLEFT_FAMILY.search(line):
                    continue
                line_hash = hashlib.sha256(line.strip().encode("utf-8")).hexdigest()
                copyleft_line_counts[(relative, line_hash)] += 1
                copyleft_mentions.append(
                    {
                        "path": relative,
                        "line": line_number,
                        "lineSha256": line_hash,
                        "classification": "exact-pinned-rejection-gate-line",
                    }
                )

            records.append(
                {
                    "path": relative,
                    "bytes": len(raw),
                    "sha256": hashlib.sha256(raw).hexdigest(),
                    "licenseConcluded": "MIT",
                    "licenseBasis": "adjacent-root-license",
                }
            )

    records.sort(key=lambda record: str(record["path"]))
    declarations.sort(key=lambda record: (record["path"], record["expression"]))
    expected_counts = Counter(EXPECTED_COPYLEFT_LINE_HASHES)
    if copyleft_line_counts != expected_counts:
        unexpected = copyleft_line_counts - expected_counts
        missing = expected_counts - copyleft_line_counts
        raise SystemExit(
            "copyleft-family text differs from exact pinned rejection-gate lines; "
            f"unexpected={dict(unexpected)} missing={dict(missing)}"
        )
    copyleft_mentions.sort(key=lambda record: (str(record["path"]), int(record["line"])))
    if not records:
        raise SystemExit("source tree is empty")
    return {
        "schemaVersion": 1,
        "projectRoot": "firmware-src/esp32s3-oled-fps",
        "scope": "complete-source-tree-including-host-tests-diagnostics-docs-generators-and-release-tools",
        "rootLicense": {
            "expression": "MIT",
            "path": "LICENSE",
            "bytes": MIT_LICENSE_BYTES,
            "sha256": MIT_LICENSE_SHA256,
        },
        "fileCount": len(records),
        "totalBytes": sum(int(record["bytes"]) for record in records),
        "treeHash": {
            "algorithm": "SHA256 over path + NUL + lowercase SHA256 + NUL + decimal byte size + LF, sorted by path",
            "sha256": tree_hash(records),
        },
        "files": records,
        "explicitLicenseDeclarations": declarations,
        "copyleftFamilyMentions": copyleft_mentions,
        "dependencyDirectoryIndicators": [],
        "conclusion": {
            "allFilesInventoried": True,
            "allFilesMit": True,
            "unknownLicenses": [],
            "copyleftFamilyDependenciesOrDeclaredLicenses": [],
            "thirdPartySourceTrees": [],
        },
    }


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--project-root", required=True, type=Path)
    parser.add_argument("--output", required=True, type=Path)
    args = parser.parse_args()
    result = scan(args.project_root)
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    print(
        "source license scan: "
        f"{result['fileCount']} files / {result['totalBytes']} bytes; "
        "MIT-only; no third-party source tree, unknown license, or copyleft-family dependency/declaration"
    )


if __name__ == "__main__":
    main()

host/CMakeLists.txt

cmake_minimum_required(VERSION 3.16)
project(oled_fps_host C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)

add_executable(oled_fps_host_test
    test_main.c
    ../main/content.c
    ../main/game.c
    ../main/render.c
)
target_include_directories(oled_fps_host_test PRIVATE ../main)
target_compile_options(oled_fps_host_test PRIVATE
    -Wall -Wextra -Werror -Wpedantic
)

option(OLED_FPS_SANITIZERS "Enable the platform-supported runtime sanitizers" ON)
if(OLED_FPS_SANITIZERS AND CMAKE_C_COMPILER_ID MATCHES "Clang|GNU")
    if(APPLE AND CMAKE_C_COMPILER_ID STREQUAL "AppleClang")
        # AppleClang 17's AddressSanitizer runtime can hang before main while
        # reserving its dynamic shadow. Keep the canonical macOS CMake path
        # useful and deterministic with UndefinedBehaviorSanitizer alone.
        set(OLED_FPS_SANITIZER_FLAGS -fsanitize=undefined -fno-omit-frame-pointer)
    else()
        set(OLED_FPS_SANITIZER_FLAGS -fsanitize=address,undefined -fno-omit-frame-pointer)
    endif()
    target_compile_options(oled_fps_host_test PRIVATE
        ${OLED_FPS_SANITIZER_FLAGS}
    )
    target_link_options(oled_fps_host_test PRIVATE
        ${OLED_FPS_SANITIZER_FLAGS}
    )
endif()

enable_testing()
add_test(NAME oled_fps_portable COMMAND oled_fps_host_test)

host/test_main.c

/* Portable deterministic acceptance harness for BRE-209. License: MIT. */
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "content.h"
#include "game.h"
#include "generated_tables.h"
#include "render.h"

/* Acceptance checks must remain active in Release/NDEBUG builds. */
#undef assert
#define assert(condition) do {                                                     \
    if (!(condition)) {                                                            \
        fprintf(stderr, "CHECK failed at %s:%d: %s\n", __FILE__, __LINE__,       \
                #condition);                                                       \
        abort();                                                                   \
    }                                                                              \
} while (0)

typedef struct {
    uint32_t state_crc;
    uint32_t frame_crc;
    uint32_t trace_crc;
} TraceResult;

static uint32_t mix_crc(uint32_t acc, uint32_t value)
{
    acc ^= value + UINT32_C(0x9e3779b9) + (acc << 6u) + (acc >> 2u);
    return acc;
}

static void begin_play(FpsGameState *state)
{
    fps_game_init(state, 1u, OLED_FPS_FIXED_SEED);
    FpsTickEvents events = fps_game_tick(state, (FpsInput){ .fire = true });
    assert(events.restarted);
    assert(state->mode == FPS_MODE_PLAY);
    (void)fps_game_tick(state, (FpsInput){ 0 });
}

static TraceResult run_fixed_trace(void)
{
    FpsGameState state;
    uint8_t framebuffer[OLED_FPS_FB_SIZE];
    begin_play(&state);
    uint32_t trace_crc = UINT32_C(0x20920920);

    for (uint32_t tick = 0u; tick < 360u; tick++) {
        FpsInput input = { 0 };
        if (tick < 42u) {
            input.move = 110;
        } else if (tick < 72u) {
            input.turn = 88;
        } else if (tick < 132u) {
            input.move = 100;
        } else if (tick < 168u) {
            input.turn = -72;
        } else if (tick < 230u) {
            input.move = -84;
        } else {
            input.turn = (tick & 16u) != 0u ? 48 : -48;
            input.move = 58;
        }
        input.fire = tick == 12u || tick == 90u || tick == 180u || tick == 260u;
        (void)fps_game_tick(&state, input);
        fps_render_frame(&state, framebuffer);
        if ((tick % 20u) == 19u) {
            trace_crc = mix_crc(trace_crc, fps_game_state_crc32(&state));
            trace_crc = mix_crc(trace_crc, fps_frame_crc32(framebuffer));
        }
        if (state.mode != FPS_MODE_PLAY) {
            break;
        }
    }
    fps_render_frame(&state, framebuffer);
    return (TraceResult){
        .state_crc = fps_game_state_crc32(&state),
        .frame_crc = fps_frame_crc32(framebuffer),
        .trace_crc = trace_crc,
    };
}

static FpsTickEvents traced_tick(FpsGameState *state, FpsInput input,
                                 uint32_t *trace_crc)
{
    uint8_t framebuffer[OLED_FPS_FB_SIZE];
    const FpsTickEvents events = fps_game_tick(state, input);
    fps_render_frame(state, framebuffer);
    *trace_crc = mix_crc(*trace_crc, fps_game_state_crc32(state));
    *trace_crc = mix_crc(*trace_crc, fps_frame_crc32(framebuffer));
    return events;
}

static void trace_turn_to(FpsGameState *state, uint8_t target, uint32_t *trace_crc)
{
    for (uint16_t ticks = 0u; state->player_angle != target && ticks < 256u; ticks++) {
        int16_t delta = (int16_t)target - (int16_t)state->player_angle;
        if (delta > 128) {
            delta -= 256;
        } else if (delta < -128) {
            delta += 256;
        }
        const int16_t step = delta < -3 ? -3 : delta > 3 ? 3 : delta;
        (void)traced_tick(state, (FpsInput){ .turn = (int16_t)(step * 32) }, trace_crc);
        assert(state->mode == FPS_MODE_PLAY);
    }
    assert(state->player_angle == target);
}

static void trace_move_x(FpsGameState *state, fps_fixed_t target, uint8_t angle,
                         uint32_t *trace_crc)
{
    trace_turn_to(state, angle, trace_crc);
    const bool increasing = angle == 0u;
    for (uint16_t ticks = 0u; ticks < 400u; ticks++) {
        if ((increasing && state->player_x >= target)
            || (!increasing && state->player_x <= target)) {
            return;
        }
        (void)traced_tick(state, (FpsInput){ .move = 127 }, trace_crc);
        if (state->mode != FPS_MODE_PLAY) {
            fprintf(stderr, "X waypoint ended mode=%u from (%" PRId32 ",%" PRId32
                    ") target=%" PRId32 " angle=%u hp=%u kills=%u\n",
                    (unsigned)state->mode, state->player_x, state->player_y, target,
                    angle, state->health, state->kills);
        }
        assert(state->mode == FPS_MODE_PLAY);
    }
    assert(false && "scripted X movement did not reach its waypoint");
}

static void trace_move_y(FpsGameState *state, fps_fixed_t target, uint8_t angle,
                         uint32_t *trace_crc)
{
    trace_turn_to(state, angle, trace_crc);
    const bool increasing = angle == 64u;
    for (uint16_t ticks = 0u; ticks < 400u; ticks++) {
        if ((increasing && state->player_y >= target)
            || (!increasing && state->player_y <= target)) {
            return;
        }
        (void)traced_tick(state, (FpsInput){ .move = 127 }, trace_crc);
        if (state->mode != FPS_MODE_PLAY) {
            fprintf(stderr, "Y waypoint ended mode=%u from (%" PRId32 ",%" PRId32
                    ") target=%" PRId32 " angle=%u hp=%u kills=%u\n",
                    (unsigned)state->mode, state->player_x, state->player_y, target,
                    angle, state->health, state->kills);
        }
        assert(state->mode == FPS_MODE_PLAY);
    }
    assert(false && "scripted Y movement did not reach its waypoint");
}

static uint8_t angle_toward(const FpsGameState *state, fps_fixed_t x, fps_fixed_t y)
{
    const int64_t dx = (int64_t)x - state->player_x;
    const int64_t dy = (int64_t)y - state->player_y;
    int64_t best_dot = INT64_MIN;
    uint8_t best_angle = 0u;
    for (uint16_t angle = 0u; angle < 256u; angle++) {
        const int64_t dir_x = FPS_SIN_Q15[(uint8_t)(angle + 64u)];
        const int64_t dir_y = FPS_SIN_Q15[angle];
        const int64_t dot = dx * dir_x + dy * dir_y;
        if (dot > best_dot) {
            best_dot = dot;
            best_angle = (uint8_t)angle;
        }
    }
    return best_angle;
}

static void trace_kill_enemy(FpsGameState *state, uint8_t enemy_index,
                             uint32_t *trace_crc)
{
    assert(enemy_index < state->enemy_count);
    for (uint8_t shot_index = 0u; state->enemies[enemy_index].alive
         && shot_index < 3u; shot_index++) {
        const FpsEnemy *enemy = &state->enemies[enemy_index];
        trace_turn_to(state, angle_toward(state, enemy->x, enemy->y), trace_crc);
        const FpsTickEvents shot = traced_tick(state, (FpsInput){ .fire = true },
                                               trace_crc);
        if (!shot.shot || !shot.hit) {
            fprintf(stderr, "enemy %u missed from (%" PRId32 ",%" PRId32
                    ") toward (%" PRId32 ",%" PRId32 ") angle=%u ammo=%u\n",
                    enemy_index, state->player_x, state->player_y, enemy->x, enemy->y,
                    state->player_angle, state->ammo);
        }
        assert(shot.shot && shot.hit);
        (void)traced_tick(state, (FpsInput){ 0 }, trace_crc);
    }
    assert(!state->enemies[enemy_index].alive);
}

static uint32_t run_complete_win_trace(void)
{
    FpsGameState state;
    uint32_t trace_crc = UINT32_C(0x209a11ce);
    begin_play(&state);

    /* A complete no-teleport run: clear the visible courtyard warden, take
     * the north bypass, sweep the east yard and keep, cross the south bypass
     * for the final warden, then return to the now-unlocked gate. */
    trace_kill_enemy(&state, 0u, &trace_crc);
    trace_move_y(&state, OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 192u,
                 &trace_crc);
    trace_move_x(&state, 13 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 0u,
                 &trace_crc);
    trace_kill_enemy(&state, 2u, &trace_crc);
    trace_move_y(&state, 7 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 64u,
                 &trace_crc);
    trace_kill_enemy(&state, 3u, &trace_crc);
    trace_move_x(&state, 9 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 128u,
                 &trace_crc);
    trace_move_x(&state, 8 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 128u,
                 &trace_crc);
    trace_move_y(&state, 4 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 192u,
                 &trace_crc);
    trace_kill_enemy(&state, 1u, &trace_crc);
    trace_move_y(&state, 7 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 64u,
                 &trace_crc);
    trace_move_x(&state, 13 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 0u,
                 &trace_crc);
    trace_move_y(&state, 12 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 64u,
                 &trace_crc);
    trace_move_x(&state, 5 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 128u,
                 &trace_crc);
    trace_move_y(&state, 7 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 192u,
                 &trace_crc);
    trace_move_x(&state, 8 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF, 0u,
                 &trace_crc);
    trace_kill_enemy(&state, 4u, &trace_crc);
    assert(state.kills == state.enemy_count);
    trace_turn_to(&state, 0u, &trace_crc);
    for (uint16_t ticks = 0u; state.mode == FPS_MODE_PLAY && ticks < 160u; ticks++) {
        (void)traced_tick(&state, (FpsInput){ .move = 127 }, &trace_crc);
    }
    assert(state.mode == FPS_MODE_WIN);

    const FpsTickEvents restart = traced_tick(&state, (FpsInput){ .fire = true },
                                               &trace_crc);
    assert(restart.restarted && state.mode == FPS_MODE_PLAY);
    return trace_crc;
}

static uint32_t run_complete_combat_trace(void)
{
    FpsGameState state;
    uint32_t trace_crc = UINT32_C(0x209c0b47);
    begin_play(&state);

    /* The first warden is deliberately visible and shootable from spawn. */
    const uint8_t enemies_before = fps_game_alive_enemies(&state);
    for (uint8_t attempt = 0u; attempt < 8u
         && fps_game_alive_enemies(&state) == enemies_before; attempt++) {
        trace_turn_to(&state, angle_toward(&state, state.enemies[0].x,
                                          state.enemies[0].y), &trace_crc);
        const FpsTickEvents shot = traced_tick(&state, (FpsInput){ .fire = true },
                                               &trace_crc);
        assert(shot.shot);
        (void)traced_tick(&state, (FpsInput){ 0 }, &trace_crc);
    }
    assert(fps_game_alive_enemies(&state) < enemies_before);

    while (state.ammo > 0u) {
        const FpsTickEvents shot = traced_tick(&state, (FpsInput){ .fire = true },
                                               &trace_crc);
        assert(shot.shot);
        (void)traced_tick(&state, (FpsInput){ 0 }, &trace_crc);
    }
    const FpsTickEvents empty = traced_tick(&state, (FpsInput){ .fire = true },
                                            &trace_crc);
    assert(!empty.shot && !empty.hit);
    return trace_crc;
}

static uint32_t run_complete_death_trace(void)
{
    FpsGameState state;
    uint32_t trace_crc = UINT32_C(0x209dea7a);
    begin_play(&state);

    /* Do nothing: the visible courtyard warden must close and defeat the
     * player, proving the opening encounter is active rather than decoration. */
    for (uint16_t ticks = 0u; state.mode == FPS_MODE_PLAY && ticks < 1000u; ticks++) {
        (void)traced_tick(&state, (FpsInput){ 0 }, &trace_crc);
    }
    assert(state.mode == FPS_MODE_DEATH && state.health == 0u);
    const FpsTickEvents restart = traced_tick(&state, (FpsInput){ .fire = true },
                                               &trace_crc);
    assert(restart.restarted && state.mode == FPS_MODE_PLAY && state.health == 100u);
    return trace_crc;
}

static uint32_t run_complete_collision_trace(void)
{
    FpsGameState state;
    uint32_t trace_crc = UINT32_C(0x209c0111);
    begin_play(&state);
    trace_turn_to(&state, 128u, &trace_crc);
    for (uint16_t ticks = 0u; ticks < 180u; ticks++) {
        (void)traced_tick(&state, (FpsInput){ .move = 127 }, &trace_crc);
        assert(state.mode == FPS_MODE_PLAY);
    }
    const fps_fixed_t stopped_x = state.player_x;
    for (uint8_t ticks = 0u; ticks < 20u; ticks++) {
        (void)traced_tick(&state, (FpsInput){ .move = 127 }, &trace_crc);
    }
    assert(state.player_x == stopped_x);
    assert(state.player_x > OLED_FPS_FIXED_ONE);
    return trace_crc;
}

static void test_complete_gameplay_traces(void)
{
    const uint32_t win_first = run_complete_win_trace();
    const uint32_t win_second = run_complete_win_trace();
    const uint32_t combat_first = run_complete_combat_trace();
    const uint32_t combat_second = run_complete_combat_trace();
    const uint32_t death_first = run_complete_death_trace();
    const uint32_t death_second = run_complete_death_trace();
    const uint32_t collision_first = run_complete_collision_trace();
    const uint32_t collision_second = run_complete_collision_trace();
    assert(win_first == win_second);
    assert(combat_first == combat_second);
    assert(death_first == death_second);
    assert(collision_first == collision_second);
    printf("complete win=%08" PRIx32 " combat=%08" PRIx32 " death=%08" PRIx32
           " collision=%08" PRIx32 "\n",
           win_first, combat_first, death_first, collision_first);
    fflush(stdout);
    assert(win_first == UINT32_C(0x41759c53));
    assert(combat_first == UINT32_C(0xd3647ce3));
    assert(death_first == UINT32_C(0xb3168e7e));
    assert(collision_first == UINT32_C(0x1478e42c));
}

static void test_title_and_renderer(void)
{
    FpsGameState state;
    uint8_t framebuffer[OLED_FPS_FB_SIZE];
    uint8_t combat_frame[OLED_FPS_FB_SIZE];
    fps_game_init(&state, 1u, OLED_FPS_FIXED_SEED);
    fps_render_frame(&state, framebuffer);
    assert(state.mode == FPS_MODE_TITLE);
    assert(fps_frame_crc32(framebuffer) != 0u);

    begin_play(&state);
    fps_render_frame(&state, framebuffer);
    const uint32_t opening_crc = fps_frame_crc32(framebuffer);
    const FpsTickEvents opening_shot = fps_game_tick(&state, (FpsInput){ .fire = true });
    assert(opening_shot.shot && opening_shot.hit && opening_shot.killed);
    fps_render_frame(&state, combat_frame);
    assert(fps_frame_crc32(combat_frame) != opening_crc);
    assert(state.muzzle_ticks > 0u && state.hit_ticks > 0u);
    (void)fps_game_tick(&state, (FpsInput){ 0 });
    for (uint16_t angle = 0u; angle < 256u; angle += 17u) {
        state.player_angle = (uint8_t)angle;
        fps_render_frame(&state, framebuffer);
        assert(fps_frame_crc32(framebuffer) != 0u);
    }

    /* Exercise near-boundary DDA divisions without moving into a wall. */
    state.player_x = OLED_FPS_FIXED_ONE + 1;
    state.player_y = OLED_FPS_FIXED_ONE + 1;
    fps_render_frame(&state, framebuffer);
    assert(fps_frame_crc32(framebuffer) != 0u);
}

static bool route_exists(const FpsLevel *level, uint8_t min_y, uint8_t max_y,
                         bool doors_are_open)
{
    bool seen[OLED_FPS_MAP_WIDTH * OLED_FPS_MAP_HEIGHT] = { false };
    uint8_t queue_x[OLED_FPS_MAP_WIDTH * OLED_FPS_MAP_HEIGHT];
    uint8_t queue_y[OLED_FPS_MAP_WIDTH * OLED_FPS_MAP_HEIGHT];
    size_t head = 0u;
    size_t tail = 0u;
    const uint8_t start_x = (uint8_t)(level->start_x >> 16);
    const uint8_t start_y = (uint8_t)(level->start_y >> 16);
    const uint8_t exit_x = (uint8_t)(level->exit_x >> 16);
    const uint8_t exit_y = (uint8_t)(level->exit_y >> 16);
    if (start_y < min_y || start_y > max_y || exit_y < min_y || exit_y > max_y) {
        return false;
    }
    queue_x[tail] = start_x;
    queue_y[tail++] = start_y;
    seen[(size_t)start_y * level->width + start_x] = true;
    while (head < tail) {
        const uint8_t x = queue_x[head];
        const uint8_t y = queue_y[head++];
        if (x == exit_x && y == exit_y) {
            return true;
        }
        static const int8_t DX[4] = { 1, -1, 0, 0 };
        static const int8_t DY[4] = { 0, 0, 1, -1 };
        for (uint8_t direction = 0u; direction < 4u; direction++) {
            const int32_t nx = (int32_t)x + DX[direction];
            const int32_t ny = (int32_t)y + DY[direction];
            if (nx < 0 || ny < min_y || nx >= level->width || ny > max_y) {
                continue;
            }
            const size_t index = (size_t)ny * level->width + (size_t)nx;
            const uint8_t cell = level->cells[index];
            if (!seen[index] && (cell == 0u || (doors_are_open && cell == 5u))) {
                seen[index] = true;
                queue_x[tail] = (uint8_t)nx;
                queue_y[tail++] = (uint8_t)ny;
            }
        }
    }
    return false;
}

static void test_original_level_inventory_and_reachability(void)
{
    const FpsLevel *level = fps_content_level(1u);
    bool seen[OLED_FPS_MAP_WIDTH * OLED_FPS_MAP_HEIGHT] = { false };
    uint8_t queue_x[OLED_FPS_MAP_WIDTH * OLED_FPS_MAP_HEIGHT];
    uint8_t queue_y[OLED_FPS_MAP_WIDTH * OLED_FPS_MAP_HEIGHT];
    size_t head = 0u;
    size_t tail = 0u;
    const uint8_t start_x = (uint8_t)(level->start_x >> 16);
    const uint8_t start_y = (uint8_t)(level->start_y >> 16);
    queue_x[tail] = start_x;
    queue_y[tail++] = start_y;
    seen[(size_t)start_y * level->width + start_x] = true;
    uint8_t materials = 0u;

    for (size_t i = 0u; i < (size_t)level->width * level->height; i++) {
        const uint8_t cell = level->cells[i];
        if (cell >= 1u && cell <= 4u) {
            materials |= (uint8_t)(1u << (cell - 1u));
        }
    }
    while (head < tail) {
        const uint8_t x = queue_x[head];
        const uint8_t y = queue_y[head++];
        static const int8_t DX[4] = { 1, -1, 0, 0 };
        static const int8_t DY[4] = { 0, 0, 1, -1 };
        for (uint8_t direction = 0u; direction < 4u; direction++) {
            const int32_t nx = (int32_t)x + DX[direction];
            const int32_t ny = (int32_t)y + DY[direction];
            if (nx < 0 || ny < 0 || nx >= level->width || ny >= level->height) {
                continue;
            }
            const size_t index = (size_t)ny * level->width + (size_t)nx;
            const uint8_t cell = level->cells[index];
            if (!seen[index] && (cell == 0u || cell == 5u)) {
                seen[index] = true;
                queue_x[tail] = (uint8_t)nx;
                queue_y[tail++] = (uint8_t)ny;
            }
        }
    }

    const size_t exit_index = (size_t)(level->exit_y >> 16) * level->width
        + (size_t)(level->exit_x >> 16);
    assert(seen[exit_index]);
    /* With both doors closed, independent north-only and south-only bands
     * still connect start to exit. The direct door route is a third choice. */
    assert(route_exists(level, 1u, 7u, false));
    assert(route_exists(level, 7u, 14u, false));
    assert(materials == 0x0fu);
    assert(level->enemy_count == 5u && level->pickup_count == 4u);
    assert(level->enemies[0].cell_x == 5u && level->enemies[0].cell_y == 7u
           && level->enemies[0].health == 1u);
    for (uint8_t i = 0u; i < level->enemy_count; i++) {
        assert(seen[(size_t)level->enemies[i].cell_y * level->width
                    + level->enemies[i].cell_x]);
    }
    for (uint8_t i = 0u; i < level->pickup_count; i++) {
        assert(seen[(size_t)level->pickups[i].cell_y * level->width
                    + level->pickups[i].cell_x]);
    }
}

static void test_collision_doors_and_pickups(void)
{
    FpsGameState state;
    begin_play(&state);

    state.player_x = OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF;
    state.player_y = OLED_FPS_FIXED_ONE + 12000;
    state.player_angle = 192u; /* north, toward the boundary wall */
    const fps_fixed_t before = state.player_y;
    for (int i = 0; i < 20; i++) {
        (void)fps_game_tick(&state, (FpsInput){ .move = 127 });
    }
    assert(state.player_y >= OLED_FPS_FIXED_ONE);
    assert(state.player_y <= before);

    state.player_x = 6 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF;
    state.player_y = 7 * OLED_FPS_FIXED_ONE + OLED_FPS_FIXED_HALF;
    (void)fps_game_tick(&state, (FpsInput){ 0 });
    assert(state.doors[0].open);

    state.health = 60u;
    state.player_x = state.pickups[1].x;
    state.player_y = state.pickups[1].y;
    (void)fps_game_tick(&state, (FpsInput){ 0 });
    assert(state.health == 90u);
    assert(!state.pickups[1].active);

    state.ammo = 1u;
    state.player_x = state.pickups[0].x;
    state.player_y = state.pickups[0].y;
    (void)fps_game_tick(&state, (FpsInput){ 0 });
    assert(state.ammo == 7u);
    assert(!state.pickups[0].active);
}

static void test_combat_and_empty_weapon(void)
{
    FpsGameState state;
    begin_play(&state);
    state.player_angle = 0u;
    state.enemies[0].x = state.player_x + OLED_FPS_FIXED_ONE;
    state.enemies[0].y = state.player_y;
    state.enemies[0].health = 1u;
    state.enemies[0].alive = true;
    FpsTickEvents events = fps_game_tick(&state, (FpsInput){ .fire = true });
    assert(events.shot && events.hit && events.killed);
    assert(!state.enemies[0].alive);
    assert(state.kills == 1u && state.muzzle_ticks > 0u && state.hit_ticks > 0u);

    (void)fps_game_tick(&state, (FpsInput){ 0 });
    state.ammo = 0u;
    events = fps_game_tick(&state, (FpsInput){ .fire = true });
    assert(!events.shot && !events.hit);
}

static void test_win_death_and_restart(void)
{
    FpsGameState state;
    const FpsLevel *level = fps_content_level(1u);
    begin_play(&state);
    state.player_x = level->exit_x;
    state.player_y = level->exit_y;
    FpsTickEvents events = fps_game_tick(&state, (FpsInput){ 0 });
    assert(!events.won && state.mode == FPS_MODE_PLAY);
    for (uint8_t i = 0u; i < state.enemy_count; i++) {
        state.enemies[i].alive = false;
    }
    state.kills = state.enemy_count;
    events = fps_game_tick(&state, (FpsInput){ 0 });
    assert(events.won);
    assert(state.mode == FPS_MODE_WIN);
    events = fps_game_tick(&state, (FpsInput){ .fire = true });
    assert(events.restarted);
    assert(state.mode == FPS_MODE_PLAY && state.health == 100u && state.ammo == 12u);

    (void)fps_game_tick(&state, (FpsInput){ 0 });
    state.health = 12u;
    state.enemies[0].alive = true;
    state.enemies[0].attack_cooldown = 0u;
    state.enemies[0].x = state.player_x + 1000;
    state.enemies[0].y = state.player_y;
    events = fps_game_tick(&state, (FpsInput){ 0 });
    assert(events.died);
    assert(state.mode == FPS_MODE_DEATH && state.health == 0u);
    events = fps_game_tick(&state, (FpsInput){ .fire = true });
    assert(events.restarted);
    assert(state.mode == FPS_MODE_PLAY && state.health == 100u);
}

static void test_restart_restores_caller_seed(void)
{
    const uint32_t caller_seed = UINT32_C(0x13579bdf);
    const FpsLevel *level = fps_content_level(1u);
    FpsGameState state;
    fps_game_init(&state, 1u, caller_seed);
    FpsTickEvents events = fps_game_tick(&state, (FpsInput){ .fire = true });
    assert(events.restarted && state.mode == FPS_MODE_PLAY);
    assert(state.initial_seed == caller_seed && state.rng == caller_seed);

    /* Model an evolved live RNG independently of enemy placement/timing. The
     * restart contract is specifically that this value is not reused. */
    state.rng = UINT32_C(0x2468ace0);
    assert(state.rng != caller_seed);
    for (uint8_t i = 0u; i < state.enemy_count; i++) {
        state.enemies[i].alive = false;
    }
    state.player_x = level->exit_x;
    state.player_y = level->exit_y;
    events = fps_game_tick(&state, (FpsInput){ 0 });
    assert(events.won && state.rng != caller_seed);
    events = fps_game_tick(&state, (FpsInput){ .fire = true });
    assert(events.restarted && state.mode == FPS_MODE_PLAY);
    assert(state.initial_seed == caller_seed && state.rng == caller_seed);
}

static void test_exit_progress_milestones(void)
{
    const FpsLevel *level = fps_content_level(1u);
    FpsGameState state;
    begin_play(&state);
    for (uint8_t i = 0u; i < state.enemy_count; i++) {
        state.enemies[i].alive = false;
    }
    assert(state.exit_progress == 0u);

    state.player_y = level->exit_y;
    state.player_x = level->exit_x - 9 * OLED_FPS_FIXED_ONE;
    (void)fps_game_tick(&state, (FpsInput){ 0 });
    assert(state.exit_progress == 1u);

    state.player_x = level->exit_x - 12 * OLED_FPS_FIXED_ONE;
    (void)fps_game_tick(&state, (FpsInput){ 0 });
    assert(state.exit_progress == 1u);

    state.player_x = level->exit_x - 5 * OLED_FPS_FIXED_ONE;
    (void)fps_game_tick(&state, (FpsInput){ 0 });
    assert(state.exit_progress == 2u);

    state.player_x = level->exit_x - OLED_FPS_FIXED_ONE;
    (void)fps_game_tick(&state, (FpsInput){ 0 });
    assert(state.exit_progress == 3u && state.mode == FPS_MODE_PLAY);

    state.player_x = level->exit_x;
    const FpsTickEvents events = fps_game_tick(&state, (FpsInput){ 0 });
    assert(events.won && state.exit_progress == 4u);
}

int main(void)
{
    test_original_level_inventory_and_reachability();
    test_title_and_renderer();
    test_collision_doors_and_pickups();
    test_combat_and_empty_weapon();
    test_win_death_and_restart();
    test_restart_restores_caller_seed();
    test_exit_progress_milestones();
    test_complete_gameplay_traces();

    const TraceResult first = run_fixed_trace();
    const TraceResult second = run_fixed_trace();
    assert(first.state_crc == second.state_crc);
    assert(first.frame_crc == second.frame_crc);
    assert(first.trace_crc == second.trace_crc);

    /* These constants make content, simulation, and selected frames a reviewable
     * golden contract. Update only after intentionally reviewing the visuals. */
    const TraceResult golden = {
        .state_crc = UINT32_C(0x6f7c3444),
        .frame_crc = UINT32_C(0x2b999c0d),
        .trace_crc = UINT32_C(0x00187486),
    };
    printf("state=%08" PRIx32 " frame=%08" PRIx32 " trace=%08" PRIx32 "\n",
           first.state_crc, first.frame_crc, first.trace_crc);
    if (first.state_crc != golden.state_crc || first.frame_crc != golden.frame_crc
        || first.trace_crc != golden.trace_crc) {
        fputs("golden mismatch\n", stderr);
        return 1;
    }
    puts("oled-fps portable tests: ok");
    return 0;
}

Related projects