Skip to content

iPhone and watch: live numbers as they land through FTW Cloud - #72

Merged
frahlg merged 4 commits into
mainfrom
claude/live-on-arrival
Oct 16, 2026
Merged

frahlg merged 4 commits into
mainfrom
claude/live-on-arrival

Conversation

@frahlg

@frahlg frahlg commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

The iPhone and watch half of sending live numbers as they land. Paired with srcfl/ftw#1649, where the box sends a frame on each driver poll through FTW Cloud instead of on its one-second clock. Either can merge first: this app works the same against a box that still sends once a second.

Before and after

A reading's trip from the box's driver to this app's Session decode, against a real local ftwcloudd and the box in process (srcfl/ftw#1649 has the method and every number):

ms, arrival → decode before p50 before p95 after p50 after p95
visible, loopback, 1 driver at 1 s 603 951 6.7 7.5
visible, loopback, 3 drivers at 1 s 539 969 6.9 7.8
visible, loopback, about 6 polls a second – – 22–103 171–194
visible, 50 ms RTT to the cloud, 20 ms on to the box 424 1 017 45.2 46.5
hidden (0.2 Hz), loopback 2 238 4 727 2 514 4 767

Launch to first live number, tunnel only: loopback p50 20 ms; at 50/20 ms round trips p50 276–296 ms, p95 283–299 ms over two runs.

What the app had to change

The box now sends a frame on every driver poll, up to five a second (three at once), and a beat each second when no poll comes; hidden stays at one frame in five seconds. I checked every rule that reads frame timing:

  • Freshness needs no change. A source's grade is the box's own (Source.state, from that source's staleAfterMs), and SiteModel.streamQuietAfterMs (3 s) is a carrier rule: three missed beats. The box still beats at least every 1.25 s, so a quiet house never reads as a lost session. Nothing grades a source from frame spacing.
  • The lit windows need no change. Light is a yes/no from freshness and flips only on that 3 s rule or a source or carrier change. foregroundFrameDeadlineMs (2.5 s) holds: after sub hz:1 the next poll goes at once, and a beat within 1.25 s.
  • LiveTrace's ring assumed 1 Hz. It took every traced field on every frame, 600 points, "ten minutes at 1 Hz". Now an unchanged value repeats at most once a second (repeatMs), as at 1 Hz, so the curve and its runs look the same, and the ring holds 1 500 points: five minutes at the box's fastest.
  • The watch (U3 is merged, so this is mine now). WatchBridge forwarded every frame, up to five a second. It now sends Now when what the wrist shows moves (a figure as PowerFormat prints it, a word, a level, the glow, "in control"), and otherwise once a second (WatchShare.worthSending). WatchTrace adds a point only when a value or its sight moved or a second passed (LiveSample.adds, the ring's rule), and builds the trace again only then.
  • The trace's slide (U4 is merged). A reading that landed while the last one still slid (0.25 s) restarted from where that slide was headed, a visible jump at frames under 200 ms apart. TraceFrame(series:after:arrivedAt:) starts the new slide where the head is drawn now (LiveSeries.midSlide), on the phone and the watch.
  • Stale 1 Hz text: CLAUDE.md (lane 0 is byte-identical in length; the constant cadence is the relay's), TunnelCarrier, SiteModel (34, 50, 484), Transport.swift's replay window (64 frames is about 13 s at five a second; the box's link holds a paced bulk frame at most 9 s, which srcfl/ftw tests), the ring file's size.

LiveLatencyTests is the phone half of the measurement, skipped unless FTW_LIVE_LATENCY_DIR names the box's directory (srcfl/ftw TestLiveBoxForTheiPhone). It logs each decoded grid reading with the wall clock, then times ten cold opens.

TLS

URLSession resumed none of 54 TLS connections to a local ftwcloudd, with a session per socket as URLSessionWebSocket does, one shared ephemeral session, or one default session, although the service issues tickets and a Go client resumes against it. The local test trusts a private CA through the session delegate, which may be why; with production's public certificate it is unproven. It costs no time: TLS 1.3 takes one round trip either way (53–58 ms at 50 ms RTT).

Proof

  • aFrameThatCarriesAnotherDevicesReadingAddsNoPoint, theRingHoldsFiveMinutesAtTheFastestAndNoMore (NodeTests); aRepeatedValueAddsAPointOnlyOnceASecond, aReadingMidSlideSlidesOnFromWhereTheHeadIs (LiveOnArrivalTests); theWatchHearsAMoveAtOnceAndOtherwiseOnceASecond (WatchShareTests).
  • FTWKit swift test: 687 tests green. The app, the watch app and its widgets build for the simulator.
  • Not checked in the simulator: the slide at five frames a second. The demo box sends once a second, where no reading lands mid-slide; the continuity is tested in LiveOnArrivalTests.

🤖 Generated with Claude Code

…land

The box now sends a reading through FTW Cloud as its driver delivers it,
up to five frames a second, with a beat each second when nothing moves.
The ring repeats an unchanged value at most once a second and holds 1500
points. LiveLatencyTests times a reading from the box's driver to this
session's decode against a local ftwcloudd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a75d1f38b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

var ring = rings[fid] ?? RingBuffer(capacity: Self.capacity)
let last = ring.last
if let last, atMs <= last.t { continue }
if let last, !last.restored, last.v == v, atMs - last.t < Self.repeatMs { continue }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track suppressed frames before deciding gaps

When an unchanged field arrives at 5 Hz, this early return leaves its recorded timestamp up to 800 ms behind the most recent frame. If the carrier then pauses for 2.6–3.5 seconds—still within gapMs and potentially still considered live—the next point can appear more than 3.5 seconds after last.t, causing record and LiveSeries to create a false break in the trace. Keep a per-field last-observed timestamp (and current hold) even when suppressing the duplicate, and use that timestamp for gap detection.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T17:54:04.405315Z 9a75d1f PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

frahlg and others added 2 commits October 15, 2026 22:43
The watch hears Now when what it shows moves and otherwise once a second;
its trace adds a point only when a value moved or a second passed. A
reading that lands mid-slide slides on from where the head is drawn.
Comments that assumed one frame a second say what holds now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg frahlg changed the title iPhone: live numbers as they land through FTW Cloud; the trace keeps five minutes iPhone and watch: live numbers as they land through FTW Cloud Oct 15, 2026
@frahlg
frahlg merged commit 40ac39f into main Oct 16, 2026
1 check passed
@frahlg frahlg mentioned this pull request Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant