diff -ruN upstream/src/compat.h src/compat.h --- upstream/src/compat.h 1969-12-31 17:00:00.000000000 -0700 +++ src/compat.h 2026-10-06 01:40:05.329988940 -0600 @@ -0,0 +1,13 @@ +#ifndef RAYCASTER_COMPAT_H +#define RAYCASTER_COMPAT_H +#include +#include +/* C89 types for VisualAge C 5 and GCC 2.95; pixels are 0xAARRGGBB. */ +#if UINT_MAX != 0xffffffffU +#error This renderer requires a 32-bit unsigned int +#endif +typedef unsigned int color_t; +typedef int bool; +#define true 1 +#define false 0 +#endif diff -ruN upstream/src/defs.h src/defs.h --- upstream/src/defs.h 2026-10-06 01:36:24.793009810 -0600 +++ src/defs.h 2026-10-06 01:42:13.298783091 -0600 @@ -1,7 +1,7 @@ #ifndef CONSTANTS_H #define CONSTANTS_H -#include +#include "compat.h" #include #define PI 3.14159265 @@ -9,10 +9,14 @@ #define TILE_SIZE 64 -#define MINIMAP_SCALE_FACTOR 0.2 +#define MINIMAP_SCALE_FACTOR (0.2 * WINDOW_WIDTH / 1280.0) -#define WINDOW_WIDTH 1280 -#define WINDOW_HEIGHT 800 +#ifndef WINDOW_WIDTH +#define WINDOW_WIDTH 640 +#endif +#ifndef WINDOW_HEIGHT +#define WINDOW_HEIGHT 400 +#endif #define FOV_ANGLE (60 * (PI / 180)) @@ -23,6 +27,5 @@ #define FPS 30 #define FRAME_TIME_LENGTH (1000 / FPS) -typedef uint32_t color_t; #endif diff -ruN upstream/src/framebuffer.c src/framebuffer.c --- upstream/src/framebuffer.c 1969-12-31 17:00:00.000000000 -0700 +++ src/framebuffer.c 2026-10-06 01:40:05.329988940 -0600 @@ -0,0 +1,71 @@ +#include +#include "graphics.h" + +static color_t *colorBuffer = NULL; + +bool initializeWindow(void) { + colorBuffer = (color_t *)malloc(sizeof(color_t) * WINDOW_WIDTH * WINDOW_HEIGHT); + if (!colorBuffer) { + fprintf(stderr, "Cannot allocate framebuffer.\n"); + return false; + } + return true; +} + +void destroyWindow(void) { + free(colorBuffer); + colorBuffer = NULL; +} + +void clearColorBuffer(color_t color) { + int i; + for (i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT; i++) + colorBuffer[i] = color; +} + +/* P6 PPM needs no graphics library and has identical RGB bytes on any CPU. */ +bool writeFrame(const char *path) { + FILE *f; + unsigned char row[WINDOW_WIDTH * 3]; + int x, y; + bool ok; + f = fopen(path, "wb"); + if (!f) { perror(path); return false; } + ok = fprintf(f, "P6\n%d %d\n255\n", WINDOW_WIDTH, WINDOW_HEIGHT) > 0; + for (y = 0; ok && y < WINDOW_HEIGHT; y++) { + for (x = 0; x < WINDOW_WIDTH; x++) { + color_t c = colorBuffer[y * WINDOW_WIDTH + x]; + row[x * 3] = (unsigned char)(c >> 16); + row[x * 3 + 1] = (unsigned char)(c >> 8); + row[x * 3 + 2] = (unsigned char)c; + } + ok = fwrite(row, 1, sizeof(row), f) == sizeof(row); + } + if (fclose(f) != 0) ok = false; + if (!ok) fprintf(stderr, "Failed writing frame: %s\n", path); + return ok; +} + +void drawPixel(int x, int y, color_t color) { + if (x >= 0 && x < WINDOW_WIDTH && y >= 0 && y < WINDOW_HEIGHT) + colorBuffer[y * WINDOW_WIDTH + x] = color; +} + +void drawRect(int x, int y, int width, int height, color_t color) { + int i, j; + for (i = x; i < x + width; i++) + for (j = y; j < y + height; j++) drawPixel(i, j, color); +} + +void drawLine(int x0, int y0, int x1, int y1, color_t color) { + int dx = abs(x1 - x0), dy = -abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1; + int err = dx + dy, e2; + for (;;) { + drawPixel(x0, y0, color); + if (x0 == x1 && y0 == y1) break; + e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } +} diff -ruN upstream/src/graphics.c src/graphics.c --- upstream/src/graphics.c 2026-10-06 01:36:24.794009855 -0600 +++ src/graphics.c 1969-12-31 17:00:00.000000000 -0700 @@ -1,104 +0,0 @@ -#include "graphics.h" - -static SDL_Window* window = NULL; -static SDL_Renderer* renderer = NULL; -static color_t* colorBuffer = NULL; -static SDL_Texture* colorBufferTexture; - -bool initializeWindow(void) { - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { - fprintf(stderr, "Error initializing SDL.\n"); - return false; - } - SDL_DisplayMode display_mode; - SDL_GetCurrentDisplayMode(0, &display_mode); - int fullScreenWidth = display_mode.w; - int fullScreenHeight = display_mode.h; - window = SDL_CreateWindow( - NULL, - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - fullScreenWidth, - fullScreenHeight, - SDL_WINDOW_BORDERLESS - ); - if (!window) { - fprintf(stderr, "Error creating SDL window.\n"); - return false; - } - renderer = SDL_CreateRenderer(window, -1, 0); - if (!renderer) { - fprintf(stderr, "Error creating SDL renderer.\n"); - return false; - } - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - - // allocate the total amount of bytes in memory to hold our colorbuffer - colorBuffer = (color_t*)malloc(sizeof(color_t) * WINDOW_WIDTH * WINDOW_HEIGHT); - - // create an SDL_Texture to display the colorbuffer - colorBufferTexture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_RGBA32, - SDL_TEXTUREACCESS_STREAMING, - WINDOW_WIDTH, - WINDOW_HEIGHT - ); - - return true; -} - -void destroyWindow(void) { - free(colorBuffer); - SDL_DestroyTexture(colorBufferTexture); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); -} - -void clearColorBuffer(color_t color) { - for (int i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT; i++) - colorBuffer[i] = color; -} - -void renderColorBuffer(void) { - SDL_UpdateTexture( - colorBufferTexture, - NULL, - colorBuffer, - (int)(WINDOW_WIDTH * sizeof(color_t)) - ); - SDL_RenderCopy(renderer, colorBufferTexture, NULL, NULL); - SDL_RenderPresent(renderer); -} - -void drawPixel(int x, int y, color_t color) { - colorBuffer[(WINDOW_WIDTH * y) + x] = color; -} - -void drawRect(int x, int y, int width, int height, color_t color) { - for (int i = x; i <= (x + width); i++) { - for (int j = y; j <= (y + height); j++) { - drawPixel(i, j, color); - } - } -} - -void drawLine(int x0, int y0, int x1, int y1, color_t color) { - int deltaX = (x1 - x0); - int deltaY = (y1 - y0); - - int longestSideLength = (abs(deltaX) >= abs(deltaY)) ? abs(deltaX) : abs(deltaY); - - float xIncrement = deltaX / (float)longestSideLength; - float yIncrement = deltaY / (float)longestSideLength; - - float currentX = x0; - float currentY = y0; - - for (int i = 0; i < longestSideLength; i++) { - drawPixel(round(currentX), round(currentY), color); - currentX += xIncrement; - currentY += yIncrement; - } -} diff -ruN upstream/src/graphics.h src/graphics.h --- upstream/src/graphics.h 2026-10-06 01:36:24.794009855 -0600 +++ src/graphics.h 2026-10-06 01:40:05.322988623 -0600 @@ -2,15 +2,13 @@ #define GRAPHICS_H #include -#include -#include -#include +#include "compat.h" #include "defs.h" bool initializeWindow(void); void destroyWindow(void); void clearColorBuffer(color_t color); -void renderColorBuffer(void); +bool writeFrame(const char *path); void drawPixel(int x, int y, color_t color); void drawRect(int x, int y, int width, int height, color_t color); void drawLine(int x0, int y0, int x1, int y1, color_t color); diff -ruN upstream/src/headless.c src/headless.c --- upstream/src/headless.c 1969-12-31 17:00:00.000000000 -0700 +++ src/headless.c 2026-10-06 01:40:05.330988985 -0600 @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include "graphics.h" +#include "map.h" +#include "player.h" +#include "ray.h" +#include "wall.h" +#include "sprite.h" +#include "texture.h" +#include "utils.h" + +static void usage(const char *program) { + fprintf(stderr, + "Usage: %s [--output DIR] [--frames 1..3600] [--scene orbit|spin]\n" + " [--x X --y Y --angle DEGREES] [--no-minimap]\n" + "Run from the project directory containing images/.\n" + "Default: 120-frame moving orbit, rendered to frames/frame-NNNN.ppm.\n" + "A custom camera (--x, --y or --angle) selects a fixed-position spin.\n", + program); +} + +static bool parseNumber(const char *text, double *value) { + char *end; + errno = 0; + *value = strtod(text, &end); + return !errno && end != text && *end == '\0' && + *value == *value && *value >= -1000000 && *value <= 1000000; +} + +int main(int argc, char **argv) { + const char *output = "frames", *scene = "orbit"; + int frames = 120, minimap = 1, i, frame, customCamera = 0; + float cameraX = 640, cameraY = 560, angle = PI; + char path[1024]; + double number; + clock_t start; + FILE *manifest; + struct stat outputStat; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0) { usage(argv[0]); return 0; } + if (strcmp(argv[i], "--no-minimap") == 0) { minimap = 0; continue; } + if (i + 1 >= argc) { usage(argv[0]); return 1; } + if (strcmp(argv[i], "--output") == 0) { output = argv[++i]; continue; } + if (strcmp(argv[i], "--scene") == 0) { + scene = argv[++i]; + if (strcmp(scene, "orbit") && strcmp(scene, "spin")) { usage(argv[0]); return 1; } + continue; + } + if (strcmp(argv[i], "--frames") && strcmp(argv[i], "--x") && + strcmp(argv[i], "--y") && strcmp(argv[i], "--angle")) { + usage(argv[0]); return 1; + } + if (!parseNumber(argv[i + 1], &number)) { + fprintf(stderr, "Invalid number: %s\n", argv[i + 1]); return 1; + } + if (strcmp(argv[i], "--frames") == 0) { + if (number < 1 || number > 3600 || number != floor(number)) { + fprintf(stderr, "Frames must be an integer from 1 to 3600.\n"); return 1; + } + frames = (int)number; + } else { + customCamera = 1; + if (strcmp(argv[i], "--x") == 0) cameraX = (float)number; + if (strcmp(argv[i], "--y") == 0) cameraY = (float)number; + if (strcmp(argv[i], "--angle") == 0) angle = (float)(number * PI / 180); + } + i++; + } + if (strlen(output) > sizeof(path) - 64) { + fprintf(stderr, "Output directory path is too long.\n"); return 1; + } + if (customCamera) scene = "spin"; + if (mapHasWallAt(cameraX, cameraY)) { + fprintf(stderr, "Camera must be inside an empty map tile.\n"); return 1; + } + if (mkdir(output, 0755) != 0 && errno != EEXIST) { perror(output); return 1; } + if (stat(output, &outputStat) || !S_ISDIR(outputStat.st_mode)) { + fprintf(stderr, "Output path is not a directory.\n"); return 1; + } + if (!initializeWindow()) return 1; + if (!loadTextures()) { destroyWindow(); return 1; } + sprintf(path, "%s/camera.csv", output); + manifest = fopen(path, "w"); + if (!manifest) { perror(path); freeTextures(); destroyWindow(); return 1; } + fprintf(manifest, "frame,x,y,angle_degrees\n"); + printf("Headless raycaster: %dx%d, %d frames, scene=%s\n", + WINDOW_WIDTH, WINDOW_HEIGHT, frames, scene); + fflush(stdout); + start = clock(); + for (frame = 0; frame < frames; frame++) { + float phase = (float)(TWO_PI * frame / frames); + if (strcmp(scene, "orbit") == 0) { + player.x = (float)(560 + 80 * cos(phase)); + player.y = (float)(560 + 40 * sin(phase)); + player.rotationAngle = phase + PI; + } else { + player.x = cameraX; player.y = cameraY; + player.rotationAngle = angle + phase; + } + normalizeAngle(&player.rotationAngle); + if (mapHasWallAt(player.x, player.y)) { + fprintf(stderr, "Scripted camera entered a wall at frame %d.\n", frame); + fclose(manifest); freeTextures(); destroyWindow(); return 1; + } + castAllRays(); + clearColorBuffer(0xFF000000U); + renderWallProjection(); renderSpriteProjection(); + if (minimap) { + renderMapGrid(); renderMapRays(); renderMapSprites(); renderMapPlayer(); + } + sprintf(path, "%s/frame-%04d.ppm", output, frame); + if (!writeFrame(path)) { fclose(manifest); freeTextures(); destroyWindow(); return 1; } + fprintf(manifest, "%d,%.6f,%.6f,%.6f\n", frame, + player.x, player.y, player.rotationAngle * 180 / PI); + if ((frame + 1) % 20 == 0 || frame + 1 == frames) { + printf("Rendered %d/%d\n", frame + 1, frames); fflush(stdout); + } + } + if (fclose(manifest)) { perror("camera.csv"); freeTextures(); destroyWindow(); return 1; } + printf("Complete: %.3f CPU seconds; RGB frames are in %s/\n", + (double)(clock() - start) / CLOCKS_PER_SEC, output); + freeTextures(); destroyWindow(); + return 0; +} diff -ruN upstream/src/main.c src/main.c --- upstream/src/main.c 2026-10-06 01:36:24.794009855 -0600 +++ src/main.c 1969-12-31 17:00:00.000000000 -0700 @@ -1,100 +0,0 @@ -#include -#include -#include -#include "defs.h" -#include "graphics.h" -#include "map.h" -#include "player.h" -#include "ray.h" -#include "wall.h" -#include "sprite.h" -#include "texture.h" - -bool isGameRunning = false; -int ticksLastFrame = 0; -int frameCount = 0; -int previousSecondTicks = 0; - -void setup(void) { - loadTextures(); -} - -void processInput(void) { - SDL_Event event; - SDL_PollEvent(&event); - switch (event.type) { - case SDL_QUIT: { - isGameRunning = false; - break; - } - case SDL_KEYDOWN: { - if (event.key.keysym.sym == SDLK_ESCAPE) - isGameRunning = false; - if (event.key.keysym.sym == SDLK_UP) - player.walkDirection = +1; - if (event.key.keysym.sym == SDLK_DOWN) - player.walkDirection = -1; - if (event.key.keysym.sym == SDLK_RIGHT) - player.turnDirection = +1; - if (event.key.keysym.sym == SDLK_LEFT) - player.turnDirection = -1; - break; - } - case SDL_KEYUP: { - if (event.key.keysym.sym == SDLK_UP) - player.walkDirection = 0; - if (event.key.keysym.sym == SDLK_DOWN) - player.walkDirection = 0; - if (event.key.keysym.sym == SDLK_RIGHT) - player.turnDirection = 0; - if (event.key.keysym.sym == SDLK_LEFT) - player.turnDirection = 0; - break; - } - } -} - -void update(void) { - float deltaTime = (SDL_GetTicks() - ticksLastFrame) / 1000.0f; - ticksLastFrame = SDL_GetTicks(); - - movePlayer(deltaTime); - castAllRays(); -} - -void render(void) { - clearColorBuffer(0xFF000000); - - // Render the walls and sprites - renderWallProjection(); - renderSpriteProjection(); - - // Render the minimap objects - renderMapGrid(); - renderMapRays(); - renderMapSprites(); - renderMapPlayer(); - - renderColorBuffer(); -} - -void releaseResources(void) { - freeTextures(); - destroyWindow(); -} - -int main(int argc, char* argv[]) { - isGameRunning = initializeWindow(); - - setup(); - - while (isGameRunning) { - processInput(); - update(); - render(); - } - - releaseResources(); - - return EXIT_SUCCESS; -} diff -ruN upstream/src/map.c src/map.c --- upstream/src/map.c 2026-10-06 01:36:24.794009855 -0600 +++ src/map.c 2026-10-06 01:40:05.323988668 -0600 @@ -17,25 +17,29 @@ }; bool mapHasWallAt(float x, float y) { + int mapGridIndexX, mapGridIndexY; if (x < 0 || x >= MAP_NUM_COLS * TILE_SIZE || y < 0 || y >= MAP_NUM_ROWS * TILE_SIZE) { return true; } - int mapGridIndexX = floor(x / TILE_SIZE); - int mapGridIndexY = floor(y / TILE_SIZE); + mapGridIndexX = (int)floor(x / TILE_SIZE); + mapGridIndexY = (int)floor(y / TILE_SIZE); return map[mapGridIndexY][mapGridIndexX] != 0; } bool isInsideMap(float x, float y) { - return (x >= 0 && x <= MAP_NUM_COLS * TILE_SIZE && y >= 0 && y <= MAP_NUM_ROWS * TILE_SIZE); + return (x >= 0 && x < MAP_NUM_COLS * TILE_SIZE && y >= 0 && y < MAP_NUM_ROWS * TILE_SIZE); } int getMapAt(int i, int j) { + if (i < 0 || i >= MAP_NUM_ROWS || j < 0 || j >= MAP_NUM_COLS) + return 1; return map[i][j]; } void renderMapGrid(void) { - for (int i = 0; i < MAP_NUM_ROWS; i++) { - for (int j = 0; j < MAP_NUM_COLS; j++) { + int i, j; + for (i = 0; i < MAP_NUM_ROWS; i++) { + for (j = 0; j < MAP_NUM_COLS; j++) { int tileX = j * TILE_SIZE; int tileY = i * TILE_SIZE; color_t tileColor = map[i][j] != 0 ? 0xFFFFFFFF : 0x00000000; diff -ruN upstream/src/map.h src/map.h --- upstream/src/map.h 2026-10-06 01:36:24.794009855 -0600 +++ src/map.h 2026-10-06 01:40:05.323988668 -0600 @@ -1,7 +1,7 @@ #ifndef MAP_H #define MAP_H -#include +#include "compat.h" #include "defs.h" #include "graphics.h" diff -ruN upstream/src/player.c src/player.c --- upstream/src/player.c 2026-10-06 01:36:24.794009855 -0600 +++ src/player.c 2026-10-06 01:42:13.299783136 -0600 @@ -2,25 +2,18 @@ #include "utils.h" player_t player = { - .x = WINDOW_WIDTH / 2, - .y = WINDOW_HEIGHT / 2, - .width = 1, - .height = 1, - .turnDirection = 0, - .walkDirection = 0, - .rotationAngle = PI / 2, - .walkSpeed = 100, - .turnSpeed = 30 * (PI / 180) + 640, 400, 5, 5, 0, 0, PI / 2, 100, 30 * (PI / 180) }; void movePlayer(float deltaTime) { + float moveStep, newPlayerX, newPlayerY; player.rotationAngle += player.turnDirection * player.turnSpeed * deltaTime; normalizeAngle(&player.rotationAngle); - float moveStep = player.walkDirection * player.walkSpeed * deltaTime; + moveStep = player.walkDirection * player.walkSpeed * deltaTime; - float newPlayerX = player.x + cos(player.rotationAngle) * moveStep; - float newPlayerY = player.y + sin(player.rotationAngle) * moveStep; + newPlayerX = player.x + cos(player.rotationAngle) * moveStep; + newPlayerY = player.y + sin(player.rotationAngle) * moveStep; if (!mapHasWallAt(newPlayerX, newPlayerY)) { player.x = newPlayerX; @@ -30,10 +23,10 @@ void renderMapPlayer(void) { drawRect( - player.x * MINIMAP_SCALE_FACTOR, - player.y * MINIMAP_SCALE_FACTOR, - player.width * MINIMAP_SCALE_FACTOR, - player.height * MINIMAP_SCALE_FACTOR, + (int)(player.x * MINIMAP_SCALE_FACTOR) - 2, + (int)(player.y * MINIMAP_SCALE_FACTOR) - 2, + 4, + 4, 0xFFFFFFFF ); } diff -ruN upstream/src/player.h src/player.h --- upstream/src/player.h 2026-10-06 01:36:24.794009855 -0600 +++ src/player.h 2026-10-06 01:40:05.324988713 -0600 @@ -10,8 +10,8 @@ float y; float width; float height; - int turnDirection; // -1 for left, +1 for right - int walkDirection; // -1 for back, +1 for front + int turnDirection; /* -1 for left, +1 for right */ + int walkDirection; /* -1 for back, +1 for front */ float rotationAngle; float walkSpeed; float turnSpeed; diff -ruN upstream/src/ray.c src/ray.c --- upstream/src/ray.c 2026-10-06 01:36:24.794009855 -0600 +++ src/ray.c 2026-10-06 01:40:05.324988713 -0600 @@ -20,44 +20,45 @@ } void castRay(float rayAngle, int stripId) { - normalizeAngle(&rayAngle); - float xintercept, yintercept; float xstep, ystep; + float nextHorzTouchX, nextHorzTouchY, nextVertTouchX, nextVertTouchY; + float horzHitDistance, vertHitDistance; + bool foundHorzWallHit = false, foundVertWallHit = false; + float horzWallHitX = 0, horzWallHitY = 0, vertWallHitX = 0, vertWallHitY = 0; + int horzWallTexture = 0, vertWallTexture = 0; + normalizeAngle(&rayAngle); - /////////////////////////////////////////// - // HORIZONTAL RAY-GRID INTERSECTION CODE - /////////////////////////////////////////// - bool foundHorzWallHit = false; - float horzWallHitX = 0; - float horzWallHitY = 0; - int horzWallTexture = 0; + /*///////////////////////////////////////// */ + /* HORIZONTAL RAY-GRID INTERSECTION CODE */ + /*///////////////////////////////////////// */ - // Find the y-coordinate of the closest horizontal grid intersection + /* Find the y-coordinate of the closest horizontal grid intersection */ yintercept = floor(player.y / TILE_SIZE) * TILE_SIZE; yintercept += isRayFacingDown(rayAngle) ? TILE_SIZE : 0; - // Find the x-coordinate of the closest horizontal grid intersection - xintercept = player.x + (yintercept - player.y) / tan(rayAngle); + /* Find the x-coordinate of the closest horizontal grid intersection */ + xintercept = fabs(sin(rayAngle)) < 0.000001 ? -1 : + player.x + (yintercept - player.y) / tan(rayAngle); - // Calculate the increment xstep and ystep + /* Calculate the increment xstep and ystep */ ystep = TILE_SIZE; ystep *= isRayFacingUp(rayAngle) ? -1 : 1; - xstep = TILE_SIZE / tan(rayAngle); + xstep = fabs(sin(rayAngle)) < 0.000001 ? 0 : TILE_SIZE / tan(rayAngle); xstep *= (isRayFacingLeft(rayAngle) && xstep > 0) ? -1 : 1; xstep *= (isRayFacingRight(rayAngle) && xstep < 0) ? -1 : 1; - float nextHorzTouchX = xintercept; - float nextHorzTouchY = yintercept; + nextHorzTouchX = xintercept; + nextHorzTouchY = yintercept; - // Increment xstep and ystep until we find a wall + /* Increment xstep and ystep until we find a wall */ while (isInsideMap(nextHorzTouchX, nextHorzTouchY)) { float xToCheck = nextHorzTouchX; float yToCheck = nextHorzTouchY + (isRayFacingUp(rayAngle) ? -1 : 0); if (mapHasWallAt(xToCheck, yToCheck)) { - // found a wall hit + /* found a wall hit */ horzWallHitX = nextHorzTouchX; horzWallHitY = nextHorzTouchY; horzWallTexture = getMapAt((int)floor(yToCheck / TILE_SIZE), (int)floor(xToCheck / TILE_SIZE)); @@ -69,39 +70,36 @@ } } - /////////////////////////////////////////// - // VERTICAL RAY-GRID INTERSECTION CODE - /////////////////////////////////////////// - bool foundVertWallHit = false; - float vertWallHitX = 0; - float vertWallHitY = 0; - int vertWallTexture = 0; + /*///////////////////////////////////////// */ + /* VERTICAL RAY-GRID INTERSECTION CODE */ + /*///////////////////////////////////////// */ - // Find the x-coordinate of the closest horizontal grid intersection + /* Find the x-coordinate of the closest horizontal grid intersection */ xintercept = floor(player.x / TILE_SIZE) * TILE_SIZE; xintercept += isRayFacingRight(rayAngle) ? TILE_SIZE : 0; - // Find the y-coordinate of the closest horizontal grid intersection - yintercept = player.y + (xintercept - player.x) * tan(rayAngle); + /* Find the y-coordinate of the closest horizontal grid intersection */ + yintercept = fabs(cos(rayAngle)) < 0.000001 ? -1 : + player.y + (xintercept - player.x) * tan(rayAngle); - // Calculate the increment xstep and ystep + /* Calculate the increment xstep and ystep */ xstep = TILE_SIZE; xstep *= isRayFacingLeft(rayAngle) ? -1 : 1; - ystep = TILE_SIZE * tan(rayAngle); + ystep = fabs(cos(rayAngle)) < 0.000001 ? 0 : TILE_SIZE * tan(rayAngle); ystep *= (isRayFacingUp(rayAngle) && ystep > 0) ? -1 : 1; ystep *= (isRayFacingDown(rayAngle) && ystep < 0) ? -1 : 1; - float nextVertTouchX = xintercept; - float nextVertTouchY = yintercept; + nextVertTouchX = xintercept; + nextVertTouchY = yintercept; - // Increment xstep and ystep until we find a wall + /* Increment xstep and ystep until we find a wall */ while (isInsideMap(nextVertTouchX, nextVertTouchY)) { float xToCheck = nextVertTouchX + (isRayFacingLeft(rayAngle) ? -1 : 0); float yToCheck = nextVertTouchY; if (mapHasWallAt(xToCheck, yToCheck)) { - // found a wall hit + /* found a wall hit */ vertWallHitX = nextVertTouchX; vertWallHitY = nextVertTouchY; vertWallTexture = getMapAt((int)floor(yToCheck / TILE_SIZE), (int)floor(xToCheck / TILE_SIZE)); @@ -113,11 +111,11 @@ } } - // Calculate both horizontal and vertical hit distances and choose the smallest one - float horzHitDistance = foundHorzWallHit + /* Calculate both horizontal and vertical hit distances and choose the smallest one */ + horzHitDistance = foundHorzWallHit ? distanceBetweenPoints(player.x, player.y, horzWallHitX, horzWallHitY) : FLT_MAX; - float vertHitDistance = foundVertWallHit + vertHitDistance = foundVertWallHit ? distanceBetweenPoints(player.x, player.y, vertWallHitX, vertWallHitY) : FLT_MAX; @@ -139,20 +137,22 @@ } void castAllRays(void) { - for (int col = 0; col < NUM_RAYS; col++) { + int col; + for (col = 0; col < NUM_RAYS; col++) { float rayAngle = player.rotationAngle + atan((col - NUM_RAYS/2) / DIST_PROJ_PLANE); castRay(rayAngle, col); } } void renderMapRays(void) { - for (int i = 0; i < NUM_RAYS; i += 50) { + int i; + for (i = 0; i < NUM_RAYS; i += 25) { drawLine( player.x * MINIMAP_SCALE_FACTOR, player.y * MINIMAP_SCALE_FACTOR, rays[i].wallHitX * MINIMAP_SCALE_FACTOR, rays[i].wallHitY * MINIMAP_SCALE_FACTOR, - 0xFF0000FF + 0xFFFF0000U ); } } diff -ruN upstream/src/ray.h src/ray.h --- upstream/src/ray.h 2026-10-06 01:36:24.795009901 -0600 +++ src/ray.h 2026-10-06 01:40:05.325988759 -0600 @@ -1,7 +1,7 @@ #ifndef RAY_H #define RAY_H -#include +#include "compat.h" #include #include #include "defs.h" diff -ruN upstream/src/sprite.c src/sprite.c --- upstream/src/sprite.c 2026-10-06 01:36:24.795009901 -0600 +++ src/sprite.c 2026-10-06 01:40:05.325988759 -0600 @@ -5,16 +5,17 @@ #define NUM_SPRITES 6 static sprite_t sprites[NUM_SPRITES] = { - { .x = 640, .y = 630, .texture = 9 }, // barrel - { .x = 660, .y = 690, .texture = 9 }, // barrel - { .x = 250, .y = 600, .texture = 11 }, // table - { .x = 250, .y = 600, .texture = 10 }, // light - { .x = 300, .y = 400, .texture = 12 }, // guard - { .x = 90, .y = 100, .texture = 13 } // armor + {640, 630, 0, 0, false, 9}, /* barrel */ + {660, 690, 0, 0, false, 9}, /* barrel */ + {250, 600, 0, 0, false, 11}, /* table */ + {250, 600, 0, 0, false, 10}, /* light */ + {300, 400, 0, 0, false, 12}, /* guard */ + { 90, 100, 0, 0, false, 13} /* armor */ }; void renderMapSprites(void) { - for (int i = 0; i < NUM_SPRITES; i++) { + int i; + for (i = 0; i < NUM_SPRITES; i++) { drawRect( sprites[i].x * MINIMAP_SCALE_FACTOR, sprites[i].y * MINIMAP_SCALE_FACTOR, @@ -28,20 +29,21 @@ void renderSpriteProjection(void) { sprite_t visibleSprites[NUM_SPRITES]; int numVisibleSprites = 0; + int i, j; - // Find sprites that are visible (inside the FOV) - for (int i = 0; i < NUM_SPRITES; i++) { + /* Find sprites that are visible (inside the FOV) */ + for (i = 0; i < NUM_SPRITES; i++) { float angleSpritePlayer = player.rotationAngle - atan2(sprites[i].y - player.y, sprites[i].x - player.x); + const float EPSILON = 0.2; - // Make sure the angle is always between 0 and 180 degrees + /* Make sure the angle is always between 0 and 180 degrees */ if (angleSpritePlayer > PI) angleSpritePlayer -= TWO_PI; if (angleSpritePlayer < -PI) angleSpritePlayer += TWO_PI; angleSpritePlayer = fabs(angleSpritePlayer); - // If sprite angle is less than half the FOV plus a small margin - const float EPSILON = 0.2; + /* If sprite angle is less than half the FOV plus a small margin */ if (angleSpritePlayer < (FOV_ANGLE / 2) + EPSILON) { sprites[i].visible = true; sprites[i].angle = angleSpritePlayer; @@ -53,9 +55,9 @@ } } - // Sort sprites by distance using a naive bubble-sort algorithm - for (int i = 0; i < numVisibleSprites - 1; i++) { - for (int j = i + 1; j < numVisibleSprites; j++) { + /* Sort sprites by distance using a naive bubble-sort algorithm */ + for (i = 0; i < numVisibleSprites - 1; i++) { + for (j = i + 1; j < numVisibleSprites; j++) { if (visibleSprites[i].distance < visibleSprites[j].distance) { sprite_t temp = visibleSprites[i]; visibleSprites[i] = visibleSprites[j]; @@ -64,54 +66,64 @@ } } - // Rendering all the visible sprites - for (int i = 0; i < numVisibleSprites; i++) { + /* Rendering all the visible sprites */ + for (i = 0; i < numVisibleSprites; i++) { sprite_t sprite = visibleSprites[i]; + float perpDistance, spriteHeight, spriteWidth, spriteTopY, spriteBottomY; + float spriteAngle, spriteScreenPosX, spriteLeftX, spriteRightX; + int textureWidth, textureHeight, x, y, left, right, top, bottom; + + /* Calculate the perpendicular distance of the sprite to prevent fish-eye effect */ + perpDistance = sprite.distance * cos(sprite.angle); + if (perpDistance <= 0.01) continue; + + /* Calculate the sprite projected height and width (the same, as sprites are squared) */ + spriteHeight = (TILE_SIZE / perpDistance) * DIST_PROJ_PLANE; + spriteWidth = spriteHeight; - // Calculate the perpendicular distance of the sprite to prevent fish-eye effect - float perpDistance = sprite.distance * cos(sprite.angle); - - // Calculate the sprite projected height and width (the same, as sprites are squared) - float spriteHeight = (TILE_SIZE / perpDistance) * DIST_PROJ_PLANE; - float spriteWidth = spriteHeight; - - // Sprite top Y - float spriteTopY = (WINDOW_HEIGHT / 2) - (spriteHeight / 2); + /* Sprite top Y */ + spriteTopY = (WINDOW_HEIGHT / 2) - (spriteHeight / 2); spriteTopY = (spriteTopY < 0) ? 0 : spriteTopY; - // Sprite bottom Y - float spriteBottomY = (WINDOW_HEIGHT / 2) + (spriteHeight / 2); + /* Sprite bottom Y */ + spriteBottomY = (WINDOW_HEIGHT / 2) + (spriteHeight / 2); spriteBottomY = (spriteBottomY > WINDOW_HEIGHT) ? WINDOW_HEIGHT : spriteBottomY; - // Calculate the sprite X position in the projection plane - float spriteAngle = atan2(sprite.y - player.y, sprite.x - player.x) - player.rotationAngle; - float spriteScreenPosX = tan(spriteAngle) * DIST_PROJ_PLANE; - - // SpriteLeftX - float spriteLeftX = (WINDOW_WIDTH / 2) + spriteScreenPosX - (spriteWidth / 2); - - // SpriteRightX - float spriteRightX = spriteLeftX + spriteWidth; - - // Query the width and height of the texture - int textureWidth = upng_get_width(textures[sprite.texture]); - int textureHeight = upng_get_height(textures[sprite.texture]); + /* Calculate the sprite X position in the projection plane */ + spriteAngle = atan2(sprite.y - player.y, sprite.x - player.x) - player.rotationAngle; + spriteScreenPosX = tan(spriteAngle) * DIST_PROJ_PLANE; + + /* SpriteLeftX */ + spriteLeftX = (WINDOW_WIDTH / 2) + spriteScreenPosX - (spriteWidth / 2); + + /* SpriteRightX */ + spriteRightX = spriteLeftX + spriteWidth; + + /* Query the width and height of the texture */ + textureWidth = upng_get_width(textures[sprite.texture]); + textureHeight = upng_get_height(textures[sprite.texture]); + if (spriteRightX <= 0 || spriteLeftX >= WINDOW_WIDTH) continue; + left = spriteLeftX < 0 ? 0 : (int)spriteLeftX; + right = spriteRightX > WINDOW_WIDTH ? WINDOW_WIDTH : (int)ceil(spriteRightX); + top = (int)spriteTopY; + bottom = (int)spriteBottomY; - // Loop all the x values - for (int x = spriteLeftX; x < spriteRightX; x++) { + /* Loop all the x values */ + for (x = left; x < right; x++) { float texelWidth = (textureWidth / spriteWidth); int textureOffsetX = (x - spriteLeftX) * texelWidth; - // Loop all the y values - for (int y = spriteTopY; y < spriteBottomY; y++) { + /* Loop all the y values */ + for (y = top; y < bottom; y++) { if (x > 0 && x < WINDOW_WIDTH && y > 0 && y < WINDOW_HEIGHT) { int distanceFromTop = y + (spriteHeight / 2) - (WINDOW_HEIGHT / 2); int textureOffsetY = distanceFromTop * (textureHeight / spriteHeight); - color_t* spriteTextureBuffer = (color_t*) upng_get_buffer(textures[sprite.texture]); - color_t texelColor = spriteTextureBuffer[(textureWidth * textureOffsetY) + textureOffsetX]; + color_t texelColor = texturePixel(sprite.texture, textureOffsetX, textureOffsetY); + float wallDistance = rays[x].distance * cos(rays[x].rayAngle - player.rotationAngle); - if (sprite.distance < rays[x].distance && texelColor != 0xFFFF00FF) { + if (perpDistance < wallDistance && texelColor != 0xFFFF00FFU && + (texelColor & 0xFF000000U)) { drawPixel(x, y, texelColor); } } diff -ruN upstream/src/texture.c src/texture.c --- upstream/src/texture.c 2026-10-06 01:36:24.795009901 -0600 +++ src/texture.c 1969-12-31 17:00:00.000000000 -0700 @@ -1,42 +0,0 @@ -#include "texture.h" - -upng_t* textures[NUM_TEXTURES]; - -static const char* textureFileNames[NUM_TEXTURES] = { - "./images/redbrick.png", // [0] - "./images/purplestone.png", // [1] - "./images/mossystone.png", // [2] - "./images/graystone.png", // [3] - "./images/colorstone.png", // [4] - "./images/bluestone.png", // [5] - "./images/wood.png", // [6] - "./images/eagle.png", // [7] - "./images/pikuma.png", // [8] - "./images/barrel.png", // [9] - "./images/light.png", // [10] - "./images/table.png", // [11] - "./images/guard.png", // [12] - "./images/armor.png" // [13] -}; - -void loadTextures(void) { - for (int i = 0; i < NUM_TEXTURES; i++) { - upng_t* upng = upng_new_from_file(textureFileNames[i]); - if (upng != NULL) { - upng_decode(upng); - if (upng_get_error(upng) == UPNG_EOK) { - textures[i] = upng; - } else { - printf("Error decoding texture file %s \n", textureFileNames[i]); - } - } else { - printf("Error loading texture %s \n", textureFileNames[i]); - } - } -} - -void freeTextures(void) { - for (int i = 0; i < NUM_TEXTURES; i++) { - upng_free(textures[i]); - } -} diff -ruN upstream/src/texture.h src/texture.h --- upstream/src/texture.h 2026-10-06 01:36:24.795009901 -0600 +++ src/texture.h 2026-10-06 01:40:05.326988804 -0600 @@ -2,7 +2,7 @@ #define TEXTURE_H #include -#include +#include "compat.h" #include "defs.h" #include "upng.h" @@ -10,7 +10,8 @@ extern upng_t* textures[NUM_TEXTURES]; -void loadTextures(void); +bool loadTextures(void); +color_t texturePixel(int texture, int x, int y); void freeTextures(void); #endif diff -ruN upstream/src/textures_headless.c src/textures_headless.c --- upstream/src/textures_headless.c 1969-12-31 17:00:00.000000000 -0700 +++ src/textures_headless.c 2026-10-06 01:40:05.330988985 -0600 @@ -0,0 +1,51 @@ +#include "texture.h" + +upng_t *textures[NUM_TEXTURES]; +static const char *textureFileNames[NUM_TEXTURES] = { + "./images/redbrick.png", "./images/purplestone.png", + "./images/mossystone.png", "./images/graystone.png", + "./images/colorstone.png", "./images/bluestone.png", + "./images/wood.png", "./images/eagle.png", "./images/pikuma.png", + "./images/barrel.png", "./images/light.png", "./images/table.png", + "./images/guard.png", "./images/armor.png" +}; + +bool loadTextures(void) { + int i; + for (i = 0; i < NUM_TEXTURES; i++) { + upng_t *p = upng_new_from_file(textureFileNames[i]); + textures[i] = p; + if (!p || upng_decode(p) != UPNG_EOK) { + fprintf(stderr, "Cannot decode %s (error %d).\n", + textureFileNames[i], p ? (int)upng_get_error(p) : -1); + freeTextures(); return false; + } + if (upng_get_format(p) != UPNG_RGBA8 || + upng_get_width(p) != TILE_SIZE || upng_get_height(p) != TILE_SIZE) { + fprintf(stderr, "Expected 64x64 RGBA texture: %s\n", textureFileNames[i]); + freeTextures(); return false; + } + } + return true; +} + +/* Never cast decoded RGBA bytes to integers: PowerPC is big-endian. */ +color_t texturePixel(int texture, int x, int y) { + const unsigned char *p; + int width, height; + if (texture < 0 || texture >= NUM_TEXTURES || !textures[texture]) return 0; + width = (int)upng_get_width(textures[texture]); + height = (int)upng_get_height(textures[texture]); + if (x < 0 || x >= width || y < 0 || y >= height) return 0; + p = upng_get_buffer(textures[texture]) + 4 * (y * width + x); + return ((color_t)p[3] << 24) | ((color_t)p[0] << 16) | + ((color_t)p[1] << 8) | (color_t)p[2]; +} + +void freeTextures(void) { + int i; + for (i = 0; i < NUM_TEXTURES; i++) { + if (textures[i]) upng_free(textures[i]); + textures[i] = NULL; + } +} diff -ruN upstream/src/upng.c src/upng.c --- upstream/src/upng.c 2026-10-06 01:36:24.795009901 -0600 +++ src/upng.c 2026-10-06 01:42:13.300783182 -0600 @@ -24,6 +24,8 @@ distribution. */ +/* Altered for the AIX headless port: unsigned chunk parsing, explicit + * bit-read sequencing, and decoder boundary corrections. See README.md. */ #include #include #include @@ -31,7 +33,7 @@ #include "upng.h" -#define MAKE_BYTE(b) ((b) & 0xFF) +#define MAKE_BYTE(b) ((unsigned long)(b) & 0xFFUL) #define MAKE_DWORD(a,b,c,d) ((MAKE_BYTE(a) << 24) | (MAKE_BYTE(b) << 16) | (MAKE_BYTE(c) << 8) | MAKE_BYTE(d)) #define MAKE_DWORD_PTR(p) MAKE_DWORD((p)[0], (p)[1], (p)[2], (p)[3]) @@ -204,7 +206,7 @@ static void huffman_tree_create_lengths(upng_t* upng, huffman_tree* tree, const unsigned *bitlen) { unsigned tree1d[MAX_SYMBOLS]; - unsigned blcount[MAX_BIT_LENGTH]; + unsigned blcount[MAX_BIT_LENGTH+1]; unsigned nextcode[MAX_BIT_LENGTH+1]; unsigned bits, n, i; unsigned nodefilled = 0; /*up to which node it is filled */ @@ -274,7 +276,7 @@ unsigned char bit; for (;;) { /* error: end of input memory reached without endcode */ - if (((*bp) & 0x07) == 0 && ((*bp) >> 3) > inlength) { + if (((*bp) & 0x07) == 0 && ((*bp) >> 3) >= inlength) { SET_ERROR(upng, UPNG_EMALFORMED); return 0; } @@ -619,7 +621,9 @@ /* read block control bits */ done = read_bit(&bp, &in[inpos]); - btype = read_bit(&bp, &in[inpos]) | (read_bit(&bp, &in[inpos]) << 1); + /* Sequence reads explicitly: operand evaluation order differs by compiler. */ + btype = read_bit(&bp, &in[inpos]); + btype |= read_bit(&bp, &in[inpos]) << 1; /* process control type appropriateyly */ if (btype == 3) { @@ -1000,7 +1004,6 @@ * verify general well-formed-ness */ while (chunk < upng->source.buffer + upng->source.size) { unsigned long length; - const unsigned char *data; /*the data in the chunk */ /* make sure chunk header is not larger than the total compressed */ if ((unsigned long)(chunk - upng->source.buffer + 12) > upng->source.size) { @@ -1021,9 +1024,6 @@ return upng->error; } - /* get pointer to payload */ - data = chunk + 8; - /* parse chunks */ if (upng_chunk_type(chunk) == CHUNK_IDAT) { compressed_size += length; diff -ruN upstream/src/utils.c src/utils.c --- upstream/src/utils.c 2026-10-06 01:36:24.796009946 -0600 +++ src/utils.c 2026-10-06 01:40:05.327988849 -0600 @@ -3,7 +3,7 @@ #include void normalizeAngle(float* angle) { - *angle = remainder(*angle, TWO_PI); + *angle = (float)fmod(*angle, TWO_PI); if (*angle < 0) { *angle = TWO_PI + *angle; } diff -ruN upstream/src/wall.c src/wall.c --- upstream/src/wall.c 2026-10-06 01:36:24.796009946 -0600 +++ src/wall.c 2026-10-06 01:40:05.328988895 -0600 @@ -1,6 +1,6 @@ #include "wall.h" -// Change the color intensity based on a factor value between 0 and 1 +/* Change the color intensity based on a factor value between 0 and 1 */ void changeColorIntensity(color_t* color, float factor) { color_t a = (*color & 0xFF000000); color_t r = (*color & 0x00FF0000) * factor; @@ -11,50 +11,57 @@ } void renderWallProjection(void) { - for (int x = 0; x < NUM_RAYS; x++) { - // Calculate the perpendicular distance to avoid the fish-eye distortion - float perpDistance = rays[x].distance * cos(rays[x].rayAngle - player.rotationAngle); + int x, y; + for (x = 0; x < NUM_RAYS; x++) { + float perpDistance, wallHeight; + int wallTopY, wallBottomY, textureOffsetX, texNum, textureWidth, textureHeight; + /* Calculate the perpendicular distance to avoid the fish-eye distortion */ + perpDistance = rays[x].distance * cos(rays[x].rayAngle - player.rotationAngle); + if (perpDistance < 0.01) perpDistance = 0.01; - // Calculate the projected wall height - float wallHeight = (TILE_SIZE / perpDistance) * DIST_PROJ_PLANE; + /* Calculate the projected wall height */ + wallHeight = (TILE_SIZE / perpDistance) * DIST_PROJ_PLANE; - // Find the wall top Y value - int wallTopY = (WINDOW_HEIGHT / 2) - (wallHeight / 2); + /* Find the wall top Y value */ + wallTopY = (int)((WINDOW_HEIGHT / 2) - (wallHeight / 2)); wallTopY = wallTopY < 0 ? 0 : wallTopY; - // Find the wall bottom Y value - int wallBottomY = (WINDOW_HEIGHT / 2) + (wallHeight / 2); + /* Find the wall bottom Y value */ + wallBottomY = (int)((WINDOW_HEIGHT / 2) + (wallHeight / 2)); wallBottomY = wallBottomY > WINDOW_HEIGHT ? WINDOW_HEIGHT : wallBottomY; - // Draw the ceiling - for (int y = 0; y < wallTopY; y++) { + /* Draw the ceiling */ + for (y = 0; y < wallTopY; y++) { drawPixel(x, y, 0xFF444444); } - // Draw the textured wall - int textureOffsetX; + /* Draw the textured wall */ if (rays[x].wasHitVertical) textureOffsetX = (int)rays[x].wallHitY % TILE_SIZE; else textureOffsetX = (int)rays[x].wallHitX % TILE_SIZE; - // Get the correct texture id number from the map content - int texNum = rays[x].texture - 1; + /* Get the correct texture id number from the map content */ + texNum = rays[x].texture - 1; - // Query the texture width and height from the upng - int textureWidth = upng_get_width(textures[texNum]); - int textureHeight = upng_get_height(textures[texNum]); + /* Query the texture width and height from the upng */ + textureWidth = upng_get_width(textures[texNum]); + textureHeight = upng_get_height(textures[texNum]); + if (textureOffsetX < 0) textureOffsetX = 0; + if (textureOffsetX >= textureWidth) textureOffsetX = textureWidth - 1; - // Render the wall from wallTopY to wallBottomY - for (int y = wallTopY; y < wallBottomY; y++) { + /* Render the wall from wallTopY to wallBottomY */ + for (y = wallTopY; y < wallBottomY; y++) { int distanceFromTop = y + (wallHeight / 2) - (WINDOW_HEIGHT / 2); int textureOffsetY = distanceFromTop * ((float)textureHeight / wallHeight); - // Set the color of the wall based on the color from the texture - color_t* wallTextureBuffer = (color_t*) upng_get_buffer(textures[texNum]); - color_t texelColor = wallTextureBuffer[(textureWidth * textureOffsetY) + textureOffsetX]; + /* Set the color of the wall based on the color from the texture */ + color_t texelColor; + if (textureOffsetY < 0) textureOffsetY = 0; + if (textureOffsetY >= textureHeight) textureOffsetY = textureHeight - 1; + texelColor = texturePixel(texNum, textureOffsetX, textureOffsetY); - // Make the pixel color darker if the ray hit was vertical + /* Make the pixel color darker if the ray hit was vertical */ if (rays[x].wasHitVertical) { changeColorIntensity(&texelColor, 0.7); } @@ -62,8 +69,8 @@ drawPixel(x, y, texelColor); } - // Draw the floor - for (int y = wallBottomY; y < WINDOW_HEIGHT; y++) { + /* Draw the floor */ + for (y = wallBottomY; y < WINDOW_HEIGHT; y++) { drawPixel(x, y, 0xFF888888); } }