From 1ae6b1db97dce3954fef203ae4c0cdbbccf1c923 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 06:02:55 +0000 Subject: [PATCH 1/6] Port to latest orion-ui and add comprehensive test coverage Agent-Logs-Url: https://github.com/corepunch/doom-ed/sessions/de6df3c8-900d-4e67-ac8d-360d961342d2 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- Makefile | 18 +- collision_test | Bin 0 -> 25696 bytes editor/windows/sprite.c | 4 +- editor/windows/things.c | 2 +- mapview/map.h | 9 +- tests/collision_test.c | 422 ++++++++++++++++++++++++++++++++++++++++ tests/wad_test.c | 314 ++++++++++++++++++++++++++++++ ui | 2 +- wad_test | Bin 0 -> 21680 bytes 9 files changed, 759 insertions(+), 12 deletions(-) create mode 100755 collision_test create mode 100644 tests/collision_test.c create mode 100644 tests/wad_test.c create mode 100755 wad_test diff --git a/Makefile b/Makefile index f798a28..b295444 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ endif OBJS = $(MAPVIEW_OBJS) $(EDITOR_OBJS) $(HEXEN_OBJS) # Targets -.PHONY: all clean test triangulate_test liborion +.PHONY: all clean test triangulate_test bbox_test bsp_test collision_test wad_test liborion all: liborion mapview @@ -138,10 +138,13 @@ $(BUILD_DIR)/hexen/%.o: $(HEXEN_DIR)/%.c $(CC) $(CFLAGS) -I. -c $< -o $@ # Test targets -test: triangulate_test bbox_test +test: triangulate_test bbox_test bsp_test collision_test wad_test @echo "=== Running all tests ===" @./triangulate_test @./bbox_test + @./bsp_test + @./collision_test + @./wad_test triangulate_test: $(TESTS_DIR)/triangulate_test.c $(MAPVIEW_DIR)/triangulate.c $(CC) -DTEST_MODE -o $@ $^ -I. -lm @@ -149,11 +152,20 @@ triangulate_test: $(TESTS_DIR)/triangulate_test.c $(MAPVIEW_DIR)/triangulate.c bbox_test: $(TESTS_DIR)/bbox_test.c $(CC) -o $@ $< -I. -lm +bsp_test: $(TESTS_DIR)/bsp_test.c + $(CC) -o $@ $< -I. -lm + +collision_test: $(TESTS_DIR)/collision_test.c + $(CC) -o $@ $< -I. -lm + +wad_test: $(TESTS_DIR)/wad_test.c + $(CC) -o $@ $< -I. -lm + # Clean clean: -@if [ -f $(UI_DIR)/Makefile ]; then $(MAKE) -C $(UI_DIR) clean; fi rm -rf $(BUILD_DIR) - -rm -f triangulate_test bbox_test doom-ed + -rm -f triangulate_test bbox_test bsp_test collision_test wad_test doom-ed -test -f mapview && rm -f mapview || true rm -f $(MAPVIEW_DIR)/*.o $(EDITOR_DIR)/*.o $(EDITOR_DIR)/windows/*.o $(EDITOR_DIR)/windows/inspector/*.o rm -f $(HEXEN_DIR)/*.o $(DOOM_DIR)/*.o diff --git a/collision_test b/collision_test new file mode 100755 index 0000000000000000000000000000000000000000..fcd37fe2b2b66898be77e97b0b84eae4dbc9c440 GIT binary patch literal 25696 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.x-2, win->frame.y-2, win->frame.w+4, win->frame.h+4)); - draw_button(&win->frame, 1, 1, true); + draw_button(win->frame, 1, 1, true); if (!*win->title) return false; if ((spr = find_sprite(win->title))) { rect_t r = fit_sprite(spr, &win->frame); - draw_rect(spr->texture, &r); + draw_rect(spr->texture, r); } else if ((tex = get_flat_texture(win->title))||(tex = get_texture(win->title))) { float scale = fminf(1, fminf(((float)win->frame.w) / tex->width, ((float)win->frame.h) / tex->height)); diff --git a/editor/windows/things.c b/editor/windows/things.c index 91ab6bb..8cb1e72 100644 --- a/editor/windows/things.c +++ b/editor/windows/things.c @@ -53,7 +53,7 @@ result_t win_things(window_t *win, uint32_t msg, uint32_t wparam, void *lparam) uint16_t y = (j / n) * (THING_SIZE+THING_LABEL_HEIGHT); uint16_t tx = x + (THING_SIZE-strwidth(ed_things[i].sprite))/2; rect_t r = fit_sprite(spr, &(rect_t){ x, y, THING_SIZE, THING_SIZE }); - draw_rect(spr->texture, &r); + draw_rect(spr->texture, r); draw_text_small(ed_things[i].sprite, tx, y + THING_SIZE+4, get_sys_color(brTextNormal)); j++; } diff --git a/mapview/map.h b/mapview/map.h index ed83717..1ee4e23 100644 --- a/mapview/map.h +++ b/mapview/map.h @@ -387,8 +387,7 @@ int send_message(window_t *win, uint32_t msg, uint32_t wparam, void *lparam); void post_message(window_t *win, uint32_t msg, uint32_t wparam, void *lparam); void invalidate_window(window_t *win); void set_window_item_text(window_t *win, uint32_t id, const char *fmt, ...); -int window_title_bar_y(window_t const *win); -window_t *get_window_item(window_t const *win, uint32_t id); + void track_mouse(window_t *win); void set_capture(window_t *win); void set_focus(window_t* win); @@ -433,9 +432,9 @@ void draw_bsp(map_data_t const *map, viewdef_t const *viewdef); void update_player_position_with_sliding(map_data_t const *map, player_t *player, float move_x, float move_y); -void fill_rect(uint32_t color, rect_t const *r); -void draw_rect(int tex, rect_t const *r); -void draw_rect_ex(int tex, rect_t const *r, int type, float alpha); +void fill_rect(uint32_t color, rect_t r); +void draw_rect(int tex, rect_t r); +void draw_rect_ex(int tex, rect_t r, int type, float alpha); void draw_icon8(int icon, int x, int y, uint32_t col); void draw_icon16(int icon, int x, int y, uint32_t col); void draw_palette(map_data_t const *map); diff --git a/tests/collision_test.c b/tests/collision_test.c new file mode 100644 index 0000000..1ff3942 --- /dev/null +++ b/tests/collision_test.c @@ -0,0 +1,422 @@ +/* + * Collision Detection Tests + * + * Tests for the pure geometric helper functions in collision.c: + * dist_sq, closest_point_on_line, calc_slide, can_enter_sector + * + * These functions have no external dependencies (no OpenGL, no SDL, no cglm), + * so they can be compiled and tested in isolation. + */ + +#include +#include +#include +#include +#include +#include +#include + +// ── Minimal type stubs ────────────────────────────────────────────────────── + +typedef char texname_t[8]; + +typedef struct { + int16_t x; + int16_t y; +} mapvertex_t; + +typedef struct { + uint16_t start; + uint16_t end; + uint16_t flags; + uint16_t special; + uint16_t tag; + uint16_t sidenum[2]; +} maplinedef_t; + +typedef struct { + int16_t textureoffset; + int16_t rowoffset; + texname_t toptexture; + texname_t bottomtexture; + texname_t midtexture; + uint16_t sector; +} mapsidedef_t; + +typedef struct { + int16_t floorheight; + int16_t ceilingheight; + texname_t floorpic; + texname_t ceilingpic; + int16_t lightlevel; + int16_t special; + int16_t tag; +} mapsector_t; + +typedef struct { + mapvertex_t *vertices; + int num_vertices; + maplinedef_t *linedefs; + int num_linedefs; + mapsidedef_t *sidedefs; + int num_sidedefs; + mapsector_t *sectors; + int num_sectors; +} map_data_t; + +typedef struct { + float x, y, z; + float angle; + float pitch; + float height; + float vel_x, vel_y; + int sector; + int mouse_x_rel; + int mouse_y_rel; + float forward_move; + float strafe_move; +} player_t; + +#define EYE_HEIGHT 48 +#define MAX_STEP 24.0f +#define P_RADIUS 12.0f +#define WALL_DIST 2.0f +#define EPSILON 0.1f + +// ── Functions under test (copied from collision.c to isolate from map.h) ─── + +float dist_sq(float x1, float y1, float x2, float y2) { + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy; +} + +float closest_point_on_line(float point_x, float point_y, + float line_x1, float line_y1, + float line_x2, float line_y2, + float *closest_x, float *closest_y, + float *t_param) { + float dx = line_x2 - line_x1; + float dy = line_y2 - line_y1; + float len_sq = dx*dx + dy*dy; + + if (len_sq < EPSILON) { + *closest_x = line_x1; + *closest_y = line_y1; + *t_param = 0.0f; + return dist_sq(point_x, point_y, line_x1, line_y1); + } + + float t = ((point_x - line_x1) * dx + (point_y - line_y1) * dy) / len_sq; + t = t < 0.0f ? 0.0f : (t > 1.0f ? 1.0f : t); + + *closest_x = line_x1 + t * dx; + *closest_y = line_y1 + t * dy; + *t_param = t; + + return dist_sq(point_x, point_y, *closest_x, *closest_y); +} + +void calc_slide(float mx, float my, float nx, float ny, float *sx, float *sy) { + float dot = mx * nx + my * ny; + + if (dot > -EPSILON) { + *sx = mx; + *sy = my; + return; + } + + *sx = mx - (nx * dot); + *sy = my - (ny * dot); + + float s_len = sqrt((*sx) * (*sx) + (*sy) * (*sy)); + if (s_len > EPSILON) { + float m_len = sqrt(mx * mx + my * my); + *sx = (*sx) * m_len / s_len; + *sy = (*sy) * m_len / s_len; + } +} + +bool can_enter_sector(mapsector_t const *current, mapsector_t const *new_sector, float player_z) { + if (!new_sector) return false; + + float player_feet = player_z - EYE_HEIGHT; + float floor_diff = new_sector->floorheight - player_feet; + + if (floor_diff > MAX_STEP) return false; + if ((new_sector->ceilingheight - new_sector->floorheight) < EYE_HEIGHT) return false; + + return true; +} + +// ── Test helpers ───────────────────────────────────────────────────────────── + +static int tests_passed = 0; +static int tests_total = 0; + +static int float_eq(float a, float b, float eps) { + return fabsf(a - b) < eps; +} + +#define TEST(name) \ + printf("\nTest %d: %s... ", ++tests_total, name); \ + fflush(stdout) + +#define PASS() \ + printf("PASSED\n"); \ + tests_passed++ + +#define ASSERT(cond, msg) \ + if (!(cond)) { \ + printf("FAILED: %s\n", msg); \ + return; \ + } + +// ── dist_sq tests ──────────────────────────────────────────────────────────── + +static void test_dist_sq_same_point(void) { + TEST("dist_sq: same point returns 0"); + ASSERT(float_eq(dist_sq(5.0f, 5.0f, 5.0f, 5.0f), 0.0f, 1e-6f), + "dist_sq of identical points should be 0"); + PASS(); +} + +static void test_dist_sq_horizontal(void) { + TEST("dist_sq: horizontal distance"); + ASSERT(float_eq(dist_sq(0.0f, 0.0f, 3.0f, 0.0f), 9.0f, 1e-6f), + "dist_sq(0,0,3,0) should be 9"); + PASS(); +} + +static void test_dist_sq_diagonal(void) { + TEST("dist_sq: 3-4-5 triangle"); + ASSERT(float_eq(dist_sq(0.0f, 0.0f, 3.0f, 4.0f), 25.0f, 1e-6f), + "dist_sq(0,0,3,4) should be 25"); + PASS(); +} + +static void test_dist_sq_negative_coords(void) { + TEST("dist_sq: negative coordinates"); + /* (-1,-1) to (2,3): dx=3, dy=4, sq=25 */ + ASSERT(float_eq(dist_sq(-1.0f, -1.0f, 2.0f, 3.0f), 25.0f, 1e-6f), + "dist_sq with negative coords should work"); + PASS(); +} + +// ── closest_point_on_line tests ────────────────────────────────────────────── + +static void test_closest_midpoint(void) { + TEST("closest_point_on_line: point projects to midpoint"); + float cx, cy, t; + /* Horizontal line (0,0)→(10,0); point (5,3) projects to (5,0) */ + float d = closest_point_on_line(5.0f, 3.0f, 0.0f, 0.0f, 10.0f, 0.0f, &cx, &cy, &t); + ASSERT(float_eq(cx, 5.0f, 1e-4f), "Closest x should be 5"); + ASSERT(float_eq(cy, 0.0f, 1e-4f), "Closest y should be 0"); + ASSERT(float_eq(t, 0.5f, 1e-4f), "t should be 0.5"); + ASSERT(float_eq(d, 9.0f, 1e-4f), "Squared distance should be 9"); + PASS(); +} + +static void test_closest_clamped_to_start(void) { + TEST("closest_point_on_line: point behind start clamped to start"); + float cx, cy, t; + /* Line (10,0)→(20,0); point (-5,0) is behind start */ + closest_point_on_line(-5.0f, 0.0f, 10.0f, 0.0f, 20.0f, 0.0f, &cx, &cy, &t); + ASSERT(float_eq(cx, 10.0f, 1e-4f), "Closest x should be clamped to line start"); + ASSERT(float_eq(t, 0.0f, 1e-4f), "t should be 0 (clamped)"); + PASS(); +} + +static void test_closest_clamped_to_end(void) { + TEST("closest_point_on_line: point beyond end clamped to end"); + float cx, cy, t; + /* Line (0,0)→(10,0); point (20,0) is beyond end */ + closest_point_on_line(20.0f, 0.0f, 0.0f, 0.0f, 10.0f, 0.0f, &cx, &cy, &t); + ASSERT(float_eq(cx, 10.0f, 1e-4f), "Closest x should be clamped to line end"); + ASSERT(float_eq(t, 1.0f, 1e-4f), "t should be 1 (clamped)"); + PASS(); +} + +static void test_closest_degenerate_line(void) { + TEST("closest_point_on_line: degenerate (zero-length) line"); + float cx, cy, t; + /* Degenerate line: both endpoints identical */ + float d = closest_point_on_line(3.0f, 4.0f, 0.0f, 0.0f, 0.0f, 0.0f, &cx, &cy, &t); + ASSERT(float_eq(cx, 0.0f, 1e-4f), "Closest x should equal line point"); + ASSERT(float_eq(cy, 0.0f, 1e-4f), "Closest y should equal line point"); + ASSERT(float_eq(t, 0.0f, 1e-4f), "t should be 0"); + ASSERT(float_eq(d, 25.0f, 1e-4f), "Squared distance should be 25 (3-4-5)"); + PASS(); +} + +static void test_closest_diagonal_line(void) { + TEST("closest_point_on_line: diagonal line"); + float cx, cy, t; + /* Line (0,0)→(10,10); point (0,10) projects to (5,5) */ + float d = closest_point_on_line(0.0f, 10.0f, 0.0f, 0.0f, 10.0f, 10.0f, &cx, &cy, &t); + ASSERT(float_eq(cx, 5.0f, 1e-4f), "Closest x should be 5"); + ASSERT(float_eq(cy, 5.0f, 1e-4f), "Closest y should be 5"); + ASSERT(float_eq(t, 0.5f, 1e-4f), "t should be 0.5"); + /* Distance from (0,10) to (5,5) is sqrt(50), squared = 50 */ + ASSERT(float_eq(d, 50.0f, 1e-3f), "Squared distance should be 50"); + PASS(); +} + +// ── calc_slide tests ───────────────────────────────────────────────────────── + +static void test_slide_perpendicular_wall(void) { + TEST("calc_slide: movement into perpendicular wall gives zero slide"); + float sx, sy; + /* Moving straight into a wall with normal (1,0) */ + calc_slide(-5.0f, 0.0f, 1.0f, 0.0f, &sx, &sy); + /* Projection: dot = -5; slide = (-5 - 1*(-5), 0) = (0, 0) */ + ASSERT(float_eq(sx, 0.0f, 1e-3f), "Slide x should be 0 for head-on impact"); + ASSERT(float_eq(sy, 0.0f, 1e-3f), "Slide y should be 0 for head-on impact"); + PASS(); +} + +static void test_slide_along_wall(void) { + TEST("calc_slide: movement parallel to wall is unchanged"); + float sx, sy; + /* Moving along a wall: move=(0,5), normal=(1,0) — dot=0 (not into wall) */ + calc_slide(0.0f, 5.0f, 1.0f, 0.0f, &sx, &sy); + ASSERT(float_eq(sx, 0.0f, 1e-3f), "Slide x should be 0"); + ASSERT(float_eq(sy, 5.0f, 1e-3f), "Slide y should be unchanged (5)"); + PASS(); +} + +static void test_slide_diagonal_into_wall(void) { + TEST("calc_slide: diagonal movement into wall slides along wall"); + float sx, sy; + /* Move (-3,-3), normal (1,0): dot=-3 (into wall) */ + /* slide = (-3 - 1*(-3), -3 - 0*(-3)) = (0, -3) */ + calc_slide(-3.0f, -3.0f, 1.0f, 0.0f, &sx, &sy); + /* Result should be (0, -3) normalized to same speed as original */ + float speed_orig = sqrtf((-3.0f)*(-3.0f) + (-3.0f)*(-3.0f)); + float speed_slide = sqrtf(sx*sx + sy*sy); + ASSERT(float_eq(speed_slide, speed_orig, 1e-3f), "Slide speed should equal move speed"); + ASSERT(sx > -1e-3f, "Slide x should be ~0 (no movement into wall)"); + PASS(); +} + +static void test_slide_away_from_wall(void) { + TEST("calc_slide: movement away from wall is unchanged"); + float sx, sy; + /* Moving away: move=(5,0), normal=(1,0): dot=5 > 0 → pass-through */ + calc_slide(5.0f, 0.0f, 1.0f, 0.0f, &sx, &sy); + ASSERT(float_eq(sx, 5.0f, 1e-3f), "Movement away from wall should be unchanged"); + ASSERT(float_eq(sy, 0.0f, 1e-3f), "Slide y should be 0"); + PASS(); +} + +// ── can_enter_sector tests ─────────────────────────────────────────────────── + +static void test_can_enter_null_sector(void) { + TEST("can_enter_sector: NULL new_sector returns false"); + mapsector_t cur = { .floorheight = 0, .ceilingheight = 128 }; + ASSERT(!can_enter_sector(&cur, NULL, EYE_HEIGHT), "NULL sector should return false"); + PASS(); +} + +static void test_can_enter_flat_floor(void) { + TEST("can_enter_sector: flat floor transition is passable"); + mapsector_t cur = { .floorheight = 0, .ceilingheight = 128 }; + mapsector_t next = { .floorheight = 0, .ceilingheight = 128 }; + ASSERT(can_enter_sector(&cur, &next, (float)EYE_HEIGHT), + "Same-height floors should be passable"); + PASS(); +} + +static void test_can_enter_small_step_up(void) { + TEST("can_enter_sector: step up within MAX_STEP is passable"); + mapsector_t cur = { .floorheight = 0, .ceilingheight = 128 }; + /* floor_diff = 16 - (EYE_HEIGHT - EYE_HEIGHT) = 16 ≤ MAX_STEP(24) */ + mapsector_t next = { .floorheight = 16, .ceilingheight = 144 }; + ASSERT(can_enter_sector(&cur, &next, (float)EYE_HEIGHT), + "Step up of 16 should be passable"); + PASS(); +} + +static void test_can_enter_step_too_high(void) { + TEST("can_enter_sector: step exceeding MAX_STEP is blocked"); + mapsector_t cur = { .floorheight = 0, .ceilingheight = 128 }; + /* floor_diff = 32 > MAX_STEP(24) */ + mapsector_t next = { .floorheight = 32, .ceilingheight = 160 }; + ASSERT(!can_enter_sector(&cur, &next, (float)EYE_HEIGHT), + "Step up of 32 should be blocked"); + PASS(); +} + +static void test_can_enter_low_ceiling(void) { + TEST("can_enter_sector: ceiling too low is blocked"); + mapsector_t cur = { .floorheight = 0, .ceilingheight = 128 }; + /* ceiling - floor = 40 < EYE_HEIGHT(48) */ + mapsector_t next = { .floorheight = 0, .ceilingheight = 40 }; + ASSERT(!can_enter_sector(&cur, &next, (float)EYE_HEIGHT), + "Sector with ceiling too low should be blocked"); + PASS(); +} + +static void test_can_enter_step_down(void) { + TEST("can_enter_sector: step down is always passable"); + mapsector_t cur = { .floorheight = 32, .ceilingheight = 160 }; + /* floor_diff = 0 - 32 = -32 ≤ MAX_STEP */ + mapsector_t next = { .floorheight = 0, .ceilingheight = 128 }; + ASSERT(can_enter_sector(&cur, &next, 32.0f + EYE_HEIGHT), + "Stepping down should always be passable"); + PASS(); +} + +static void test_can_enter_exact_max_step(void) { + TEST("can_enter_sector: step equal to MAX_STEP is passable"); + mapsector_t cur = { .floorheight = 0, .ceilingheight = 128 }; + /* floor_diff = 24 == MAX_STEP → should pass (≤ MAX_STEP) */ + mapsector_t next = { .floorheight = (int16_t)MAX_STEP, .ceilingheight = 128 + (int16_t)MAX_STEP }; + ASSERT(can_enter_sector(&cur, &next, (float)EYE_HEIGHT), + "Step equal to MAX_STEP should be passable"); + PASS(); +} + +// ── main ───────────────────────────────────────────────────────────────────── + +int main(void) { + printf("\n=== Running Collision Detection Tests ===\n"); + + /* dist_sq */ + test_dist_sq_same_point(); + test_dist_sq_horizontal(); + test_dist_sq_diagonal(); + test_dist_sq_negative_coords(); + + /* closest_point_on_line */ + test_closest_midpoint(); + test_closest_clamped_to_start(); + test_closest_clamped_to_end(); + test_closest_degenerate_line(); + test_closest_diagonal_line(); + + /* calc_slide */ + test_slide_perpendicular_wall(); + test_slide_along_wall(); + test_slide_diagonal_into_wall(); + test_slide_away_from_wall(); + + /* can_enter_sector */ + test_can_enter_null_sector(); + test_can_enter_flat_floor(); + test_can_enter_small_step_up(); + test_can_enter_step_too_high(); + test_can_enter_low_ceiling(); + test_can_enter_step_down(); + test_can_enter_exact_max_step(); + + printf("\n=== Test Results ===\n"); + printf("Passed: %d/%d\n", tests_passed, tests_total); + + if (tests_passed == tests_total) { + printf("\n=== All Tests Passed! ===\n"); + return 0; + } + printf("\n=== Some Tests Failed ===\n"); + return 1; +} diff --git a/tests/wad_test.c b/tests/wad_test.c new file mode 100644 index 0000000..1362301 --- /dev/null +++ b/tests/wad_test.c @@ -0,0 +1,314 @@ +/* + * WAD Structure Tests + * + * Tests for WAD file parsing logic that can be exercised without a real + * WAD file on disk: + * - is_map_block_valid (lump-sequence validation) + * - find_lump / find_lump_num style name matching + * - WAD header and directory structure layout + */ + +#include +#include +#include +#include +#include +#include + +// ── Minimal type stubs ────────────────────────────────────────────────────── + +typedef char lumpname_t[8]; + +typedef struct { + uint32_t filepos; + uint32_t size; + lumpname_t name; +} filelump_t; + +typedef struct { + char identification[4]; + uint32_t numlumps; + uint32_t infotableofs; +} wadheader_t; + +// ── Functions under test (copied from wad.c to isolate from map.h) ────────── + +bool is_map_block_valid(filelump_t *dir, int index, int total_lumps) { + static const char *expected[] = { + "THINGS", "LINEDEFS", "SIDEDEFS", "VERTEXES", + "SEGS", "SSECTORS", "NODES", "SECTORS", "REJECT", "BLOCKMAP" + }; + for (int i = 0; i < 10; i++) { + if (index + 1 + i >= total_lumps) return false; + if (strncmp(dir[index + 1 + i].name, expected[i], 8) != 0) return false; + } + return true; +} + +static int find_lump_num_impl(filelump_t const *dir, int num_lumps, const char *name) { + for (int i = 0; i < num_lumps; i++) { + if (strncmp(dir[i].name, name, sizeof(lumpname_t)) == 0) + return i; + } + return -1; +} + +// ── Test helpers ───────────────────────────────────────────────────────────── + +static int tests_passed = 0; +static int tests_total = 0; + +#define TEST(name) \ + printf("\nTest %d: %s... ", ++tests_total, name); \ + fflush(stdout) + +#define PASS() \ + printf("PASSED\n"); \ + tests_passed++ + +#define ASSERT(cond, msg) \ + if (!(cond)) { \ + printf("FAILED: %s\n", msg); \ + return; \ + } + +// Helper: set a lump name, zero-padding to 8 bytes (WAD convention) +static void set_lump_name(filelump_t *lump, const char *name) { + memset(lump->name, 0, sizeof(lumpname_t)); + strncpy(lump->name, name, sizeof(lumpname_t)); +} + +// Build a minimal but valid map directory block starting at index 0: +// [0]=marker, [1]=THINGS, ..., [10]=BLOCKMAP +static void build_valid_map_block(filelump_t *dir, const char *marker) { + static const char *lumps[] = { + "THINGS", "LINEDEFS", "SIDEDEFS", "VERTEXES", + "SEGS", "SSECTORS", "NODES", "SECTORS", "REJECT", "BLOCKMAP" + }; + set_lump_name(&dir[0], marker); + for (int i = 0; i < 10; i++) { + set_lump_name(&dir[i + 1], lumps[i]); + dir[i + 1].filepos = 0; + dir[i + 1].size = 0; + } +} + +// ── is_map_block_valid tests ───────────────────────────────────────────────── + +static void test_valid_doom_map_e1m1(void) { + TEST("is_map_block_valid: valid E1M1 marker block"); + filelump_t dir[11]; + memset(dir, 0, sizeof(dir)); + build_valid_map_block(dir, "E1M1"); + ASSERT(is_map_block_valid(dir, 0, 11), "Valid E1M1 map block should pass"); + PASS(); +} + +static void test_valid_doom2_map01(void) { + TEST("is_map_block_valid: valid MAP01 marker block"); + filelump_t dir[11]; + memset(dir, 0, sizeof(dir)); + build_valid_map_block(dir, "MAP01"); + ASSERT(is_map_block_valid(dir, 0, 11), "Valid MAP01 map block should pass"); + PASS(); +} + +static void test_valid_map_block_not_at_start(void) { + TEST("is_map_block_valid: valid block not at directory start"); + /* Put some other lumps before the map block */ + filelump_t dir[15]; + memset(dir, 0, sizeof(dir)); + set_lump_name(&dir[0], "PLAYPAL"); + set_lump_name(&dir[1], "COLORMAP"); + set_lump_name(&dir[2], "TEXTURE1"); + /* Map block starts at index 3 */ + build_valid_map_block(&dir[3], "E2M1"); + ASSERT(is_map_block_valid(dir, 3, 14), "Valid block at offset 3 should pass"); + PASS(); +} + +static void test_invalid_wrong_lump_name(void) { + TEST("is_map_block_valid: wrong lump name fails"); + filelump_t dir[11]; + memset(dir, 0, sizeof(dir)); + build_valid_map_block(dir, "E1M1"); + /* Corrupt the SECTORS lump name */ + set_lump_name(&dir[8], "JUNK"); + ASSERT(!is_map_block_valid(dir, 0, 11), + "Block with wrong lump name should fail"); + PASS(); +} + +static void test_invalid_too_few_lumps(void) { + TEST("is_map_block_valid: too few lumps in directory"); + filelump_t dir[11]; + memset(dir, 0, sizeof(dir)); + build_valid_map_block(dir, "E1M1"); + /* total_lumps = 5 means we can't reach all 10 required lumps */ + ASSERT(!is_map_block_valid(dir, 0, 5), + "Block with insufficient total_lumps should fail"); + PASS(); +} + +static void test_invalid_index_near_end(void) { + TEST("is_map_block_valid: index too close to end of directory"); + filelump_t dir[11]; + memset(dir, 0, sizeof(dir)); + build_valid_map_block(dir, "E1M1"); + /* index=8 means there are only 2 lumps after it, not 10 */ + ASSERT(!is_map_block_valid(dir, 8, 11), + "Block index leaving too few trailing lumps should fail"); + PASS(); +} + +static void test_valid_all_doom2_maps(void) { + TEST("is_map_block_valid: all 32 MAP0x/MAP1x/MAP2x/MAP3x markers validate"); + const char *markers[] = { + "MAP01","MAP02","MAP03","MAP04","MAP05","MAP06","MAP07","MAP08","MAP09","MAP10", + "MAP11","MAP12","MAP13","MAP14","MAP15","MAP16","MAP17","MAP18","MAP19","MAP20", + "MAP21","MAP22","MAP23","MAP24","MAP25","MAP26","MAP27","MAP28","MAP29","MAP30", + "MAP31","MAP32" + }; + for (int m = 0; m < 32; m++) { + filelump_t dir[11]; + memset(dir, 0, sizeof(dir)); + build_valid_map_block(dir, markers[m]); + ASSERT(is_map_block_valid(dir, 0, 11), markers[m]); + } + PASS(); +} + +// ── Lump name matching tests ───────────────────────────────────────────────── + +static void test_find_lump_found(void) { + TEST("find_lump_num: finds existing lump by name"); + filelump_t dir[4]; + memset(dir, 0, sizeof(dir)); + set_lump_name(&dir[0], "PLAYPAL"); + set_lump_name(&dir[1], "COLORMAP"); + set_lump_name(&dir[2], "TEXTURE1"); + set_lump_name(&dir[3], "PNAMES"); + + ASSERT(find_lump_num_impl(dir, 4, "TEXTURE1") == 2, "TEXTURE1 should be at index 2"); + PASS(); +} + +static void test_find_lump_not_found(void) { + TEST("find_lump_num: returns -1 for missing lump"); + filelump_t dir[2]; + memset(dir, 0, sizeof(dir)); + set_lump_name(&dir[0], "PLAYPAL"); + set_lump_name(&dir[1], "COLORMAP"); + + ASSERT(find_lump_num_impl(dir, 2, "MISSING") == -1, + "Missing lump should return -1"); + PASS(); +} + +static void test_find_lump_first_match(void) { + TEST("find_lump_num: returns first matching lump when duplicates exist"); + filelump_t dir[4]; + memset(dir, 0, sizeof(dir)); + set_lump_name(&dir[0], "FLAT1"); + set_lump_name(&dir[1], "FLAT2"); + set_lump_name(&dir[2], "FLAT1"); // duplicate + set_lump_name(&dir[3], "FLAT3"); + + ASSERT(find_lump_num_impl(dir, 4, "FLAT1") == 0, + "Should return first occurrence of duplicate lump"); + PASS(); +} + +static void test_find_lump_empty_directory(void) { + TEST("find_lump_num: empty directory returns -1"); + ASSERT(find_lump_num_impl(NULL, 0, "ANYTHING") == -1, + "Empty directory should return -1"); + PASS(); +} + +static void test_find_lump_8char_name(void) { + TEST("find_lump_num: 8-character lump name (no null terminator) matches"); + filelump_t dir[1]; + memset(dir, 0, sizeof(dir)); + /* Write all 8 bytes without a null terminator */ + memcpy(dir[0].name, "LONGNAME", 8); + + ASSERT(find_lump_num_impl(dir, 1, "LONGNAME") == 0, + "8-character lump name should match"); + PASS(); +} + +// ── WAD header structure tests ─────────────────────────────────────────────── + +static void test_wadheader_size(void) { + TEST("wadheader_t: struct size is 12 bytes (WAD spec)"); + ASSERT(sizeof(wadheader_t) == 12, + "WAD header must be exactly 12 bytes per specification"); + PASS(); +} + +static void test_filelump_size(void) { + TEST("filelump_t: struct size is 16 bytes (WAD spec)"); + /* 4 (filepos) + 4 (size) + 8 (name) = 16 */ + ASSERT(sizeof(filelump_t) == 16, + "WAD directory entry must be exactly 16 bytes per specification"); + PASS(); +} + +static void test_wadheader_identification_iwad(void) { + TEST("wadheader_t: IWAD identification field parses correctly"); + wadheader_t hdr; + memset(&hdr, 0, sizeof(hdr)); + memcpy(hdr.identification, "IWAD", 4); + ASSERT(strncmp(hdr.identification, "IWAD", 4) == 0, + "IWAD identification should parse correctly"); + PASS(); +} + +static void test_wadheader_identification_pwad(void) { + TEST("wadheader_t: PWAD identification field parses correctly"); + wadheader_t hdr; + memset(&hdr, 0, sizeof(hdr)); + memcpy(hdr.identification, "PWAD", 4); + ASSERT(strncmp(hdr.identification, "PWAD", 4) == 0, + "PWAD identification should parse correctly"); + PASS(); +} + +// ── main ───────────────────────────────────────────────────────────────────── + +int main(void) { + printf("\n=== Running WAD Structure Tests ===\n"); + + /* is_map_block_valid */ + test_valid_doom_map_e1m1(); + test_valid_doom2_map01(); + test_valid_map_block_not_at_start(); + test_invalid_wrong_lump_name(); + test_invalid_too_few_lumps(); + test_invalid_index_near_end(); + test_valid_all_doom2_maps(); + + /* find_lump_num */ + test_find_lump_found(); + test_find_lump_not_found(); + test_find_lump_first_match(); + test_find_lump_empty_directory(); + test_find_lump_8char_name(); + + /* WAD struct layout */ + test_wadheader_size(); + test_filelump_size(); + test_wadheader_identification_iwad(); + test_wadheader_identification_pwad(); + + printf("\n=== Test Results ===\n"); + printf("Passed: %d/%d\n", tests_passed, tests_total); + + if (tests_passed == tests_total) { + printf("\n=== All Tests Passed! ===\n"); + return 0; + } + printf("\n=== Some Tests Failed ===\n"); + return 1; +} diff --git a/ui b/ui index 0dd5b62..b570da0 160000 --- a/ui +++ b/ui @@ -1 +1 @@ -Subproject commit 0dd5b62fa96c2b21a87f04de4aeee4079d9da18d +Subproject commit b570da0e2fb712ec47571b69dc6d1f01d7dfae05 diff --git a/wad_test b/wad_test new file mode 100755 index 0000000000000000000000000000000000000000..91bddfbe86086a24fcdf095ee86aedc9f9e1346d GIT binary patch literal 21680 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 Ubhs6={{^0Tp)%8EU}Lra0#?RSX8-^I literal 0 HcmV?d00001 From b6fa6afff92f459a912d9e35d299280ec52ac339 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 06:04:37 +0000 Subject: [PATCH 2/6] Remove compiled test binaries from repo and update .gitignore Agent-Logs-Url: https://github.com/corepunch/doom-ed/sessions/de6df3c8-900d-4e67-ac8d-360d961342d2 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- .gitignore | 4 ++++ collision_test | Bin 25696 -> 0 bytes wad_test | Bin 21680 -> 0 bytes 3 files changed, 4 insertions(+) delete mode 100755 collision_test delete mode 100755 wad_test diff --git a/.gitignore b/.gitignore index 8d8da23..64e0731 100644 --- a/.gitignore +++ b/.gitignore @@ -17,9 +17,13 @@ build/libgoldie-ui.dylib tests/triangulate_test tests/bsp_test tests/bbox_test +tests/collision_test +tests/wad_test triangulate_test bsp_test bbox_test +collision_test +wad_test *.o # Build logs diff --git a/collision_test b/collision_test deleted file mode 100755 index fcd37fe2b2b66898be77e97b0b84eae4dbc9c440..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25696 zcmeHPdw5jUwLb|QUL_ZQ( zHQ$(ueW@rt%u`tUaRsG!gS^?`3x)S;k8L8X>D_qX=m>&%{$MDFc(|GD3n z{e5%R-s`p3T6^t1=j_>Yu69?<&&|m(xb!k+8=^H>V#*X@%W~#0j51@ok%#Lj<7}f3 z(7o{~Q)DMRkShrc`zgL&fFw7JDqX-45^XSK3ki~3cV%Z^NnvOd$TrEDR8?&!oO8D1 zW5{&7zY<9|9I$+l#52_8C^jvXlJ10?Ra}0dAj?scR-i!PHmHrkfxvOSKJx+L1q78=J-?tz~arvJX9_GcWKWF^y zwu)nmf}Cd)L&2J9r4vK`qEIlLXfA4=KCNh4X>lx4JXx4Pc3JpPPF-?YH4RHU;Zzun z+!onpeMB3o=*0hO$HspQ>O1?XyOxK~*n8>iPxyvT>`gpmH^~qW71F0n6K$%010Uj% z@%SDZ1p-G5g(Ghqu`lrFMyhvJ57Cz20ABe8> z*LQ=T0KA2dvs3`lm7Xy0gN@-v!6GTad4D~c=SWoc)w^IxrPm*b2I_;ccp$o@a$YDB z4lME2gaU@wTi+N7dt-55H172pg0)M=*qUg(&Y-K&l!(V52$|Z|-r9!M-a20}WYpD# z60rs&7WYRIagdR1wMuWJFBk@3&ZME$OLIpwnaHxaN;4Gl;)|D2Lxkr_*(tMVoHO4)4#j z=|=4ElbJT%ART@*)217v!*^%ebVGFbhju$Xb|reN%O6#4i2l-JznY!h{@l_2$kBe^ z(SFm>ekrRx>B9xB`&N2d-|=iZ)UjxZds6$PU7ogCO2(j677TEDy3)0)`nX|0BAfg9N6 z)aPJ#2Q?2+6MYVLAE#!A)*TSCJED?prsq85;zaj}wF^B-_g>;BMFtweK2k%C_PYF! z(br%D9MwP;RD!L*ol3fo<`=%2U$_sQ)a8fUQ*R@Hho3`Oi3}HDnC!n&*oG(w+m?}S zOLrX0UrA;lFr*)LoLt+7YDY$sI7KV(;jv`kjS=TBInyv&|Af`>9aKE6&kFxi^#T?! zIUupAlPAcLC^{F81X0l)S;2%Qs3gtHaD^kU4^xg*pkh0+531nEaN@zJyn`mJ8#z;2 z=^8JrBxjCV%HEyAS2-eml{5M5%zgi~ow-(YL(aJL4p*TEF)H7X*wjSf%zYOsXYN2n zcV;sau16(leh*hTOP*N1+c(%IY`7fo>^YN{Dc4J!t13O|8;|I z`W#=BZ@?RTg_4TXgs7UOKgozsw8c$bd=ZJ?Da1QS{Drfn1$zO-{8%hiSg;`1R`H}u z3rQYS99fwZcL}Z^EBQFf56Q^y7ah|GUZczJ`Am&qv&5$E03vA)nW{#xKPp&EPxw6- z$Z?CCsaCLPt9nCY!IXR%uvmROQ8;<`Xl1Os9s6Uw(8>; zRgA3rX!fZ7cBC9t3fEn-jtP3ds9{vELO9e|Tn`)#=XaQ9VkJ`Pe7+rwgl z0(XdmNMTA3C*7amDzfGySWc^JW{vq&kkkSZ$NHt#mmi3vp;#WmzP}xdiRk(8E1C7> zSGxGrjQBcR{61a0;}eMAo>^bwy7=dqDrzLJu*K_i@kbzTuPxIdOD#+gj+?`xcvYeTM4L-=q$98<`I15iV+=w0m&XwOA6*4XO=rLd(^0& zBmAaO9i$tz3|1U|J2R?pK$JrJ2&(l$8^q`)2We3#fI7_6WyFKg9ZgNK;JG_ItuFyZ zBZ}k5}N|L3Z^_SRG5fDl9o+1^r+fi`_Z8%s?5wv=iUx`Z6TuNHnTn`}qM9?nJ(}MQe zhazZWhzCJCO3f~V_9fP5O3UH+!b_Sdv_yb@jnq?tD+PtZD)9e-si6W_Yv=vxde1&| zWv?eEl=eTc-nxwZbX)!<{PYMbwC0C<*keCtqui4zMOSL&A%+fhm+}bu!IQig zfiXyFH3FC0bNAh%*$(g* zmXW>3mL03hHjyj^?ktH-T?|Chd~BQw+*VYaftvu9tiY9YS|Zo8d=n~3^V_7Q%{7Gs z*U(=J+=qun;HD7|0{0b~2v2+dIQL%ce!N=utb2b8q}_Xyt|3^m;MkM@MU#z~%Mm!0 z8GURA%XRHHL7i}Lro^UJ0)gj0Jm87D|L0M0I`~bnWI4E~lY_Uh{7tAN%~sOV=DHh8 zrpS!z`w9ExqA(EZkQvp)1Di*psT|w}2Y&|#A8nzG=;U(sUbww2nRgZz8@Rngeq*E- zVv)(rh&r)MQ&Lpuo!**Ke$SQIR2>jW^Y!zU-+x8L>30=aPT}_!mcJF1q$!= zzi;ZR`8^cs;P(pRf!|}%Jf+`z3k(|Y&Ctr;kHnPMy#E$fnT+>4#p+GoU#55c(+`yQ zMx?=^u8J_r||x6mj5X#cpfAzZLSb(5Tkx`AI)()+#hedYQBiA^;EfxR$nX$#k1MaAj*6<|4q z>-V$#ov0+u+ek~BYZtzGgzLAM!cCd*XF(lY_Y)6XpMd5mUEg1*IXxcvq&dA33yyO7 z2Jux#2v@rr*QgFmVW>htpB8?%~i@y-_=`l z`aGzE(-Go<(-)%YaC(~<-AA{;weIKotr%6!^;#?znQZ@?_;w@jSLt0pcu0AFxx}V6 z0Fg91us-c{Z&mDto(KqSpiu^lbEKa7gg`yf~} z@7qjE-8Ynhkoq0EO4kvDY^TL`H?U_&PLFm`lOzrY?>>P~D`rhb48*wppdQpOM-P~W(F@T+Xn zbqTCYnzL{f!QH2SNk1<*MhlKnxXySoaXJ+IK`FQqWHIrM3JcSGW z>L-3Pwtpq2uh5?I|1knNS*ME)!;e$1-q@O%R?OEJu$m$`cWOlg@kBHnvr3EsO9HXD zHP%1V8XGGvF1BobD^h0#{ef^iSnCT(v6vNWh$KROt0n-+`EwRjxXVdyfU#&!Rh7Fu z(^W$x8r%>G$014G`@*#WCKi@VD48&2LdiHy*$gJmw5Al57EQI{(V#C}kMp_8cqLS- z3r(KN)R|T|Q16Qe*9NTGNF?gV8QXXu#$%^cSnGoE1_z_<(7H%;HS^boA~D3oDHEf9y*IPK?TeT+Piw67_ zhe;#PkQRNdJvD)bVAyYo^UGFk$k*5eU9e3cqb+0Wdhe!Rf1o}P4n#51R^f&~G*T1_gzMuC<1A8026ys1 zfKZ17Sq`Rj%jUDksS}@WS;^Op*yO2Jp-5ngS$B2^ef1F>0+)8Ok#^^pT3?$KQ%jWD z2Bswrn1?V&XIhPswSh*=RST0lVl@S#O^9f)HWBhgt#!Uo$g0QWh*=aTONc;86^Ul$ zY>8DDiCPT-pT8&)wt|gKzS=nR=>~Sq*QGs8z9_m6geWJ4A;FlH2-h}Xb_e`jnb1{# zwt!VQbzGL;ehy}K07Os1&zR-IVgGv3w+fu}JJuAyOi}BZ9C6ZN3B2I!9G{IPR$&

WT`ibn6ttUhV6Ap;*8WRLRQz%dZ1V(mU&`3;E(!T?k7n7G_^K7K{feKVnvsFBTJj zqaatR@byyE5D3;cC}w+gMNVs+o%Q2i8BJEANvzPpuvIx{skds0dy%7uD#eBGrAgD+ z@@&;2qTT2ZG}mIpgUAJ4e@!S-yBe8D_Gk1@nXKt&md<)>13@e{^~mjr6^g7QdzqQ^ ziP4Z>J5J8b*k%Me>lbnHN7jj0`$9-1J>+C>6Ll{*lt#s+hou|6GUqWZ^>ApzB;(PU zD`UFO41WjAnl;N>oCt@>rg@Q2C>WzODG$UE2&&PYM$7`2WED3aivzJlNMRQtn*wwr z;-5IyKR`0g!P+I|Bz1H~zAA#-8A+RuKj2_ClWN1*vwJrIl$MK8f5I@z=Hjnhb0iK2 z0MqHKxh~Mo$j4$8212<0c?6Boh}EQ@n$;h2ki3}ych7H zw;>PsIqvKa0bY!UtloI$>5u2@F@PUni=iBF1fE>{fXnEy8E_vSUG4=OfTx$I0gnJ4 z0(=(_GV}+wx3Nn&2Jm$})06}5!()vf@Gsczz7}u~o^b93#9pi@IU5!mIn4z*XASJz zZ!`QLJbpwq8W0cccHobPeX(VOc;HitPt%L(bQ2*470e$r?9%*itn1feTs(Zn_$gz~ zAvDP^$7ehEgdibN2p`918}O?Dxlms>;`288t0ts-P{H?e=jHXut$;<+maRaaj&b-I z?6c{;ewafHB)=PxB2O|HbARnL0lHXy=PXPT530?4t1d zcWD2L3e!LPDjlKrHazoF!4Viy3RDH>fJ8Z<=r|xC%6a&H{zw6QZqN zrbYX`qRn<;8&kC3QU2o`hG?@NQv3himEZT~)L5c6Gs_fQq~IzAn-pwO&?$eaHos@- zl6muHT7}g$iEun&O`1|XrMRSMT0%4@Z=76OTvA#*X`CW_oleT*-o__sCl8HJ4k)ze zM8y(U!@xn3hC$h$@m)tzsr8F72|1scIEGO!9Uy>q58$vwchxqzSIL;Ob-UhDw_HvkS2{xLOynBP+PYt#f0d%obOy;0(?QVWRK>jl0_ z;m4_kMC|bbzeeGsY5`$^!@nR1a)!!?8|2)xw^YZ$A6 z@9I5!?+AV*mCAD#-Pn!(n}9Dc&a~gV#{9Psf9_y|zvmc#M>l$Y(+&PPfiEy-E9b;- zI#6~HcR(M+1sJIorO06fGCEB3Vour@NZ>P{kjpAQWBrz#Ps8{$` zC{a<;3*DHJsW|yh|y7Q6M7s-G#>!|oX%+DY2wetT~C37SXCR1Pb!Y!O^Lpm zpf~QTH-L!66LodPwMORsjox^p7iVk2IN{~>`Xk=@P^88e^7?V~A?EcZnvL2>V^avf zI{S-Dy5^v_Fb2K8XwjAyad@-N%oIDXHDWdveg z`Y{~m2xKE3!O1rRCnkJxZ(xl;det#BFP*}ZXUnqjnIm)Acs}BijpYMxUUA?pCW(CF z$Uc}x)=;d(c{X+C${UZ!*HUK7XjE|+OP93*X>bMPx)fQdr8H@xpOMK3+KP4nDf^>1lWu2ge4pOLc&-va}uRn$` z`@(*DC+mWR;KETIZ-SpEoeo2x*P2*Nbt>M&nmMKg8r}uV(WjTh zcKhbntiH0}ortO zB8ayX2mJ~)XYPU`S`7tzgAXs6E%vVuqYDY+QAt@Fh|+$!v$|xncj;?%-K`4{r2)|K` zC7F&VIu`tYtx317_$tM{p)AAj{Yix(KmSUsDi>;_EJ}&~u9}&(BQ^tt{@&^8XI{=Xhpi`}w(^p}NiG$6xz7nbrDG zp=WxQKcM_)IGuuvLN-#B{paWJAaEqY^1P40a6f2zQR-~e8r0~SoaOmBoFVUL@V*4| zG294wddBCr?Whu1rF!BRv;7Kfw4p*{!t%WD!0>=#}QVL5K&Kwg&r{QST$j~Nwo#!o({XU3oJ>lt3vKU;yb ze|qkw_;dd7^B2RD+2Wn%|7#BU=9yA};chh_Sq!QO)~;44wSkX7~wY z^EG+iCt=8ZU=fAe4F3r%VWql}JY5c|RstvBK*w!{G=pfXMwQ8)?+4no=e0T1>OtepT zpM9R)CnrztcfR-gJLkLSo_p_^d+!%Kb?b|ZiZm``v^5%W`V@&&D#1{ls(@5#E44}3 z&e6`%P6IzyU{YQw38b8yE~N~(;<}z zrkqd83H`5B{_ND&Y|U=huI%iz8a}Bor5IEik0t|uO}u(#*LR`xW2Y}jSYyiZJ`Fp{ z%U@b}H#ewwBkhMyH9ncD^lD4Pp_UcPmWBgM!l6j2b4lmQ6-!nub0wm#a?u28myUlL zQ#G5KHMr>~jCW^om*m+W$)+he>3gndTJ+Cfp8e+jse9+94*O@n@yx9oNQc@@Hl#y^ z{HZ1ip6V0vPdYLmH-jk^7%426z3c2l!RI=tUZ^Yq_^AQ{@>K}GW(552f$R9U7iw1_ zKa)qmsh5THH;sTl09?nvz03hn$j^S@x?LIk`S_or&C~|!rI3$@OTo^QkaZhNB@@<23|gczfzm2~1sUxz;w0n*y(_qBy0 z{&48Vpw4bjl-aJny&SSUbc{*Vp`=Y_;=5Ipuwvsv3nuwp9|N0s$|63dXOB?^3 zjep9Uq%Aygf&g6TRux=Yz@q_@i7d8$dRn zavTL;bS-R+_+)=<8-!G8h8+fj?(O+}wkLI@r+M(8=OnfEcqHvPlBq9BdrlS&tnv(g z;q-k1ZUNxE)0>(0B(do9vskw9rpFein~!8R6s4O_7X6#I^?7g4A@8HFjP<6Qvt9t1 z4deFHHIBFEDR0luv#ZcPhd>3WCbTo+F>@--q7kEMZC!X4u=48iJ2u zZ+E|J?g7!qo`cz^;Ymnjlp%AJYar{jI1|}~vq5wp|7c&&FTL=x3sJcbH`?=f^b*eF zNy=aMV@0+cQtDpJdf`KwcJ~)~Eh#uit<^>nR*GwCBh_8K<~HAaq93LK?8o^FQ1SM7 zhTZ*X#}Cmagz0hYK_%_DmztPyK0Zs+(3p(lbyUzVF)*@UJxM8YL`VmH{XVgoTPo4q z{e5t0r1JB50etNhE<|>If#dlCiJHADZGR`%#R1#j9X-Yyb zJR%7p(m@jLA+}HwI`abfngd^Ih&)UKO<7oyuo?Z!Ct+9wr6hdY9DWMiiX?nhqS?Ve zQ4(IiNG0KARP0H37Alq`&`sGXBjG-FaW^Vy$DQOT1?vcREO3Sl%atg#3&BAi#}g^Mi0G6-w(!mIMa z{j@lBfKF1>!gtV+d~VMc7L?m{=3f2F2P(I#B%0j;Ano|_DwW%tQL*RtdZ<`(+mDE+ z2g-W=J&K3IKBf+9KvqTGz2>pdKopoPkB` zVlFCa$1HM`aW-)VeE1;)y_Ff5Og&3G-dG{9>>OeXW#GN{3;}#C5iUdqmgPszelZ5* zdHZ1YS-4Uem||o=H^;sWK1BwmOEl{TkalcETSNvvi;6u1o1kLJz(AqtX92sIgG$U@ za$(6`A-RC$wn*+8$+b(aNpdmC)k&^Xa@B(K;9z%LDyutDy+~GjQC%*pxU6%O$?9%Y zFObzesGcvYeW=cr)qSX*Evx;go++zOqxvaX9YA%GtR6;noU9I_I!0Er2XDCq`KROd zSQ;A|rr@nt3!DFl5hV0*(V4#eNxA=oqMEaIi2W-~EpyiFcX?J{nD?+ldJso%V5(`< z{PG=)-STb`s;K)BYT2BUuc+j%mzvpIp_F!ftwN3Gub^Td&o@EEG@iRp)SoinU(NOn zsH7crDzvaEHM>gLE*V)f1cX^zE-i!i>3TkwW2R%~Vnp2l6@S zKk2Gd=;FL&Y+j#MECep2J=;XOe95lk)0828?<7nUkx|*aGeHwW8aIv^F3kY?Z z7{{CY@V9?Z(?Sj_|75cPK-y7XuF_b8iam`NLdBBCvQyGHjqT4sCGBvKmyGix+@gsz z*2-XVRvdm?r16kjHefuwqniW!b#oeDBPF2l^1d(@9e+_K7dOb_O5|u>@(4>Z&L1J_ z0lEjK?yUjdJ)9FMxfcR?u6VChNM(E45lYOICnTD^5J1|o8toG^c}c$`{6Q5$se#qXV!5_R+yV`bV#J+0O_F7?-6^-$Qn>6ysQyD5h-~>rjJL~qP*n( zyo5&9guJ9TFQJijGC$^SU`fXLE1<^6dLRA8O(Og_UJ}|gu=+|&S)7N~i7b9!qS?3q zld|~!QkBKGP_bw6w@}GvG3|Jcm7hciB6C8l@}!4?N&nskt|$BE52 z?iP)EOdO3r#SqZ4tI={E0ADeh{w}ULVI~K_R@jQBAD3u$1pr+8!L=9w6{uWmz7N(< z9sjI*1}Yf;*rMe$Rmt&h4mbRkX!#+RZ25T6a?jv?42b+R@){}atJ}P(W>eKhkGBW+ ztr^cCd@h5}19WFjz21VPtKG85=-XN5U}^XfeJfFYy9z+su?4|J-!7+C*amkERLr~O zLRNO6l6EX0lZ^9~PpQs@@xzXh%URU!wBwIUWCIryn{nJGRkAPto`Syyp$PsfRPu?% z{%66Hs9^tNi$Hrg(3nZ4Ko7wc?SImt{ZDMhu~4dHlOoWK5Q;$8qmmCqYb>Yi!3Nf? zMJ4T6hb>}X!?C}FU#W~vu8^@yNC&ZNL~PHA{q#$gaUz%t^q}WN?d4U~*mWoNi%B{& zX)Qc@d!8e^pPn%G-ZFvGL3On`(6i7Gd+*Z{&E5q7_x_7jzrK!&eed-^CBOFy_O*@e zuSF&8xSYIXoP(H6#c2H|PTfXdH^3e8d@kvr%~umk-I-|a>vEvzYrUwg?K z`npe`)Yqsv&@0doeGN)9+Xo=+c=!U<*B_u_@9VdqV(M$gu}kNH#>;y=wJVq@uF$88T&? zm^9E6?a@>?pvU})gtoq_w$4*crW3UKs>Vi7wXhsH{>G~MGFt-#Bm>HLtWzkU)En8q z=#w6aCUt*O4}{{u)?_rkT^A4UYV~zhSJqe6Y3nxEZEipmtqBiqH#d0Pnx|a!S;m(> zU>|L3!-Ktgg{8~XfTfjjwXVr>A^? zVq4Lv#%4keSAR07kz%>nR*3B~v0W~AX8Ef==s!nQ)#RtVdQa*Pu^A|4RC!52w& zT%;45(1V?L%9|6Ww`>=~MXRTUnMPwo@@{Jh(mH>uu4v-VsV9 z*sr#+wz08xQ;oLK1hP|U2QzszozhPB+ZKvrYUuDMTie;MzO6loA()DVL#^05syEvD zx~e9(*p`cJh4kDgrwSEMx{J29rsDBnq%|nU7<&@KXN2|yJ7UT0`QfN@`_O@^O;MjO`lSA@T+-U^kNaEk?7is#UJ!}ukrYNN;0`?NkBMZVOhz!F@lu+MTiAh$ zF7hY!w)q3?K|h}K_a!gFoD@&BCiMhfA<#n!-Cd5cKp9O>W(K6s5OfH(3%Wy*GQFGSja^6BIbvU$wYL> z5|5T^{=#^qN)8H{d|bQhq8&Z)74Mz&sC~}yJ2X#7&C3MI&p(IXf4Z%bztk8O# zu7Y%u3oM-`v^uyduIl+MlDH%2jqOWAtNZ^3{NiJ{i0)zxnDRD0GpYK^tkvho>E z1I{Hlej7bC*fe_9HEnKa)HZFd7MSE3JR5*(YwI?zy9~1w`d>6_%*8K&;&*o~b~u+C zht--P&@x)Jc?nBnpfS)Puvh*nm)ipV9ndb&tA3r!?FPLMv>){9-{f+Gpj%$X4{!Lr zBKX@}P6vJTcez|8=-?piL0^3Z_Mk`b$lq?zy+>gW`r+%a2c4XSJszKEItF{t4?!zI zk7G4y3*lIb>H>WPbT{ZfVzI6t^s88)90a9;-v&e}y0Jkk>MSjqHSzRuy=WWZXXC#U zeLSBus!yk7YVADyFT?+q7vURJn_Rkn@{A27XKWkSrCmC6)uM{?<`GQx*WiC2^n@XS zD1;~R-v>NKfGQMg7yjQyyl)WZnOypJ#p@=WR(va3B-`>J;Omg%trq!b4f!*`AA)?N zMP6;lPXPZN8OejD=#ey!Znn{)51;!WbDz{tV=oVC)QFY#8kybFi4>pMd-X#!`*h z|ClvK{E4uedL);dYLQPcse(yHgABOx#kkc_@w6FLL zL%s|0?l*F|kVU@5kUt3dGan;=2688k3EIzj|F0SLCm?_2W8@QYO#A@H&38roC>lH}h}Qj^#ug`X`ZMR`6Th~K-zLxiHx^Ey=U7@H{k{vFK& zR9OD*N4Y5ReQG}DcbOKb3ctg2SjqWaBr!%|cLo`w%#cLGkN=jU%(R`GC5%Hrls$@0 zQ}lF2@l=Q?rRpUw#;;Hf;dbv+j`&?Lj(?7_XT1+|vVX{{fbesQ=(*VQFKBK_MUpAqUE!*5l`UD35^VWR<;z@U z%Ute-N>I2(5~Xo4R(o4KkYv`QaaIJ$E;XQaCB%>41@&nh7UM12di=s>E;<;(yKFe! z1`*El0Nd1}QiP-Uu-Z>NPMZZjUE|}L^*@IS`Qi6&8Q&@NoyI)DIGrmC_4gs6f0m}J zxI9cA0Z#tEqvoxx%F%$(pQ-&uDUMb6%L*T~;m3fJp9z#e6miW2`ab>%zg*$sS_#-B zbi4>xzg^0YBAOF8>7St{5^-$=eV4*Z)r7>jN8!%tP(l&cN}yX6e!V)-#WfP}9SZMN z6O6d_0sd`;-$obYD2)F^;kT=aS6mB0{tJZ%W(##qTmzx{rotak6PLL50X_!v8};kJ z9H}p^c~Ct^;bZ4Yoc14;)e3(=;X@MEE?4+B=Sd)>@ax#mxmNrpg?A`iT+2Y^9)({? z7vLz2k2L)@RHESVBmqNY&%Er-vhi9oBj8VrfWHWw;@aDJ1h~$@O!OV#Q;N^hPB~BD0G~pcE|bc9yqBO-DE|)^QQVnY z)|Q`_MLSE4@6M>p8f~8NQ>xK3gjB>m8R&B0(dqMv915K>!qW$DV2?Nn+htLSRJKT0bIZ4VaaQ?>#tjzoTN%=VoX&8!# zUnYEFVcgUh5srmeZ1!OhTrO!_?Zn!sFB0^}k-&htF<5EFPKkDi1|_5}3`?PQM`Cr-XZ+S+7q6tXMIZenASL-VGg|p$ZMDvqhw^IHB|KjU61yjx zN{m!|zV!`QQucW^Rr~PWkm@V3z+AfyFg`}(^LUliTiu}fYU(zxt*Y~FUcbK4)8uQa zT3hFVh5fT9mHZtOe6d7*I>mtKgC+v&h*_|Y`?87ryoyDgKFeYOe8GS}>DRDvJfeF$!;L@Yz4C;tM3AzIJ~kKp$PH-3+ZjDB??D(NKL4#^UPK zUsBM#mPA51SD%T&ms~ji^of^(-)*seOvVyK9Pzm7No?;(`ddJgaY@^`hA++p<1x*J zaT|1Pjig+$cr+G_C%2m*aa1n}1r$`Zwsr|kXhOZ+kMG>L0^1`9A!#x$B{u})39J}f z7$0QuVAxLvsul|;H5W}xnhRCe)~Ko^f~}e>8SDg0GaR&|Vo$n)?P^YI55OC9(ubVf zqy^Uz%HI)!Gii<#Yc9;H9hfYwQ}$oO(S0>eVYnufWd_b$RG9L8wM3;Z*NL8sf4Y}t z`<2R`sXC34swjN_$~@k6vDn+c*L5B%DvHyUrOUVFx4&EbWdO{mxIMS7Ex|L zuNN>~pwxN2z}|j8vfnbuQsQK*enWqZD_r|VWSVS8SmVEO`N=Azsg(DkU% zy*}IX{XJ9Of82i7V|o+pY3+b{UO!-3sUq3iuV5{M3gwjTc|C(EuV-+4d;hmq{TtUY>UxIPji4fmegEGD)NHRSJEmRI%-Hf}64U!^_PdpU zDeJKV=9%`{?E92}X^agOWjm%nw%P0I{+sCs>i&`4upRUCOuxDRe0|Q8*X3B>p1&uR zJ@0?MZf5#VG;mCX_a8&gqC)Z6p6`d4+Q+{={;zHJJ6B52Oc$%;QKI?r!~9WHCgY#g zc)gLouV-&B^Gx4{ZHdXA*DZSS;Q%VEC%HV&_@5!5vCHxKK9RqdI7G=o;WV-x@5cuq zBNevi^~jxPsx{aR*WAerE`F>=eQY!L1$7URt z`6()X83T&4{Sq5aZKYzSJa(i+ZL1{ps)>P?Cl=zL<8!;l1LL+!3%T{yNSd(?IF8G@ Ubhs6={{^0Tp)%8EU}Lra0#?RSX8-^I From 15f8c24a19e50ae845cbd61866009eeef403178d Mon Sep 17 00:00:00 2001 From: corePunch game studio <83646194+corepunch@users.noreply.github.com> Date: Fri, 1 May 2026 08:22:46 +0200 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- mapview/map.h | 1 + 1 file changed, 1 insertion(+) diff --git a/mapview/map.h b/mapview/map.h index 1ee4e23..32f3f30 100644 --- a/mapview/map.h +++ b/mapview/map.h @@ -387,6 +387,7 @@ int send_message(window_t *win, uint32_t msg, uint32_t wparam, void *lparam); void post_message(window_t *win, uint32_t msg, uint32_t wparam, void *lparam); void invalidate_window(window_t *win); void set_window_item_text(window_t *win, uint32_t id, const char *fmt, ...); +window_t *get_window_item(window_t const *win, uint32_t id); void track_mouse(window_t *win); void set_capture(window_t *win); From 81429ef7884464cc581a1649a41a9638ad1f0ac7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 06:28:16 +0000 Subject: [PATCH 4/6] Build only the orion-ui library, not examples, to fix CI failure Agent-Logs-Url: https://github.com/corepunch/doom-ed/sessions/4d0d5bc8-be34-4e77-b9b6-1e10ce95e0a5 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b295444..f850327 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,7 @@ liborion: $(LIBORION) $(LIBORION): @echo "Building liborion via ui/Makefile..." - @$(MAKE) -C $(UI_DIR) all + @$(MAKE) -C $(UI_DIR) library # mapview executable (main executable) mapview: $(OBJS) $(LIBORION) From 98e87ce891b2b1942f17bfb371d852e0f2127dbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 06:34:48 +0000 Subject: [PATCH 5/6] Replace removed axPollEvent with get_message in wi_stuff.c Agent-Logs-Url: https://github.com/corepunch/doom-ed/sessions/ab8307cf-dc0f-4172-ba7b-72e2e7db814a Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- mapview/wi_stuff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapview/wi_stuff.c b/mapview/wi_stuff.c index 20b425f..3830df6 100644 --- a/mapview/wi_stuff.c +++ b/mapview/wi_stuff.c @@ -94,7 +94,7 @@ void goto_intermisson(void) { void handle_intermission_input(float delta_time) { ui_event_t event; - while (axPollEvent(&event)) { + while (get_message(&event)) { if (event.message == kEventWindowClosed) { g_ui_runtime.running = false; } From ab073b3f1f1278891ec57465497f592c81057de6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 06:54:42 +0000 Subject: [PATCH 6/6] Add walls_test and player_test suites (33 new tests, 85 total) Agent-Logs-Url: https://github.com/corepunch/doom-ed/sessions/8cc82710-3175-4ad3-8e7d-e452e588dd0c Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- .gitignore | 4 + Makefile | 14 ++- tests/player_test.c | 297 ++++++++++++++++++++++++++++++++++++++++++++ tests/walls_test.c | 239 +++++++++++++++++++++++++++++++++++ 4 files changed, 551 insertions(+), 3 deletions(-) create mode 100644 tests/player_test.c create mode 100644 tests/walls_test.c diff --git a/.gitignore b/.gitignore index 64e0731..ac3dcc2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,11 +19,15 @@ tests/bsp_test tests/bbox_test tests/collision_test tests/wad_test +tests/walls_test +tests/player_test triangulate_test bsp_test bbox_test collision_test wad_test +walls_test +player_test *.o # Build logs diff --git a/Makefile b/Makefile index f850327..1b3ff0c 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ endif OBJS = $(MAPVIEW_OBJS) $(EDITOR_OBJS) $(HEXEN_OBJS) # Targets -.PHONY: all clean test triangulate_test bbox_test bsp_test collision_test wad_test liborion +.PHONY: all clean test triangulate_test bbox_test bsp_test collision_test wad_test walls_test player_test liborion all: liborion mapview @@ -138,13 +138,15 @@ $(BUILD_DIR)/hexen/%.o: $(HEXEN_DIR)/%.c $(CC) $(CFLAGS) -I. -c $< -o $@ # Test targets -test: triangulate_test bbox_test bsp_test collision_test wad_test +test: triangulate_test bbox_test bsp_test collision_test wad_test walls_test player_test @echo "=== Running all tests ===" @./triangulate_test @./bbox_test @./bsp_test @./collision_test @./wad_test + @./walls_test + @./player_test triangulate_test: $(TESTS_DIR)/triangulate_test.c $(MAPVIEW_DIR)/triangulate.c $(CC) -DTEST_MODE -o $@ $^ -I. -lm @@ -161,11 +163,17 @@ collision_test: $(TESTS_DIR)/collision_test.c wad_test: $(TESTS_DIR)/wad_test.c $(CC) -o $@ $< -I. -lm +walls_test: $(TESTS_DIR)/walls_test.c + $(CC) -o $@ $< -I. -lm + +player_test: $(TESTS_DIR)/player_test.c + $(CC) -o $@ $< -I. -lm + # Clean clean: -@if [ -f $(UI_DIR)/Makefile ]; then $(MAKE) -C $(UI_DIR) clean; fi rm -rf $(BUILD_DIR) - -rm -f triangulate_test bbox_test bsp_test collision_test wad_test doom-ed + -rm -f triangulate_test bbox_test bsp_test collision_test wad_test walls_test player_test doom-ed -test -f mapview && rm -f mapview || true rm -f $(MAPVIEW_DIR)/*.o $(EDITOR_DIR)/*.o $(EDITOR_DIR)/windows/*.o $(EDITOR_DIR)/windows/inspector/*.o rm -f $(HEXEN_DIR)/*.o $(DOOM_DIR)/*.o diff --git a/tests/player_test.c b/tests/player_test.c new file mode 100644 index 0000000..bc8a215 --- /dev/null +++ b/tests/player_test.c @@ -0,0 +1,297 @@ +/* + * Player Physics Tests + * + * Tests for the mathematical operations governing player movement in input.c: + * - Angle wrapping (kept in 0–360 range) + * - Pitch clamping (kept in –89 to +89) + * - Velocity clamping (speed capped at MAX_SPEED) + * - Friction / deceleration (speed reduced toward 0 without going negative) + * + * These are pure floating-point calculations with no external dependencies, + * so they can be exercised entirely in isolation. + */ + +#include +#include +#include +#include +#include +#include +#include + +// ── Constants (from map.h / input.c) ───────────────────────────────────────── + +#define MAX_SPEED 300.0f +#define ACCELERATION 1000.0f +#define FRICTION 1200.0f + +// ── Pure-math helpers extracted from game_tick() in input.c ────────────────── + +/* Wrap angle into [0, 360). */ +static float wrap_angle(float angle) { + if (angle < 0.0f) angle += 360.0f; + if (angle >= 360.0f) angle -= 360.0f; + return angle; +} + +/* Clamp pitch into [-89, 89]. */ +static float clamp_pitch(float pitch) { + if (pitch > 89.0f) pitch = 89.0f; + if (pitch < -89.0f) pitch = -89.0f; + return pitch; +} + +/* Clamp a velocity vector (vx, vy) so its magnitude never exceeds max_speed. + * Direction is preserved; if speed <= max_speed the vector is unchanged. */ +static void clamp_velocity(float *vx, float *vy, float max_speed) { + float speed = sqrtf(*vx * *vx + *vy * *vy); + if (speed > max_speed) { + float scale = max_speed / speed; + *vx *= scale; + *vy *= scale; + } +} + +/* Apply one step of friction to a velocity vector. + * Reduces speed by (friction * dt) but never below zero. */ +static void apply_friction(float *vx, float *vy, float friction, float dt) { + float speed = sqrtf(*vx * *vx + *vy * *vy); + if (speed <= 0.0f) return; + float decel = friction * dt; + float new_speed = fmaxf(0.0f, speed - decel); + float scale = new_speed / speed; + *vx *= scale; + *vy *= scale; +} + +// ── Test helpers ───────────────────────────────────────────────────────────── + +static int tests_passed = 0; +static int tests_total = 0; + +static int float_eq(float a, float b, float eps) { + return fabsf(a - b) < eps; +} + +#define TEST(name) \ + printf("\nTest %d: %s... ", ++tests_total, name); \ + fflush(stdout) + +#define PASS() \ + printf("PASSED\n"); \ + tests_passed++ + +#define ASSERT(cond, msg) \ + if (!(cond)) { \ + printf("FAILED: %s\n", msg); \ + return; \ + } + +// ── Angle wrapping tests ───────────────────────────────────────────────────── + +static void test_angle_wrap_below_zero(void) { + TEST("angle wrap: -10 wraps to 350"); + float a = wrap_angle(-10.0f); + ASSERT(float_eq(a, 350.0f, 1e-4f), "angle -10 should wrap to 350"); + PASS(); +} + +static void test_angle_wrap_exactly_zero(void) { + TEST("angle wrap: 0 stays 0"); + float a = wrap_angle(0.0f); + ASSERT(float_eq(a, 0.0f, 1e-4f), "angle 0 should stay 0"); + PASS(); +} + +static void test_angle_wrap_below_360(void) { + TEST("angle wrap: 359 stays 359"); + float a = wrap_angle(359.0f); + ASSERT(float_eq(a, 359.0f, 1e-4f), "angle 359 should be unchanged"); + PASS(); +} + +static void test_angle_wrap_exactly_360(void) { + TEST("angle wrap: 360 wraps to 0"); + float a = wrap_angle(360.0f); + ASSERT(float_eq(a, 0.0f, 1e-4f), "angle 360 should wrap to 0"); + PASS(); +} + +static void test_angle_wrap_above_360(void) { + TEST("angle wrap: 370 wraps to 10"); + float a = wrap_angle(370.0f); + ASSERT(float_eq(a, 10.0f, 1e-4f), "angle 370 should wrap to 10"); + PASS(); +} + +// ── Pitch clamping tests ────────────────────────────────────────────────────── + +static void test_pitch_clamp_above_max(void) { + TEST("pitch clamp: 100 clamped to 89"); + float p = clamp_pitch(100.0f); + ASSERT(float_eq(p, 89.0f, 1e-4f), "pitch above 89 should be clamped to 89"); + PASS(); +} + +static void test_pitch_clamp_below_min(void) { + TEST("pitch clamp: -100 clamped to -89"); + float p = clamp_pitch(-100.0f); + ASSERT(float_eq(p, -89.0f, 1e-4f), "pitch below -89 should be clamped to -89"); + PASS(); +} + +static void test_pitch_clamp_at_max(void) { + TEST("pitch clamp: exactly 89 stays 89"); + float p = clamp_pitch(89.0f); + ASSERT(float_eq(p, 89.0f, 1e-4f), "pitch at exactly 89 should not be clamped"); + PASS(); +} + +static void test_pitch_clamp_at_min(void) { + TEST("pitch clamp: exactly -89 stays -89"); + float p = clamp_pitch(-89.0f); + ASSERT(float_eq(p, -89.0f, 1e-4f), "pitch at exactly -89 should not be clamped"); + PASS(); +} + +static void test_pitch_clamp_in_range(void) { + TEST("pitch clamp: 45 unchanged"); + float p = clamp_pitch(45.0f); + ASSERT(float_eq(p, 45.0f, 1e-4f), "pitch in valid range should be unchanged"); + PASS(); +} + +// ── Velocity clamping tests ─────────────────────────────────────────────────── + +static void test_velocity_clamp_above_max(void) { + TEST("velocity clamp: speed above MAX_SPEED gets clamped"); + float vx = MAX_SPEED * 2.0f; + float vy = 0.0f; + clamp_velocity(&vx, &vy, MAX_SPEED); + float speed = sqrtf(vx*vx + vy*vy); + ASSERT(float_eq(speed, MAX_SPEED, 1e-3f), "speed should be clamped to MAX_SPEED"); + PASS(); +} + +static void test_velocity_clamp_below_max(void) { + TEST("velocity clamp: speed below MAX_SPEED unchanged"); + float vx = MAX_SPEED * 0.5f; + float vy = 0.0f; + float orig_vx = vx; + clamp_velocity(&vx, &vy, MAX_SPEED); + ASSERT(float_eq(vx, orig_vx, 1e-4f), "vx should not change when speed < MAX_SPEED"); + ASSERT(float_eq(vy, 0.0f, 1e-4f), "vy should remain 0"); + PASS(); +} + +static void test_velocity_clamp_preserves_direction(void) { + TEST("velocity clamp: direction preserved after clamping"); + float vx = MAX_SPEED * 3.0f; + float vy = MAX_SPEED * 4.0f; /* 3-4-5 scaled by MAX_SPEED */ + float orig_angle = atan2f(vy, vx); + clamp_velocity(&vx, &vy, MAX_SPEED); + float new_angle = atan2f(vy, vx); + ASSERT(float_eq(orig_angle, new_angle, 1e-4f), "direction should be preserved"); + float speed = sqrtf(vx*vx + vy*vy); + ASSERT(float_eq(speed, MAX_SPEED, 1e-3f), "speed should be clamped to MAX_SPEED"); + PASS(); +} + +static void test_velocity_clamp_zero(void) { + TEST("velocity clamp: zero vector stays zero"); + float vx = 0.0f, vy = 0.0f; + clamp_velocity(&vx, &vy, MAX_SPEED); + ASSERT(float_eq(vx, 0.0f, 1e-6f), "zero vx stays zero"); + ASSERT(float_eq(vy, 0.0f, 1e-6f), "zero vy stays zero"); + PASS(); +} + +// ── Friction tests ──────────────────────────────────────────────────────────── + +static void test_friction_reduces_speed(void) { + TEST("friction: speed decreases after one tick"); + float vx = MAX_SPEED; + float vy = 0.0f; + float dt = 1.0f / 60.0f; /* one frame at 60 fps */ + apply_friction(&vx, &vy, FRICTION, dt); + float speed = sqrtf(vx*vx + vy*vy); + ASSERT(speed < MAX_SPEED, "speed should decrease after friction"); + ASSERT(speed >= 0.0f, "speed should not go negative"); + PASS(); +} + +static void test_friction_stops_at_zero(void) { + TEST("friction: large dt brings speed to zero, not below"); + float vx = 1.0f; + float vy = 0.0f; + apply_friction(&vx, &vy, FRICTION, 100.0f); /* huge dt */ + float speed = sqrtf(vx*vx + vy*vy); + ASSERT(float_eq(speed, 0.0f, 1e-6f), "speed should reach 0, not go negative"); + PASS(); +} + +static void test_friction_stationary_object(void) { + TEST("friction: stationary object stays stationary"); + float vx = 0.0f, vy = 0.0f; + apply_friction(&vx, &vy, FRICTION, 1.0f / 60.0f); + ASSERT(float_eq(vx, 0.0f, 1e-6f), "stationary vx should stay 0"); + ASSERT(float_eq(vy, 0.0f, 1e-6f), "stationary vy should stay 0"); + PASS(); +} + +static void test_friction_preserves_direction(void) { + TEST("friction: direction preserved while decelerating"); + float vx = MAX_SPEED * 0.7f; + float vy = MAX_SPEED * 0.7f; + float orig_angle = atan2f(vy, vx); + float dt = 1.0f / 60.0f; + apply_friction(&vx, &vy, FRICTION, dt); + float speed = sqrtf(vx*vx + vy*vy); + if (speed > 0.0f) { + float new_angle = atan2f(vy, vx); + ASSERT(float_eq(orig_angle, new_angle, 1e-4f), "direction should be preserved"); + } + PASS(); +} + +// ── main ───────────────────────────────────────────────────────────────────── + +int main(void) { + printf("\n=== Running Player Physics Tests ===\n"); + + /* Angle wrapping */ + test_angle_wrap_below_zero(); + test_angle_wrap_exactly_zero(); + test_angle_wrap_below_360(); + test_angle_wrap_exactly_360(); + test_angle_wrap_above_360(); + + /* Pitch clamping */ + test_pitch_clamp_above_max(); + test_pitch_clamp_below_min(); + test_pitch_clamp_at_max(); + test_pitch_clamp_at_min(); + test_pitch_clamp_in_range(); + + /* Velocity clamping */ + test_velocity_clamp_above_max(); + test_velocity_clamp_below_max(); + test_velocity_clamp_preserves_direction(); + test_velocity_clamp_zero(); + + /* Friction */ + test_friction_reduces_speed(); + test_friction_stops_at_zero(); + test_friction_stationary_object(); + test_friction_preserves_direction(); + + printf("\n=== Test Results ===\n"); + printf("Passed: %d/%d\n", tests_passed, tests_total); + + if (tests_passed == tests_total) { + printf("\n=== All Tests Passed! ===\n"); + return 0; + } + printf("\n=== Some Tests Failed ===\n"); + return 1; +} diff --git a/tests/walls_test.c b/tests/walls_test.c new file mode 100644 index 0000000..e7c855c --- /dev/null +++ b/tests/walls_test.c @@ -0,0 +1,239 @@ +/* + * Wall Normal Tests + * + * Tests for compute_normal_packed() from walls.c: + * - Packs a 2D perpendicular normal into signed 8-bit components. + * - Input (dx, dy) is the wall direction vector. + * - Normal = (-dy, dx) normalised to unit length, packed into int8_t * 127. + * - Returns the length of the input vector (useful as a wall length). + */ + +#include +#include +#include +#include +#include +#include +#include + +// ── Function under test ────────────────────────────────────────────────────── +// +// Copied verbatim from walls.c. walls.c cannot be #included in a standalone +// test because it pulls in OpenGL, cglm and map.h (which in turn requires SDL2 +// and the ui submodule). All other test files in this repo use the same +// copy-and-compile pattern for the same reason. If the production +// implementation changes, update this copy to match. + +static float compute_normal_packed(float dx, float dy, int8_t out[3]) { + float nx = -dy; + float ny = dx; + + float length = sqrtf(nx * nx + ny * ny); + if (length == 0.0f) { + out[0] = out[1] = out[2] = 0; + return 0; + } + + nx /= length; + ny /= length; + + out[0] = (int8_t)(nx * 127.0f); + out[1] = (int8_t)(ny * 127.0f); + out[2] = 0; + + return length; +} + +// ── Test helpers ───────────────────────────────────────────────────────────── + +static int tests_passed = 0; +static int tests_total = 0; + +#define TEST(name) \ + printf("\nTest %d: %s... ", ++tests_total, name); \ + fflush(stdout) + +#define PASS() \ + printf("PASSED\n"); \ + tests_passed++ + +#define ASSERT(cond, msg) \ + if (!(cond)) { \ + printf("FAILED: %s\n", msg); \ + return; \ + } + +static int float_eq(float a, float b, float eps) { + return fabsf(a - b) < eps; +} + +/* Packed normals are rounded toward zero; allow ±2 for platform rounding. */ +static int i8_near(int8_t a, int expected) { + return abs((int)a - expected) <= 2; +} + +// ── Tests ──────────────────────────────────────────────────────────────────── + +/* Rightward wall: dx=1, dy=0 → normal = (-0, 1) = (0,+1) + * out[0]=0, out[1]=+127, return=1 */ +static void test_rightward_wall(void) { + TEST("rightward wall (dx=1, dy=0): normal is (0, +127)"); + int8_t n[3]; + float len = compute_normal_packed(1.0f, 0.0f, n); + ASSERT(i8_near(n[0], 0), "out[0] should be 0"); + ASSERT(i8_near(n[1], 127), "out[1] should be +127"); + ASSERT(n[2] == 0, "out[2] should always be 0"); + ASSERT(float_eq(len, 1.0f, 1e-5f), "return value should be 1.0"); + PASS(); +} + +/* Leftward wall: dx=-1, dy=0 → normal = (0, -1) + * out[0]=0, out[1]=-127 */ +static void test_leftward_wall(void) { + TEST("leftward wall (dx=-1, dy=0): normal is (0, -127)"); + int8_t n[3]; + float len = compute_normal_packed(-1.0f, 0.0f, n); + ASSERT(i8_near(n[0], 0), "out[0] should be 0"); + ASSERT(i8_near(n[1], -127), "out[1] should be -127"); + ASSERT(n[2] == 0, "out[2] should always be 0"); + ASSERT(float_eq(len, 1.0f, 1e-5f), "return value should be 1.0"); + PASS(); +} + +/* Upward wall: dx=0, dy=1 → normal = (-1, 0) + * out[0]=-127, out[1]=0 */ +static void test_upward_wall(void) { + TEST("upward wall (dx=0, dy=1): normal is (-127, 0)"); + int8_t n[3]; + float len = compute_normal_packed(0.0f, 1.0f, n); + ASSERT(i8_near(n[0], -127), "out[0] should be -127"); + ASSERT(i8_near(n[1], 0), "out[1] should be 0"); + ASSERT(n[2] == 0, "out[2] should always be 0"); + ASSERT(float_eq(len, 1.0f, 1e-5f), "return value should be 1.0"); + PASS(); +} + +/* Downward wall: dx=0, dy=-1 → normal = (+1, 0) + * out[0]=+127, out[1]=0 */ +static void test_downward_wall(void) { + TEST("downward wall (dx=0, dy=-1): normal is (+127, 0)"); + int8_t n[3]; + float len = compute_normal_packed(0.0f, -1.0f, n); + ASSERT(i8_near(n[0], 127), "out[0] should be +127"); + ASSERT(i8_near(n[1], 0), "out[1] should be 0"); + ASSERT(n[2] == 0, "out[2] should always be 0"); + ASSERT(float_eq(len, 1.0f, 1e-5f), "return value should be 1.0"); + PASS(); +} + +/* Degenerate wall (zero vector): length==0 → all zeros, return 0 */ +static void test_degenerate_wall(void) { + TEST("degenerate wall (dx=0, dy=0): returns 0 and all-zero normal"); + int8_t n[3] = {99, 99, 99}; + float len = compute_normal_packed(0.0f, 0.0f, n); + ASSERT(float_eq(len, 0.0f, 1e-10f), "return value should be 0"); + ASSERT(n[0] == 0, "out[0] should be 0 for degenerate input"); + ASSERT(n[1] == 0, "out[1] should be 0 for degenerate input"); + ASSERT(n[2] == 0, "out[2] should be 0 for degenerate input"); + PASS(); +} + +/* 45° diagonal wall (dx=1, dy=1): + * nx = -1/√2 ≈ -0.707 → out[0] ≈ -89 or -90 + * ny = 1/√2 ≈ +0.707 → out[1] ≈ +89 or +90 + * length = √2 */ +static void test_diagonal_wall(void) { + TEST("45° diagonal wall (dx=1, dy=1)"); + int8_t n[3]; + float len = compute_normal_packed(1.0f, 1.0f, n); + ASSERT(float_eq(len, sqrtf(2.0f), 1e-5f), "return should be sqrt(2)"); + /* nx = -1/√2 → packed ≈ -90; ny = 1/√2 → packed ≈ 90 */ + ASSERT(n[0] < 0, "out[0] should be negative for 45° diagonal"); + ASSERT(n[1] > 0, "out[1] should be positive for 45° diagonal"); + ASSERT(n[2] == 0, "out[2] should always be 0"); + /* The two components should be roughly equal in magnitude */ + ASSERT(abs((int)n[0]) == abs((int)n[1]) || abs(abs((int)n[0]) - abs((int)n[1])) <= 1, + "components should have equal magnitude for 45°"); + PASS(); +} + +/* 3-4-5 right triangle: dx=3, dy=4 + * nx = -4/5 = -0.8 → out[0] = (int8_t)(-101.6) = -101 + * ny = 3/5 = +0.6 → out[1] = (int8_t)( 76.2) = 76 + * length = 5 */ +static void test_3_4_5_wall(void) { + TEST("3-4-5 wall (dx=3, dy=4): return=5, normals correct"); + int8_t n[3]; + float len = compute_normal_packed(3.0f, 4.0f, n); + ASSERT(float_eq(len, 5.0f, 1e-4f), "return value should be 5 for 3-4-5"); + ASSERT(i8_near(n[0], -101), "out[0] ≈ -101 for 3-4-5"); + ASSERT(i8_near(n[1], 76), "out[1] ≈ 76 for 3-4-5"); + ASSERT(n[2] == 0, "out[2] should always be 0"); + PASS(); +} + +/* Scale independence: doubling the input length doubles the return value + * but should produce the same packed normal components. */ +static void test_scale_independence(void) { + TEST("scale independence: double length → same normal, double return"); + int8_t n1[3], n2[3]; + float len1 = compute_normal_packed(1.0f, 0.0f, n1); + float len2 = compute_normal_packed(2.0f, 0.0f, n2); + ASSERT(float_eq(len2, 2.0f * len1, 1e-5f), "return should scale with input length"); + ASSERT(n1[0] == n2[0], "packed normal x should be same regardless of scale"); + ASSERT(n1[1] == n2[1], "packed normal y should be same regardless of scale"); + ASSERT(n2[2] == 0, "out[2] should always be 0"); + PASS(); +} + +/* The z component should always be zero regardless of input. */ +static void test_z_component_always_zero(void) { + TEST("z component is always 0 for any valid input"); + int8_t n[3]; + compute_normal_packed(1.0f, 1.0f, n); + ASSERT(n[2] == 0, "out[2] should be 0 for diagonal"); + compute_normal_packed(0.0f, 5.0f, n); + ASSERT(n[2] == 0, "out[2] should be 0 for vertical"); + compute_normal_packed(-3.0f, -7.0f, n); + ASSERT(n[2] == 0, "out[2] should be 0 for arbitrary direction"); + PASS(); +} + +/* Opposite directions produce opposite normals (anti-parallel walls). */ +static void test_opposite_directions_flip_normal(void) { + TEST("opposite wall directions produce negated normals"); + int8_t n_fwd[3], n_rev[3]; + compute_normal_packed( 1.0f, 0.0f, n_fwd); + compute_normal_packed(-1.0f, 0.0f, n_rev); + /* n_fwd and n_rev should be negations of each other (within rounding) */ + ASSERT(i8_near(n_fwd[0], -n_rev[0]), "x components should be negated"); + ASSERT(i8_near(n_fwd[1], -n_rev[1]), "y components should be negated"); + PASS(); +} + +// ── main ───────────────────────────────────────────────────────────────────── + +int main(void) { + printf("\n=== Running Wall Normal Tests ===\n"); + + test_rightward_wall(); + test_leftward_wall(); + test_upward_wall(); + test_downward_wall(); + test_degenerate_wall(); + test_diagonal_wall(); + test_3_4_5_wall(); + test_scale_independence(); + test_z_component_always_zero(); + test_opposite_directions_flip_normal(); + + printf("\n=== Test Results ===\n"); + printf("Passed: %d/%d\n", tests_passed, tests_total); + + if (tests_passed == tests_total) { + printf("\n=== All Tests Passed! ===\n"); + return 0; + } + printf("\n=== Some Tests Failed ===\n"); + return 1; +}