Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
b73934d
Add $PMTXTS per-PPS host timestamping (opt-in)
peterlewis Jul 4, 2026
5ba7320
SOF-correlation timestamping: latch (USB frame, DWT) for sub-ms PPS o…
peterlewis Jul 7, 2026
1fdbf33
SOF timestamping: audit fixes — valid-gated tail, latch only when pps=on
peterlewis Jul 7, 2026
4d62738
Add astro pack: sun / moon / Maidenhead / lat-lon display modes
peterlewis Jun 30, 2026
e1eeeb8
astro: give LAT/LON a sign slot so negatives keep the label gap
peterlewis Jul 5, 2026
02c9823
astro: configurable per-screen page dwell (page_ms, default 5500 ms)
peterlewis Jul 5, 2026
8217fc4
astro pack: MODE_DARK — observing twilight ladder + darkness countdown
peterlewis Jul 21, 2026
085edee
qspi: document MODE_DARK in the sample config
peterlewis Jul 26, 2026
d010615
astro: fix the native test against the 10-arg sun_times, and cover as…
peterlewis Jul 26, 2026
0e0d808
astro: sample config and page_ms comment agree with the code
peterlewis Jul 26, 2026
50ac8ba
Harden firmware: bounds + ISR-safety fixes from broad audit
peterlewis Jun 30, 2026
23aa7d4
Fix charger-only-power CDC hard-fault and date-board CMD_LOAD_TEXT OOB
peterlewis Jul 4, 2026
207c238
tz: distance-gate the ZoneDetect lookup — kill the 300 ms/second main…
peterlewis Jul 12, 2026
f21b82f
Add sidereal (LST) and apparent-solar time-row modes
peterlewis Jul 5, 2026
392cc7c
sidereal: rename config key alt_colon_mode -> colon_alt_mode
peterlewis Jul 12, 2026
b825a08
astro: subsolar-point helper (sun_subsolar) + shared-series comments
peterlewis Jul 7, 2026
1dc1eb8
astro test: pass the astro_dusk out-param at the solar-noon call site…
peterlewis Jul 26, 2026
c8be1ef
sidereal: sample config names the key the parser accepts
peterlewis Jul 26, 2026
c661029
Add self-learning holdover temperature compensation
peterlewis Jul 5, 2026
e6ecdaf
Holdover significance-fade + persistent, evolving warm-start temp model
peterlewis Jul 6, 2026
11cc155
Fix significance_fade boot flash: digit_bright defaults to 0 (dashed)…
peterlewis Jul 7, 2026
ded8b44
significance_fade: compute per-digit dash thresholds (duty-mirror wir…
peterlewis Jul 10, 2026
a05b7a7
tempcomp: MODE_TEMPCOMP pages on page_ms + final enum position
peterlewis Jul 12, 2026
fa8dd19
seg_balance: equalise per-segment brightness by duty (config-gated, d…
peterlewis Jul 10, 2026
416be85
seg_balance: power-law overdrive (101..300) — linear duty can't out-s…
peterlewis Jul 10, 2026
2ac51ca
seg_balance_bright: track the rail — dim and full-brightness strength…
peterlewis Jul 10, 2026
7e5f163
seg_balance: date-board port + duty-table forwarding + adaptive dithe…
peterlewis Jul 10, 2026
5ba5f9b
seg_balance = on: the calibrated auto curve — one key, correct at eve…
peterlewis Jul 10, 2026
31003a6
date board: restore version 0.0.2 — version trains are not the featur…
peterlewis Jul 10, 2026
f550b3c
Brightness: exponential seg_balance AUTO curve + colon_balance (dim c…
peterlewis Jul 11, 2026
4588ac0
seg_balance/colon_balance: anchor the exponential AUTO curve at 4095,…
peterlewis Jul 11, 2026
a0cf5ad
emu: bake the measured brightness curve as the compiled-in default
peterlewis Jul 12, 2026
74ae963
colon_balance: 2-anchor AUTO colon curve baked from HW sweep
peterlewis Jul 12, 2026
b6de692
seg_balance: gradual significance-fade via the duty mirror
peterlewis Jul 12, 2026
0cf39b6
segbal: nested bit-reversed dither order — duty steps stop re-phasing…
peterlewis Jul 12, 2026
eb0dbe1
segbal: per-digit phase rotation of the nested dither — restore cross…
peterlewis Jul 12, 2026
ba90291
segbal: ISR-fresh mirrors — the once-per-second freeze was refill sta…
peterlewis Jul 12, 2026
bae16c0
segbal: document colon_full_at and colon_floor in the sample config
peterlewis Jul 26, 2026
00c6dd1
ADEV: free-running Allan-deviation engine (RAM2 ring + overlapping ADEV)
peterlewis Jul 11, 2026
27beff4
ADEV: MODE_ADEV date-row display + $PMADEV serial dump
peterlewis Jul 11, 2026
10ca306
adev: review fix bundle — overflow UB, gap honesty, missed-second tol…
peterlewis Jul 12, 2026
c2d00b1
adev: self-describing $PMADEV (epoch+tau0) + $PMHDEV Hadamard dump
peterlewis Jul 12, 2026
e58aa99
adev: document MODE_ADEV in the golden config
peterlewis Jul 12, 2026
2a55689
CDC: size the transmit staging buffer for the longest dump sentence
peterlewis Jul 19, 2026
a395588
Star transit: bright-star meridian-transit predictor (MODE_STAR + $PM…
peterlewis Jul 11, 2026
75490b7
star §6: transit countdown as bare M SS / Hh MM values (drop the dash)
peterlewis Jul 11, 2026
394fd0b
star catalogue: generate-stars.py -> stars.bin (HYG v4, famous ~90)
peterlewis Jul 11, 2026
3d7e362
star: load the transit catalogue from SD /STARS.BIN (fallback to bake…
peterlewis Jul 12, 2026
4f4be3a
star: harden loadStars vs untrusted card data (adversarial review fixes)
peterlewis Jul 12, 2026
089a992
star: review fix bundle — rigorous precession + proper motion, 4-char…
peterlewis Jul 12, 2026
08fb37f
star: transit NOW tell + N/S direction + refraction band + curated ca…
peterlewis Jul 12, 2026
d378ba9
star: MODE_STAR requires STARS.BIN — remove the baked fallback
peterlewis Jul 12, 2026
72e45a4
star: document MODE_STAR in the golden config
peterlewis Jul 12, 2026
2528491
star: document star_max_mag in the sample config
peterlewis Jul 26, 2026
81e616f
Menu: on-device 2-button menu — receiving FSM + live editor (no persi…
peterlewis Jul 11, 2026
22d6192
Menu: persistence — firmware-owned emulated-EEPROM + config.txt prece…
peterlewis Jul 11, 2026
2c94d3c
Menu: date board emits the rolling chord protocol (companion to the m…
peterlewis Jul 11, 2026
50be387
rollup: version 0.0.5 — upstream's latest release is 0.0.4
peterlewis Jul 7, 2026
4e9b9df
rollup: version 0.0.6 — the next-release candidate (PRs #5–#9)
peterlewis Jul 7, 2026
f0fbec0
rollup: rebuild fwd.bin (date board) from this tree, CRC-gated
peterlewis Jul 7, 2026
13e653f
rollup: date board -> 0.0.2; refresh both release images
peterlewis Jul 7, 2026
b30cc1a
golden config + refreshed release images (menu + ADEV + star)
peterlewis Jul 11, 2026
36c0a96
Menu: "menu_reset = on" — factory-reset the on-device menu store
peterlewis Jul 11, 2026
8a038a7
emu: auto-persist learned tempcomp model to flash retained memory
peterlewis Jul 12, 2026
c893c3a
emu: fix menu/tempcomp flash commit never firing on a GPS-locked cloc…
peterlewis Jul 12, 2026
cea0c89
emu: fix menu/tempcomp flash commit never firing on a GPS-locked cloc…
peterlewis Jul 12, 2026
f147e2e
menu v2 §0+§3a: linchpin widenings + PAGE MS effective-value fix
peterlewis Jul 11, 2026
153aad8
menu v2 §1: section model — MItem.section + SEC_* + tag all rows
peterlewis Jul 11, 2026
e96fb49
menu v2 §2: sectioned FSM — insert the SECTION layer (the endless rin…
peterlewis Jul 11, 2026
7791188
menu v2 §3b+§3c: hold-acceleration + large-value ring render
peterlewis Jul 11, 2026
16b367a
menu v2 §4: MATRIX display-frequency as a live SYS menu item
peterlewis Jul 11, 2026
d26855e
menu v2: real TEMPCOMP enable toggle (DIAG) + rename display mode to …
peterlewis Jul 11, 2026
6effb33
menu v2: single BALANCE toggle arms seg+colon AUTO
peterlewis Jul 12, 2026
ec99933
menu v2 §3.5: live colon context-preview while editing COLON/ALTCOLON
peterlewis Jul 11, 2026
3304aeb
menu v2 §5: COLONALT menu label
peterlewis Jul 12, 2026
8464600
version: per-build id so same-day 0.0.5 reflashes are always detected
peterlewis Jul 11, 2026
5c00f8e
menu v2: fix 3 issues from the adversarial flash-safety review
peterlewis Jul 11, 2026
6eb4be6
date board: per-build id (match the time board) so fwd.bin always ref…
peterlewis Jul 11, 2026
ba272cd
menu v2: ENTER lands on the first item — drop the confusing section b…
peterlewis Jul 11, 2026
6f8b60f
menu v2: MATRIX shows clean kHz (drop unrenderable KHZ glyphs)
peterlewis Jul 11, 2026
7bfcc70
date board: forgiving two-button chord (tolerate ~100ms stagger, taps…
peterlewis Jul 12, 2026
f901494
menu v2: one-press toggle persists immediately (no separate SAVE)
peterlewis Jul 12, 2026
5018e28
menu v2: split reboot from factory reset — SYS>RESET item + REBOOT chord
peterlewis Jul 12, 2026
999d2bf
menu v2: toggles are an enter/toggle/exit editor that saves on exit
peterlewis Jul 12, 2026
b6e7787
menu: "menu_dump = on" serial diagnostic — is the store flash-backed …
peterlewis Jul 12, 2026
55488bc
settings: QSPI SETTINGS.BIN store — persistence on 256K silicon (Desi…
peterlewis Jul 12, 2026
f11f3a3
menu v2: review fix bundle — 6 FSM bugs + legibility/ergonomics refin…
peterlewis Jul 12, 2026
01d9733
menu: read-only INFO rows (PPS lock / catalogue count) + factory-rese…
peterlewis Jul 12, 2026
a776d9a
menu v2 §7A: editor blink + idle warning + VBAT row (display refineme…
peterlewis Jul 12, 2026
e6c5ae5
qspi: repackage fwt.bin (0.0.5+1783851034, §7A build)
peterlewis Jul 12, 2026
3feda2b
menu: factory reset forgets the learned tempcomp model (+ golden-conf…
peterlewis Jul 12, 2026
9a657ab
qspi: repackage fwt.bin (no-fallback + factory-forget build)
peterlewis Jul 12, 2026
69a1815
docs: firmware-additions.md — the complete manual for the PR series
peterlewis Jul 12, 2026
3bb0633
menu: TEMPCOMP toggle arms the WHOLE compensator — learn + apply + pe…
peterlewis Jul 12, 2026
5f1b7b5
qspi: repackage fwt.bin (TEMPCOMP-trio build)
peterlewis Jul 12, 2026
4d9e83e
audit polish: docs/config/comment accuracy + drop the IDE env-hash fr…
peterlewis Jul 12, 2026
349de0a
qspi: repackage fwt.bin (audit-polish build)
peterlewis Jul 12, 2026
dc1b7ab
qspi: repackage release images (tz-distance-gate build)
peterlewis Jul 12, 2026
2d70c7a
Integration delta: align the PR union to the validated rollup tree
peterlewis Jul 12, 2026
da34e38
menu: MATRIX edits the number only, applies the rate on commit (fixes…
peterlewis Jul 12, 2026
c205fac
menu: rename menu_reset -> factory_reset (no alias)
peterlewis Jul 19, 2026
6707add
menu: DARK row + widen the mode-enable mask to 64-bit
peterlewis Jul 21, 2026
62fb946
menu: document MODE_DARK, and fix the factory-reset name and chord
peterlewis Jul 26, 2026
6e19126
cuckoo: scheduled display flourishes — the quarter nod + the trust ho…
peterlewis Jul 20, 2026
309be05
cuckoo: document the feature in firmware-additions
peterlewis Jul 26, 2026
302e781
zone2: a correct second timezone on the date row (config-only)
peterlewis Jul 21, 2026
8f51b60
qspi: repackage the union release image from the reflowed build
peterlewis Jul 21, 2026
46692ed
zone2: document the second-timezone keys in the sample config
peterlewis Jul 21, 2026
65fd805
zone2: document MODE_ZONE2 in firmware-additions
peterlewis Jul 26, 2026
66eb3f2
qspi: rebuild both release images from the current sources
peterlewis Jul 26, 2026
667365c
qspi: fresh release images from the hygiene-clean stack
peterlewis Jul 26, 2026
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
163 changes: 163 additions & 0 deletions CUCKOO_SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# CUCKOO — scheduled display animations for the Precision Clock Mk IV

Status: v2 · supersedes v1 · branch `cuckoo-v2` (off rollup)

A cuckoo is a short, scheduled display flourish. It lives entirely on the TIME board; the
date row is never addressed. Every animation modulates the true display — the clock never
shows wrong digits, and every animation's final frame is the plain live face. The v1
primitives carry forward with one integration change: per-segment brightness rides the
seg_balance 80-slot bit-reversed dither interleave already shipped on this branch (one
interleave engine, two clients) instead of the v1 branch's own scan. The 10 ms
terminating-countdown tick and the second-edge scheduler (never preempted, colliding
trigger skipped) are unchanged. PCC inherits everything through the byte-faithful WASM
build.

## The cadence

Cadence is intrinsic, not scheduled. Two tiers, after the Westminster pattern: each
quarter-hour (:15/:30/:45) gets one fixed small gesture — the nod; the hour (:00) gets the
full configured piece. **Cadence law: the clock nods on each quarter edge and performs its
piece on the hour edge — one gesture per quarter, one piece per hour, nothing configurable
between them.**

## Config vocabulary

```
# off (default) | trust
cuckoo = off
```

One key, enable-and-select fused. The value names the piece — `cuckoo = heartbeat` reads
"the cuckoo is heartbeat" — so `cuckoo_effect` / `cuckoo_transition` would merely restate
the value; the noun *is* the feature. `off` disables both tiers. `rain` is not in the enum
(date board, deferred).

`cuckoo_interval` is omitted from this spec because a cuckoo's cadence is intrinsic — the
quarter gesture and the hourly piece are anchored to the face's own structure, not a user
knob — and an interval would only license flourishes that no longer mark the hour.

## The nod — the quarter gesture

One fixed gesture, identical on every :15/:30/:45 second edge; never the selected piece in
miniature.

- **Elements:** the six big time digits (HH MM SS) only. Colons untouched (they are the
live pulse); sub-second and ms digits untouched (fast elements get no blooms).
- **Envelope:** a single shallow brightness trough sweeps left to right. Digit *k*
(k = 0 leftmost) begins at `k·30 ms`; each digit dips nominal → 8/16 and back on a
symmetric integer trough, 200 ms down, 200 ms up. Floor 8/16 — never below half, so
every digit stays fully legible. The wave clears the row and settles by ~550 ms.
Brightness only: no glyph, colour, or data change.
- **Mechanism:** the dither interleave at digit resolution on 10 ms countdowns; the
envelope terminates at nominal, so the final frame *is* the plain live face.
- **Numbers provisional:** depth (8/16) and sweep rate (30 ms/digit) are implemented as
written but held for a bench check at across-the-room distance — respiration, not a
power sag — before the PR locks them.

**Perceptual gamma (bench-tuned 2026-07-20):** pieces write INTENT levels; the render maps
them through `duty = 16·(L/16)^2.2` (floored at one lit cycle for any lit intent). Light
output is linear in dither duty but the eye is not — the first bench check found `carry`
and `pendulum` nearly invisible (their action lives at the top of the duty range, where
perceived differences compress) while `trust` (a 2→16 swing) read fine. Under the gamma an
intent difference reads evenly anywhere in the range. The tuning knob is this one curve;
piece bodies stay in intent space and never re-tune per piece.

Why it survives 96 plays/day: shallow, brief, monochrome, carrying no data — nothing in it
can be wrong and there is no novelty to exhaust. Identical every quarter, it reads as the
clock's respiration, not a message. In holdover it is unchanged (pure brightness, no PPS
or GPS dependency).

## The hourly flourish

At :00 the configured piece performs. :00 is also a quarter edge; the nod is suppressed —
`if minute == 0 → piece; elif minute ∈ {15,30,45} → nod`. One performance per hour
boundary. If the configured piece cannot run honestly (`pendulum` in holdover), **the nod
stands in** — the hour boundary is never wholly silent, and the honest minimum gesture
never forges the absent performance.

There is no artificial hour-count: the hour digits are lit and exact; counted blinks would
duplicate legible data and read as a fault. (`trust` is honest in every state, so with the
shipped catalogue the stand-in never fires; the law remains for the parked pieces' return.)

## Pose and the date row

Cuckoo is **time-board-only**; the date row is never touched. Both constraints the client
raised dissolve at this boundary, for two hardware reasons:

1. **Pose is automatic.** The TIME row reads left-to-right in both poses (stacked: date
above time; extended: date left of time), and the time board cannot sense orientation —
only the date board senses pose (GPIOC bit 0) and self-corrects its glyphs, decimal
points and button codes; the date→time UART carries pre-corrected button bytes only. A
time-board-local animation is therefore pose-invariant by construction. No pose flag.
2. **The date row is unreachable, honestly.** Its UART carries rendered text plus a latch
byte and no brightness — per-segment intensity on the date row is impossible today (the
wall v1 deferred rain against). So whatever it holds — civil date (also under
LST/SOLAR), ADEV ladder, star transit, COUNTDOWN, TEXT — it keeps reading truthfully
*through* the flourish. A countdown that keeps counting is correct; freezing it to
match the time row would be the lie.

**v2 hook:** date-board participation (rain's canvas, trust's date sweep, showcase names)
waits on one UART extension — a per-element intensity frame atop the existing text+latch
framing.

## Preview — hearing a piece without waiting for it

Two ways to play a piece immediately, outside the schedule:

- **The menu.** The on-device CUCKOO row (DISP section, values OFF / TRUST) **performs each
value as it is selected** — the menu's own colon-preview idiom applied to the flourishes.
The menu owns the date row while open, the piece plays on the time row; the two never
collide. Cancel restores the old value and stops the piece; commit persists the value and
stops the piece (it then waits for its hour); the idle auto-exit stops it too. (When the
parked pieces return: a preview must not forge what the schedule refuses — a PPS-dependent
piece previews the NOD in holdover.)
- **Serial.** `cuckoo_preview = trust|nod|on` (serial-only, never valid in config.txt —
a file must not perform) plays once without touching the `cuckoo` config; `on` previews
the configured piece.

A preview preempts a running preview — a tap-through of the menu ring must respond per
tap. That is a deliberate act at the clock, not a scheduler exception; the scheduler itself
still never preempts.

## The catalogue

**Shipping: `trust` only** (first bench verdict, 2026-07-20: it was the one piece that read
well on hardware). The others are **parked, not deleted** — parked means absent from the
firmware, per this project's own rule:

- **`carry`, `heartbeat`, `pendulum`** — bodies preserved in git (59fc649); they return
through the same enum when their visual rework earns a second bench verdict. Candidate
direction on record: even under the perceptual gamma their action may need structural
changes (bigger level strides, slower beats), not just brighter rendering.
- **`rain`** stays on the date board — deferred, not degraded.

The nod-stand-in law stands as design law (an hour whose piece cannot run honestly gets the
nod); with `trust` — honest in every state — it is currently dormant.

## What was rejected, and why it stays rejected

- **`cuckoo_interval`** — cadence is intrinsic; an interval would desync the flourish from
the hour it marks.
- **Per-piece or scaled quarter gestures** — one fixed nod only; a shrunk piece would let
a data piece (`trust`, `pendulum`) fire un-earned four times an hour.
- **Westminster phrase-growth across the quarters** — it would encode the quarter number
the digits already state; redundant decoration.
- **An artificial hour-strike count** — duplicates the legible, exact hour digits and
reads as a fault; `carry`'s real rollover chain is the only honest "count."
- **A substitute piece for an unrunnable hour flourish** — the nod stands in; a swapped-in
*piece* would forge "it played."
- **`MODE_CUCKOO_SHOWCASE`** — deferred: it prints piece names on the date row, the one
place cuckoo must not touch; it returns with the v2 date extension.
- v1's standing rejections (three-timescale tour, moon dial, equation-of-time walk,
multiplex-scan reveal, count-based rain) remain rejected for their original reasons.

## Emulator / PCC integration

- The shim exports `emu_seg_bright(cell, seg)` mirroring `emu_digit_fade`; PCC renders it
through the existing per-element `setSegField` face surface. The web-side prototype
engine (demo7.js) is retired when this branch reaches the rollup.
- Conformance: the golden suite must show byte-identical display behaviour with
`cuckoo = off` (the feature costs nothing when disabled); a fixture drives one nod and
one `carry` and asserts every envelope terminates at nominal.
- Standby-gated: the scheduler refuses while the display is dark (MODE_STANDBY) — a dark
clock stays dark.
Loading