240 lines
9 KiB
C
240 lines
9 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// tileLayerHost.c - Host-cc harness for tile layers (src/core/tileLayer.c
|
|
// and the generic ring, src/generic/genericTileLayer.c). Every draw is
|
|
// checked pixel for pixel against the map itself -- the tile a band pixel
|
|
// falls in, worked out from the origin with no ring -- so the ring's
|
|
// bookkeeping (which slots a move uncovers, wraps at the map's and the
|
|
// ring's edges) cannot hide behind a second implementation. Runs of moves
|
|
// of every size (a pixel, a tile, a view, a jump across the map's seam,
|
|
// both ways), maps smaller than the view, random bands, and cells changed
|
|
// with jlTileLayerSetTile, in and out of view. Also: rows outside the band
|
|
// keep their pixels, the band's rows are marked dirty full width, a
|
|
// JL_TILE_MAP_SKIP cell draws color 0, and jlTileLayerCreate refuses bad
|
|
// arguments.
|
|
//
|
|
// Built by scripts/check-tilelayer.sh with -DJOEYLIB_PLATFORM_BLANK.
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "joey/joey.h"
|
|
#include "surfaceInternal.h"
|
|
|
|
#define TILE_COUNT 64u
|
|
#define RUNS 300u
|
|
#define STEPS_PER_RUN 40u
|
|
#define OUTSIDE_COLOR 9u
|
|
#define MAX_MAP_SIDE 300u
|
|
|
|
static jlTileT gTiles[TILE_COUNT];
|
|
static uint8_t gMap[MAX_MAP_SIDE * MAX_MAP_SIDE];
|
|
static uint32_t gChecks;
|
|
|
|
static bool checkBand(jlSurfaceT *stage, uint8_t viewBy, uint8_t viewHeight, uint16_t mapW, uint16_t mapH, uint32_t x, uint32_t y, const char *what);
|
|
static bool checkCreateRefusals(void);
|
|
static uint8_t expectedPixel(uint16_t mapW, uint16_t mapH, uint32_t mx, uint32_t my);
|
|
static uint32_t nextRand(void);
|
|
static bool runOne(jlSurfaceT *stage, uint32_t run);
|
|
static uint16_t stepFor(uint32_t mapPx);
|
|
|
|
static uint32_t gSeed = 12345u;
|
|
|
|
|
|
static bool checkBand(jlSurfaceT *stage, uint8_t viewBy, uint8_t viewHeight, uint16_t mapW, uint16_t mapH, uint32_t x, uint32_t y, const char *what) {
|
|
int16_t px;
|
|
int16_t py;
|
|
int16_t top;
|
|
int16_t bottom;
|
|
uint32_t mapPxW;
|
|
uint32_t mapPxH;
|
|
uint8_t got;
|
|
uint8_t want;
|
|
|
|
mapPxW = (uint32_t)mapW * 8u;
|
|
mapPxH = (uint32_t)mapH * 8u;
|
|
top = (int16_t)(viewBy * 8);
|
|
bottom = (int16_t)((viewBy + viewHeight) * 8);
|
|
gChecks++;
|
|
for (py = 0; py < JL_SURFACE_HEIGHT; py++) {
|
|
bool inBand = (py >= top && py < bottom);
|
|
|
|
if (inBand && (jlgStageMinWord[py] != 0u || jlgStageMaxWord[py] != STAGE_DIRTY_FULL_MAX)) {
|
|
printf(" FAIL %s: band row %d not marked dirty full width (%u..%u)\n", what, py, jlgStageMinWord[py], jlgStageMaxWord[py]);
|
|
return false;
|
|
}
|
|
for (px = 0; px < JL_SURFACE_WIDTH; px++) {
|
|
got = jlSamplePixel(stage, px, py);
|
|
want = inBand ? expectedPixel(mapW, mapH, (x + (uint32_t)px) % mapPxW, (y + (uint32_t)(py - top)) % mapPxH) : OUTSIDE_COLOR;
|
|
if (got != want) {
|
|
printf(" FAIL %s: pixel (%d,%d) is %u, want %u (origin %lu,%lu map %ux%u band %u+%u)\n", what, px, py, got, want, (unsigned long)x, (unsigned long)y, mapW, mapH, viewBy, viewHeight);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool checkCreateRefusals(void) {
|
|
bool ok = true;
|
|
|
|
ok = ok && jlTileLayerCreate(NULL, gMap, 10u, 10u, 0u, 25u) == NULL;
|
|
ok = ok && jlTileLayerCreate(gTiles, NULL, 10u, 10u, 0u, 25u) == NULL;
|
|
ok = ok && jlTileLayerCreate(gTiles, gMap, 0u, 10u, 0u, 25u) == NULL;
|
|
ok = ok && jlTileLayerCreate(gTiles, gMap, 10u, 0u, 0u, 25u) == NULL;
|
|
ok = ok && jlTileLayerCreate(gTiles, gMap, (uint16_t)(JL_TILE_LAYER_MAX_SIDE + 1u), 10u, 0u, 25u) == NULL;
|
|
ok = ok && jlTileLayerCreate(gTiles, gMap, 10u, (uint16_t)(JL_TILE_LAYER_MAX_SIDE + 1u), 0u, 25u) == NULL;
|
|
ok = ok && jlTileLayerCreate(gTiles, gMap, 10u, 10u, 0u, 0u) == NULL;
|
|
ok = ok && jlTileLayerCreate(gTiles, gMap, 10u, 10u, 1u, 25u) == NULL;
|
|
jlTileLayerDestroy(NULL);
|
|
printf(" create refusals: %s\n", ok ? "PASS" : "FAIL");
|
|
return ok;
|
|
}
|
|
|
|
|
|
// The color of map pixel (mx, my), straight from the map and the tiles.
|
|
static uint8_t expectedPixel(uint16_t mapW, uint16_t mapH, uint32_t mx, uint32_t my) {
|
|
uint8_t idx;
|
|
uint8_t b;
|
|
|
|
(void)mapH;
|
|
idx = gMap[(my >> 3) * mapW + (mx >> 3)];
|
|
if (idx == JL_TILE_MAP_SKIP) {
|
|
return 0u;
|
|
}
|
|
b = gTiles[idx].pixels[(my & 7u) * JL_TILE_BYTES_PER_ROW + ((mx & 7u) >> 1)];
|
|
return (uint8_t)(((mx & 1u) == 0u) ? (b >> 4) : (b & 0x0Fu));
|
|
}
|
|
|
|
|
|
static uint32_t nextRand(void) {
|
|
gSeed = gSeed * 1103515245u + 12345u;
|
|
return (gSeed >> 8) & 0xFFFFFFu;
|
|
}
|
|
|
|
|
|
static bool runOne(jlSurfaceT *stage, uint32_t run) {
|
|
jlTileLayerT *layer;
|
|
uint16_t mapW;
|
|
uint16_t mapH;
|
|
uint8_t viewBy;
|
|
uint8_t viewHeight;
|
|
uint32_t x;
|
|
uint32_t y;
|
|
uint32_t i;
|
|
uint32_t step;
|
|
char what[64];
|
|
|
|
// Every fifth run a map smaller than the view, the rest up to MAX_MAP_SIDE.
|
|
if (run % 5u == 0u) {
|
|
mapW = (uint16_t)(1u + nextRand() % 12u);
|
|
mapH = (uint16_t)(1u + nextRand() % 8u);
|
|
} else {
|
|
mapW = (uint16_t)(1u + nextRand() % MAX_MAP_SIDE);
|
|
mapH = (uint16_t)(1u + nextRand() % MAX_MAP_SIDE);
|
|
}
|
|
for (i = 0u; i < (uint32_t)mapW * mapH; i++) {
|
|
gMap[i] = (nextRand() % 17u == 0u) ? JL_TILE_MAP_SKIP : (uint8_t)(nextRand() % TILE_COUNT);
|
|
}
|
|
viewHeight = (uint8_t)((run % 3u == 0u) ? JL_TILE_BLOCKS_PER_COL : 1u + nextRand() % JL_TILE_BLOCKS_PER_COL);
|
|
viewBy = (uint8_t)(nextRand() % (uint32_t)(JL_TILE_BLOCKS_PER_COL - viewHeight + 1u));
|
|
layer = jlTileLayerCreate(gTiles, gMap, mapW, mapH, viewBy, viewHeight);
|
|
if (layer == NULL) {
|
|
printf(" FAIL run %lu: create %ux%u band %u+%u\n", (unsigned long)run, mapW, mapH, viewBy, viewHeight);
|
|
return false;
|
|
}
|
|
jlFillRect(stage, 0, 0, JL_SURFACE_WIDTH, JL_SURFACE_HEIGHT, OUTSIDE_COLOR);
|
|
x = nextRand() % ((uint32_t)mapW * 8u);
|
|
y = nextRand() % ((uint32_t)mapH * 8u);
|
|
for (step = 0u; step < STEPS_PER_RUN; step++) {
|
|
uint32_t kind = nextRand() % 10u;
|
|
|
|
if (kind < 7u) {
|
|
// A move: dx, dy each of stepFor's sizes, either way.
|
|
uint32_t pxW = (uint32_t)mapW * 8u;
|
|
uint32_t pxH = (uint32_t)mapH * 8u;
|
|
|
|
x = (x + ((nextRand() & 1u) ? stepFor(pxW) : pxW - stepFor(pxW) % pxW)) % pxW;
|
|
y = (y + ((nextRand() & 1u) ? stepFor(pxH) : pxH - stepFor(pxH) % pxH)) % pxH;
|
|
} else if (kind < 9u) {
|
|
// A cell change, in view half the time.
|
|
uint16_t tx = (uint16_t)((nextRand() & 1u) ? ((x >> 3) + nextRand() % 41u) % mapW : nextRand() % mapW);
|
|
uint16_t ty = (uint16_t)((nextRand() & 1u) ? ((y >> 3) + nextRand() % 26u) % mapH : nextRand() % mapH);
|
|
|
|
jlTileLayerSetTile(layer, tx, ty, (nextRand() % 9u == 0u) ? JL_TILE_MAP_SKIP : (uint8_t)(nextRand() % TILE_COUNT));
|
|
}
|
|
// jlStagePresent clears the dirty marks, so the check sees only the
|
|
// draw's.
|
|
jlStagePresent();
|
|
// Sometimes an origin past the map's size: the layer takes it modulo.
|
|
if (nextRand() % 6u == 0u) {
|
|
jlTileLayerDraw(stage, layer, (uint16_t)(x + (uint32_t)mapW * 8u * (nextRand() % 2u)), (uint16_t)(y + (uint32_t)mapH * 8u * (nextRand() % 2u)));
|
|
} else {
|
|
jlTileLayerDraw(stage, layer, (uint16_t)x, (uint16_t)y);
|
|
}
|
|
snprintf(what, sizeof(what), "run %lu step %lu", (unsigned long)run, (unsigned long)step);
|
|
if (!checkBand(stage, viewBy, viewHeight, mapW, mapH, x, y, what)) {
|
|
jlTileLayerDestroy(layer);
|
|
return false;
|
|
}
|
|
}
|
|
jlTileLayerDestroy(layer);
|
|
return true;
|
|
}
|
|
|
|
|
|
// A move size: a pixel or a few, about a tile, about a view, or anywhere.
|
|
static uint16_t stepFor(uint32_t mapPx) {
|
|
uint32_t pick = nextRand() % 8u;
|
|
uint32_t s;
|
|
|
|
if (pick < 3u) {
|
|
s = nextRand() % 4u;
|
|
} else if (pick < 5u) {
|
|
s = 6u + nextRand() % 12u;
|
|
} else if (pick < 7u) {
|
|
s = 300u + nextRand() % 60u;
|
|
} else {
|
|
s = nextRand();
|
|
}
|
|
return (uint16_t)(s % mapPx);
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
jlConfigT config;
|
|
jlSurfaceT *stage;
|
|
uint32_t i;
|
|
uint32_t run;
|
|
bool ok;
|
|
|
|
config.codegenBytes = 0u;
|
|
config.noAudio = true;
|
|
if (!jlInit(&config)) {
|
|
printf("tileLayerHost: FAIL -- jlInit: %s\n", jlLastError());
|
|
return 1;
|
|
}
|
|
stage = jlStageGet();
|
|
for (i = 0u; i < TILE_COUNT; i++) {
|
|
uint32_t b;
|
|
|
|
for (b = 0u; b < JL_TILE_BYTES; b++) {
|
|
gTiles[i].pixels[b] = (uint8_t)nextRand();
|
|
}
|
|
}
|
|
printf("tileLayerHost: layers against the map, pixel for pixel\n");
|
|
ok = checkCreateRefusals();
|
|
for (run = 0u; run < RUNS && ok; run++) {
|
|
ok = runOne(stage, run);
|
|
}
|
|
printf(" %lu draws checked over %lu runs: %s\n", (unsigned long)gChecks, (unsigned long)run, ok ? "PASS" : "FAIL");
|
|
jlShutdown();
|
|
printf("tileLayerHost: %s\n", ok ? "PASS" : "FAIL");
|
|
return ok ? 0 : 1;
|
|
}
|