JARVIGManual

Stable C ABI

The binary contract. Headers are C. C++ callers include them inside extern "C".

What crosses

  • Opaque handles. See handles.md.
  • Plain integer and float fields with an explicit width (uint32_t, uint64_t, float, double).
  • Structs of those fields, with a documented size and alignment. Do not rely on compiler packing guesses. A new field is a new struct version, not a silent append, unless the versioning note for that struct says append-only.
  • Pointer plus length for strings and arrays. See memory.md.
  • Function pointers in a versioned table, for modules the engine loads. See game-module.md.

What does not cross

  • Rust references, slices, String, Vec, Result, trait objects.
  • C++ classes, references, templates, std::string, std::vector, exceptions.
  • wgpu, winit, or any other backend type.
  • A raw pointer to an entity, a mesh, or a GPU resource.

Calls the engine exports

Engine functions use a jarvig_ prefix. They take explicit context. They do not read a hidden process-global editor. A session or world handle is an argument.

JarvigResult jarvig_entity_create(JarvigWorldHandle world,
                                  const JarvigEntityDesc *desc,
                                  JarvigEntityHandle *out);

That signature is an example of the shape, not a frozen function. Do not add it until a world ticket needs it.

Calls the module exports

The engine looks up one bind symbol and a version. It does not depend on a C++ vtable. See game-module.md.

Failures

Return JarvigResult. See errors.md. On failure, out-parameters are left unchanged. A panic inside the engine is a bug: catch it at the ABI edge, log it, and return a failure code. Do not let it unwind into the caller.

Calling convention

The SDK owns the declaration. Compiler defaults are not the contract, even where Windows x64 and System V happen to agree today.

Future sdk/c headers define:

MacroRole
JARVIG_EXTERN_Cextern "C" in C++, empty in C
JARVIG_CALLThe calling convention on every exported function. Empty on the 64-bit ABIs we ship first. Present so a later target cannot silently change it.
JARVIG_APIdllexport when building the engine library, dllimport when a game includes the header. Empty for a static link, chosen in one place.

Every exported function is JARVIG_API JARVIG_CALL. The bootstrap header does not define these macros. Adding them there would make that file look like the SDK.

Struct and table negotiation

A global JARVIG_API_VERSION is not enough for a struct that lives for years. Each long-lived ABI struct and each function table starts with:

uint32_t struct_size;
uint32_t api_version;

The caller sets struct_size to the size it was compiled with. The engine reads only the prefix it understands. A newer engine may accept an older struct. An older engine rejects a newer struct it does not understand, with JARVIG_VERSION_MISMATCH, instead of reading off the end.

Reserved words at the end of a struct (uint64_t reserved[4] or similar) are zero. They are not a place to hide a new field without bumping the version.

A module function table carries struct_size, api_version, a capability bit set, then the function pointers. See game-module.md.

The bootstrap header

native/jarvig_core/include/jarvig_core.h is a bootstrap / transitional ABI slice. It is not the public JARVIG SDK.

It exports jarvig_clock_* and jarvig_runtime_* only. Success is 0. Failures are -1 and -2. It has no struct_size, no JARVIG_API, and no mesh or world types. Leave it until a ticket migrates those two objects. Do not grow it.

The SDK, when it exists, lives apart from that file:

sdk/
  c/include/jarvig/     jarvig.h, version.h, result.h, handles.h, ...
  cpp/include/jarvig/   Engine.hpp, World.hpp, Entity.hpp, ...

That tree is not created yet. New public C functions do not land in jarvig_core.h by convenience.

The RHI

The RHI does not cross this ABI. A plugin that needs to draw uses a renderer extension capability, which the engine implements. It does not receive a wgpu::Device.