-
Notifications
You must be signed in to change notification settings - Fork 0
Developer Guide
| Directory | Responsibility |
|---|---|
src/main.c |
Entry point, CLI parsing, VFS init, game loop |
src/engine/ |
Game state, map generation, combat, inventory, shop, puzzle, UI, Liberation |
src/data/ |
Containers (VFS, ADF, ISO9660, RNC), decoders (PL5, ANM), loaders (GFX, texture atlas, MIDI) |
src/render/ |
Software framebuffer, viewport, HUD, enhanced renderer |
src/audio/ |
MIDI parser/synthesizer, 8SVX sample loader, 8-channel mixer, music, SFX |
include/ |
All public headers |
tests/ |
Decoder, VFS, hash, ISO, map, game-state, save and Liberation checks |
tools/ |
Inspection tools (pl5_to_bmp, anm_extract, rnc_decode) — never redistributes game data |
docs/ |
Technical documentation and wiki source |
assets/ |
Application icons (SVG, ICO, ICNS) |
- Pure C (C17 standard). No C++ code.
-
Compiler:
cc(system clang on macOS). Do not use gcc. - Build system: CMake 3.20+ with Ninja generator.
- Dependencies: SDL3, zlib.
cmake -S . -B build -DCMAKE_C_COMPILER=cc -G Ninja
ninja -C build
ctest --test-dir build -j4 --output-on-failureAlways test both Debug and Release configurations. Test targets compile with assertions enabled in Release builds.
DataVFS (src/data/data_vfs.c) provides transparent access to game data from directories and ZIP archives. Resources are identified by SHA-256 content hash, never by filename.
-
vfs_init()/vfs_free()— lifecycle -
vfs_find_sha256()— returns caller-owned buffer matching a digest -
vfs_file_exists()— check if a file is accessible -
vfs_read_file()— read a named file (for non-hash-identified resources)
-
DataVFSowns archive indexing, not returned match buffers. -
vfs_find_sha256()returns caller-owned bytes. -
MusicSystemowns the current MIDI bytes until track replacement or stop. -
LiberationDataowns the raw disc buffer and closes it as one unit. -
GameState,CreatureListandPuzzleListare saved together for Captive.
The Captive save header has magic, version, campaign identity, party state, objective counters, gold and dynamic-list counts. The loader regenerates the deterministic base, overlays saved cells, validates every record and assigns only after complete success. Corrupt input leaves the active session intact.
Never extend a save record without incrementing its version and adding tests for old-version rejection and new-version round trip.
Never use original media filenames as identity. Introduce a resource with:
- A SHA-256 digest in source
- A content-hash lookup via VFS
- Checked lengths/offsets before decode
- A deterministic test or verifier
- No copied game payload in the repository
For Amiga OFS media, use amiga_ofs_inventory during discovery. For Liberation CD32 resources, use liberation_inventory. Both emit only digest, byte count and container class.
Do not use a generated renderer hash as proof of parity. Capture an original frame and an OpenCaptive frame at the same native resolution, then compare:
./build/visual_compare original.ppm opencaptive.ppm diff.ppmReports exact-pixel coverage and mean absolute RGB error. Keep captures outside the repository and record their SHA-256 digests in the test report.
- Preserve unrelated dirty work in the checkout
- Add a focused test that fails before the change
- Build Debug and Release
- Run the complete CTest suite
- Test hash-verified startup when changing loading or runtime code
- Document implementation status honestly — do not label a placeholder as original-game parity
- Bump version in
CMakeLists.txtandinclude/opencaptive.h - Commit and tag with
vX.Y.Z - Push with
--tags— GitHub Actions builds all platforms and creates a release - CI matrix: Ubuntu 24.04 (deb, rpm, AppImage), macOS 14, Windows 2022 (Inno Setup installer)