/*
 * sim.c -- offline replication of the snake_saver() movement engine
 *          (sys/dev/misc/syscons/snake/snake_saver.c:83-111)
 *
 * Proves the wall-bounce arithmetic claim for DF-5194:
 *   - geometry ysize==1 (permitted by sc_set_text_mode's clamp,
 *     scvidctl.c:94-96): the FIRST move escapes the vtb and the
 *     row test (f/xsize) can never fire again -> unbounded forward
 *     drift, one sc_vtb_putc OOB write per tick.
 *   - control run with real geometry (80x25): never escapes.
 *
 * The arithmetic is copied verbatim from the kernel source, including
 * C truncating division/modulo on ints.
 */
#include <stdio.h>
#include <stdlib.h>

static int xsize, ysize, messagelen;
static int dirx, diry;
static int savs[64];

static void run(const char *label, int xs, int ys, int xp, int yp, int ticks)
{
	long vtb_size = (long)xs * ys;
	long maxpos, minpos;
	int i;

	xsize = xs; ysize = ys;
	messagelen = 20;

	/* snake_saver.c:83-87 init */
	dirx = (xp ? 1 : -1);
	diry = (yp ? xsize : -xsize);
	for (i = 0; i < messagelen; i++)
		savs[i] = xp + yp * xsize;

	maxpos = minpos = savs[0];

	for (i = 0; i < ticks; i++) {
		int f = savs[0];
		/* snake_saver.c:101-108, krandom() replaced by rand() */
		if ((f % xsize) == 0 || (f % xsize) == xsize - 1 ||
		    (rand() % 50) == 0)
			dirx = -dirx;
		if ((f / xsize) == 0 || (f / xsize) == ysize - 1 ||
		    (rand() % 20) == 0)
			diry = -diry;
		savs[0] += dirx + diry;		/* :109 */
		if (savs[0] > maxpos) maxpos = savs[0];
		if (savs[0] < minpos) minpos = savs[0];
	}
	printf("%-28s vtb_size=%-7ld final=%-9d max=%-9ld min=%-9ld %s\n",
	    label, vtb_size, savs[0], maxpos, minpos,
	    (maxpos >= vtb_size || minpos < 0) ? "ESCAPED (OOB writes)" : "in-bounds");
}

int main(void)
{
	srand(1234);
	/* cursor mid-screen, degenerate 1-row geometry */
	run("ysize=1 xsize=80 (latent)", 80, 1, 40, 0, 2000);
	/* top-left corner */
	run("ysize=1 xsize=80 corner", 80, 1, 0, 0, 2000);
	/* real VGA geometry, several cursors, 100k ticks */
	run("80x25 cursor mid", 80, 25, 40, 12, 100000);
	run("80x25 corner 0,0", 80, 25, 0, 0, 100000);
	run("80x25 corner 79,24", 80, 25, 79, 24, 100000);
	run("132x60 cursor mid", 132, 60, 66, 30, 100000);
	return 0;
}
