Compatibility stage-result format (for tools and the runtime)¶
The compatibility matrix (compat/matrix.json, rendered to COMPATIBILITY.md) is built from small JSON files,
one per (game, build, stage). Any tool, including the C/ObjC runtime, reports a result by writing one file.
napple compat render (or napple compat run) merges them into the matrix. Files never contain game bytes.
Location¶
work/compat/<TITLEID>/<BUILDID>/<stage>.json
TITLEID: the application (base) title id, 16 hex digits (0100000000010000; for an update/DLC use the base id). Case-insensitive on read; write uppercase.BUILDID: first 16 hex chars (lowercase) of the main NSO build id, which is the 32-byte ModuleId in the NSO header at 0x40, hex-encoded; the first 8 bytes. The runtime already knows it from the NSO header it loads.napple compat show <titleid>prints it.unknownis used only when the build id could not be read.stage: one ofingest build-plan nso-load site-scan rewrite rewrite-verify runtime-boot video audio input saves title-screen ingame. Owned by napple-compat: ingest, build-plan, nso-load, site-scan. Reserved for the rewriter: rewrite, rewrite-verify. For the runtime: runtime-boot (reached the guestmain), video (frames presented), audio, input, saves, title-screen, ingame.- Write atomically (write
<stage>.json.tmp, thenrename). Create the directories as needed. Overwrite on each run; the file with the newesttimewins when it is merged with the matrix.
File content¶
{
"stage": "runtime-boot",
"status": "partial",
"reason": "reached nnMain, aborted at first nvn call",
"evidence": "work/runtime/logs/0100000000010000-20261020-0215.log",
"time": 1760926000,
"tool": "runtime",
"counters": {
"unsupported_svcs": [38, 51],
"missing_ipc": ["fsp-srv:OpenSaveDataFileSystem", "nvdrv:Ioctl 0x40044801"],
"missing_nvn": ["nvnQueueSubmitCommands"],
"crashes": [{"signal": 11, "pc": "0x00000071a0003c10", "note": "null deref in sdk"}],
"fps_notes": ["avg 24 fps in the first scene"]
}
}
| Field | Type | Required | Meaning |
|---|---|---|---|
status |
string | yes | untested, pass, partial, fail, blocked (case-insensitive on read; write lowercase) |
reason |
string | recommended | one short sentence: why this status |
evidence |
string | recommended | path of a log/artifact, relative to the repo root, under work/ |
time |
integer | recommended | Unix seconds (UTC). If omitted, the file mtime is used |
tool |
string | no | producer name |
stage |
string | no | informational; the file name is authoritative |
counters |
object | no | every member optional; arrays are unioned across the stages of a build |
Status meaning: pass works as intended; partial works with known gaps (say which in reason); fail was
attempted and does not work; blocked cannot be attempted yet (a previous stage or a missing component, say which);
untested nothing known.
counters members: unsupported_svcs (integers, SVC numbers the guest hit that the runtime lacks),
missing_ipc (strings service:Command or just the service name), missing_nvn (NVN function names),
crashes (objects with signal integer, pc guest address as a string or a number, note string),
fps_notes (free text). Unknown members are ignored, strings are not length-limited but a file over 4 MiB is rejected.
A malformed file is skipped with a warning; it never corrupts the matrix.
Tiny C-friendly writer (no JSON library needed)¶
Escape " and \ in strings and write control characters as spaces. Example:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
/* mkdir -p for "root/TITLE/BUILD" (3 levels); ignore EEXIST */
static void mk(const char *p) { mkdir(p, 0755); }
static void jstr(FILE *f, const char *s) {
fputc('"', f);
for (; *s; s++) {
if (*s == '"' || *s == '\\') { fputc('\\', f); fputc(*s, f); }
else if ((unsigned char)*s < 0x20) fputc(' ', f);
else fputc(*s, f);
}
fputc('"', f);
}
int compat_report(const char *root, const char *title, const char *build16,
const char *stage, const char *status, const char *reason, const char *evidence) {
char dir[512], tmp[560], path[560];
snprintf(dir, sizeof dir, "%s/%s", root, title); mk(root); mk(dir);
snprintf(dir, sizeof dir, "%s/%s/%s", root, title, build16); mk(dir);
snprintf(path, sizeof path, "%s/%s.json", dir, stage);
snprintf(tmp, sizeof tmp, "%s.tmp", path);
FILE *f = fopen(tmp, "w");
if (!f) return -1;
fprintf(f, "{\"stage\":"); jstr(f, stage);
fprintf(f, ",\"status\":"); jstr(f, status);
fprintf(f, ",\"reason\":"); jstr(f, reason);
fprintf(f, ",\"evidence\":"); jstr(f, evidence);
fprintf(f, ",\"time\":%lld,\"tool\":\"runtime\"", (long long)time(NULL));
fprintf(f, ",\"counters\":{\"missing_ipc\":[\"fsp-srv:OpenSaveDataFileSystem\"]}}\n"); /* optional */
if (fclose(f) != 0) return -1;
return rename(tmp, path);
}
root is work/compat (the default of napple compat). Then run napple compat render to refresh
compat/matrix.json and COMPATIBILITY.md. Manual correction: napple compat set <titleid> <stage> <status> "<reason>".
matrix.json (committed)¶
{"schema":1,"last_run":<unix>,"last_run_iso":"...","games":[{title_id,name,publisher,versions:[{kind,title_id,version,
display_version,build_id,stages:{<stage>:{status,reason,evidence,time,tool,counters?}},modules:[...],npdm:{...},notes:[...]}],dlc:[...],notes:[...]}]}.
Only metadata: ids, counts, names, service names. Notes starting with [auto] are regenerated by compat run; other notes are kept.