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>
49 lines
1.7 KiB
Text
49 lines
1.7 KiB
Text
# baked_stream.ludic - a large bake read a piece at a time (placements by chunk, terrain by tile): the
|
|
# header checked once, the file kept open, and each read into the caller's own buffer at a payload
|
|
# offset, so a ring streaming chunks in makes nothing per chunk. Pack-aware (file_open)
|
|
export property BakedFile {
|
|
f: pointer = null
|
|
off: int = 0 # where the payload starts in the file
|
|
n: long = 0 # the payload's length
|
|
}
|
|
|
|
# this bake's file kept open for reading, or null (missing, or another format, key or version)
|
|
@alloc_ok("once per baked file opened: its header and record")
|
|
export function baked_open_range(path: string, key: string, version: int) -> BakedFile {
|
|
let f = file_open(path, "rb")
|
|
if f == null { return null }
|
|
let want = 32 + len(key) + 1
|
|
let h = buffer(want)
|
|
if file_read(f, h, want) != want or bk_head_ok(h, want, key, version) < 0 {
|
|
file_close(f)
|
|
return null
|
|
}
|
|
let bf = new BakedFile
|
|
bf.f = f
|
|
bf.off = bk_u32(h, 12)
|
|
bf.n = bk_u64(h, 24)
|
|
return bf
|
|
}
|
|
|
|
# `n` bytes from payload offset `at` into `into` (from its start): the count read, 0 on a failure;
|
|
# held to the buffer and to the payload
|
|
export function baked_read(bf: BakedFile, at: int, n: int, into: []byte) -> int {
|
|
if bf == null or bf.f == null or at < 0 or n <= 0 { return 0 }
|
|
var k = n
|
|
if k > len(into) { k = len(into) }
|
|
if long(at) + long(k) > bf.n { k = int(bf.n - long(at)) }
|
|
if k <= 0 { return 0 }
|
|
if file_seek(bf.f, bf.off + at, 0) != 0 { return 0 }
|
|
return file_read(bf.f, into, k)
|
|
}
|
|
|
|
export function baked_len(bf: BakedFile) -> long {
|
|
if bf == null { return 0 }
|
|
return bf.n
|
|
}
|
|
|
|
export function baked_close(bf: BakedFile) -> void {
|
|
if bf == null or bf.f == null { return }
|
|
file_close(bf.f)
|
|
bf.f = null
|
|
}
|