250 lines
8.2 KiB
C
250 lines
8.2 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// dirtyMarkHost.c - randomized check of the stage's dirty-band writers
|
|
// (src/core/surface.c, src/m68k/stageBands.s) against the plain per-row
|
|
// widen they must match byte for byte.
|
|
//
|
|
// surfaceMarkDirtyRows takes rows four at a time and reuses a group's result
|
|
// for the next group that held the same bytes, so its answer depends on what
|
|
// the rows held: a mark of fresh rows, of rows an earlier rect marked, and of
|
|
// rows every earlier mark left different all take different paths. Each
|
|
// scenario seeds the bands one of several ways -- clean, per-row random, runs
|
|
// of equal bands, every row full -- then applies a few random marks of every
|
|
// height and phase, comparing all 400 band bytes with a reference after each.
|
|
// Also checks the whole-stage fills (stageDirtyClearAll, surfaceMarkDirtyAll).
|
|
//
|
|
// Built for the host by scripts/check-dirtymark.sh (the C marker), and for the
|
|
// Atari ST by scripts/verify-atarist-dirtymark.sh, which runs it under Hatari
|
|
// against the 68000 asm the Amiga and X68000 share. -DDIRTY_MARK_SCENARIOS
|
|
// sets the count and -DDIRTY_MARK_LOG a file to write the result line to.
|
|
// Exits nonzero on any mismatch.
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "surfaceInternal.h"
|
|
|
|
#ifndef DIRTY_MARK_SCENARIOS
|
|
#define DIRTY_MARK_SCENARIOS 200000ul
|
|
#endif
|
|
|
|
#define MARKS_PER_SCENARIO 8u
|
|
#define RNG_SEED 0x2545F491ul
|
|
#define SEED_KINDS 4u
|
|
#define SEED_CLEAN 0u
|
|
#define SEED_RANDOM_ROWS 1u
|
|
#define SEED_RUNS 2u
|
|
#define SEED_FULL 3u
|
|
#define RUN_ROWS_MAX 24u
|
|
#define CLEAN_ONE_IN 3u
|
|
#define HEIGHT_CLASSES 3u
|
|
#define HEIGHT_SMALL_MAX 12u
|
|
#define HEIGHT_MEDIUM_MAX 64u
|
|
#define FAILURES_SHOWN 5u
|
|
|
|
static void bandRandom(uint8_t *minWord, uint8_t *maxWord);
|
|
static bool bandsMatch(const char *what);
|
|
static uint16_t heightRandom(uint16_t y);
|
|
static void refMark(uint16_t y, uint16_t yEnd, uint8_t minWord, uint8_t maxWord);
|
|
static uint32_t rngNext(void);
|
|
static uint16_t rngBelow(uint16_t n);
|
|
static void seedBands(uint16_t kind);
|
|
static void setRow(uint16_t y, uint8_t minWord, uint8_t maxWord);
|
|
|
|
static uint32_t gRng = RNG_SEED;
|
|
static uint8_t gRefMin[SURFACE_HEIGHT];
|
|
static uint8_t gRefMax[SURFACE_HEIGHT];
|
|
static uint32_t gChecks = 0ul;
|
|
static uint32_t gFailures = 0ul;
|
|
|
|
|
|
// A random real band: first and last word anywhere on the row, first <= last.
|
|
static void bandRandom(uint8_t *minWord, uint8_t *maxWord) {
|
|
uint8_t a;
|
|
uint8_t b;
|
|
|
|
a = (uint8_t)rngBelow(SURFACE_WORDS_PER_ROW);
|
|
b = (uint8_t)rngBelow(SURFACE_WORDS_PER_ROW);
|
|
*minWord = (a < b) ? a : b;
|
|
*maxWord = (a < b) ? b : a;
|
|
}
|
|
|
|
|
|
// Compare the stage's bands with the reference; report the first few misses.
|
|
static bool bandsMatch(const char *what) {
|
|
uint16_t y;
|
|
|
|
gChecks++;
|
|
if (memcmp(gStageMinWord, gRefMin, SURFACE_HEIGHT) == 0 && memcmp(gStageMaxWord, gRefMax, SURFACE_HEIGHT) == 0) {
|
|
return true;
|
|
}
|
|
gFailures++;
|
|
if (gFailures <= FAILURES_SHOWN) {
|
|
for (y = 0u; y < SURFACE_HEIGHT; y++) {
|
|
if (gStageMinWord[y] != gRefMin[y] || gStageMaxWord[y] != gRefMax[y]) {
|
|
printf("dirtyMark: FAIL %s: row %u is [%u,%u], expected [%u,%u]\n", what, (unsigned)y, (unsigned)gStageMinWord[y], (unsigned)gStageMaxWord[y], (unsigned)gRefMin[y], (unsigned)gRefMax[y]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// A mark height for rows from y: short, medium and tall about equally often,
|
|
// clipped to the stage.
|
|
static uint16_t heightRandom(uint16_t y) {
|
|
uint16_t room;
|
|
uint16_t h;
|
|
|
|
room = (uint16_t)(SURFACE_HEIGHT - y);
|
|
switch (rngBelow(HEIGHT_CLASSES)) {
|
|
case 0u:
|
|
h = (uint16_t)(1u + rngBelow(HEIGHT_SMALL_MAX));
|
|
break;
|
|
case 1u:
|
|
h = (uint16_t)(1u + rngBelow(HEIGHT_MEDIUM_MAX));
|
|
break;
|
|
default:
|
|
h = (uint16_t)(1u + rngBelow(SURFACE_HEIGHT));
|
|
break;
|
|
}
|
|
return (h < room) ? h : room;
|
|
}
|
|
|
|
|
|
// The widen every marker must match: each row, one at a time.
|
|
static void refMark(uint16_t y, uint16_t yEnd, uint8_t minWord, uint8_t maxWord) {
|
|
for (; y < yEnd; y++) {
|
|
if (minWord < gRefMin[y]) {
|
|
gRefMin[y] = minWord;
|
|
}
|
|
if (maxWord > gRefMax[y]) {
|
|
gRefMax[y] = maxWord;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static uint32_t rngNext(void) {
|
|
gRng ^= gRng << 13;
|
|
gRng ^= gRng >> 17;
|
|
gRng ^= gRng << 5;
|
|
return gRng;
|
|
}
|
|
|
|
|
|
static uint16_t rngBelow(uint16_t n) {
|
|
return (uint16_t)(rngNext() % n);
|
|
}
|
|
|
|
|
|
// Put both the stage's bands and the reference in one of the seed states.
|
|
static void seedBands(uint16_t kind) {
|
|
uint16_t y;
|
|
uint16_t run;
|
|
uint8_t minWord;
|
|
uint8_t maxWord;
|
|
|
|
y = 0u;
|
|
while (y < SURFACE_HEIGHT) {
|
|
switch (kind) {
|
|
case SEED_CLEAN:
|
|
run = SURFACE_HEIGHT;
|
|
minWord = STAGE_DIRTY_CLEAN_MIN;
|
|
maxWord = STAGE_DIRTY_CLEAN_MAX;
|
|
break;
|
|
case SEED_FULL:
|
|
run = SURFACE_HEIGHT;
|
|
minWord = 0u;
|
|
maxWord = STAGE_DIRTY_FULL_MAX;
|
|
break;
|
|
case SEED_RUNS:
|
|
run = (uint16_t)(1u + rngBelow(RUN_ROWS_MAX));
|
|
if (rngBelow(CLEAN_ONE_IN) == 0u) {
|
|
minWord = STAGE_DIRTY_CLEAN_MIN;
|
|
maxWord = STAGE_DIRTY_CLEAN_MAX;
|
|
} else {
|
|
bandRandom(&minWord, &maxWord);
|
|
}
|
|
break;
|
|
default:
|
|
run = 1u;
|
|
if (rngBelow(CLEAN_ONE_IN) == 0u) {
|
|
minWord = STAGE_DIRTY_CLEAN_MIN;
|
|
maxWord = STAGE_DIRTY_CLEAN_MAX;
|
|
} else {
|
|
bandRandom(&minWord, &maxWord);
|
|
}
|
|
break;
|
|
}
|
|
for (; run != 0u && y < SURFACE_HEIGHT; run--, y++) {
|
|
setRow(y, minWord, maxWord);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void setRow(uint16_t y, uint8_t minWord, uint8_t maxWord) {
|
|
gStageMinWord[y] = minWord;
|
|
gStageMaxWord[y] = maxWord;
|
|
gRefMin[y] = minWord;
|
|
gRefMax[y] = maxWord;
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
uint32_t scenario;
|
|
uint16_t mark;
|
|
uint16_t y;
|
|
uint16_t h;
|
|
uint8_t minWord;
|
|
uint8_t maxWord;
|
|
#ifdef DIRTY_MARK_LOG
|
|
FILE *log;
|
|
#endif
|
|
|
|
// The whole-stage fills. (gStage is NULL here, which is the stage.)
|
|
seedBands(SEED_RANDOM_ROWS);
|
|
stageDirtyClearAll();
|
|
memset(gRefMin, STAGE_DIRTY_CLEAN_MIN, SURFACE_HEIGHT);
|
|
memset(gRefMax, STAGE_DIRTY_CLEAN_MAX, SURFACE_HEIGHT);
|
|
bandsMatch("stageDirtyClearAll");
|
|
seedBands(SEED_RANDOM_ROWS);
|
|
surfaceMarkDirtyAll(gStage);
|
|
memset(gRefMin, 0, SURFACE_HEIGHT);
|
|
memset(gRefMax, STAGE_DIRTY_FULL_MAX, SURFACE_HEIGHT);
|
|
bandsMatch("surfaceMarkDirtyAll");
|
|
|
|
for (scenario = 0ul; scenario < DIRTY_MARK_SCENARIOS; scenario++) {
|
|
seedBands((uint16_t)(scenario % SEED_KINDS));
|
|
for (mark = 0u; mark < MARKS_PER_SCENARIO; mark++) {
|
|
y = rngBelow(SURFACE_HEIGHT);
|
|
h = heightRandom(y);
|
|
bandRandom(&minWord, &maxWord);
|
|
surfaceMarkDirtyRows(y, (uint16_t)(y + h), minWord, maxWord);
|
|
refMark(y, (uint16_t)(y + h), minWord, maxWord);
|
|
if (!bandsMatch("surfaceMarkDirtyRows")) {
|
|
if (gFailures <= FAILURES_SHOWN) {
|
|
printf("dirtyMark: scenario %lu mark %u: rows %u..%u [%u,%u]\n", (unsigned long)scenario, (unsigned)mark, (unsigned)y, (unsigned)(y + h - 1u), (unsigned)minWord, (unsigned)maxWord);
|
|
}
|
|
memcpy(gStageMinWord, gRefMin, SURFACE_HEIGHT);
|
|
memcpy(gStageMaxWord, gRefMax, SURFACE_HEIGHT);
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("dirtyMark: %lu passed, %lu failed\n", (unsigned long)(gChecks - gFailures), (unsigned long)gFailures);
|
|
#ifdef DIRTY_MARK_LOG
|
|
log = fopen(DIRTY_MARK_LOG, "w");
|
|
if (log != NULL) {
|
|
fprintf(log, "dirtyMark: %lu passed, %lu failed\n", (unsigned long)(gChecks - gFailures), (unsigned long)gFailures);
|
|
fclose(log);
|
|
}
|
|
#endif
|
|
return (gFailures == 0ul) ? 0 : 1;
|
|
}
|