What is deterministic is made at build time by the game's own code (`ludic bake`) and read at run time. baked.ludic, baked_hash.ludic, baked_stream.ludic: - The header, 32 bytes little-endian: "LBAK" | u32 format (BAKE_FORMAT 1) | u32 generator version | u32 payload offset | u64 inputs hash | u64 payload length | then the key (UTF-8, zero-padded to 8), then the payload at its offset, whose layout is the bake's own. - bake_write(path, key, version, inputs_hash, payload, n) makes the directories and writes it; bake_inputs_hash(inputs) is FNV-1a 64 over each space-separated input: its path, a 0, its bytes. - baked_open(path, key, version) -> []byte: the payload as a view of the file (no copy), or null for a missing file or another format, key or version. The runtime never hashes inputs; `ludic bake --check` holds a baked file to them at build time. - baked_open_range(path, key, version) -> BakedFile kept open (header checked once), baked_read(bf, at, n, into) -> count read into the caller's buffer from payload offset `at` (held to the buffer and the payload), baked_len, baked_close: a streamed bake (placements by chunk, terrain by tile) with nothing made per read. Pack-aware through file_open: on macOS a packed entry is an fmemopen over the mapped pack (a seek is free), on Windows a temporary copy of the entry made once when it is opened. - baked_path(map, file), bake_missing(bake_st, key) (a dev build's line, once a key). tests/baked_test.ludic: a payload round-trips for its key and version only, a streamed read at an offset and one past the end, the inputs hash follows path and bytes. Written, type-checked, not run (compile-only rule). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
40 lines
1.6 KiB
Text
40 lines
1.6 KiB
Text
# baked_hash.ludic - a bake's inputs as one number, and the header's little-endian fields. The hash
|
|
# is FNV-1a 64 over each input in order: its path's bytes, a 0, then its bytes (tools/bake/baked.py
|
|
# and the check compute the same)
|
|
const BK_FNV_OFFSET: long = -3750763034362895579 # 0xcbf29ce484222325
|
|
const BK_FNV_PRIME: long = 1099511628211
|
|
|
|
# `inputs` space-separated, as a Bakes row lists them; a missing input hashes as its path alone
|
|
export function bake_inputs_hash(inputs: string) -> long {
|
|
var h = BK_FNV_OFFSET
|
|
var a = 0
|
|
while a < len(inputs) {
|
|
while a < len(inputs) and inputs[a] == 32 { a += 1 }
|
|
var e = a
|
|
while e < len(inputs) and inputs[e] != 32 { e += 1 }
|
|
if e > a {
|
|
let p = inputs[a .. e]
|
|
for i in 0 .. len(p) { h = bk_fnv(h, p[i]) }
|
|
h = bk_fnv(h, 0)
|
|
let b = Fs.read_bytes(p)
|
|
if b != null { for i in 0 .. len(b) { h = bk_fnv(h, b[i]) } }
|
|
}
|
|
a = e
|
|
}
|
|
return h
|
|
}
|
|
function bk_fnv(h: long, c: int) -> long { return (h ^ long(c & 255)) * BK_FNV_PRIME }
|
|
|
|
function bk_u32(b: []byte, at: int) -> int { return (b[at] & 255) | ((b[at + 1] & 255) << 8) | ((b[at + 2] & 255) << 16) | ((b[at + 3] & 255) << 24) }
|
|
function bk_u64(b: []byte, at: int) -> long { return long(bk_u32(b, at)) & 4294967295 | (long(bk_u32(b, at + 4)) << 32) }
|
|
function bk_put32(b: []byte, at: int, v: int) -> void {
|
|
for i in 0 .. 4 { b[at + i] = (v >> (i * 8)) & 255 }
|
|
}
|
|
function bk_put64(b: []byte, at: int, v: long) -> void {
|
|
for i in 0 .. 8 { b[at + i] = int((v >> long(i * 8)) & 255) }
|
|
}
|
|
function bk_last_slash(p: string) -> int {
|
|
var k = -1
|
|
for i in 0 .. len(p) { if p[i] == 47 { k = i } }
|
|
return k
|
|
}
|