Skip to content

Philosophy

abduznik edited this page Jul 15, 2026 · 1 revision

Philosophy

Core design principles that apply to every layer of libsavesync.

1. Passive, never a watchdog

The library never inspects processes, never decides when to act on its own. Every operation is explicitly triggered by the caller. Process-lifecycle detection is the caller's problem, not this library's — cross-platform process introspection is a different, riskier class of problem (permissions, PID races, security software false-positives) that doesn't belong inside a save-integrity library.

2. Never touch the live file unsafely

All reads of the live save happen via atomic copy-out into a versioned "magazine" entry. All writes to the live save happen via atomic replace (temp file + rename), never in-place overwrite. The live path is only ever touched at two chokepoints: copy-out (save) and copy-in (pull/pull_select).

3. Nothing destructive without an explicit choice

Conflicts are always reported by default, never silently resolved. Every function that could clobber existing data takes an explicit on_conflict policy; the default is always the safe, non-destructive option.

4. Every field is optional, zero-default

No field is ever required unless it's structurally necessary (e.g. live_path). Unset fields are zeroed/NULL and treated as "unknown," never as an error. This is both a usability rule (users can do a bare 1:1 sync with zero metadata) and an ABI-stability rule (new fields can be added later without breaking old callers, since old callers simply leave them zeroed).

5. Plain C ABI, opaque structs at the boundary

No struct is ever handed across the API boundary by value for callers to allocate — only opaque handles + accessor functions, or read-only output snapshots. This is standard practice for ABI-stable C libraries: C's minimal binary surface is why it's used as the stable boundary even for C++/Rust projects. Never reorder or remove struct fields once shipped.

6. Everything is entry-first; registration is just a pointer

A magazine entry ("child") belongs to a registration ("parent") by reference, not by hard binding. Parents can be deleted, leaving orphaned entries; entries can be explicitly re-parented. Nothing is silently deleted as a side effect of an unrelated operation.

7. Networking-free core, pluggable transport

The core never contains socket/HTTP code. Cross-device sync is delegated entirely to caller-supplied function pointers (a push/pull contract), so USB, HTTP, LAN, or terminal-based transports can all be implemented externally without touching library source — same pattern as libcurl/libgit2/rclone.

8. Identity without a heavy database

Game identity resolution is tiered: embedded serial extraction first (PSP/PS1/PS2 SYSTEM.CNF/PARAM.SFO, Saturn IP.BIN, GameCube/Wii boot headers — zero external data needed), header checksums second (SNES/Genesis internal checksum + file size), and an optional pluggable hash-database module last, for the remaining edge cases only. The core library ships database-free.

9. A default mode must exist for testing

SV_MODE_DEFAULT allows registering and exercising the full pipeline (register → save → pull → rotate) against any arbitrary file or folder, with zero platform/emulator-specific logic. This is what makes the library testable on day one with fake files, before a single real emulator strategy is implemented.


See also: Architecture-Overview | Layer-1-Interface | Data-Types-Reference

Clone this wiki locally