code wiki / (root) / nx_game_save.nx

nx_game_save.nx source

↩ module page · 90 lines · 3998 B

1// nx_game_save.nx -- SOVEREIGN SAVE / LOAD for game state. Built because the gamebench ruler ranks 2// save-load-persistence as the #1 blocker across the ingested corpus (11 of 12 real titles need it), i.e. 3// this is demand-ranked by measurement, not chosen by taste. 4// STATE IS AN i64 VECTOR, so this is game-agnostic: any game that can describe itself as integers can use it. 5// THE CONTRACT -- a save file is either loaded EXACTLY or REFUSED WITH A REASON. It is never partially 6// applied and never silently accepted when damaged, because a corrupted save that half-loads is worse than 7// one that fails: it strands the player in an impossible world state and looks like a game bug forever. 8// Layout (all i64, little-endian): [magic][version][count][payload...][checksum-of-everything-before]. 9// license_tier: ORIGINAL 10import "nx_syscalls.nx" 11const GS_MAGIC_1469598103: i64 = 1469598103 12const GS_MAGIC_1000000007: i64 = 1000000007 13 14const GS_MAGIC: i64 = 5182089520394403 // distinctive, so a wrong file type is rejected on sight 15const GS_VERSION: i64 = 1 16const GS_HDR: i64 = 3 // magic, version, count 17 18// explicit refusal codes -- the caller can tell the player WHY, instead of guessing 19const GS_ERR_UNREADABLE: i64 = 0 - 1 20const GS_ERR_TRUNCATED: i64 = 0 - 2 21const GS_ERR_MAGIC: i64 = 0 - 3 22const GS_ERR_VERSION: i64 = 0 - 4 23const GS_ERR_CHECKSUM: i64 = 0 - 5 24const GS_ERR_TOOBIG: i64 = 0 - 6 25 26func gs_put64(b: *u8, at: i64, v: i64) -> i64 { 27 var i: i64 = 0 28 var x: i64 = v 29 while i < 8 { b[at + i] = (x % 256) as u8; x = x / 256; i = i + 1 } 30 return 0 31} 32func gs_get64(b: *u8, at: i64) -> i64 { 33 var i: i64 = 7 34 var v: i64 = 0 35 while i >= 0 { v = v * 256 + (b[at + i] as i64); i = i - 1 } 36 return v 37} 38 39// order-sensitive rolling checksum: a swapped pair of fields changes it, which a plain sum would not catch 40func gs_ck(words: *i64, n: i64) -> i64 { 41 var h: i64 = GS_MAGIC_1469598103 42 var i: i64 = 0 43 while i < n { h = (h * 31 + words[i] + i * 7) % GS_MAGIC_1000000007; i = i + 1 } 44 return h 45} 46 47// write `n` state words to `path`. Returns bytes written, or a negative code. 48func gs_save(path: *u8, words: *i64, n: i64) -> i64 { 49 if n < 0 { return GS_ERR_TOOBIG } 50 let total: i64 = GS_HDR + n + 1 51 let buf: *u8 = sys_mmap(total * 8 + 64) 52 let all: *i64 = sys_mmap((total + 4) * 8) as *i64 53 all[0] = GS_MAGIC 54 all[1] = GS_VERSION 55 all[2] = n 56 var i: i64 = 0 57 while i < n { all[GS_HDR + i] = words[i]; i = i + 1 } 58 all[GS_HDR + n] = gs_ck(all, GS_HDR + n) // checksum covers header AND payload 59 i = 0 60 while i < total { gs_put64(buf, i * 8, all[i]); i = i + 1 } 61 let fd: i64 = sys_openat_wr(path, 420) 62 if fd < 0 { return GS_ERR_UNREADABLE } 63 sys_write(fd, buf, total * 8) 64 sys_close(fd) 65 return total * 8 66} 67 68// read state from `path` into `out` (capacity `maxn`). Returns the word count, or a negative code. 69// Every failure path returns BEFORE touching `out`, so a refused load cannot leave a half-applied world. 70func gs_load(path: *u8, out: *i64, maxn: i64) -> i64 { 71 let szp: *i64 = sys_mmap(16) as *i64 72 let raw: *u8 = sys_read_file(path, szp) 73 if (raw as i64) == 0 { return GS_ERR_UNREADABLE } 74 let sz: i64 = szp[0] 75 if sz < (GS_HDR + 1) * 8 { return GS_ERR_TRUNCATED } 76 if gs_get64(raw, 0) != GS_MAGIC { return GS_ERR_MAGIC } 77 if gs_get64(raw, 8) != GS_VERSION { return GS_ERR_VERSION } 78 let n: i64 = gs_get64(raw, 16) 79 if n < 0 { return GS_ERR_TRUNCATED } 80 if n > maxn { return GS_ERR_TOOBIG } 81 let total: i64 = GS_HDR + n + 1 82 if sz < total * 8 { return GS_ERR_TRUNCATED } 83 let all: *i64 = sys_mmap((total + 4) * 8) as *i64 84 var i: i64 = 0 85 while i < total { all[i] = gs_get64(raw, i * 8); i = i + 1 } 86 if all[GS_HDR + n] != gs_ck(all, GS_HDR + n) { return GS_ERR_CHECKSUM } 87 i = 0 88 while i < n { out[i] = all[GS_HDR + i]; i = i + 1 } // only now is the caller's state touched 89 return n 90}