Files
NX2navi/tests/prp_file_check.c
chrg6 cba95f8d0a Record in shader-prepared.bin which seed it was prepared from, and prepare again at an update when the seed changed.
The prepared list named its shader variants and nothing about where they came from, so an update that keeps the prepared shaders (shader_carry) kept the old list whatever seed the new kit writes: variants only the new seed names, from a newer pipeline list or a change to which variants are prepared, were translated when play first drew them. The existing identities do not cover this: nx2navi_xlate.id and identities.txt name translators, which decide cache keys and what is carried over, not which variants are listed.

The list is now version 2 (runtime/prp_file.h): after the head come the sha256 of seed-programs.bin, the sha256 of seed-answers.bin and the translator identity, written by gfx_shader_prepare_seed; the three readers go through gfx_shader_prepared_read and still read a version 1 list. At an update step_shaders writes the seed first (prepare_shaders.py --no-run, about 4 s) and keeps the list only when its digests are that seed's (make_game.py seed_changed); another seed, or a list that does not say, is prepared again. Another translator alone still carries over: input ids do not depend on the translator, and preparing everything again for it is what carrying was made to avoid.

Checked with tests/prp_file_check.py (the C layout against make_game's reader), three launcher tests, and the exe's --prepare-shaders on a seed without records in a folder of its own: the digests it wrote are those of the seed files, one byte more in the seed reads as changed, and after preparing again it does not. A version 1 list of 42,142 variants and the same list as version 2 are both read whole by --probe-prewarm.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 04:36:33 -05:00

67 lines
3.3 KiB
C

// runtime/prp_file.h: the layout of shader-prepared.bin. Checks the parse of a version 1 list, of a version 2 list and
// of damaged ones, then writes a version 2 list into the folder given as argv[1] for tests/prp_file_check.py, which
// reads its head with tools/make_game.py prepared_from. Prints each failed check and exits 1 on any.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "prp_file.h"
static int fails;
#define CHECK(c, ...) do { if (!(c)) { fails++; printf("FAIL line %d: ", __LINE__); printf(__VA_ARGS__); printf("\n"); } } while (0)
int main(int argc, char **argv) {
if (argc < 2) return printf("usage: prp_file_check <folder>\n"), 2;
uint8_t file[PRP_HEAD + PRP_FROM_SIZE + 3 * PRP_REC + 8];
PrpFrom from, got;
for (int i = 0; i < 32; i++) from.programs[i] = (uint8_t)i, from.answers[i] = (uint8_t)(0x40 + i), from.translator[i] = (uint8_t)(0x80 + i);
size_t n = 99;
int has = 9;
// version 2: the head, then three records
size_t head = prp_head_write(file, &from);
CHECK(head == PRP_HEAD + PRP_FROM_SIZE && head == 112, "the head is %zu bytes", head);
for (int r = 0; r < 3; r++) {
uint8_t *rec = file + head + r * PRP_REC;
memset(rec, 0, PRP_REC);
rec[0] = (uint8_t)(r + 1), rec[1] = (uint8_t)r, memset(rec + 4, 0xa0 + r, 32);
}
const uint8_t *recs = prp_parse(file, head + 3 * PRP_REC, &n, &got, &has);
CHECK(recs == file + head && n == 3 && has == 1, "version 2: %zu records, has_from %d", n, has);
CHECK(!memcmp(&got, &from, sizeof from), "version 2: what it was prepared from comes back");
CHECK(recs && recs[PRP_REC * 2] == 3 && recs[PRP_REC * 2 + 4] == 0xa2, "version 2: the third record");
// a cut last record is not counted
recs = prp_parse(file, head + 2 * PRP_REC + 35, &n, &got, &has);
CHECK(recs && n == 2, "a cut record: %zu", n);
// the head alone, or less: no list
CHECK(!prp_parse(file, head, &n, &got, &has) && n == 0, "no records");
CHECK(!prp_parse(file, head - 1, &n, &got, &has) && n == 0, "a cut head");
CHECK(!prp_parse(NULL, 0, &n, &got, &has), "no file");
// version 1: records right after the 16 bytes, and nothing about where they came from
uint8_t v1[PRP_HEAD + 2 * PRP_REC];
uint32_t one = 1, rec = PRP_REC;
memcpy(v1, PRP_MAGIC, 8), memcpy(v1 + 8, &one, 4), memcpy(v1 + 12, &rec, 4);
memset(v1 + PRP_HEAD, 0x11, 2 * PRP_REC);
recs = prp_parse(v1, sizeof v1, &n, &got, &has);
CHECK(recs == v1 + PRP_HEAD && n == 2 && has == 0, "version 1: %zu records, has_from %d", n, has);
// another version, record size or magic: not read
uint32_t three = 3, other = 40;
memcpy(v1 + 8, &three, 4);
CHECK(!prp_parse(v1, sizeof v1, &n, &got, &has), "version 3");
memcpy(v1 + 8, &one, 4), memcpy(v1 + 12, &other, 4);
CHECK(!prp_parse(v1, sizeof v1, &n, &got, &has), "another record size");
memcpy(v1 + 12, &rec, 4), v1[0] = 'X';
CHECK(!prp_parse(v1, sizeof v1, &n, &got, &has), "another magic");
char path[1024];
snprintf(path, sizeof path, "%s/shader-prepared.bin", argv[1]);
FILE *f = fopen(path, "wb");
CHECK(f && fwrite(file, 1, head + 3 * PRP_REC, f) == head + 3 * PRP_REC, "cannot write %s", path);
if (f) fclose(f);
if (!fails) printf("prepared list file: all C checks passed\n");
return fails != 0;
}