Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
All notable changes to the Tombstack Native SDK (the `tombstone_*` C ABI and
the `tombstone` library name are stable — Tombstack is the product name).

## [0.9.2] - 2026-08-25

### Fixed — heartbeat ceiling lowered to 240s (behaviour change; no API/ABI change)

- **`options.heartbeat_interval_s` is now clamped to `[15, 240]`, was `[15, 600]`.** The ceiling is a
BILLING constant, not a preference. The server merges consecutive beats into one live session only
while the gap stays under its 300s session window, so a 600s cadence shattered a continuously-online
player into isolated instants: concurrent sessions stopped overlapping, monthly **peak CCU** read low,
and the studio was invoiced on the low figure. An integrator raising the interval to cut telemetry
cost — explicitly permitted by the header's own comment — silently reduced their own bill.

This is bit-for-bit the defect the Unity SDK shipped in 0.19.3. The Tombstack repo's
`tests/heartbeat-cadence.test.ts` was written to stop it recurring, but read the C# source **only**,
so this SDK kept 600 with a green suite. That guard now parses the ceiling out of this repo's public
header as well, and asserts all three SDKs (Unity, Godot, native) agree.

A caller that passed a value above 240 gets 240 instead. Nothing else about the cadence changes; the
default (60s) and the floor (15s) are untouched.

## [0.9.1] - 2026-07-20

### Hardening (production audit; no API/ABI change)
Expand Down
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
cmake_minimum_required(VERSION 3.16)
project(tombstone VERSION 0.9.1 LANGUAGES C CXX)
# NOTE: this version is DUPLICATED as a literal in src/tombstone_api.cpp (`sdk_version`), which is what
# tombstone_version() returns. Bump both together — 0.9.1's changelog records that the reported version
# was once stuck at 0.8.0 for exactly this reason. Deriving it from PROJECT_VERSION is the real fix.
project(tombstone VERSION 0.9.2 LANGUAGES C CXX)

option(TOMBSTONE_BUILD_STATIC "Also build a static tombstone library" OFF)
option(TOMBSTONE_BUILD_EXAMPLES "Build the example programs" ON)
Expand Down
6 changes: 5 additions & 1 deletion include/tombstone/tombstone.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ typedef struct tombstone_options {
* "staging", "dev"; clamped to 64). NULL/empty = unset (the server
* defaults to "production"). tombstone_set_environment() overrides this. */
const char *environment;
/** Seconds between session heartbeats; clamped to [15, 600]. 0 = default (60). */
/** Seconds between session heartbeats; clamped to [15, 240]. 0 = default (60).
* The ceiling is a BILLING constant, not a preference: the server merges
* consecutive beats into one live session only while the gap stays under its
* 300s window, so a slower cadence shatters a continuous session into isolated
* instants and the studio's monthly peak-CCU invoice comes out low. */
int heartbeat_interval_s;
/** Nonzero (default) = emit periodic session heartbeats. */
int enable_heartbeats;
Expand Down
13 changes: 12 additions & 1 deletion src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,18 @@ constexpr const char *metrics_batch_path = "/api/v1/ingest/metrics:batch";
constexpr const char *pull_requests_path = "/api/v1/pull-requests";

constexpr int min_heartbeat_interval_s = 15;
constexpr int max_heartbeat_interval_s = 600;
// 240, NOT 600 — the server's session window is 300s and this is a BILLING constant.
//
// computePeakConcurrency merges consecutive beats into one live span only while the gap is <= that
// window. At 600s a player online for an hour produces six isolated instants instead of one span, so
// concurrent players stop overlapping, the monthly PEAK CCU comes out low, and the studio is invoiced
// on the low figure. Unity shipped exactly this (0.19.3, MAX_HEARTBEAT_INTERVAL_SECONDS = 600f) and
// the Tombstack repo carries tests/heartbeat-cadence.test.ts to stop it recurring — but that guard read
// the C# source only, which is why this side kept 600. It now reads the C header's range as well.
//
// The 20% headroom is deliberate: at exactly the window each beat expires as the next arrives, so any
// scheduling jitter or upload latency opens a gap.
constexpr int max_heartbeat_interval_s = 240;
constexpr int default_heartbeat_interval_s = 60;

constexpr const char *identity_file_name = "identity";
Expand Down
5 changes: 4 additions & 1 deletion src/tombstone_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

namespace {

constexpr const char *sdk_version = "0.9.1";
// Kept in lockstep with project(... VERSION ...) in CMakeLists.txt BY HAND, and that has failed TWICE:
// the changelog records this string pinned at "0.1.0" once and stuck at "0.8.0" once. Deriving it from
// PROJECT_VERSION is the real fix; until then, bump both or ship a third wrong version.
constexpr const char *sdk_version = "0.9.2";

// The process-wide client. Entry points snapshot the shared_ptr under the
// mutex and call outside it, so a long flush() neither blocks other calls nor
Expand Down
Loading