fix(rtc): bootstrap from contacts is a floor, never a rollback

BaseChatMesh::bootstrapRTCfromContacts() called setCurrentTime(latest+1)
unconditionally. The newest contact's lastmod proves the clock must be at
*least* that late -- it says nothing about it being any earlier -- so on
any node that already has a better time source (a board with a real RTC,
or the Emscripten sim whose SimRTCClock is backed by the host's wall
clock) this dragged a correct clock backwards to whatever timestamp
happened to be persisted alongside the contact list. Measured in the
sim: ~9s behind real time after every reboot, growing with how long the
previous session ran.

Also adds sim_test_sync_time(epoch_secs) next to the other
SIM_PLATFORM/__EMSCRIPTEN__ test hooks, so a host page can re-anchor the
RTC on every (re)boot. The clock is live state the real mesh writes to at
runtime -- a peer whose own clock runs ahead pushes ours forward, which
is right on a node with no better source and, with meshcore-solo-site's
cross-visitor relay, means one skewed visitor can drag everyone. On a
demo running on someone's computer, "reset the device" should also mean
"the clock is correct again".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018iubftDmKNWmkNnhJRz8UH
This commit is contained in:
Jakub
2026-09-04 20:34:36 +02:00
co-authored by Claude Opus 5
parent cc01583d53
commit c7d0cd23b6
2 changed files with 27 additions and 1 deletions
+19
View File
@@ -498,6 +498,25 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_disable_screen_timeout() {
return 1;
}
// Re-anchors this instance's RTC to the host's real wall clock. SimRTCClock
// (variants/sim/SimRTCClock.h) already starts out reading time(NULL), so a
// freshly booted instance needs no help -- but the clock is a shared, live
// thing the real mesh code legitimately writes to at runtime: a received
// packet or a contact-list bootstrap carrying a timestamp ahead of ours
// pushes it forward (BaseChatMesh::bootstrapRTCfromContacts(),
// MyMesh's own timestamp handling), which is correct behaviour on a real
// node with no better time source, and means one peer with a badly skewed
// clock can drag every node that hears it. Calling this on every (re)boot
// makes "reset the device" mean "the demo's clock matches the visitor's own
// computer again", the same guarantee the timezone hook above gives.
// Seconds since the Unix epoch, as a double because a JS Date.now()/1000
// value has no exact int32 representation to pass through ccall.
extern "C" EMSCRIPTEN_KEEPALIVE int sim_test_sync_time(double epoch_secs) {
if (epoch_secs < 1000000000.0) return 0; // obvious nonsense (pre-2001) -- leave the clock alone
rtc_clock.setCurrentTime((uint32_t)epoch_secs);
return 1;
}
#ifdef DISPLAY_CLASS
// Jumps the on-device UI straight to the DM thread with the first known
// ADV_TYPE_CHAT contact (UITask::openContactDM() -- the exact same real
+8 -1
View File
@@ -62,7 +62,14 @@ void BaseChatMesh::bootstrapRTCfromContacts() {
latest = contacts[i].lastmod;
}
}
if (latest != 0) {
// A bootstrap is a *floor*, not an assignment: the newest contact we know
// of proves the clock must be at least that late, it says nothing about it
// being any earlier. Setting it unconditionally drags a clock that already
// knows better (a board with a real RTC, or the sim's host wall clock --
// see variants/sim/SimRTCClock.h) *backwards* to whatever timestamp
// happened to be persisted with the contact list, which then reads as a
// stopped/lagging clock on the Home screen after every reboot.
if (latest != 0 && latest + 1 > getRTCClock()->getCurrentTime()) {
getRTCClock()->setCurrentTime(latest + 1);
}
}