SGKEMBEDDED PERFORMANCEEpicTunerSDK · PrereleaseContact ↗
EpicTuner SDK/ M0 foundation contract

Download the complete Markdown manual ↓
One file with guides, API headers and examples for developers and coding agents.

M0 foundation contract

SDK 1.0 retains the ABI 1.1 foundation frozen in M0. The original ABI 1.0 prefixes remain binary compatible. These definitions are frozen for the initial Windows x64, Linux x64 and Linux ARM64 targets. Breaking layouts/semantics require an ABI or named feature major change. macOS is a later certification target. This contract does not advertise later services as implemented merely because their identifiers are reserved.

Query, tables and caller-owned storage

et_plugin_query takes the largest ABI minor the host supports and the granted capability mask. Return the lower supported minor within the same major. Reject a different major, missing required capability, or insufficient mandatory prefix. An error leaves the output table unchanged. A success writes only the negotiated prefix and reports its actual size. Extra caller capacity remains untouched.

et_plugin_api stays 56 bytes. et_host_api retains its original 32-byte prefix; ABI 1.1 appends get_service at offset 32, total 40 bytes. Check struct_size before reading that pointer. An ABI 1.0 host has no service discovery and the C++ wrapper returns ET_ERROR_UNSUPPORTED without reading past its prefix. A newer host can run an older plugin. Append-only service tables negotiate versions independently.

get_service(context, id, requestedVersion, table, capacity, error) copies a table into caller-owned storage; it never returns heap ownership. The context table v1 is 32 bytes and provides limit and named-feature queries. Unknown/unimplemented services return UNSUPPORTED; incompatible service majors return ABI; insufficient output capacity returns ARGUMENT. Optional et_error must have a valid size prefix. Its fixed 512-byte detail buffer belongs to the caller, is NUL terminated, and reports byte length excluding the terminator. Immediate calls use operation ID 0. Too-short error/output prefixes are never overwritten.

Current features: et.context.v1, et.log.v1, et.events.v1. Feature availability and capability grants are separate: metadata can describe an implemented feature whose capability was not granted. An unavailable future service is not returned as an empty success table. See contracts.h for stable service IDs 1–11 and capability bits 0–14. Reserved capability names are catalogued in the package schema. Only log/events are granted by this development runtime; context metadata needs no grant.

Values and handle lifetime

et_value is a 40-byte tagged union: null, bool (0 or 1), signed/unsigned 64-bit integer, finite binary64, UTF-8 string, bytes, or handle. Reserved fields are zero. Inactive union storage is ignored and never serialized. All pointed-to storage is borrowed only during the call/callback. Text rejects NUL, malformed/overlong UTF-8, surrogates and out-of-range scalars. Bytes may contain arbitrary data. Use the SDK validators before enqueueing. Copy inputs before returning from any submit call; copy completion data before returning from its callback. No module frees another module's allocation, including error messages and output tables.

et_handle is 32 bytes: opaque session tag, project generation (0 means global), slot, slot generation, kind and reserved. Slot/session/generation 0 is invalid. Handles confer no authority unless the host registry resolves the full tuple in the owning session and required scope/kind. Closing a panel invalidates its nodes and panel-scoped resources; closing a project invalidates its scoped handles. Global handles survive project changes, but no handle survives worker replacement. Reusing a slot increments generation; exhausted generations retire the slot rather than wrap. Stale/project/kind/session mismatches return STALE without dereferencing any plugin-supplied pointer. The host reference registry and tests exercise these rules; future service adapters must use equivalent checks at dispatch.

UI placement flags are workspace=1, floating=2, modal=4. Every panel instance has independent handles; single-instance reopening resolves/focuses the existing valid instance. Node/property schemas and concrete prefab service tables belong to named et.ui.v1 feature work in M2/M4; raw QML injection is never a fallback. This freezes the common identifiers/lifetimes without claiming a working renderer.

Async syntax, ordering and cancellation

Future asynchronous service submissions use a caller-owned et_async_options:

et_async_options options{};
options.struct_size = sizeof(options);
options.timeout_ms = 1000;
options.context = myPluginState;
options.complete = [](void* state, const et_completion* result) {
    // Inspect result->code/operation_id; copy any needed value/detail now.
    // Callback and its data belong to the worker dispatcher for this call only.
};
// A service's submit(..., &options, &operationId) returns a synchronous et_result.
// Concrete submit functions are supplied only by implemented, negotiated services.

Accepted submission returns OK and a nonzero operation ID; rejected submission returns an error, operation ID 0, and schedules no callback. IDs monotonically increase without reuse in a session. Local callback pointers/context never cross IPC: the worker stores them and routes wire completions by session/operation ID. Successful submission has one terminal completion while the worker remains alive; stop revokes its callback registrations after draining/cancelling unsent work. Worker death cannot deliver callbacks into dead code; the host still retains the operation outcome for diagnostics/reconciliation and never replays side effects.

Operation phases are queued -> running -> committed -> complete. “Committed” means an irreversible host operation has been dispatched, not that an ECU flash burn completed. Cancellation before that boundary yields CANCELLED exactly once. After it, cancellation returns BUSY and the host preserves the real outcome. Deadlines use monotonic host time, 1..3000 ms, and cancel undispatched/ordinary work with TIMEOUT. Project change yields STALE for unfinished pre-commit work. Already-sent host writes retain their outcome through cancellation, expiry and worker loss; M4 implements the ECU readback/partial-write adapter. Completion acknowledgement releases ledger capacity. Unknown, duplicate or replayed IDs never repeat a write.

et_completion is 80 bytes with size, code, operation ID, project generation, tagged value and borrowed detail. Failed completions use a null value. Numeric 64-bit IDs/values use canonical decimal strings on future JSON service bodies to avoid precision loss; existing worker wire correlation IDs remain uint32. Never serialize these native structs, padding, pointers or callbacks verbatim.

Limits and execution policy

contracts.h is the numeric authority and the context service reports it. These are contract ceilings, not grants: a service unavailable today still cannot be used merely because a future limit is nonzero.

Resource Per-worker ceiling
Wire frame / outgoing queue 64 KiB / 256 KiB
Pending operations 32, including unacknowledged terminal outcomes
Text / structured error detail 4096 / 511 UTF-8 bytes plus error terminator
UI nodes / nesting 4096 / 32
Panel definitions / instances 32 / 64
Subscriptions / background jobs 64 / 16
UI updates / data source 30 Hz / 100 Hz
Logs / wire messages 100 / 200 per fixed second
Startup / ordinary operation 3000 ms / 3000 ms
Heartbeat interval / response 500 ms / 3000 ms
Shutdown / supervisor resolution 1000 ms / 100 ms

Future display streams coalesce at 30 Hz and report losses; acquisition batches preserve source timestamps/gap counters. M0 validates transport/state contracts; M3 validates real channel/recording adapters against a matched ECU-poll baseline. No callback runs on or synchronously blocks the application GUI/ECU thread. Service calls require the dispatcher. M1 background tasks publish through bounded thread-safe result mailboxes; submission rejects at capacity. See JOBS.md.

Performance gate: five isolated test workers, 100 Hz timestamped synthetic source, 30 seconds, one crash/manual restart, no completion replay, queue occupancy <=32, explicit cancellation/gap counts, p95 <50 ms, p99 <100 ms and maximum <3000 ms. Startup/shutdown must retain their stated deadlines. This is a transport benchmark, not endurance, UI rendering, ECU polling or physical Raspberry Pi proof.

Full lifecycle contract

State Allowed next state and condition
Discovered Verified after package authenticity policy; Failed/Disabled on rejection
Verified Compatible after target/host/ABI/required-feature checks
Compatible Enabled after user grant/license policy; Disabled otherwise
Enabled Starting on explicit activation, or Disabled
Starting Ready after authenticated handshake + successful start; Failed on deadline/error
Ready Stopping on disable/update/close; Failed on crash/protocol/heartbeat error
Stopping Stopped after both stop acknowledgement and normal process exit; Failed on deadline/error
Stopped Starting only after renewed compatibility/grant checks, or Disabled
Failed Disabled or explicit revalidation/restart; never automatic write replay
Disabled Verified/Compatible/Enabled only after explicit re-enable and renewed checks

The development checker explicitly substitutes unsigned development opt-in for production verification and does not claim a verified signature. Safe mode prevents all starts. The GUI manager and production installer implement the preceding policy states in M1/M5; the runtime skeleton enforces starting through stopped/failed.