ADR-0023 — ABI struct negotiation and calling convention
Status: Accepted Date: 2026-09-22
Context
ADR-0022 names one JARVIG_API_VERSION and a C ABI. It does not say how a single struct or function table survives a newer engine, and it does not name the export macros. native/jarvig_core/include/jarvig_core.h is easy to mistake for the SDK because it is the only C header in the tree.
Decision
Every long-lived ABI struct and every function table begins with struct_size and api_version. The engine trusts struct_size as the caller's compiled size. It does not read past that. Reserved words stay zero until a version assigns them. A global version number remains, and it does not replace the per-struct fields.
The SDK headers, not the compiler's default, declare the boundary:
JARVIG_EXTERN_CJARVIG_CALLon every exported functionJARVIG_APIfor import and export
On the 64-bit ABIs JARVIG ships first, JARVIG_CALL may expand to nothing. The macro still exists so that emptiness is a choice.
jarvig_core.h is a bootstrap transitional slice. It is not the public SDK. The SDK will live under sdk/c/include/jarvig/ and sdk/cpp/include/jarvig/ when a ticket creates it. Those directories are not created by this decision. New public functions do not accumulate in the bootstrap header.
Alternatives Considered
- One global version and flexible struct tails. Rejected. Callers and engines then disagree about how long a struct is.
- Leave calling convention to the platform compiler. Rejected. It has worked on Windows x64 by accident. The SDK should not depend on that accident.
- Rename
jarvig_core.hnow and start the SDK tree. Rejected. Nothing public is ready to move, and an empty SDK tree would look finished.
Consequences
Plugin tables in ../api/game-module.md follow the size-and-version prefix. The bootstrap header's comment states what it is not.
Supersedes
Nothing. It fills in ADR-0022.
Superseded By
Nothing.