Support large maps up to 8192x8192 tiles

Raise hard map size cap from 4096 to 8192 via MAX_MAP_SIZE constant.
Replace fixed 16 KB fgets buffer with dynamic line reader that handles
arbitrarily long tile rows. Check calloc returns for tile layer
allocation.

Add entity distance culling: skip updates for entities beyond 2x screen
width from camera, skip rendering beyond 64 px margin. Dead entities
bypass culling so cleanup callbacks always run. Player, spacecraft,
drone, and moving platforms set ENTITY_ALWAYS_UPDATE to opt out.

Replace naive 4-neighbor flood fill with scanline algorithm (O(height)
stack instead of O(area)) for safe use on large maps.

Raise MAX_ENTITY_SPAWNS from 128 to 512 and MAX_EXIT_ZONES from 8 to
16 to support populated large levels.
This commit is contained in:
Thomas
2026-03-01 15:17:58 +00:00
parent ad2d68a8b4
commit df3bc29fb7
11 changed files with 181 additions and 45 deletions

View File

@@ -279,36 +279,82 @@ static void set_tile(Tilemap *map, uint16_t *layer, int tx, int ty, uint16_t id)
}
/* ═══════════════════════════════════════════════════
* Flood fill (iterative, stack-based)
* Flood fill (scanline algorithm)
*
* Processes entire horizontal spans at once, then
* pushes only the boundary seeds above and below.
* Much more memory-efficient than naive 4-neighbor
* fill on large maps (O(height) stack vs O(area)).
* ═══════════════════════════════════════════════════ */
typedef struct FillNode { int x, y; } FillNode;
typedef struct FillSpan { int x_left, x_right, y, dir; } FillSpan;
static void flood_fill(Tilemap *map, uint16_t *layer, int sx, int sy, uint16_t new_id) {
uint16_t old_id = get_tile(map, layer, sx, sy);
if (old_id == new_id) return;
int capacity = 1024;
FillNode *stack = malloc(capacity * sizeof(FillNode));
FillSpan *stack = malloc(capacity * sizeof(FillSpan));
if (!stack) return;
int top = 0;
stack[top++] = (FillNode){sx, sy};
/* Fill the initial span containing (sx, sy). */
int xl = sx, xr = sx;
while (xl > 0 && get_tile(map, layer, xl - 1, sy) == old_id) xl--;
while (xr < map->width - 1 && get_tile(map, layer, xr + 1, sy) == old_id) xr++;
for (int x = xl; x <= xr; x++) set_tile(map, layer, x, sy, new_id);
/* Seed rows above and below. */
stack[top++] = (FillSpan){xl, xr, sy, -1};
stack[top++] = (FillSpan){xl, xr, sy, 1};
while (top > 0) {
FillNode n = stack[--top];
if (n.x < 0 || n.x >= map->width || n.y < 0 || n.y >= map->height) continue;
if (get_tile(map, layer, n.x, n.y) != old_id) continue;
FillSpan s = stack[--top];
int ny = s.y + s.dir;
if (ny < 0 || ny >= map->height) continue;
set_tile(map, layer, n.x, n.y, new_id);
/* Scan the row for sub-spans that match old_id and are seeded
* by the parent span [x_left, x_right]. */
int x = s.x_left;
while (x <= s.x_right) {
/* Skip non-matching tiles. */
if (get_tile(map, layer, x, ny) != old_id) { x++; continue; }
/* Grow stack if needed */
if (top + 4 >= capacity) {
capacity *= 2;
stack = realloc(stack, capacity * sizeof(FillNode));
/* Found a matching run; expand it fully. */
int run_l = x;
while (run_l > 0 && get_tile(map, layer, run_l - 1, ny) == old_id) run_l--;
int run_r = x;
while (run_r < map->width - 1 && get_tile(map, layer, run_r + 1, ny) == old_id) run_r++;
/* Fill this run. */
for (int fx = run_l; fx <= run_r; fx++) set_tile(map, layer, fx, ny, new_id);
/* Grow stack if needed. */
if (top + 2 >= capacity) {
capacity *= 2;
FillSpan *tmp = realloc(stack, capacity * sizeof(FillSpan));
if (!tmp) { fprintf(stderr, "Warning: flood fill out of memory\n"); free(stack); return; }
stack = tmp;
}
/* Continue in the same direction. */
stack[top++] = (FillSpan){run_l, run_r, ny, s.dir};
/* Also seed the opposite direction for portions that extend
* beyond the parent span (leak-around fills). */
if (run_l < s.x_left)
stack[top++] = (FillSpan){run_l, s.x_left - 1, ny, -s.dir};
if (run_r > s.x_right) {
if (top + 1 >= capacity) {
capacity *= 2;
FillSpan *tmp = realloc(stack, capacity * sizeof(FillSpan));
if (!tmp) { fprintf(stderr, "Warning: flood fill out of memory\n"); free(stack); return; }
stack = tmp;
}
stack[top++] = (FillSpan){s.x_right + 1, run_r, ny, -s.dir};
}
x = run_r + 1;
}
stack[top++] = (FillNode){n.x + 1, n.y};
stack[top++] = (FillNode){n.x - 1, n.y};
stack[top++] = (FillNode){n.x, n.y + 1};
stack[top++] = (FillNode){n.x, n.y - 1};
}
free(stack);
@@ -430,8 +476,8 @@ static void resize_layer(uint16_t **layer, int old_w, int old_h, int new_w, int
static void editor_resize(Editor *ed, int new_w, int new_h) {
if (new_w < 10) new_w = 10;
if (new_h < 10) new_h = 10;
if (new_w > 4096) new_w = 4096;
if (new_h > 4096) new_h = 4096;
if (new_w > MAX_MAP_SIZE) new_w = MAX_MAP_SIZE;
if (new_h > MAX_MAP_SIZE) new_h = MAX_MAP_SIZE;
int old_w = ed->map.width;
int old_h = ed->map.height;