Files
Frametop--frametop/pointer/tests/feedage-test.cpp
T
DeeJanuzandClaude Opus 5.5 d1ceb3d744 ft-pointer: a hands frame newer than the loop's clock is fresh, not 584 years old
The loop reads its clock at the top and ft-hands' files later in the pass.
A frame written in between has publish_ns after tnow, and the unsigned
"now - publish_ns" in HandsInRoom::Live wrapped round: the feed read as
stale for that one pass, both sides went "off", and the "hands" word to
ft-screens flickered to "off off" and back. 2026-10-10 20:30-20:37: 24
flickers in 7 minutes, each with ft-hands publishing at full rate (pinches
logged at the same moments), each ending ft-screens' holds and the ray.

feedage.h: AgeNs/Fresh, age 0 for a frame from after "now". Live and the
gestures check that ends holds ("hand tracking stopped") use them.
pointer/tests/feedage-test.cpp replays the loop against 30, 15 and 1 Hz
publishing: the old check calls 28-2408 passes stale, the new one none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:03:15 -06:00

91 lines
4.4 KiB
C++

// Unit tests for the hands feed's age (pointer/helper/feedage.h): the helper's loop reads its
// clock at the top and ft-hands' files later in the pass, so a frame can be newer than "now".
// Build and run: just test-c (or g++ -std=c++17 -I. pointer/tests/feedage-test.cpp).
#include "pointer/helper/feedage.h"
#include <cstdint>
#include <cstdio>
#include <initializer_list>
static int failures = 0;
#define CHECK(cond, ...) \
do { \
if (!(cond)) { \
std::printf("FAIL %s:%d: ", __FILE__, __LINE__); \
std::printf(__VA_ARGS__); \
std::printf("\n"); \
++failures; \
} \
} while (0)
namespace {
constexpr uint64_t kMs = 1'000'000ull, kLive = 1'500 * kMs;
// The check before the fix, for the replay below.
bool OldLive(uint64_t nowNs, uint64_t publishNs) { return publishNs && nowNs - publishNs < kLive; }
// The helper's loop against ft-hands publishing every periodMs: each pass reads the clock (tnow),
// then the hands file readMs later, then asks Live(tnow). Counts the passes that read the feed as
// stale while ft-hands kept publishing. live: the check under test.
template <class F>
int StalePasses(F live, uint64_t periodMs, uint64_t readMs, int passes) {
const uint64_t t0 = 1'000'000 * kMs; // CLOCK_MONOTONIC some 17 minutes after boot
int stale = 0;
for (int k = 0; k < passes; ++k) {
const uint64_t tnow = t0 + uint64_t(k) * 8 * kMs + uint64_t(k % 7) * 300'000; // 8 ms, jittered
const uint64_t readAt = tnow + readMs * kMs / 4 + uint64_t(k % 5) * 250'000; // later in the pass
// the newest frame ft-hands wrote by readAt (it has run for a while: frames at
// base + n * period, base 10 s before t0 plus 0.7 ms)
const uint64_t base = t0 - 10'000 * kMs + 700'000;
const uint64_t published = base + (readAt - base) / (periodMs * kMs) * (periodMs * kMs);
if (!live(tnow, published)) ++stale;
}
return stale;
}
} // namespace
int main() {
using hin::AgeNs;
using hin::Fresh;
// ages
CHECK(AgeNs(2'000 * kMs, 1'000 * kMs) == 1'000 * kMs, "1 s old");
CHECK(AgeNs(1'000 * kMs, 1'000 * kMs) == 0, "published now: age 0");
CHECK(AgeNs(1'000 * kMs, 1'000 * kMs + 300'000) == 0, "published 0.3 ms after now: age 0, not 584 years");
// fresh
CHECK(!Fresh(5'000 * kMs, 0, kLive), "nothing published yet");
CHECK(Fresh(5'000 * kMs, 5'000 * kMs - 1'499 * kMs, kLive), "1.499 s old: fresh");
CHECK(!Fresh(5'000 * kMs, 5'000 * kMs - 1'500 * kMs, kLive), "1.5 s old: stale");
CHECK(!Fresh(500'000 * kMs, 440'000 * kMs, kLive), "ft-hands stopped a minute ago");
CHECK(Fresh(5'000 * kMs, 5'000 * kMs + 2 * kMs, kLive), "a frame written after the loop read its clock");
// The 2026-10-10 flicker: the old check calls the feed stale on passes whose file read caught a
// frame newer than tnow; the new one never does, at 30 Hz, 15 Hz or ft-hands' idle 1 Hz.
const auto newLive = [](uint64_t now, uint64_t pub) { return Fresh(now, pub, kLive); };
const int passes = 7 * 60 * 125; // 7 minutes at 8 ms
for (uint64_t period : {33ull, 66ull, 995ull}) {
for (uint64_t readMs : {1ull, 4ull}) {
const int before = StalePasses(OldLive, period, readMs, passes);
const int after = StalePasses(newLive, period, readMs, passes);
std::printf("publish every %llu ms, file read %.2f ms into the pass: stale passes before %d, after %d\n",
(unsigned long long)period, double(readMs) / 4, before, after);
CHECK(after == 0, "period %llu ms: %d stale passes", (unsigned long long)period, after);
}
}
CHECK(StalePasses(OldLive, 33, 4, passes) > 0, "the replay reproduces the old flicker");
// ft-hands stopping still reads as stale, 1.5 s after its last frame.
{
const uint64_t last = 10'000 * kMs;
CHECK(Fresh(last + 1'400 * kMs, last, kLive), "1.4 s after the last frame: still live");
CHECK(!Fresh(last + 1'600 * kMs, last, kLive), "1.6 s after the last frame: stopped");
}
if (failures) return std::printf("%d failures\n", failures), 1;
std::printf("feedage: ok\n");
return 0;
}